Skip to content

feat: CORS iframes + closed shadow DOM parity#226

Draft
aryanku-dev wants to merge 4 commits into
masterfrom
feat/cors-iframes-and-shadow-dom
Draft

feat: CORS iframes + closed shadow DOM parity#226
aryanku-dev wants to merge 4 commits into
masterfrom
feat/cors-iframes-and-shadow-dom

Conversation

@aryanku-dev
Copy link
Copy Markdown

Summary

Brings percy-selenium-python to parity with the canonical Percy CORS iframe + closed shadow DOM feature set.

Implemented

  • Inlined Python helpers (DEFAULT_MAX_FRAME_DEPTH, clamp_frame_depth, normalize_ignore_selectors, is_unsupported_iframe_src, resolve_max_frame_depth, resolve_ignore_selectors)
  • Nested cross-origin iframe capture (depth-capped, cycle-guarded)
  • data-percy-ignore attribute opt-out
  • ignoreIframeSelectors option
  • Post-switch URL re-check via is_unsupported_iframe_src
  • PercyContextLost recovery merges partial_capture
  • Closed shadow DOM capture via CDP (expose_closed_shadow_roots)

Skipped

  • ElementInternals preflight (Feature 8): N/A — selenium-python has no before-page-load hook.
  • @percy/sdk-utils version bump (Feature 9): not applicable to Python; helpers inlined.

Reference

Mirrored from percy/percy-nightwatch#869 (PER-7292-add-cors-iframe-support); CDP from percy/percy-playwright#609.

Test plan

  • Full repo test suite passed locally
  • Manual smoke: cross-origin iframes
  • Manual smoke: closed shadow roots in Chromium

🤖 Generated with Claude Code via /percy-sdk-sync

aryanku-dev and others added 4 commits May 11, 2026 12:23
…arity

Brings the same helper surface used by percy-nightwatch / percy-webdriverio
into percy-selenium-python directly: DEFAULT_MAX_FRAME_DEPTH, clamp_frame_depth,
normalize_ignore_selectors, is_unsupported_iframe_src, get_origin,
resolve_max_frame_depth, resolve_ignore_selectors, and the PercyContextLost
exception. No SDK version bump — Python doesn't share a utils package with
the JS SDKs, so the helpers ship inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements feature parity with percy-nightwatch / percy-webdriverio for
cross-origin iframe serialization:

- Replaces the flat single-level iframe scan with a recursive
  ``process_frame_tree`` walk bounded by ``DEFAULT_MAX_FRAME_DEPTH``
  (5, overridable via ``maxIframeDepth`` option or
  ``percy.config.snapshot.maxIframeDepth``).
- Adds an ancestor-URL cycle guard so frames that link back to a
  previously-visited URL stop descending instead of recursing forever.
- Adds an ``enumerate_iframes_script`` JS helper that runs inside the
  current frame context and returns metadata for every iframe
  (src, srcdoc, percyElementId, dataPercyIgnore, matchesIgnoreSelector,
  index). Nested-frame discovery now uses this script in the child
  context so nested-frame origin comparisons are against the *immediate*
  parent origin, not the page origin.
- ``data-percy-ignore`` attribute opt-out: any iframe with this attribute
  is dropped before any switch.
- ``ignoreIframeSelectors`` option (and ``ignore_iframe_selectors`` /
  ``percy.config.snapshot.ignoreIframeSelectors``): selectors are baked
  into the in-browser enumeration script so matching iframes are dropped
  before being processed.
- Post-switch URL re-check via ``is_unsupported_iframe_src``: after
  switching into a frame we read ``document.URL`` and bail if the
  loaded document is about:blank, about:srcdoc, a net-error page, or
  another unsupported scheme.
- ``PercyContextLost`` recovery: if ``switch_to.parent_frame()`` fails
  at depth > 1 we raise ``PercyContextLost`` carrying the
  ``partial_capture`` collected so far. The top-level walk merges that
  partial capture into the final ``corsIframes`` payload before
  aborting sibling iteration (whose enumeration was performed in a
  now-lost context).

All per-frame serialize calls force ``enableJavaScript=True`` to bypass
the standard iframe inlining path inside PercyDOM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ``expose_closed_shadow_roots`` (mirrored from percy-playwright's
``exposeClosedShadowRoots``) so that PercyDOM.serialize can capture
closed-mode shadow DOM that ordinary DOM traversal cannot reach.

Flow:
1. ``DOM.enable`` — gates non-Chromium drivers silently (Firefox/WebKit
   will fail this call and we no-op without touching the page).
2. ``DOM.getDocument`` with ``depth=-1, pierce=True`` — walks the full
   DOM tree including every shadow root.
3. Recurse the tree, collecting (host, shadowRoot) backend-node pairs
   for each ``shadowRootType=='closed'`` entry. Subtrees inside an
   iframe's ``contentDocument`` are skipped — their JS execution
   contexts can't see the page's WeakMap.
4. Create ``window.__percyClosedShadowRoots`` (same key PercyDOM uses).
5. For each pair, ``DOM.resolveNode`` both ends, then
   ``Runtime.callFunctionOn`` to stash the shadow root in the WeakMap
   keyed by its host element.

Wired into ``percy_snapshot`` immediately after PercyDOM injection and
re-primed after ``driver.refresh()`` inside ``capture_responsive_dom``
(the WeakMap is destroyed on navigation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant