[refactor]: use CDP for shadow root resolution#2032
[refactor]: use CDP for shadow root resolution#2032seanmcguire12 wants to merge 13 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: f76077f The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@seanmcguire12 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
5 issues found across 22 files
Confidence score: 2/5
- There is a concrete regression risk in
packages/core/lib/v3/understudy/page.ts:!pierceShadowcan passtext=,css=, or(-prefixed selectors intodocument.querySelector(), and the thrown error is swallowed, which can cause selector waits/lookups to fail in hard-to-debug ways. - Several high-confidence error-handling issues introduce generic/raw errors in
packages/core/lib/v3/understudy/page.ts,packages/core/lib/v3/understudy/selectorResolver.ts, andpackages/core/lib/v3/understudy/cdpClosedRoots.ts, which conflicts with typed/sanitized error requirements and raises integration/runtime consistency risk. - Pay close attention to
packages/core/lib/v3/understudy/page.ts,packages/core/lib/v3/understudy/selectorResolver.ts, andpackages/core/lib/v3/understudy/cdpClosedRoots.ts- selector parsing, sanitized exception flow, and CDP resource cleanup need tightening before merge.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/lib/v3/understudy/page.ts">
<violation number="1" location="packages/core/lib/v3/understudy/page.ts:1326">
P1: The `!pierceShadow` inline expression doesn't handle `text=`, `css=`-prefixed selectors, or `(`-starting XPath expressions. These are silently passed to `document.querySelector()`, which throws, and the catch swallows the error — causing the call to poll until timeout with no useful diagnostic.
Consider reusing or mirroring `FrameSelectorResolver.parseSelector`'s prefix-stripping logic to keep both code paths consistent.</violation>
<violation number="2" location="packages/core/lib/v3/understudy/page.ts:1428">
P1: Custom agent: **Exception and error message sanitization**
`waitForSelector` introduces generic `new Error()` throws with raw selector values in messages, violating typed/sanitized error requirements.</violation>
</file>
<file name="packages/core/lib/v3/understudy/cdpClosedRoots.ts">
<violation number="1" location="packages/core/lib/v3/understudy/cdpClosedRoots.ts:49">
P2: Custom agent: **Exception and error message sanitization**
Generic `new Error()` is thrown in core code instead of a typed/sanitized custom error class as required.</violation>
<violation number="2" location="packages/core/lib/v3/understudy/cdpClosedRoots.ts:92">
P2: When one `DOM.resolveNode` succeeds but the other fails, `Promise.all` rejects and the catch block swallows the error—leaking the successfully-resolved remote object handle. Resolve them independently so you can release any handle that was allocated before continuing.</violation>
</file>
<file name="packages/core/lib/v3/understudy/selectorResolver.ts">
<violation number="1" location="packages/core/lib/v3/understudy/selectorResolver.ts:218">
P1: Custom agent: **Exception and error message sanitization**
New selector resolver CDP failures can bubble as raw unsanitized errors instead of typed/sanitized Stagehand errors.</violation>
</file>
Architecture diagram
sequenceDiagram
participant P as Page / Frame
participant SR as FrameSelectorResolver
participant CR as cdpClosedRoots
participant CDP as CDP (Chrome DevTools Protocol)
participant B as Browser (Page Context)
Note over SR, B: NEW: CDP-Based Shadow Piercing Flow
P->>SR: resolveSelector(selector, pierceShadow: true)
SR->>CR: NEW: collectClosedShadowRoots(frame)
CR->>CDP: DOM.describeNode(objectId: document, pierce: true)
CDP-->>CR: Node tree (includes closed shadow roots)
loop For each closed host/root pair
CR->>CDP: NEW: DOM.resolveNode(backendNodeId)
CDP-->>CR: RemoteObjectId (handle)
end
CR-->>SR: ClosedShadowRootBundle (handles)
SR->>SR: NEW: select browser-side function (CSS/XPath/Text)
SR->>CDP: CHANGED: Runtime.callFunctionOn(documentHandle)
Note right of CDP: Arguments: [selector, ...hostRootPairs]
CDP->>B: Execute selectorRuntime (injected bootstrap)
B->>B: NEW: buildClosedRootsMap(pairs)
B->>B: Query elements (piercing open + mapped closed roots)
B-->>CDP: Return Element Handle(s) / Count
CDP-->>SR: Response (RemoteObjectIds)
SR->>SR: NEW: releaseClosedRootBundle()
loop For each temporary handle (host/root/doc)
SR->>CDP: Runtime.releaseObject(objectId)
end
SR-->>P: ResolvedNode(s)
alt NEW: Page.waitForSelector (pierceShadow: false)
P->>B: CHANGED: Runtime.evaluate (native-only polling)
Note right of B: Skips all shadow DOM logic
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| let resultArrayId: Protocol.Runtime.RemoteObjectId | undefined; | ||
| try { | ||
| const called = | ||
| await this.frame.session.send<Protocol.Runtime.CallFunctionOnResponse>( |
There was a problem hiding this comment.
P1: Custom agent: Exception and error message sanitization
New selector resolver CDP failures can bubble as raw unsanitized errors instead of typed/sanitized Stagehand errors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/understudy/selectorResolver.ts, line 218:
<comment>New selector resolver CDP failures can bubble as raw unsanitized errors instead of typed/sanitized Stagehand errors.</comment>
<file context>
@@ -108,260 +145,271 @@ export class FrameSelectorResolver {
+ let resultArrayId: Protocol.Runtime.RemoteObjectId | undefined;
+ try {
+ const called =
+ await this.frame.session.send<Protocol.Runtime.CallFunctionOnResponse>(
+ "Runtime.callFunctionOn",
+ {
</file context>
|
@seanmcguire12 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
5 issues found across 23 files
Confidence score: 3/5
- There is some real merge risk: multiple medium-to-high severity findings (6–7/10 with high confidence) indicate concrete behavioral and error-handling problems rather than just style issues.
- Most severe impact is user-facing error leakage in
packages/core/lib/v3/understudy/page.tsandpackages/core/lib/v3/understudy/cdpClosedRoots.ts, where typed exceptions include unsanitized selector/exception text that can expose runtime or CDP details. - Functional consistency issues may affect callers:
countXPathNativeinpackages/core/lib/v3/dom/selectorRuntime/index.tscan overcount versusqueryXPathNative, and visibility checks inpackages/core/lib/v3/understudy/page.tsdiverge betweenpierceShadowand non-pierceShadowpaths. - Pay close attention to
packages/core/lib/v3/understudy/cdpClosedRoots.ts,packages/core/lib/v3/understudy/page.ts, andpackages/core/lib/v3/dom/selectorRuntime/index.ts- sanitize user-facing errors, ensure handle cleanup on throw paths, and align selector counting/visibility semantics.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/lib/v3/understudy/cdpClosedRoots.ts">
<violation number="1" location="packages/core/lib/v3/understudy/cdpClosedRoots.ts:138">
P2: Remote object handles leak when `collectClosedShadowRoots` throws after acquiring `documentObjectId`. The catch block re-throws without releasing the document handle or any already-collected root/host handles in `roots`. Add cleanup before re-throwing to avoid accumulating leaked CDP remote objects.</violation>
<violation number="2" location="packages/core/lib/v3/understudy/cdpClosedRoots.ts:142">
P1: Custom agent: **Exception and error message sanitization**
User-facing typed error includes unsanitized underlying exception text, which can leak sensitive runtime/CDP details.</violation>
</file>
<file name="packages/core/lib/v3/dom/selectorRuntime/index.ts">
<violation number="1" location="packages/core/lib/v3/dom/selectorRuntime/index.ts:308">
P2: `countXPathNative` returns `snapshot.snapshotLength` which includes non-Element nodes, but `queryXPathNative` filters to `Element` instances only. This means `count` can report more matches than `query` actually returns for the same XPath. Either filter to Elements in the count, or use `XPathResult.ORDERED_NODE_SNAPSHOT_TYPE` and count only Element snapshot items.</violation>
</file>
<file name="packages/core/lib/v3/understudy/page.ts">
<violation number="1" location="packages/core/lib/v3/understudy/page.ts:1413">
P2: Visibility logic in the `!pierceShadow` inline expression diverges from `isElementVisible` used in the pierceShadow path. The inline check requires both `rect.width > 0` AND `rect.height > 0`, while `isElementVisible` only treats an element as invisible when `Math.max(width, height) === 0`. An element with one zero dimension (e.g. a horizontal rule with `height: 0`) would be considered invisible in the `!pierceShadow` path but visible in the `pierceShadow` path. Align the checks to avoid inconsistent `waitForSelector` behavior depending on the `pierceShadow` flag.</violation>
<violation number="2" location="packages/core/lib/v3/understudy/page.ts:1445">
P1: Custom agent: **Exception and error message sanitization**
`waitForSelector` throws a typed `TimeoutError` but leaks raw selector content in the user-facing error message instead of using a sanitized message.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client as SDK / User
participant Page as Page / Frame
participant Resolver as FrameSelectorResolver
participant CDP as CDP Session (Server)
participant Browser as Browser Runtime (Page)
Note over Client,Browser: NEW: CDP-based Selector Resolution Flow (Closed Shadow DOM)
Client->>Page: waitForSelector(".my-selector")
Page->>Resolver: resolveFirst(selector)
rect rgb(240, 240, 240)
Note right of Resolver: NEW: Closed Shadow Root Discovery
Resolver->>CDP: DOM.describeNode(document, depth: -1, pierce: true)
CDP-->>Resolver: Node tree with backendNodeIds
loop For each closed shadow host/root pair
Resolver->>CDP: DOM.resolveNode(backendNodeId)
CDP-->>Resolver: RemoteObjectId (host & root handles)
end
end
rect rgb(230, 240, 255)
Note right of Resolver: NEW: Ephemeral Runtime Execution
Resolver->>CDP: Runtime.callFunctionOn(document)
Note over Resolver,CDP: Arguments: [selector, ...hostRootHandles]
CDP->>Browser: Execute injected selectorRuntime
Browser->>Browser: Build temporary host-to-root map
Browser->>Browser: Perform shadow-piercing CSS/XPath/Text query
Browser-->>CDP: Array of matching Element handles
CDP-->>Resolver: RemoteObjectIds
end
rect rgb(255, 240, 240)
Note right of Resolver: NEW: Resource Cleanup
Resolver->>CDP: Runtime.releaseObject(host/root handles)
CDP-->>Resolver: OK
end
alt Element Found
Resolver-->>Page: RemoteObjectId
Page-->>Client: Success
else Timeout / Not Found
Resolver-->>Page: null
Page->>Page: CHANGED: Retry or throw TimeoutError
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
|
@seanmcguire12 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
3 issues found across 23 files
Confidence score: 3/5
- There is a concrete user-facing risk in
packages/core/lib/v3/understudy/cdpClosedRoots.ts: raw underlying error text is propagated intoStagehandDomProcessError, which conflicts with the stated sanitization requirement and could expose internal details. - Traversal logic in
packages/core/lib/v3/understudy/cdpClosedRoots.tsappears fragile because thebackendNodeIdguard can block recursive shadow-root walking, which may cause missed nodes and inconsistent DOM processing behavior. packages/core/lib/v3/dom/locatorScripts/xpathResolver.tsmay misclassify "fewer matches than target index" as an evaluation error, creating avoidable false-error paths when native XPath results are simply insufficient.- Pay close attention to
packages/core/lib/v3/understudy/cdpClosedRoots.tsandpackages/core/lib/v3/dom/locatorScripts/xpathResolver.ts- error sanitization and shadow/XPath edge-case handling are the main merge risks.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/lib/v3/understudy/cdpClosedRoots.ts">
<violation number="1" location="packages/core/lib/v3/understudy/cdpClosedRoots.ts:75">
P2: The `backendNodeId` guard inadvertently gates the recursive traversal into shadow roots. Move the `node.shadowRoots` iteration outside the `backendNodeId` check so subtree traversal always occurs, and only guard the `pairs.push` call that actually needs the host's `backendNodeId`.</violation>
<violation number="2" location="packages/core/lib/v3/understudy/cdpClosedRoots.ts:146">
P1: Custom agent: **Exception and error message sanitization**
Raw underlying error text is interpolated into a thrown `StagehandDomProcessError`, violating the sanitization requirement for user-facing errors.</violation>
</file>
<file name="packages/core/lib/v3/dom/locatorScripts/xpathResolver.ts">
<violation number="1" location="packages/core/lib/v3/dom/locatorScripts/xpathResolver.ts:54">
P2: Same pattern as `countXPathMatches`: `native.length > targetIndex` conflates "not enough matches" with "evaluation error". When no shadow DOM exists and the native evaluator found fewer matches than `targetIndex`, the old code returned `null`. The new code falls through to `queryXPathWithRoots`, which could return a spurious result from the custom XPath parser.</violation>
</file>
Architecture diagram
sequenceDiagram
participant P as Page / Frame
participant SR as FrameSelectorResolver
participant CDP as CDP (DOM / Runtime)
participant JS as Browser Context (selectorRuntime)
Note over P,JS: Shadow-Aware Selector Resolution Flow
P->>SR: resolveFirst(selector, pierceShadow: true)
rect rgb(240, 240, 240)
Note right of SR: NEW: CDP-based Discovery
SR->>CDP: collectClosedShadowRoots(frame)
CDP->>CDP: DOM.describeNode (depth: -1, pierce: true)
CDP-->>SR: Node tree with closedShadowRoots
loop For each closed host/root pair
SR->>CDP: DOM.resolveNode(backendNodeId)
CDP-->>SR: hostObjectId, rootObjectId (handles)
end
end
SR->>CDP: CHANGED: Runtime.callFunctionOn(documentObjectId)
Note right of SR: Passes remote handles as arguments
rect rgb(230, 245, 255)
Note over JS: selectorRuntime execution
JS->>JS: NEW: buildClosedRootsMap(handles)
JS->>JS: queryCss/Text/XPathWithRoots()
Note right of JS: Traverses DOM + Open/Closed ShadowRoots
end
CDP-->>SR: RemoteObjectId (Match)
rect rgb(240, 240, 240)
Note right of SR: NEW: Cleanup Handles
SR->>CDP: releaseRemoteObject(host/root handles)
SR->>CDP: releaseRemoteObject(resultArray)
end
SR-->>P: ResolvedNode (objectId)
alt pierceShadow: false
P->>JS: NEW: Native-only polling (no CDP overhead)
JS->>JS: document.querySelector / findByText
JS-->>P: boolean (settled)
else pierceShadow: true (Polling)
loop until timeout
P->>SR: resolveFirst(...)
SR-->>P: handle
opt state is "visible"
P->>CDP: Runtime.callFunctionOn(isElementVisible)
CDP-->>P: boolean
end
end
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| await releaseRemoteObject(frame, pair.rootObjectId); | ||
| } | ||
| if (error instanceof StagehandDomProcessError) throw error; | ||
| const message = error instanceof Error ? error.message : String(error); |
There was a problem hiding this comment.
P1: Custom agent: Exception and error message sanitization
Raw underlying error text is interpolated into a thrown StagehandDomProcessError, violating the sanitization requirement for user-facing errors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/understudy/cdpClosedRoots.ts, line 146:
<comment>Raw underlying error text is interpolated into a thrown `StagehandDomProcessError`, violating the sanitization requirement for user-facing errors.</comment>
<file context>
@@ -0,0 +1,151 @@
+ await releaseRemoteObject(frame, pair.rootObjectId);
+ }
+ if (error instanceof StagehandDomProcessError) throw error;
+ const message = error instanceof Error ? error.message : String(error);
+ throw new StagehandDomProcessError(
+ `failed to collect closed shadow roots: ${message}`,
</file context>
|
@seanmcguire12 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
4 issues found across 23 files
Confidence score: 3/5
- There is some meaningful regression risk here: in
packages/core/lib/v3/understudy/cdpClosedRoots.ts, raw caught error text is surfaced in a typed user-facing error, which can leak internal/CDP details and create confusing or unsafe error exposure. packages/core/lib/v3/dom/selectorRuntime/index.tsmixes globaldocumentcalls with functions typed asthis: Document; usingdocument.createTreeWalker/document.evaluateinstead ofthiscan produce incorrect behavior when called against non-global documents (for example iframe contexts).- The unused export in
packages/core/lib/v3/dom/locatorScripts/xpathResolver.tslooks low-severity housekeeping on its own, but the higher-severity document-context and error-sanitization issues keep this from being a low-risk merge. - Pay close attention to
packages/core/lib/v3/understudy/cdpClosedRoots.tsandpackages/core/lib/v3/dom/selectorRuntime/index.ts- sanitize surfaced errors and ensure all DOM APIs use the boundthis: Documentcontext consistently.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/lib/v3/dom/selectorRuntime/index.ts">
<violation number="1" location="packages/core/lib/v3/dom/selectorRuntime/index.ts:278">
P2: Uses global `document.evaluate` instead of `this.evaluate`. Since the function binds `this: Document`, callers can pass a different document (e.g. an iframe's). Mixing the global `document` with a foreign context node throws `WRONG_DOCUMENT_ERR`.</violation>
<violation number="2" location="packages/core/lib/v3/dom/selectorRuntime/index.ts:322">
P2: Uses global `document.createTreeWalker` instead of `this.createTreeWalker`. Should use `this` consistently with the `this: Document` binding.</violation>
</file>
<file name="packages/core/lib/v3/dom/locatorScripts/xpathResolver.ts">
<violation number="1" location="packages/core/lib/v3/dom/locatorScripts/xpathResolver.ts:79">
P3: `resolveXPathComposedMatches` is exported but never imported or called anywhere in the codebase (including tests). Consider removing it to avoid maintaining dead code, or adding a test if it's intended as part of the public API.</violation>
</file>
<file name="packages/core/lib/v3/understudy/cdpClosedRoots.ts">
<violation number="1" location="packages/core/lib/v3/understudy/cdpClosedRoots.ts:149">
P1: Custom agent: **Exception and error message sanitization**
Raw caught error text is interpolated into a user-facing typed error, leaking unsanitized internal/CDP details.</violation>
</file>
Architecture diagram
sequenceDiagram
participant P as Page / Frame
participant R as FrameSelectorResolver
participant D as NEW: cdpClosedRoots
participant CDP as Chrome DevTools Protocol
participant B as Browser (Page Context)
Note over P,B: Selector Resolution Flow (with Shadow Piercing)
P->>R: resolveFirst(selector, pierceShadow: true)
R->>D: NEW: collectClosedShadowRoots(frame)
D->>CDP: DOM.describeNode(pierce: true, depth: -1)
CDP-->>D: Node tree with BackendNodeIds
loop For each closed shadow host/root pair
D->>CDP: DOM.resolveNode(backendNodeId)
CDP-->>D: RemoteObjectId (JS Handle)
end
D-->>R: ClosedShadowRootBundle (Handles)
R->>CDP: CHANGED: Runtime.callFunctionOn(selector, ...handles)
Note over CDP,B: Passes handles for Document, Hosts, and Roots
B->>B: NEW: selectorRuntime.queryCssWithRoots()
Note right of B: Reconstructs host-to-root map<br/>traverses shadow trees ephemeralally
B-->>CDP: Result (Element RemoteObjectId)
CDP-->>R: ResolvedNode Handle
R->>D: NEW: releaseRemoteObject(bundle handles)
D->>CDP: Runtime.releaseObject(objectId)
R-->>P: Element Handle
Note over P,B: Wait For Selector Flow (pierceShadow: false)
alt pierceShadow: false
P->>B: CHANGED: Runtime.evaluate(nativePollingScript)
Note right of B: Uses native querySelector<br/>No shadow piercing or CDP discovery
B-->>P: boolean (success/timeout)
end
Note over P,B: A11y / Snapshot Flow
P->>CDP: CHANGED: Runtime.evaluate(idOnly serialization)
Note right of B: resolveCssSelectorDeep() used<br/>instead of global piercer scripts
CDP-->>P: Element ObjectId
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| if (error instanceof StagehandDomProcessError) throw error; | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| throw new StagehandDomProcessError( | ||
| `failed to collect closed shadow roots: ${message}`, |
There was a problem hiding this comment.
P1: Custom agent: Exception and error message sanitization
Raw caught error text is interpolated into a user-facing typed error, leaking unsanitized internal/CDP details.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/understudy/cdpClosedRoots.ts, line 149:
<comment>Raw caught error text is interpolated into a user-facing typed error, leaking unsanitized internal/CDP details.</comment>
<file context>
@@ -0,0 +1,152 @@
+ if (error instanceof StagehandDomProcessError) throw error;
+ const message = error instanceof Error ? error.message : String(error);
+ throw new StagehandDomProcessError(
+ `failed to collect closed shadow roots: ${message}`,
+ );
+ }
</file context>
why
this pr replaces the old v3 shadow-piercing path with a cdp-based selector flow for closed shadow roots.
before this change, closed shadow root access depended on injected page-side code in files like
packages/core/lib/v3/dom/piercer.runtime.tsandpackages/core/lib/v3/understudy/piercer.ts.after this change, selector resolution uses cdp to collect closed roots in
packages/core/lib/v3/understudy/cdpClosedRoots.ts, then runs browser-side selector functions frompackages/core/lib/v3/dom/selectorRuntime/index.tswithout installing a persistent page global.this avoids persistent
windowobjects because cdp resolves real remote object handles fordocument, each closed shadow host, and each closedShadowRoot.packages/core/lib/v3/understudy/selectorResolver.tspasses those handles intoRuntime.callFunctionOn(...), and the browser-side helpers inpackages/core/lib/v3/dom/selectorRuntime/index.tsbuild a temporary host-to-root map for that single call. no shadow state is stored onwindow, and nothing patchesattachShadow.what changed
added
packages/core/lib/v3/understudy/cdpClosedRoots.tscollectClosedShadowRoots(...)finds closed shadow roots withDOM.describeNodeand resolves host/root handles withDOM.resolveNodereleaseRemoteObject(...)centralizes remote object cleanup for those handlesrewrote
packages/core/lib/v3/understudy/selectorResolver.tsresolveCss(...)andresolveText(...)now callqueryElementsAcrossRoots(...)resolveXPath(...)now goes throughqueryXPath(...)countCss(...),countText(...), andcountXPath(...)now use cdp-backed counting instead of the old injected locator invocation pathqueryXPath(...)andcountXPathMatches(...)choose betweenqueryXPathNative/countXPathNativeandqueryXPathWithRoots/countXPathWithRootsbased on whetherhasOpenShadowRoots(...)or any closed roots are presentreleaseClosedRootBundle(...)is used to clean up all host/root/document handles in one placeadded
packages/core/lib/v3/dom/selectorRuntime/index.tsqueryCssWithRoots(...)countCssWithRoots(...)queryTextWithRoots(...)countTextWithRoots(...)queryXPathWithRoots(...)countXPathWithRoots(...)queryXPathNative(...)countXPathNative(...)hasOpenShadowRoots(...)applyPredicates(...)andparseXPathSteps(...)frompackages/core/lib/v3/dom/locatorScripts/xpathParser.tssimplified
packages/core/lib/v3/dom/locatorScripts/xpathResolver.tsresolveXPathAtIndex(...),countXPathMatches(...), andresolveXPathComposedMatches(...)now delegate to the shared selector runtime functions instead of carrying a separate xpath engineupdated
packages/core/lib/v3/understudy/page.tswaitForSelector(...)now usesFrameSelectorResolverfor the shadow-aware pathpierceShadow: falseis passed,waitForSelector(...)uses a native-only in-page polling path so open and closed shadow dom are skippedupdated
packages/core/lib/v3/understudy/a11y/snapshot/focusSelectors.tsresolveObjectIdForXPath(...)andresolveObjectIdForCss(...)now use directRuntime.evaluatecalls in the frame's main worldresolveXPathSelector(...)andresolveCssSelectorDeep(...)removed the old injected shadow-piercing path
packages/core/lib/v3/understudy/piercer.tspackages/core/lib/v3/understudy/locatorInvocation.tspackages/core/lib/v3/dom/piercer.entry.tspackages/core/lib/v3/dom/piercer.runtime.tspackages/core/lib/v3/dom/rerenderMissingShadows.entry.tspackages/core/lib/v3/dom/rerenderMissingShadows.runtime.tspackages/core/lib/v3/dom/global.d.tspackages/core/lib/v3/dom/index.tspackages/core/lib/v3/understudy/context.tsno longer installs or preloads the piercer scriptsremoved old selector helper files that are no longer used by the runtime
packages/core/lib/v3/dom/locatorScripts/counts.tspackages/core/lib/v3/dom/locatorScripts/selectors.tspackages/core/lib/v3/dom/locatorScripts/waitForSelector.tsupdated
packages/core/scripts/build-dom-scripts.tsiife-stringbuild path that was used for the piercer artifactsselectorRuntime/index.tstest plan
packages/core/tests/integration/wait-for-selector.spec.tswas updated for the newpierceShadow: falsebehaviorpackages/core/tests/unit/snapshot-a11y-resolvers.test.tswas updated to match the new helper namingSummary by cubic
Replaced the v3 shadow-piercing path with a CDP-backed selector resolver for closed shadow DOM. This improves stability with handle cleanup, standardizes
TimeoutErrormessages, and adds non-piercingtext=support inwaitForSelector.New Features
Promise.allSettledand cleans up partial objects.selectorRuntimefor CSS/Text/XPath (native and composed) withhasOpenShadowRoots(...); XPath switches between native/composed based on open/closed roots.Page.waitForSelector:pierceShadow: trueusesFrameSelectorResolver;falseuses native-only polling and supportstext=; clearerTimeoutErrormessages.FrameSelectorResolverscoped to the target frame to return CSS/XPathobjectIds.Refactors
understudy/context.ts; deleted old DOM helpers and rerender scripts.dom/locatorScripts/xpathResolver.ts; XPath query/count now live inselectorRuntime.selectorRuntime.generated.tsand drops the IIFE build path.pierceShadow: falsebehavior, a11y focus resolvers, andselectorRuntimeXPath helpers.Written for commit f76077f. Summary will update on new commits. Review in cubic