diff --git a/packages/browser-playwright/src/playwright.ts b/packages/browser-playwright/src/playwright.ts index cbe09c45b..b945a5da2 100644 --- a/packages/browser-playwright/src/playwright.ts +++ b/packages/browser-playwright/src/playwright.ts @@ -246,6 +246,7 @@ export class PlaywrightBrowserProvider implements BrowserProvider { private browserPromise: Promise | null = null private closing = false + private armedContexts = new WeakMap>() public tracingContexts: Set = new Set() public pendingTraces: Map = new Map() @@ -390,6 +391,54 @@ export class PlaywrightBrowserProvider implements BrowserProvider { return this.browserPromise } + private armInterception(page: Page): Promise { + if (this.browserName !== 'chromium') { + return Promise.resolve() + } + const context = page.context() + const armed = + this.armedContexts.get(context) ?? + this.probeInterception(page).catch((error) => { + this.armedContexts.delete(context) + throw error + }) + this.armedContexts.set(context, armed) + return armed + } + + private async probeInterception(page: Page): Promise { + const probeUrl = '/__vitest_interception_probe__' + const probePattern = `**${probeUrl}` + const context = page.context() + await context.route(probePattern, (route) => + route.fulfill({ + status: 204, + headers: { 'x-vitest-probe': '1' }, + }), + ) + const deadline = Date.now() + 5_000 + while (Date.now() < deadline) { + const intercepted = await page + .evaluate( + (url) => + fetch(url, { cache: 'no-store', signal: AbortSignal.timeout(1_000) }).then( + (r) => r.headers.has('x-vitest-probe'), + () => false, + ), + probeUrl, + ) + .catch(() => false) + if (intercepted) { + return + } + await new Promise((resolve) => setTimeout(resolve, 10)) + } + await context.unroute(probePattern) + throw new Error( + `Cannot verify that ${this.browserName} request interception is active after 5s; module mocks would not apply reliably (#8339)`, + ) + } + private createMocker(): BrowserModuleMocker { const idPredicates = new Map boolean>() const sessionIds = new Map>() @@ -434,6 +483,7 @@ export class PlaywrightBrowserProvider implements BrowserProvider { return { register: async (sessionId: string, module: MockedModule): Promise => { const page = this.getPage(sessionId) + await this.armInterception(page) const { url: moduleUrl, predicate } = createPredicate(module.url) const key = predicateKey(sessionId, moduleUrl) const existingPredicate = idPredicates.get(key) diff --git a/test/browser/fixtures/mocking/interception-probe.test.ts b/test/browser/fixtures/mocking/interception-probe.test.ts new file mode 100644 index 000000000..29328c620 --- /dev/null +++ b/test/browser/fixtures/mocking/interception-probe.test.ts @@ -0,0 +1,9 @@ +import { expect, test, vi } from 'vitest' +import { server } from 'vitest/browser' + +vi.mock(import('./src/mocks_factory')) + +test.runIf(server.config.name === 'chromium')('request interception is live once a mock is registered', async () => { + const response = await fetch('/__vitest_interception_probe__', { cache: 'no-store' }) + expect(response.headers.get('x-vitest-probe')).toBe('1') +})