Skip to content

Add Robot Framework support via PercyLibrary#220

Open
neha-sanse wants to merge 12 commits intomasterfrom
feature/robot-framework-support
Open

Add Robot Framework support via PercyLibrary#220
neha-sanse wants to merge 12 commits intomasterfrom
feature/robot-framework-support

Conversation

@neha-sanse
Copy link
Copy Markdown

@neha-sanse neha-sanse commented Apr 21, 2026

Summary

Adds native Robot Framework keywords for Percy visual testing directly into percy-selenium-python. No separate SDK needed — users install robotframework and robotframework-seleniumlibrary alongside percy-selenium and get Robot keywords automatically.

What's added

  • percy/robot_library.py — Robot Framework library with keywords
  • tests/test_robot_library.py — 17 tests covering all functionality
  • Graceful import in percy/__init__.py — no impact when robotframework is not installed

Keywords

  • Percy Snapshot — capture DOM snapshots with all options (widths, percy_css, scope, labels, regions, responsive capture, layout mode, etc.)
  • Percy Screenshot — BrowserStack Automate support with element region handling
  • Create Percy Region — build ignore/consider region definitions (CSS, XPath, bounding box, padding, algorithm config)
  • Percy Is Running — check Percy CLI availability

Key design decisions

  • import robot wrapped in try/except — existing percy-selenium behavior is unchanged when robotframework is not installed
  • PercyLibrary delegates to existing percy_snapshot() and percy_automate_screenshot() — no code duplication
  • All keyword arguments are parsed from Robot string types (comma-separated, JSON, booleans) into Python types before forwarding
  • When robotframework is not installed, a stub PercyLibrary class raises ImportError with a clear message

Usage

*** Settings ***
Library    SeleniumLibrary
Library    percy.robot_library.PercyLibrary

*** Test Cases ***
Homepage Visual Test
    Open Browser    https://example.com    chrome
    Percy Snapshot    Homepage
    Percy Snapshot    Responsive    widths=375,768,1280
    Percy Snapshot    Tagged    labels=regression,v2
    Close Browser
percy exec -- robot tests/

Test plan

References

🤖 Generated with Claude Code

Adds native Robot Framework keywords for Percy visual testing:
- Percy Snapshot — capture DOM snapshots with all options (widths,
  percy_css, scope, labels, regions, responsive capture, etc.)
- Percy Screenshot — BrowserStack Automate support
- Create Percy Region — build ignore/consider region definitions
- Percy Is Running — check Percy CLI availability

Robot Framework import is wrapped in try/except so the existing
percy-selenium package works unchanged when robotframework is not
installed. When it is installed, PercyLibrary becomes available as
a Robot library that delegates to the existing percy_snapshot() and
percy_automate_screenshot() functions.

Usage:
  Library    percy.robot_library.PercyLibrary

17 new tests covering parse helpers, keyword dispatch, and graceful import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@neha-sanse neha-sanse requested a review from a team as a code owner April 21, 2026 13:42
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
neha-sanse and others added 10 commits April 21, 2026 19:37
28 end-to-end Robot tests covering all Percy keywords: basic snapshots,
responsive widths, min height, Percy CSS, scoped snapshots, JS/layout/
shadow DOM, labels, test case metadata, ignore/consider/intelliignore
regions, responsive capture, all options combined, and utility keywords.

Run with: percy exec -- robot tests/test_robot_integration.robot

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-error in pylint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ic-methods

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cy_snapshot

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show full workflow for using Create Percy Region with both
Percy Snapshot (DOM) and Percy Screenshot (Automate), including
multiple regions, padding, and bounding box examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… in build info

When Robot Framework tests use percy.robot_library.PercyLibrary, the
snapshot module's CLIENT_INFO was hardcoded to percy-selenium-python,
causing Percy builds to display "selenium" in the Environment field.

Override the snapshot module's CLIENT_INFO and ENV_INFO when PercyLibrary
is instantiated so the Percy CLI sends the correct wrapper identity
(percy-robotframework-selenium) to the Percy API. The override only
applies in Robot Framework context — plain Selenium users are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move selenium import before percy imports to fix wrong-import-order.
Shorten docstring examples to stay within 100-char limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@amandeepsingh333 amandeepsingh333 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Robot Framework support (Selenium backend)

Reviewed alongside the sister PR percy-playwright-python#138. Good overall design — keywords delegate to existing SDK functions, no code duplication, graceful degradation when robotframework isn't installed. A few issues below, mostly shared with the Playwright PR.

Summary of findings:

# Severity File Issue
1 HIGH robot_library.py labels is joined back into a string after parsing — likely a bug (test asserts a list)
2 MEDIUM robot_library.py _apply_robot_client_info() mutates module-level state, leaking to non-Robot usage
3 LOW robot_library.py _parse_json doesn't catch json.JSONDecodeError — malformed JSON gets opaque error
4 LOW robot_library.py create_percy_region_keyword has inconsistent parameter indentation
5 LOW .pylintrc Globally disabling import-error suppresses legitimate import issues
6 LOW robot_library.py _parse_padding converts int to dict — Playwright PR passes raw int, inconsistency
7 LOW test_robot_library.py Missing test for _parse_padding and percy_screenshot_keyword

Comment thread percy/robot_library.py
| Percy Snapshot Homepage
| Percy Snapshot Login widths=375,1280 min_height=1024
| Percy Snapshot Dashboard labels=dashboard,admin enable_layout=True
| Percy Snapshot Scoped scope=.main-content percy_css=.ad { display: none; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH — labels is joined back into a string after being parsed into a list

labels=",".join(_parse_csv(labels)) if labels else None,

_parse_csv("regression,v2") returns ["regression", "v2"], then ",".join(...) converts it back to "regression,v2" — a string. But the test at test_robot_library.py:105 asserts:

assert call_kwargs["labels"] == ["regression", "v2"]  # a list!

This assertion should fail because the code passes a string, not a list. Either:

  • The code has a bug — it should pass labels=_parse_csv(labels) (a list)
  • The test has a bug — it should assert == "regression,v2" (a string)

Please verify what percy_snapshot() expects for labels and fix both this PR and the Playwright PR (percy-playwright-python#138) consistently.

Comment thread percy/robot_library.py
def _apply_robot_client_info():
"""Override snapshot module client/env info for Robot Framework context."""
if _ROBOT_CLIENT_INFO:
_snapshot_module.CLIENT_INFO = _ROBOT_CLIENT_INFO
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — _apply_robot_client_info() mutates module-level state, leaking to non-Robot usage

This function overwrites _snapshot_module.CLIENT_INFO and ENV_INFO when PercyLibrary is instantiated. Since PercyLibrary has scope="GLOBAL", it's instantiated once per Robot run — which is fine in a Robot context.

But if someone does:

from percy.robot_library import PercyLibrary
lib = PercyLibrary()  # mutates _snapshot_module.CLIENT_INFO

# Later, non-Robot usage:
from percy.snapshot import percy_snapshot
percy_snapshot(driver, "test")  # now reports as Robot Framework!

The mutation is permanent for the process lifetime. Consider either:

  1. Saving and restoring the original values
  2. Passing client_info/env_info as kwargs to percy_snapshot() instead of mutating globals (if the SDK supports it)
  3. At minimum, documenting this is intentional

Comment thread percy/robot_library.py
if not val:
return None
if isinstance(val, str):
return json.loads(val)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — json.loads() can throw json.JSONDecodeError on malformed input

Robot Framework users pass all values as strings. A typo like scope_options={not-valid-json} or bounding_box={x:0} (missing quotes) produces an opaque json.JSONDecodeError with a line/column pointer into the string — confusing from a Robot Framework test report.

Consider wrapping in try/except with a user-friendly error:

if isinstance(val, str):
    try:
        return json.loads(val)
    except json.JSONDecodeError as exc:
        raise ValueError(f"Invalid JSON: {val!r}") from exc

Same issue exists in the Playwright PR.

Comment thread percy/robot_library.py
parsed_options["consider_region_selenium_elements"] = [
selib.find_element(loc) for loc in locators
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Inconsistent parameter indentation

The last four parameters (carousels_enabled, banners_enabled, ads_enabled, diff_ignore_threshold) are aligned to the opening parenthesis rather than following the 12-space indent used by the preceding parameters:

            diff_sensitivity=None,
            image_ignore_threshold=None,
                                         carousels_enabled=None,     # ← jumped right
                                         banners_enabled=None, ...   # ← jumped right

Align them consistently with the rest:

            diff_sensitivity=None,
            image_ignore_threshold=None,
            carousels_enabled=None,
            banners_enabled=None,
            ads_enabled=None,
            diff_ignore_threshold=None,
        ):

Comment thread .pylintrc
broad-except,
broad-exception-raised,
global-statement,
import-error,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Globally disabling import-error is too broad

This suppresses legitimate import errors across the entire codebase, not just for the optional robotframework imports. The Robot-specific imports are already wrapped in try/except ImportError in the code.

Consider using targeted inline disables instead:

from robot.api.deco import keyword, library  # pylint: disable=import-error

Or scope it to the specific file only.

Comment thread percy/robot_library.py
return None


def _parse_padding(val):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — _parse_padding converts int to dict, but Playwright PR passes raw int

This PR converts padding=10{"top": 10, "bottom": 10, "left": 10, "right": 10} via _parse_padding(). The Playwright PR (percy-playwright-python#138) passes padding=int(padding) if padding else None — a raw integer.

This means the same Robot keyword (Create Percy Region ... padding=10) produces different shapes on different backends. Please verify what create_region() expects in each SDK and align both PRs. If both SDKs accept both formats, pick one and be consistent.

assert call_kwargs["min_height"] == 1024
assert call_kwargs["percy_css"] == "h1 { color: red; }"
assert call_kwargs["enable_javascript"] is True
assert call_kwargs["labels"] == ["regression", "v2"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Test asserts labels is a list, but code passes a string

assert call_kwargs["labels"] == ["regression", "v2"]

The code does labels=",".join(_parse_csv(labels)) which produces "regression,v2" (a string). This assertion should fail: "regression,v2" != ["regression", "v2"].

If the tests are actually passing, the mock setup may not be exercising this code path. Either way, after fixing the labels handling in robot_library.py, update this assertion to match the corrected behavior.

Also missing: tests for _parse_padding() and percy_screenshot_keyword() — two features unique to this PR that the Playwright PR doesn't have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants