Skip to content

Fix humanized selector resolution and actionability - #515

Closed
Cloak-HQ wants to merge 7 commits into
mainfrom
fix/humanized-selector-parity
Closed

Fix humanized selector resolution and actionability#515
Cloak-HQ wants to merge 7 commits into
mainfrom
fix/humanized-selector-parity

Conversation

@Cloak-HQ

Copy link
Copy Markdown
Member

Summary

  • align supported selector resolution and actionability behavior across Python, JavaScript, and .NET
  • preserve expected target ordering and validate target identity through humanized actions
  • support rendered text and visible descendants when a target has no box of its own
  • keep unsupported and complex locator paths on their existing compatibility behavior
  • add cross-wrapper unit and real-browser regression coverage

This addresses #512 and incorporates the relevant geometry correction identified in #514 without adding the proposed visibility fallback.

Validation

  • Python focused selector/actionability tests: 112 passed
  • Python Preview browser regressions: 12 passed
  • JavaScript build and typecheck: passed
  • JavaScript tests: 564 passed, 19 skipped
  • JavaScript Preview selector regressions: 8 passed
  • .NET tests: 453 passed, 1 skipped
  • .NET Preview validation: 443 passed, 1 skipped; 10 browser regressions passed
  • cross-wrapper resolver parity verified
  • git diff --check clean

Resolve #512 by ignoring non-rendered document content during text
matching and selecting the same visible target as Playwright.

Improve text, structural, selector-list, and open shadow-root handling.
Align element state checks and forced-click behavior, and prevent pointer
dispatch when the original target is detached or replaced during mouse
movement.

Add sync, async, and real-browser regression coverage.
Collect light-DOM matches before recursively merging open Shadow Root
matches, preserving Playwright order for broad selectors using first or nth.

Add resolver and real-browser coverage for mixed and nested Shadow Roots.
Port the proven Python interaction behavior to the JavaScript wrapper.

- align selector resolution and actionability semantics
- preserve exact targets across delayed interactions
- improve text, state, and open shadow tree consistency
- add regression coverage for ordering, mutation races, and force behavior
Port the proven selector resolution and actionability behavior to the .NET wrapper.

- preserve exact targets across delayed interactions
- align supported locator semantics and legacy fallbacks
- add regression coverage for ordering, mutation races, and scrolling
Derive actionable geometry from rendered text and visible descendants when an element has no box of its own. Preserve existing geometry behavior for normal elements and exclude hidden descendant content. Add Python, JavaScript, and .NET unit and browser regressions covering text, nested content, visibility, and humanized clicks.
…eads

Removing the Playwright fallback closed a main-world read leak but left every
get_by_* locator raising UnsupportedHumanizeSelectorError. Reimplement the four
cheap engines in the isolated-world resolver so those locators work again.

Selector engines (shared resolver, byte-identical in all three wrappers):
- internal:testid and internal:attr (placeholder/alt/title). The i flag is a
  case-insensitive substring on the raw attribute value; test ids stay strict.
- internal:text. Exact compares the full normalized subtree text, unlike the
  public text= engine which compares immediate fragments.
- internal:label, resolving aria-labelledby, then aria-label, then .labels.
- internal:role and >> chaining stay unsupported; the error now names what is
  supported and the workaround.

Text parity fixes in __elementText, checked against playwright-core:
- drop a <br> to newline branch Playwright does not have
- concatenate shadow-root text into the host, which Playwright does
- give the public text= engine Playwright's self/selfAndChildren rule, since
  contains() cannot see into a shadow root and kept the host as a match

Target identity: payloads carry a per-world generation (protocol v2). The world
is rebuilt on navigation and its id counter restarts, so an id alone could name
a different element. Generation is bounded to Int32, which the .NET wrapper
requires.

force=True now skips the pointer check entirely, matching Playwright, instead of
suppressing only the coverage rejection.

Also: drop dead _BOX_OP, _ACTIONABLE_OP and readSnapshot, and add a test
asserting the three hand-copied resolver literals stay identical and free of
characters that would terminate their host string literals.

Verified against Playwright's own locator resolution in a real browser:
20/20 engine parity, 7/7 text parity.
The test inlines the resolver once per builder call, so adding the selector
engines took the script past 110 KB. Linux caps a single argv entry at
MAX_ARG_STRLEN (128 KB), so "node -e <script>" failed with E2BIG on CI while
passing on macOS, which allows a larger argument.

Write the script to a temp file and pass the path instead, which has no size
ceiling.
@Cloak-HQ

Copy link
Copy Markdown
Member Author

Merged into main via fast-forward (commits rebased onto latest main; hashes differ so GitHub didn't auto-close). Content is identical to this PR.

@Cloak-HQ Cloak-HQ closed this Aug 25, 2026
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