Skip to content

Browser mode: collect-only (vitest list) shares one page across test files, leaking vi.mock between them #10886

Description

@nazar-ch

Describe the bug

In browser mode, vitest list (collect-only) imports several test files through one browser page
when file parallelism doesn't give each file its own page. The mocker registry is scoped to the
page, while each vi.mock call is written to bind its own file — so during collection, one file's
mock is visible to (or missing for) the files that share its page. vitest run isolates the same
files per page and passes.

Two error shapes, same cause:

  1. A factory mock that replaces the module: a page-mate importing any export the factory doesn't
    provide fails with SyntaxError: The requested module '…' does not provide an export named '…'.
  2. A factory mock that spreads importOriginal: the page-mate fails with Error: Mock …/m.ts wasn't registered. This is probably a Vitest error. Please, open a new issue with reproduction.
    (ModuleMocker.resolveFactoryModule is asked for an id the page's registry no longer holds).

--no-file-parallelism makes it deterministic (below). Without it, the same leak still fires
whenever the worker budget packs several files into one page — in our monorepo (11 projects sharing
maxWorkers: '50%'), a bare vitest list at the root fails 3 runs out of 3 with rotating victim
files, which is what makes this nasty: the errors read like a broken tree, not a broken listing.

Related issues

Disclosure

Human posted. Prepared with Claude Fable.

Reproduction

package.json deps: vitest@4.1.10, @vitest/browser@4.1.10, @vitest/browser-playwright@4.1.10,
playwright@1.61.1.

// vitest.config.ts
import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';

export default defineConfig({
    test: {
        browser: {
            enabled: true,
            provider: playwright(),
            headless: true,
            instances: [{ browser: 'chromium' }],
        },
    },
});
// test/m.ts
export const a = 'real-a';
export const b = 'real-b';
// test/mocker.test.ts
import { expect, test, vi } from 'vitest';

vi.mock('./m', () => ({ a: 'mocked-a' }));

import { a } from './m';

test('sees the mock', () => {
    expect(a).toBe('mocked-a');
});
// test/victim.test.ts
import { expect, test } from 'vitest';

import { b } from './m';

test('sees the real module', () => {
    expect(b).toBe('real-b');
});
$ npx vitest run --no-file-parallelism   # 2 passed — run isolates the files
$ npx vitest list                        # exit 0 — each file got its own page
$ npx vitest list --no-file-parallelism  # exit 1:
Error: Failed to import test file …/test/victim.test.ts
Caused by: SyntaxError: The requested module '/test/m.ts' does not provide an export named 'b'

Changing the factory to async (importOriginal) => ({ ...(await importOriginal()), a: 'mocked-a' })
switches the failure to shape 2:

$ npx vitest list --no-file-parallelism  # exit 1:
Error: Failed to import test file …/test/victim.test.ts
Caused by: Error: Mock /test/m.ts wasn't registered. This is probably a Vitest error. …

Expected behavior

Collection sees the same per-file mock scoping a run does — either by isolating collect-only imports
per file, or by scoping the mocker registry to the importing file rather than the page.

System Info

System:
    OS: macOS 26.4
    CPU: (6) arm64 Apple M1 Max (Virtual)
    Memory: 1.94 GB / 10.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 26.7.0 - ~/.vite-plus/js_runtime/node/26.7.0/bin/node
    Yarn: 1.22.22 - ~/.vite-plus/bin/yarn
    npm: 11.19.0 - ~/.vite-plus/js_runtime/node/26.7.0/bin/npm
    pnpm: 11.20.0 - ~/.vite-plus/bin/pnpm
  npmPackages:
    @vitest/browser: ^4.1.10 => 4.1.10
    @vitest/browser-playwright: ^4.1.10 => 4.1.10
    playwright: ^1.61.1 => 1.61.1
    vitest: ^4.1.10 => 4.1.10

Chromium via playwright, headless.

Used Package Manager

yarn

Validations

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions