You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Don't rely on module-level state for Playwright selector engine registration (#1022)
# why
This is to fix some undesired behavior for a common dev workflow with
`next dev`. Introduced in
#954.
There is now module-level state in `lib/StagehandPage.ts` (the
`stagehandSelectorRegistered` boolean) used to guard against multiple
calls to `selectors.register` (a Playwright function which sets
module-level state).
This used in the function `ensureStagehandSelectorEngine`. This guard
exists because calling `selectors.register` with the same string more
than once will cause an error.
The problem is that `next dev` repeatedly reloads the `stagehand` module
whenever we first start up our dev server or make changes, but without
always reloading the underlying `playwright` module.
<details>
<summary>So, we get lots of errors like this.</summary>
```zsh
[2025-08-22 12:58:10] web:dev: Error in Inngest task {
[2025-08-22 12:58:10] web:dev: error: {
[2025-08-22 12:58:10] web:dev: error: 'NonRetriableError',
[2025-08-22 12:58:10] web:dev: message: "Hey! We're sorry you ran into an error. \n" +
[2025-08-22 12:58:10] web:dev: 'Stagehand version: 2.4.3 \n' +
[2025-08-22 12:58:10] web:dev: 'If you need help, please open a Github issue or reach out to us on Slack: https://stagehand.dev/slack\n' +
[2025-08-22 12:58:10] web:dev: '\n' +
[2025-08-22 12:58:10] web:dev: 'Full error:\n' +
[2025-08-22 12:58:10] web:dev: 'selectors.register: "stagehand" selector engine has been already registered',
[2025-08-22 12:58:10] web:dev: name: 'Error',
[2025-08-22 12:58:10] web:dev: stack: 'StagehandDefaultError: \n' +
[2025-08-22 12:58:10] web:dev: "Hey! We're sorry you ran into an error. \n" +
[2025-08-22 12:58:10] web:dev: 'Stagehand version: 2.4.3 \n' +
[2025-08-22 12:58:10] web:dev: 'If you need help, please open a Github issue or reach out to us on Slack: https://stagehand.dev/slack\n' +
[2025-08-22 12:58:10] web:dev: '\n' +
[2025-08-22 12:58:10] web:dev: 'Full error:\n' +
[2025-08-22 12:58:10] web:dev: 'selectors.register: "stagehand" selector engine has been already registered\n' +
[2025-08-22 12:58:10] web:dev: ' at _StagehandPage.eval (webpack-internal:///(rsc)/../../node_modules/.pnpm/@[email protected][email protected][email protected][email protected][email protected]/node_modules/@browserbasehq/stagehand/dist/index.js:4077:15)\n' +
[2025-08-22 12:58:10] web:dev: ' at Generator.throw (<anonymous>)\n' +
[2025-08-22 12:58:10] web:dev: ' at rejected (webpack-internal:///(rsc)/../../node_modules/.pnpm/@[email protected][email protected][email protected][email protected][email protected]/node_modules/@browserbasehq/stagehand/dist/index.js:73:29)'
[2025-08-22 12:58:10] web:dev: },
```
</details>
**TL;DR The `stagehand` module state guard, to guard the Playwright
module state, becomes out of sync with Playwright.**
This is not really `stagehand`'s "fault". It appears to be
`next`-specific behavior combined with some logic to get around funky
module-level `playwright` state. But it is causing a lot of friction on
our team; I think module-level state is risky in general for this
reason.
# what changed
My proposed fix is to wrap this `selectors.`register call in a specific
`try`/`catch` that looks for, and ignores, the specific error `/selector
engine has been already registered/` in
`packages/playwright-core/src/client/selectors.ts` instead of using the
`stagehandSelectorRegistered` boolean.
# test plan
Existing evals.
And this works locally as expected when I build our system against this
version, but without the error, no matter how many times the module is
reloaded.
0 commit comments