diff --git a/packages/browser/src/client/tester/tester.ts b/packages/browser/src/client/tester/tester.ts index db415b6d70b9..ac076f49ff61 100644 --- a/packages/browser/src/client/tester/tester.ts +++ b/packages/browser/src/client/tester/tester.ts @@ -51,6 +51,12 @@ channel.addEventListener('message', async (e) => { return } + if (data.event.startsWith('response:')) { + return + } + + let eventHandled = true + switch (data.event) { case 'execute': { const { method, files, context } = data @@ -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) diff --git a/test/browser/fixtures/response-event-loop/response-guard.test.ts b/test/browser/fixtures/response-event-loop/response-guard.test.ts new file mode 100644 index 000000000000..1660efce260e --- /dev/null +++ b/test/browser/fixtures/response-event-loop/response-guard.test.ts @@ -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) +}) diff --git a/test/browser/fixtures/response-event-loop/vitest.config.ts b/test/browser/fixtures/response-event-loop/vitest.config.ts new file mode 100644 index 000000000000..52962b06a2cc --- /dev/null +++ b/test/browser/fixtures/response-event-loop/vitest.config.ts @@ -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, + }, + }, +}) diff --git a/test/browser/package.json b/test/browser/package.json index 03007895835e..7df18b22a309 100644 --- a/test/browser/package.json +++ b/test/browser/package.json @@ -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", diff --git a/test/browser/specs/response-event-loop.test.ts b/test/browser/specs/response-event-loop.test.ts new file mode 100644 index 000000000000..58c36eb5f95a --- /dev/null +++ b/test/browser/specs/response-event-loop.test.ts @@ -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', + }, + }) +})