Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions packages/browser/src/client/tester/tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ channel.addEventListener('message', async (e) => {
return
}

if (data.event.startsWith('response:')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not receive this even here at all, this doesn't fix the issue, it hides it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @sheremet-va - agreed this is a symptom and not the root cause.

investigating further and adding test case. will share plan

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't know exactly how iframes of two or more tests end up communicating, potentially an iframeId collison.

I've simulated the behavior with a second broadcast channel that echos with the response: prefix unguarded. Additionally added the guard around postMessage. With the browser package from main, the test exhibits the infinite loop that crashed my computer a few times. With the guards, it passes without error.

return
}

let eventHandled = true

switch (data.event) {
case 'execute': {
const { method, files, context } = data
Expand Down Expand Up @@ -92,15 +98,18 @@ channel.addEventListener('message', async (e) => {
break
}
default: {
eventHandled = false
const error = new Error(`Unknown event: ${(data as any).event}`)
unhandledError(error, 'Unknown Event')
}
}

channel.postMessage({
event: `response:${data.event}`,
iframeId: getBrowserState().iframeId!,
})
if (eventHandled) {
channel.postMessage({
event: `response:${data.event}`,
iframeId: getBrowserState().iframeId!,
})
}
})

const url = new URL(location.href)
Expand Down
33 changes: 33 additions & 0 deletions test/browser/fixtures/response-event-loop/response-guard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { expect, test } from 'vitest'
import { channel } from '@vitest/browser/client'

test('response:prefixed event is not processed by tester', async () => {
const url = new URL(location.href)
const sessionId = url.searchParams.get('sessionId')!
const iframeId = url.searchParams.get('iframeId')!

const secondChannel = new BroadcastChannel(`vitest:${sessionId}`)

const nestedResponses: string[] = []
const handler = (e: MessageEvent) => {
if (typeof e.data?.event === 'string' && e.data.event.startsWith('response:response:')) {
nestedResponses.push(e.data.event)
}
secondChannel.postMessage({
event: `response:${e.data.event}`,
iframeId: e.data.iframeId!,
})
}
secondChannel.addEventListener('message', handler)
secondChannel.postMessage({
event: 'response:test',
iframeId,
})

await new Promise(resolve => setTimeout(resolve, 5))

secondChannel.removeEventListener('message', handler)
secondChannel.close()

expect(nestedResponses).toHaveLength(0)
})
14 changes: 14 additions & 0 deletions test/browser/fixtures/response-event-loop/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
import { provider, instances } from '../../settings'

export default defineConfig({
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
test: {
browser: {
enabled: true,
provider,
instances,
},
},
})
1 change: 1 addition & 0 deletions test/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"test-setup-file": "vitest --root ./fixtures/setup-file",
"test-snapshots": "vitest --root ./fixtures/update-snapshot",
"test-broken-iframe": "vitest --root ./fixtures/broken-iframe",
"test-response-event-loop": "vitest --root ./fixtures/response-event-loop",
"coverage": "vitest --coverage.enabled --coverage.provider=istanbul --browser.headless=yes",
"test:browser:preview": "PROVIDER=preview vitest",
"test:browser:playwright": "PROVIDER=playwright vitest",
Expand Down
15 changes: 15 additions & 0 deletions test/browser/specs/response-event-loop.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect, test } from 'vitest'
import { runBrowserTests } from './utils'

test('unknown events do not cause infinite response event loop', async () => {
const { exitCode, testTree } = await runBrowserTests({
root: './fixtures/response-event-loop',
})

expect(exitCode).toBe(0)
expect(testTree()).toMatchObject({
'response-guard.test.ts': {
'response:prefixed event is not processed by tester': 'passed',
},
})
})
Loading