From 5cded74b6d7c6854c12e9adbaf0b16fd908a4e05 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:22:34 +0800 Subject: [PATCH] fix: bound browser scenario initial navigation during startup --- docs/browser-scenario-contract.md | 6 ++-- src/browser/PlaywrightScenarioSession.test.ts | 31 +++++++++++++++++++ src/browser/PlaywrightScenarioSession.ts | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/browser-scenario-contract.md b/docs/browser-scenario-contract.md index 4a7626bfe6..6364c977a5 100644 --- a/docs/browser-scenario-contract.md +++ b/docs/browser-scenario-contract.md @@ -27,10 +27,12 @@ Duration, action, and navigation timeouts remain separate from CDP command deadlines. `command_timeout_ms` (default 20000; omit it to keep that default and leave it out of Evidence) bounds launch startup, the connect-over-CDP handshake, target lookup, environment commands, and storage CDP commands, -including replies from attached child targets. Set it in the scenario JSON. +including replies from attached child targets. It also bounds the initial +`start_url` navigation through the load event; a redirect or replacement +navigation cannot leave startup waiting indefinitely. Set it in the scenario JSON. `capture-browser-scenario` has no `--command-timeout-ms` option. It does not change action `timeout_ms`, `wait_for_timeout.duration_ms`, or -navigation. Service-worker restoration keeps a fixed 5000 ms grace and does +action navigation. Service-worker restoration keeps a fixed 5000 ms grace and does not inherit a raised `command_timeout_ms`. CDP discovery stays on its own fixed deadline. When service workers are blocked, the registration check before discovery has its own fixed 10000 ms deadline; `command_timeout_ms` diff --git a/src/browser/PlaywrightScenarioSession.test.ts b/src/browser/PlaywrightScenarioSession.test.ts index 3f80db464f..eeea4ae1ca 100644 --- a/src/browser/PlaywrightScenarioSession.test.ts +++ b/src/browser/PlaywrightScenarioSession.test.ts @@ -1453,3 +1453,34 @@ it("retains late opening failure cleanup after cancellation until the exact prof await provider.close(); } }); + +it.each(["launch", "connect"] as const)( + "bounds never-settling initial navigation and awaits %s cleanup", + async (mode) => { + vi.useFakeTimers(); + const { cleanup, page, storage } = fixture(); + const request = browserScenarioSchema.parse({ + ...(mode === "launch" ? scenario : connectedScenario("allow")), + command_timeout_ms: 250, + }); + page.goto.mockImplementation(() => new Promise(() => {})); + try { + const opening = PlaywrightScenarioSession.open(request, {}, {}).catch( + (cause: unknown) => cause, + ); + await vi.advanceTimersByTimeAsync(250); + const failure = await opening; + expect(failure).toMatchObject({ + _tag: "AnalysisTimeoutError", + operation: "capture_browser_scenario", + timeoutMs: 250, + }); + expect(cleanup.close).toHaveBeenCalledOnce(); + expect(storage?.close).toHaveBeenCalledWith(true); + expect(page.goto).toHaveBeenCalledOnce(); + } finally { + await cleanup.cleanupLease.release(); + vi.useRealTimers(); + } + }, +); diff --git a/src/browser/PlaywrightScenarioSession.ts b/src/browser/PlaywrightScenarioSession.ts index 414c14476d..8556ea2c58 100644 --- a/src/browser/PlaywrightScenarioSession.ts +++ b/src/browser/PlaywrightScenarioSession.ts @@ -221,7 +221,7 @@ export class PlaywrightScenarioSession implements BrowserScenarioSessionPort { waitUntil: "load", timeout: 0, }), - undefined, + scenario.command_timeout_ms, options.signal, ); await withPlaywrightExecutionBoundary(