From 361c463d53451c8dea78d4bc08d563988b7215f7 Mon Sep 17 00:00:00 2001 From: Shane Daniel <135388+simshanith@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:58:42 -0700 Subject: [PATCH] fix(browser): ignore channel events in a tester opened as a top-level window (fix #9379) A test can open the tester page as a top-level window, e.g. by clicking an anchor with target="_blank" whose href resolves to the tester URL (href="" resolves to the current page, ?sessionId=...&iframeId=... included). The duplicate tester joins the BroadcastChannel with the same iframeId and echoes ack:/response: events back to the real tester, each received message spawning an ack and a response in the twin, generating events recursively until the run fails with hundreds of thousands of "Unknown event" errors and the orchestrator times out waiting for the real tester's acknowledgements. The tester now only participates in the iframe channel when it is actually embedded in the orchestrator. --- packages/browser/src/client/tester/tester.ts | 18 ++++-- test/browser/specs/duplicate-tester.test.ts | 60 ++++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 test/browser/specs/duplicate-tester.test.ts diff --git a/packages/browser/src/client/tester/tester.ts b/packages/browser/src/client/tester/tester.ts index 508378f05c3b..00a479874e29 100644 --- a/packages/browser/src/client/tester/tester.ts +++ b/packages/browser/src/client/tester/tester.ts @@ -34,9 +34,17 @@ const traces = new Traces({ let rootTesterSpan: ReturnType | undefined getBrowserState().traces = traces +// a tester opened as a top-level window (e.g. a test clicking +// at the tester URL) would echo `ack:`/`response:` events back recursively +const isEmbedded = window.self !== window.top + channel.addEventListener('message', async (e) => { const data = e.data + if (!isEmbedded) { + return + } + if (!isEvent(data)) { await client.waitForConnection() const error = new Error(`Unknown message: ${JSON.stringify(e.data)}`) @@ -139,10 +147,12 @@ getBrowserState().iframeId = iframeId registerPageMarkHandler((name, options) => page.mark(name, options)) -channel.postMessage({ - event: 'ready', - iframeId, -}) +if (isEmbedded) { + channel.postMessage({ + event: 'ready', + iframeId, + }) +} let contextSwitched = false diff --git a/test/browser/specs/duplicate-tester.test.ts b/test/browser/specs/duplicate-tester.test.ts new file mode 100644 index 000000000000..667bd3555a2e --- /dev/null +++ b/test/browser/specs/duplicate-tester.test.ts @@ -0,0 +1,60 @@ +import { expect, test } from 'vitest' +import { instances, runInlineBrowserTests } from './utils' + +// clicking opens the tester URL (query string included) +// as a top-level window — the duplicate tester must not join the iframe channel +test('a popup opened at the tester URL does not corrupt the iframe channel', { timeout: 60_000 }, async () => { + const { stderr, exitCode, testTree } = await runInlineBrowserTests( + { + 'popup.test.ts': ` + import { expect, test } from 'vitest' + import { userEvent } from 'vitest/browser' + + test('clicking a target="_blank" anchor', async () => { + const anchor = document.createElement('a') + // href="" resolves to location.href — the tester page itself + anchor.setAttribute('href', '') + anchor.setAttribute('target', '_blank') + anchor.textContent = 'open' + document.body.appendChild(anchor) + + // real user gesture so the browser allows the popup + await userEvent.click(anchor) + + // give the popup time to boot the duplicate tester + await new Promise(resolve => setTimeout(resolve, 1000)) + + expect(anchor.target).toBe('_blank') + }) + `, + // more execute/cleanup events after the duplicate tester exists + 'second.test.ts': ` + import { expect, test } from 'vitest' + + test('a following test file still runs cleanly', () => { + expect(1 + 1).toBe(2) + }) + `, + }, + { + // fail fast: a corrupted channel loses the real tester's `ack:cleanup` + env: { VITEST_BROWSER_IFRAME_TIMEOUT: '10000' }, + browser: { + instances: [instances[0]], + }, + }, + ) + + expect(stderr).not.toContain('Unknown event') + expect(exitCode).toBe(0) + expect(testTree()).toMatchInlineSnapshot(` + { + "popup.test.ts": { + "clicking a target="_blank" anchor": "passed", + }, + "second.test.ts": { + "a following test file still runs cleanly": "passed", + }, + } + `) +})