From 46a3cf750b51c0f3b0226d0225675ed559882b80 Mon Sep 17 00:00:00 2001 From: Shane Daniel <135388+simshanith@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:35:15 -0700 Subject: [PATCH] fix(browser): ignore channel events in a tester opened as a top-level window (fix #9379) (#9381) --- 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 7129a15c6..c2790dda3 100644 --- a/packages/browser/src/client/tester/tester.ts +++ b/packages/browser/src/client/tester/tester.ts @@ -35,9 +35,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)}`) @@ -140,10 +148,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 000000000..667bd3555 --- /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", + }, + } + `) +}) -- 2.51.2