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
53 changes: 33 additions & 20 deletions packages/browser-playwright/src/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,15 @@ export class PlaywrightBrowserProvider implements BrowserProvider {

private createMocker(): BrowserModuleMocker {
const idPredicates = new Map<string, (url: URL) => boolean>()
const sessionIds = new Map<string, string[]>()
const sessionIds = new Map<string, Set<string>>()

function predicateKey(sessionId: string, url: string) {
return `${sessionId}:${url}`
}

function normalizeUrl(url: string) {
return new URL(url, 'http://localhost').href
}

function createPredicate(sessionId: string, url: string) {
const moduleUrl = new URL(url, 'http://localhost')
Expand Down Expand Up @@ -293,20 +301,36 @@ export class PlaywrightBrowserProvider implements BrowserProvider {

return true
}
const ids = sessionIds.get(sessionId) || []
ids.push(moduleUrl.href)
const ids = sessionIds.get(sessionId) || new Set<string>()
ids.add(moduleUrl.href)
sessionIds.set(sessionId, ids)
idPredicates.set(predicateKey(sessionId, moduleUrl.href), predicate)
return predicate
}

function predicateKey(sessionId: string, url: string) {
return `${sessionId}:${url}`
async function unregisterPredicate(page: Page, sessionId: string, url: string): Promise<void> {
const normalizedUrl = normalizeUrl(url)
const key = predicateKey(sessionId, normalizedUrl)
const predicate = idPredicates.get(key)
if (!predicate) {
return
}

await page.context().unroute(predicate).finally(() => {
idPredicates.delete(key)

const ids = sessionIds.get(sessionId)
ids?.delete(normalizedUrl)
if (!ids?.size) {
sessionIds.delete(sessionId)
}
})
}

return {
register: async (sessionId: string, module: MockedModule): Promise<void> => {
const page = this.getPage(sessionId)
await unregisterPredicate(page, sessionId, module.url)
await page.context().route(createPredicate(sessionId, module.url), async (route) => {
if (module.type === 'manual') {
const exports = Object.keys(await module.resolve())
Expand Down Expand Up @@ -373,24 +397,13 @@ export class PlaywrightBrowserProvider implements BrowserProvider {
},
delete: async (sessionId: string, id: string): Promise<void> => {
const page = this.getPage(sessionId)
const key = predicateKey(sessionId, id)
const predicate = idPredicates.get(key)
if (predicate) {
await page.context().unroute(predicate).finally(() => idPredicates.delete(key))
}
await unregisterPredicate(page, sessionId, id)
},
clear: async (sessionId: string): Promise<void> => {
const page = this.getPage(sessionId)
const ids = sessionIds.get(sessionId) || []
const promises = ids.map((id) => {
const key = predicateKey(sessionId, id)
const predicate = idPredicates.get(key)
if (predicate) {
return page.context().unroute(predicate).finally(() => idPredicates.delete(key))
}
return null
})
await Promise.all(promises).finally(() => sessionIds.delete(sessionId))
const ids = sessionIds.get(sessionId)
const promises = [...(ids || [])].map(id => unregisterPredicate(page, sessionId, id))
await Promise.all(promises)
},
}
}
Expand Down
1 change: 1 addition & 0 deletions test/browser/fixtures/manual-mock-alias-leak/src/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function useModalStore() {}
17 changes: 17 additions & 0 deletions test/browser/fixtures/manual-mock-alias-leak/src/probe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it, vi } from 'vitest'

vi.mock('~/modal', () => ({
useModalStore() {},
}))

vi.mock('./modal', () => ({
useModalStore() {},
}))

import { probe } from './probe'

describe('probe', () => {
it('passes with duplicate manual mocks for the same module', () => {
expect(probe).toBe(true)
})
})
5 changes: 5 additions & 0 deletions test/browser/fixtures/manual-mock-alias-leak/src/probe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useModalStore } from '~/modal'

useModalStore()

export const probe = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { describe, expect, it } from 'vitest'

import { target } from './target'

describe('target', () => {
it('passes without registering a mock', () => {
expect(target).toBe(true)
})
})
5 changes: 5 additions & 0 deletions test/browser/fixtures/manual-mock-alias-leak/src/target.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useModalStore } from '~/modal'

useModalStore()

export const target = true
21 changes: 21 additions & 0 deletions test/browser/fixtures/manual-mock-alias-leak/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig } from 'vitest/config'
import { instances, provider } from '../../settings'

export default defineConfig({
resolve: {
alias: {
'~/': `${new URL('src/', import.meta.url).pathname}`,
},
},
test: {
browser: {
enabled: true,
provider,
instances,
headless: true,
},
fileParallelism: false,
maxWorkers: 1,
include: ['src/**/*.spec.ts'],
},
})
20 changes: 20 additions & 0 deletions test/browser/specs/mocking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,23 @@ test('mocking out of root', async () => {
expect(vitest.stdout).toReportPassedTest('basic.test.js', browser)
})
})

test('manual mocks do not leak across browser spec files when the same module is mocked via different ids', async () => {
const result = await runVitest({
root: 'fixtures/manual-mock-alias-leak',
})

onTestFailed(() => {
console.error(result.stdout)
console.error(result.stderr)
})

expect(result.stderr).toReportNoErrors()

instances.forEach(({ browser }) => {
expect(result.stdout).toReportPassedTest('src/probe.spec.ts', browser)
expect(result.stdout).toReportPassedTest('src/target.spec.ts', browser)
})

expect(result.exitCode).toBe(0)
})
Loading