Skip to content

Browser mode keeps the pages of finished projects open until the end of the run #10990

Description

@JCQuintas

Describe the bug

In browser mode, the pages of a project that has finished running its tests stay open until the whole workspace run ends. They are only closed by the provider's close(), which runs after every project is done.

Since projects run concurrently and each one opens up to maxWorkers pages, a run holds projects * maxWorkers pages open at its peak, and every project that finishes early keeps its browser page (and everything the tests loaded into it) alive until the slowest project is done.

With empty tests the pages are cheap, so the reproduction only shows the count. In a real suite each page holds the modules, DOM and component trees of every file that ran in it.

In mui/mui-x the browser suite has 21 projects and maxWorkers: 2, so the run keeps 42 Chromium tabs open and peaks at ~12.8GB (measured as the delta in system memory over a full run). That is more than the 16GB CI container can hold, so a renderer is OOM-killed and the run fails with Browser page crashed followed by [vitest] Browser connection was closed while running tests, always on whichever project happens to finish last, while every test passes. Splitting the suite into two sequential vitest invocations dropped the peak to 7.2GB and the wall time from 144s to 112s, purely because half as many pages are alive at once.

Reproduction

https://github.com/JCQuintas/vitest-browser-pages-repro

npm install
npx playwright install chromium
npm test

Six browser projects, each with a single test that sleeps for a different duration, so they finish 500ms apart. A reporter prints how many pages each provider still holds whenever a project finishes:

 ✓ |project-1 (chromium)| project-1.test.ts (1 test) 504ms
[open-pages] 2.4s project "project-1 (chromium)" finished: 6 page(s) open | project-1=1 project-2=1 project-3=1 project-4=1 project-5=1 project-6=1
 ✓ |project-2 (chromium)| project-2.test.ts (1 test) 1003ms
[open-pages] 2.9s project "project-2 (chromium)" finished: 6 page(s) open | project-1=1 project-2=1 project-3=1 project-4=1 project-5=1 project-6=1
...
 ✓ |project-6 (chromium)| project-6.test.ts (1 test) 6003ms
[open-pages] 7.9s project "project-6 (chromium)" finished: 6 page(s) open | project-1=1 project-2=1 project-3=1 project-4=1 project-5=1 project-6=1

project-1 is done at 2.4s, but its page is still open at 7.9s, and the total never drops below 6.

Expected behaviour

Once a project's queue is empty and its testers have been cleaned up, its pages are closed and the memory released, so the peak follows the projects that are actually running rather than the total project count.

Where it comes from

packages/vitest/src/node/pools/browser.ts, in runNextTest: when the queue is empty the session is cleaned up and marked ready, but the page is left open.

if (!file) {
  const isolate = this.project.config.isolate
  if (isolate) {
    this.finishSession(sessionId)
    return
  }

  const orchestrator = this.getOrchestrator(sessionId)
  orchestrator.cleanupTesters()
    .catch(error => this.reject(error))
    .finally(() => this.finishSession(sessionId))
  return
}

Workarounds that do not work

  • Closing the pages from a custom reporter. onTestModuleEnd fires before the pool calls cleanupTesters() on that page, so closing it there fails the run with [birpc] rpc is closed, cannot call "createTesters". There is no reporter hook that runs after the cleanup.
  • isolate: true. Recreates the iframe per file but keeps the same page, so the page count is unchanged. It was also 2.5x slower on the suite above.
  • Lowering maxWorkers. It is per project, so it scales the peak down but the pages of finished projects still accumulate.

Splitting the run into several vitest invocations does work, because the browsers go away with each process, but the projects have to be split by hand and the reports merged afterwards.

System Info

System:
  OS: macOS 26.5.2
  CPU: (11) arm64 Apple M3 Pro
  Memory: 4.61 GB / 36.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 24.15.0
  npm: 11.12.1
  pnpm: 10.34.3
npmPackages:
  vitest: 4.1.10
  @vitest/browser: 4.1.10
  @vitest/browser-playwright: 4.1.10
  playwright: 1.61.1

The same code is on main at the time of writing.

Used Package Manager

npm

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions