Skip to content

Commit 361c463

Browse files
committed
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.
1 parent b4dddb1 commit 361c463

2 files changed

Lines changed: 74 additions & 4 deletions

File tree

packages/browser/src/client/tester/tester.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,17 @@ const traces = new Traces({
3434
let rootTesterSpan: ReturnType<Traces['startContextSpan']> | undefined
3535
getBrowserState().traces = traces
3636

37+
// a tester opened as a top-level window (e.g. a test clicking <a target="_blank">
38+
// at the tester URL) would echo `ack:`/`response:` events back recursively
39+
const isEmbedded = window.self !== window.top
40+
3741
channel.addEventListener('message', async (e) => {
3842
const data = e.data
3943

44+
if (!isEmbedded) {
45+
return
46+
}
47+
4048
if (!isEvent(data)) {
4149
await client.waitForConnection()
4250
const error = new Error(`Unknown message: ${JSON.stringify(e.data)}`)
@@ -139,10 +147,12 @@ getBrowserState().iframeId = iframeId
139147

140148
registerPageMarkHandler((name, options) => page.mark(name, options))
141149

142-
channel.postMessage({
143-
event: 'ready',
144-
iframeId,
145-
})
150+
if (isEmbedded) {
151+
channel.postMessage({
152+
event: 'ready',
153+
iframeId,
154+
})
155+
}
146156

147157
let contextSwitched = false
148158

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { expect, test } from 'vitest'
2+
import { instances, runInlineBrowserTests } from './utils'
3+
4+
// clicking <a href="" target="_blank"> opens the tester URL (query string included)
5+
// as a top-level window — the duplicate tester must not join the iframe channel
6+
test('a popup opened at the tester URL does not corrupt the iframe channel', { timeout: 60_000 }, async () => {
7+
const { stderr, exitCode, testTree } = await runInlineBrowserTests(
8+
{
9+
'popup.test.ts': `
10+
import { expect, test } from 'vitest'
11+
import { userEvent } from 'vitest/browser'
12+
13+
test('clicking a target="_blank" anchor', async () => {
14+
const anchor = document.createElement('a')
15+
// href="" resolves to location.href — the tester page itself
16+
anchor.setAttribute('href', '')
17+
anchor.setAttribute('target', '_blank')
18+
anchor.textContent = 'open'
19+
document.body.appendChild(anchor)
20+
21+
// real user gesture so the browser allows the popup
22+
await userEvent.click(anchor)
23+
24+
// give the popup time to boot the duplicate tester
25+
await new Promise(resolve => setTimeout(resolve, 1000))
26+
27+
expect(anchor.target).toBe('_blank')
28+
})
29+
`,
30+
// more execute/cleanup events after the duplicate tester exists
31+
'second.test.ts': `
32+
import { expect, test } from 'vitest'
33+
34+
test('a following test file still runs cleanly', () => {
35+
expect(1 + 1).toBe(2)
36+
})
37+
`,
38+
},
39+
{
40+
// fail fast: a corrupted channel loses the real tester's `ack:cleanup`
41+
env: { VITEST_BROWSER_IFRAME_TIMEOUT: '10000' },
42+
browser: {
43+
instances: [instances[0]],
44+
},
45+
},
46+
)
47+
48+
expect(stderr).not.toContain('Unknown event')
49+
expect(exitCode).toBe(0)
50+
expect(testTree()).toMatchInlineSnapshot(`
51+
{
52+
"popup.test.ts": {
53+
"clicking a target="_blank" anchor": "passed",
54+
},
55+
"second.test.ts": {
56+
"a following test file still runs cleanly": "passed",
57+
},
58+
}
59+
`)
60+
})

0 commit comments

Comments
 (0)