Skip to content

fix(extension): resolve XPath self steps against the document - #3142

Open
mikhail-koviazin wants to merge 1 commit into
browserbase:mainfrom
mikhail-koviazin:fix/xpath-self-steps
Open

mikhail-koviazin wants to merge 1 commit into
browserbase:mainfrom
mikhail-koviazin:fix/xpath-self-steps

Conversation

@mikhail-koviazin

@mikhail-koviazin mikhail-koviazin commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

why

When the document contains a shadow root, XPath locators go through the composed-tree parser instead of document.evaluate(). parseXPathSteps reads a . step as a tag name, and no element has the local name ., so any path with a self step matches nothing: .//div, //section/./div, ./html/body.

document.evaluate() is called with the document as the context node, so natively .//div means //div. The composed-tree traversal starts at the document too, so the two should agree.

Measured with the published @browserbasehq/stagehand@4.0.0, fixture served over http. Without the shadow root every row agrees with native. With one unrelated attachShadow() call on the page:

XPath document.evaluate() Stagehand
.//div[@class='rel'] 2 0
//section/./div 2 0
//section/.//div[@class='rel'] 2 0
./html/body/section/div 2 0
//div[@class='rel'] 2 2 (control)

Fixture:

<section><div class="rel">one</div><div class="rel">two</div></section>
<div id="widget"></div>

There is no error, only a count of 0, so a script that checks for the element concludes it is not on the page.

what changed

  • parseXPathSteps drops a . step on the child axis. A leading . is the document, where traversal already starts, and /. selects the node it is applied to.
  • . inside a predicate ([.='Save']) is untouched, it never reaches this branch.

If #3096 lands first, the only conflict is the neighbouring line in the group list in scripts/test-integration.ts.

test plan

packages/extension/tests/xpath-self-steps.test.ts (new, unit): a leading ., a . step in the middle and at the end of a path, and a . predicate left as it was.

packages/sdk-ts/tests/integration/locatorXPathSelfSteps.test.ts (new, integration): the first three rows of the table against real Chrome, plus the element the first match resolves to. Served over http, because data: URLs stay on the native engine and never reach this parser. Registered in scripts/test-integration.ts.

On main (7ad8cb6a) the integration test fails with expected 0 to be 2 in both cases. With the change it passes, and so do locatorXPathTextPredicates, locatorCount, locatorNth, nestedDiv and waitForSelector, each run as its own file, and the extension unit suite.


Summary by cubic

Fixes XPath self steps like .//div and //section/./div returning no matches when a shadow root in the document routes locators through the composed-tree parser. The parser treated . as a tag name, so these expressions matched nothing; now they resolve against the document, matching document.evaluate() behavior.

  • Drops child-axis . steps in parseXPathSteps; a leading . refers to the document where traversal starts, and /. selects nothing new.
  • Leaves self steps inside predicates ([.='Save']) untouched.
  • Adds unit tests for the parser and integration tests covering real Chrome with a shadow root present.

Written for commit 2166665. Summary will update on new commits.

View guided diff

parseXPathSteps took a `.` step for a tag name, so `.//div` and
`//section/./div` matched nothing once a shadow root routed locators
through the composed-tree parser, while document.evaluate() found them.
Drop child-axis `.` steps: traversal already starts at the document,
which is also the context node document.evaluate() uses.
@changeset-bot

changeset-bot Bot commented Oct 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2166665

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 24 packages
Name Type
@browserbasehq/stagehand-extension Patch
@browserbasehq/stagehand-go Patch
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-python Patch
browse Patch
@browserbasehq/stagehand-examples Patch
@browserbasehq/stagehand-integrations-claude-cua-sdk Patch
@browserbasehq/stagehand-integrations Patch
@browserbasehq/eve Patch
@browserbasehq/stagehand-integrations-gemini-cua-sdk Patch
@browserbasehq/stagehand-integrations-example-pi-facade Patch
@browserbasehq/stagehand-integrations-claude-agent-sdk Patch
@browserbasehq/stagehand-integrations-example-claude-code-facade Patch
@browserbasehq/stagehand-integrations-codex-sdk Patch
@browserbasehq/stagehand-integrations-example-codex-facade Patch
@browserbasehq/stagehand-integrations-cursor-sdk Patch
@browserbasehq/stagehand-integrations-example-cursor-facade Patch
@browserbasehq/stagehand-integrations-deepagents-sdk Patch
@browserbasehq/stagehand-integrations-eve-sdk Patch
@browserbasehq/stagehand-integrations-fx-sdk Patch
@browserbasehq/stagehand-integrations-mastra-sdk Patch
@browserbasehq/stagehand-integrations-example-mastra-facade Patch
@browserbasehq/stagehand-integrations-pi-sdk Patch
@browserbasehq/stagehand-integrations-example-vercel-ai-facade Patch

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

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run.
Approving the latest commit mirrors it into an internal PR owned by the approver.
If new commits are pushed later, the internal PR stays open but is marked stale until someone approves the latest external commit and refreshes it.

@github-actions github-actions Bot added external-contributor Tracks PRs mirrored from external contributor forks. external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. labels Oct 8, 2026

This branch has not been deployed

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

Labels

external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. external-contributor Tracks PRs mirrored from external contributor forks.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant