diff --git a/scripts/tests/test_release_lifecycle_rehearsal.py b/scripts/tests/test_release_lifecycle_rehearsal.py index 97e5a7c7e6..00131f2f5b 100644 --- a/scripts/tests/test_release_lifecycle_rehearsal.py +++ b/scripts/tests/test_release_lifecycle_rehearsal.py @@ -490,11 +490,12 @@ def verify_browser_recovery_controls(controls_path: Path) -> None: fixed = {(kind, "upgrade") for kind in ("success", "anonymous-probes", "restart-gap", "wrong-preview", "silent-stream", "stuck-modal")} fixed.add(("success", "recovery")) + fixed.update((f"cleanup-{status}", "upgrade") for status in (401, 403)) for endpoint in ("navigation", "authenticated-navigation", "login", "initial-version", "authenticated-api", "check", "apply", "stream", "version", "health", "read"): fixed.update((f"{endpoint}-{status}", "upgrade") for status in (401, 403)) results = controls["results"] - assert len(results) == len(fixed) == 29 + assert len(results) == len(fixed) == 31 assert {(row["fixture"], row["mode"]) for row in results} == fixed source = HARNESS_PATH.read_text(encoding="utf-8") function = "run_browser_journey() {" + source.split("run_browser_journey() {", 1)[1].split("\n}\n", 1)[0] + "\n}" diff --git a/tests/integration/scripts/release-browser-journey.cjs b/tests/integration/scripts/release-browser-journey.cjs index 12fe55ce9b..1cb3c280fd 100644 --- a/tests/integration/scripts/release-browser-journey.cjs +++ b/tests/integration/scripts/release-browser-journey.cjs @@ -243,8 +243,13 @@ async function runJourney(browser, options) { throw error; } finally { await closeContext(); + if (record.access_refused) { + record.status = 'failed'; + record.failure_type ||= 'AssertionOrOperationError'; + } if (options.outputDir) fs.writeFileSync(path.join(options.outputDir, `browser-${options.mode}.json`), JSON.stringify(record, null, 2) + '\n'); assert.equal(record.context_closed, true, 'browser context cleanup failed'); + requireAccess(); // A response received during cleanup cannot become a pass. } return record; } diff --git a/tests/integration/scripts/release-browser-journey.test.cjs b/tests/integration/scripts/release-browser-journey.test.cjs index dcca53da86..8a20120df5 100644 --- a/tests/integration/scripts/release-browser-journey.test.cjs +++ b/tests/integration/scripts/release-browser-journey.test.cjs @@ -79,6 +79,7 @@ function fixture(kind) { if (refuse('authenticated-api')) return; return json(200, {}); } + if (url.pathname === '/api/cleanup-control') { refuse('cleanup'); return; } if (url.pathname === '/api/updates/check') { if (refuse('check')) return; return json(200, { latestVersion: kind === 'wrong-preview' ? '6.5.0-rc.1' : to.slice(1), downloadUrl }); @@ -143,6 +144,7 @@ async function runControls(outputDir) { ['anonymous-probes', 'upgrade', true, 1, 1], ['restart-gap', 'upgrade', true, 1, 1], ['wrong-preview', 'upgrade', false, 0, 1], ['silent-stream', 'upgrade', false, 1, 1], ['stuck-modal', 'upgrade', false, 1, 1], + ['cleanup-401', 'upgrade', false, 1, 1], ['cleanup-403', 'upgrade', false, 1, 1], ]; for (const endpoint of ['navigation', 'authenticated-navigation', 'login', 'initial-version', 'authenticated-api', 'check', 'apply', 'stream', 'version', 'health', 'read']) { @@ -158,7 +160,22 @@ async function runControls(outputDir) { const folder = path.resolve(outputDir, `${kind}-${mode}`); fs.mkdirSync(folder, { recursive: true }); let passed = false; try { - await runJourney(browser, { origin: `http://127.0.0.1:${target.server.address().port}`, mode, from, to, + // A real denied HTTP response deliberately arrives during cleanup, + // after the main checks. The observer must not leave a passed receipt. + // Only this fixture delays context disposal; no response is fabricated. + const testBrowser = kind.startsWith('cleanup-') ? { + version: () => browser.version(), + newContext: async options => { + const context = await browser.newContext(options); + const close = context.close.bind(context); + context.close = async () => { + await context.pages()[0].evaluate(() => fetch('/api/cleanup-control')); + await close(); + }; + return context; + }, + } : browser; + await runJourney(testBrowser, { origin: `http://127.0.0.1:${target.server.address().port}`, mode, from, to, expected: mode === 'upgrade' ? to : from, auth: { username: 'synthetic', password: 'synthetic' }, timeout: 1500, readinessTimeout: 3000, outputDir: folder }); passed = true;