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
{{ message }}
Repository navigation
[Bug]: MCP browser_navigate blocks for the whole navigation timeout when the page opens a dialog during load #42817
Repro with the repo's MCP fixtures and timeouts: { navigation: 3000 } to keep it short:
NAV after 4600 ms: ### Error
TimeoutError: browserBackend.callTool: Timeout 3000ms exceeded.
Call log:
- navigating to "http://localhost:PORT/", waiting until "domcontentloaded"
SNAP= ### Error
Error: Tool "browser_snapshot" does not handle the modal state.
### Modal state
- ["alert" dialog with message "hi"]: can be handled by browser_handle_dialog
Expected behavior
browser_navigate returns as soon as the dialog opens and reports it in the modal state, the same way browser_click does when a click opens a dialog (see alert dialog w/ race in tests/mcp/dialogs.spec.ts). The agent can then call browser_handle_dialog and continue.
Actual behavior
page.goto never reaches domcontentloaded while the dialog is open, so the call blocks for the full navigation timeout (60s by default) and then fails with a TimeoutError. Only after that does the modal state become visible. With the default timeout this is long enough to trip client-side tool timeouts, and one hostile or buggy page stalls the whole server for a minute per navigation.
Additional context
Tab.navigate() (packages/playwright-core/src/tools/backend/tab.ts) awaits page.goto directly, while other actions go through waitForCompletion, which races the action against TabEvents.modalState (_raceAgainstModalStates). Navigation should use the same race, returning early when a modal state appears.
The repro maps cleanly to the existing modal-state race. A few acceptance details may help keep the fix deterministic:
Make the navigation result discriminated: completed, blocked_by_dialog, failed, timeout or unknown, with the page/document generation and modal state captured at the same boundary.
Settle the race once. If a dialog appears after navigation starts but before DOMContentLoaded, return the modal state without losing the target page/context generation; browser_handle_dialog should then operate on that same generation rather than a newly selected page.
Cover dialog-before-commit and dialog-after-commit, alert/confirm/prompt, multiple dialogs, a dialog that closes during the race, ordinary navigation timeout with no dialog, and a second navigation while a modal is pending. Assert there is no stale modal receipt and no hidden retry.
Keep a transport/tool timeout distinct from a page result; after a timeout following dispatch, preserve unknown/reconciliation-required rather than replaying navigation automatically.
I maintain Hronaut, a local visible Browser/MCP workspace, and use this action-versus-modal/read-back boundary in evaluator material. I have not run Playwright’s fix, so I’m not claiming a result. AI-assisted note.
Activity
Hronom commentedon Sep 21, 2026
The repro maps cleanly to the existing modal-state race. A few acceptance details may help keep the fix deterministic:
I maintain Hronaut, a local visible Browser/MCP workspace, and use this action-versus-modal/read-back boundary in evaluator material. I have not run Playwright’s fix, so I’m not claiming a result. AI-assisted note.
chore: mark v0.0.83 (#1779)
chore: mark v0.1.22 (#473)
chore(deps): update all non-major dependencies (#199)
chore(deps): update dependency @playwright/mcp to v0.0.83 (#1108)
chore: mark v0.0.83 (microsoft#1779)
Update agent CLIs