Repository navigation
fix: make each Extension ref resolve to exactly one element - #66
Draft
cryptotavares wants to merge 2 commits into
Draft
cryptotavares wants to merge 2 commits into
cryptotavares wants to merge 2 commits into
Conversation
Ticket P1-11. Browser a11y refs mapped to `role=<role>[name="<name>"]`, which is not unique: two "Send" buttons shared one selector, so a click failed with a strict-mode violation, and with `within` it could hit another row. - Refs whose role and name are shared with other refs of the snapshot get a DOM selector that is verified unique the way Playwright's CSS engine matches (unique data-testid, then id, then a path anchored at the nearest unique ancestor, nth-of-type or nth-child). Elements in open shadow roots get a chained `css=<host> >> css=<path>` selector. Built concurrently (at most 16 at a time) in a CDP isolated world, with no page globals (CSS.escape inlined), so LavaMoat scuttling cannot break it. - No index fallback: when no unique DOM selector can be built, the ref keeps the role selector, so Playwright's strict mode fails the action instead of acting on another element. Refs inside a closed shadow root cannot be targeted and fail with a clear error. - Role selectors use the explicit exact flag `s`. text= fallbacks stay strict as well. - Scoped snapshots prefix role selectors with the root (`css=<root> >> nth=0 >> ...`). - `within` + ref: the ref resolves page-wide and must be inside the parent; a ref outside it fails at once with a clear error, and an ambiguous ref is rejected instead of being narrowed by the parent. The refMap stays Map<string, string>; ISessionManager is unchanged.
cryptotavares
added this pull request to stack #63
October 24, 2026 17:18
- Queue wait: the second request could take tens of ms to reach the queue under host load, so queueMs came out below the 50 ms threshold. Hold the first request for 250 ms instead of 60 ms. - waitForTarget: the timeout passed to waitFor is the time left until the deadline, which can be 4999 ms on a slow runner. Accept (budget - 100, budget].
cryptotavares
force-pushed
the
cryptotavares/mmcli-p1-11-exact-refs
branch
from
October 25, 2026 09:46
41a404f to
e93a529
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack context: mm CLI v2, Phase 1, P1-11. Base: #65 (
cryptotavares/mmcli-p1-10-ext-speedups); followed by #67. Review #65 first.Problem
Browser refs use role-and-name selectors. Two elements with the same role and name therefore share one selector. A click fails in Playwright strict mode. With
--within, the parent can narrow that selector to a different element and click the wrong row.The Extension has repeated labels such as “Show QR code”, “Copy address”, and “Network options”. Ref identity must distinguish these elements without assuming AX-tree order matches Playwright locator order.
Solution
Build ref selectors at the browser discovery boundary. Keep the existing selector map consumed by Playwright tools and session managers.
flowchart TD A[AX snapshot candidates] --> B[Group normalized role-and-name selectors] B -->|unique role| C[Keep exact role selector with snapshot scope] B -->|duplicate role or generic| D[Build unique DOM selector in cached isolated world] D --> E{Selector result?} E -->|unique| H[Store selector in ref map] E -->|unavailable| F[Keep strict role or text fallback] E -->|closed shadow root| G[Store unreachable marker] C --> H F --> H H --> I[Resolve Playwright locator for action] G --> X[Reject ref action before locator creation] classDef new fill:#fff3cd,stroke:#d39e00 classDef stop fill:#f8d7da,stroke:#c82333 class B,C,D,E,F,G new class X stopYellow boxes are new. Red boxes stop the action.
The DOM builder prefers a unique
data-testid, then a uniqueid, then a unique ancestor-anchored path. It usesnth-of-typeornth-childonly within a path whose uniqueness it checks. Open shadow roots use chained host selectors:css=<host> >> css=<inside>.callOnNodeInIsolatedWorldresolves the backend node in the cached session and isolated world introduced by P1-10. This keeps selector construction outside the Extension's scuttled main-world globals. Lookups run concurrently with a fixed bound. Session or world failures retry once; missing nodes and page-function exceptions do not.Role and text selectors normalize whitespace and escape quoted values. The explicit
sflag documents the role engine's existing exact, case-sensitive behavior. Scoped snapshots prefix role and text selectors withcss=<root> >> nth=0. Unique DOM selectors remain page-wide; the scope root itself uses the root selector.For
--withinplus a ref,waitForTargetchecks page-wide ambiguity before containment. It rechecks an outside-parent result after a short delay to allow DOM movement. Late targets wait for attachment within the shared timeout budget.What changes
nth=guess--withinMap<string, string>andISessionManagerRef-specific errors include
Ref e2 is not inside the within elementandRef e2 is inside a closed shadow root: it cannot be targeted. An ambiguous ref reportsRef e2 is ambiguous (9 matches); take a new snapshot or target it by testId or selector.Decisions for reviewers
ISessionManagerwith selector strings.inert, shadow roots, slots, andaria-ownscan produce different AX and Playwright orders.cdp.ts.--withinas containment for ref targetsReview guide
Read the files in this order:
src/tools/utils/discovery.tswithinchecks.src/tools/utils/in-page.tssrc/tools/utils/cdp.tssrc/tools/utils/{discovery,in-page,cdp}.test.tssrc/server/create-server.test.tsREADME.md,SKILL.md--withinguidance.Testing
e93a529e34cc3b1c65615e87bbd282a23e16f504:CI=true yarn vitest run src/tools/utils/cdp.test.ts src/tools/utils/discovery.test.ts src/tools/utils/in-page.test.ts src/server/create-server.test.ts src/platform/playwright-driver.test.ts --coverage.enabled=false --maxWorkers=2passes: 5 files, 352 tests, no type errors.withintargets.ProofandUnit testssections below retain the original historical evidence. The fresh check does not repeat that live validation.Breaking changes
None. Refs are created per snapshot, so no caller can depend on the old selector strings. Behaviour changes, both towards failing instead of hitting the wrong element:
within+ a ref outside the parent now fails fast instead of acting inside the parent;Consumer impact
test/e2e/playwright/llm-workflow/tests/llm-workflow/Proof
Tested with a yalc build of this branch in the Extension (e2e build, headless, fixture wallet). For the "before" I built #65 (50eb396) in a temp clone and ran the same script.
--within <first row>+ ref of the Solana row's QR buttonrole=was already exact)within, scoped snapshot)describe-screentoolMs p50: home / networks / receiveGeneric Chromium cases (built dist, real Chromium; none of these occur on the Extension today): modal
<dialog>,inert, open, nested and slotted shadow DOM,aria-owns, and a page whose main world makesCSS,Element,Node,windowandArray.fromthrow. 39/41 refs click the right element. The 2 closed-shadow refs fail on purpose, and none hits a wrong element. On 40 duplicate buttons, snapshot time went from 42 to 33 ms, because lookups now run concurrently.Pre-existing issues found (not caused by this PR, unchanged on both builds):
href.describe-screentakes ~4 s on networks, receive, the account list and About; a fixed timeout is suspected.Unit tests
New tests:
text=stays strict; nonthfallback.in-pagebuilder in avmwith a fake DOM: testid, then id, then path; shadow roots and host chaining; slotted:nth-child; closed shadow marker; scuttled globals;CSS.escapeparity.callOnNodeInIsolatedWorld: retry, node gone, exceptions.within: ambiguous ref, ref not inside the parent, and the race when the element moves into the parent between checks.41a404fdeflakes the P1-01 queue-wait timing test: under host load it saw 45 ms against a 50 ms threshold, so it now holds the first request for 250 ms.