Skip to content

perf: fix quadratic text/nth lookup (12.5s to 27ms) - #3127

Open
antonvishal wants to merge 4 commits into
browserbase:mainfrom
antonvishal:vishal/perf-text-selectors
Open

antonvishal wants to merge 4 commits into
browserbase:mainfrom
antonvishal:vishal/perf-text-selectors

Conversation

@antonvishal

@antonvishal antonvishal commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Why

Text lookups rescanned large lists repeatedly. On a page with 10,000 text matches:

Operation Stagehand before Playwright This PR
count() 4,124 ms 18 ms 26 ms
nth(2).innerText() 12,498 ms 19 ms 27 ms

Medians across three local browser runs, with three calls per run. Every call returned the expected count and text.

What

Filter innermost matches by walking ancestors, resolve the requested index directly, and skip unnecessary DOM-node materialization for locator actions. Preserve match order, shadow-root boundaries, masks, cancellation and low-level node IDs.

How

1,178 extension/TypeScript tests and 21 browser integration tests passed. Builds and extension typecheck passed.

Metric Stagehand before Playwright This PR
Wall time 11.44 s 16.61 s 11.20 s
Flow p50 255 ms 456 ms 244 ms
Session p50 317 ms 484 ms 305 ms
Browser CPU seconds 50.82 39.33 48.74
Peak browser RSS 5,693 MB 4,443 MB 5,578 MB
Failures 0/384 0/384 0/384

Medians of three local runs, each with 128 billing flows and four reused browsers. No LLM calls.


Summary by cubic

Fixes the quadratic text-selector lookup that made large text queries painfully slow. Across three local browser runs, count on 10,000 matches fell from 4,124 to 26 ms, and reading the third match fell from 12,498 to 27 ms. Locator actions now skip unnecessary DOM-node materialization while low-level resolver callers retain frontend node IDs. Filtering of innermost matches is now a single pass over ancestors instead of an all-pairs containment comparison, and nth resolves the requested index directly so masks still acquire and release all matches.

Behavior changes

  • Invalid indices return null without acquiring handles.
  • Match order, shadow-root boundaries, and cancellation behavior are preserved.
  • Synchronized the Go extension protocol version with the updated extension assets.

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

View guided diff

@changeset-bot

changeset-bot Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b75a841

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 Patch
@browserbasehq/stagehand-go Patch
@browserbasehq/stagehand-python Patch
@browserbasehq/stagehand-extension 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 7, 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 7, 2026
@antonvishal antonvishal changed the title perf: fix quadratic text/nth lookup (12.4s to 28ms) perf: fix quadratic text/nth lookup (12.5s to 27ms) Oct 7, 2026
@antonvishal
antonvishal marked this pull request as ready for review October 7, 2026 16:46
…lectors

# Conflicts:
#	packages/sdk-go/internal/extensionassets/stagehand-extension.zip
…erf-text-selectors

# Conflicts:
#	packages/sdk-go/internal/extensionassets/stagehand-extension.zip

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