Installs patch-package (^8.0.0) and a postinstall script, then applies the diff from vitest PR #10267 against @vitest/browser-playwright@4.1.0. What the patch changes (in dist/index.js): - createPredicate(sessionId, url) → createPredicate(url): factory becomes pure, returns { url, predicate } instead of mutating sessionIds / idPreficates as a side-effect. - sessionIds value type: array → Set (deduplicates resolved URLs). - register handler now looks up any existing predicate for the (sessionId, resolvedUrl) pair and unroutes it BEFORE installing the new route. This is the actual race fix: without it, the second vi.mock for a duplicate-id leaks an orphan Playwright route that fires after birpc closes. - clear handler iterates the Set via spread. Why this matters even though Layer 1 normalised the only known duplicate in our suite: every future vi.mock call is a class of race we shouldn't have to think about. The patch closes the upstream gap at the route-handler level, so a contributor reintroducing the duplicate-id pattern can't reopen the race. When to remove: when @vitest/browser-playwright ships a release containing PR #10267. Delete patches/@vitest+browser-playwright+4.1.0.patch and the postinstall hook (or keep the hook if other patches accumulate). Refs: #553 · vitest-dev/vitest#9957 · vitest-dev/vitest#10267 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
2.7 KiB
Diff
63 lines
2.7 KiB
Diff
diff --git a/node_modules/@vitest/browser-playwright/dist/index.js b/node_modules/@vitest/browser-playwright/dist/index.js
|
|
index 5d0d37b..821d7b4 100644
|
|
--- a/node_modules/@vitest/browser-playwright/dist/index.js
|
|
+++ b/node_modules/@vitest/browser-playwright/dist/index.js
|
|
@@ -935,7 +935,7 @@ class PlaywrightBrowserProvider {
|
|
createMocker() {
|
|
const idPreficates = new Map();
|
|
const sessionIds = new Map();
|
|
- function createPredicate(sessionId, url) {
|
|
+ function createPredicate(url) {
|
|
const moduleUrl = new URL(url, "http://localhost");
|
|
const predicate = (url) => {
|
|
if (url.searchParams.has("_vitest_original")) {
|
|
@@ -960,11 +960,7 @@ class PlaywrightBrowserProvider {
|
|
}
|
|
return true;
|
|
};
|
|
- const ids = sessionIds.get(sessionId) || [];
|
|
- ids.push(moduleUrl.href);
|
|
- sessionIds.set(sessionId, ids);
|
|
- idPreficates.set(predicateKey(sessionId, moduleUrl.href), predicate);
|
|
- return predicate;
|
|
+ return { url: moduleUrl.href, predicate };
|
|
}
|
|
function predicateKey(sessionId, url) {
|
|
return `${sessionId}:${url}`;
|
|
@@ -972,7 +968,23 @@ class PlaywrightBrowserProvider {
|
|
return {
|
|
register: async (sessionId, module) => {
|
|
const page = this.getPage(sessionId);
|
|
- await page.context().route(createPredicate(sessionId, module.url), async (route) => {
|
|
+ const { url: moduleUrl, predicate } = createPredicate(module.url);
|
|
+ const key = predicateKey(sessionId, moduleUrl);
|
|
+ // Backport of vitest PR #10267: if a route handler is already
|
|
+ // registered for this resolved module URL in this session,
|
|
+ // unroute it before installing the new one. Without this guard,
|
|
+ // duplicate-id mocks (e.g. '$lib/foo.svelte' + '$lib/foo.svelte.js')
|
|
+ // leak an orphan route whose handler crashes after the next
|
|
+ // session's birpc channel closes.
|
|
+ const existingPredicate = idPreficates.get(key);
|
|
+ if (existingPredicate) {
|
|
+ await page.context().unroute(existingPredicate);
|
|
+ }
|
|
+ const ids = sessionIds.get(sessionId) ?? new Set();
|
|
+ ids.add(moduleUrl);
|
|
+ sessionIds.set(sessionId, ids);
|
|
+ idPreficates.set(key, predicate);
|
|
+ await page.context().route(predicate, async (route) => {
|
|
if (module.type === "manual") {
|
|
const exports$1 = Object.keys(await module.resolve());
|
|
const body = createManualModuleSource(module.url, exports$1);
|
|
@@ -1033,8 +1045,8 @@ class PlaywrightBrowserProvider {
|
|
},
|
|
clear: async (sessionId) => {
|
|
const page = this.getPage(sessionId);
|
|
- const ids = sessionIds.get(sessionId) || [];
|
|
- const promises = ids.map((id) => {
|
|
+ const ids = sessionIds.get(sessionId) ?? new Set();
|
|
+ const promises = [...ids].map((id) => {
|
|
const key = predicateKey(sessionId, id);
|
|
const predicate = idPreficates.get(key);
|
|
if (predicate) {
|