Vitest doctor cut our CI test time from 263s to 115s

Context:

  • Environment: SPA React application, 615 test files / 1813 tests, happy-dom
  • Dependencies:
    • Vitest version: "vitest": "5.0.0"
    • Vitest 5 requires Vite >= 6.4.0 and Node.js >= 22.12.0

Problem:

The duration breakdown tells you setup or environment dominates, not whether a different pool would fix it. You find that out by running the suite under each one and comparing.

Solution:

Vitest 5 adds vitest doctor.

npx vitest doctor

Output:

Results (min of 3 runs each)

  baseline (pool: forks · isolate: true)  4.08s
  pool: 'threads'                         3.64s (-11%)
  pool: 'vmThreads'                       1.33s (-67%)
  isolate: false                          1.28s (-69%)

Recommendation: pool: 'vmThreads' (-67%)

  // vitest.config.ts
  export default defineConfig({
    test: {
      pool: 'vmThreads',
    },
  })

Real numbers on a 615-file suite

Link to heading: Real numbers on a 615-file suite

Doctor recommended pool: 'vmThreads' over the forks we had been running. Local, 615 test files / 1813 tests:

PoolDuration
forks (baseline)293.59s
vmThreads45s

On CI the same switch, measured on the latest happy-dom environment:

RunDurationBreakdown
Before263.36ssetup 65%, tests 14%, import 8%, transform 7%, environment 5%
After114.74ssetup 40%, tests 26%, transform 16%, worker 13%, import 4%, environment 1%

That is -56% on CI.

The custom environment caveat

Link to heading: The custom environment caveat

If your project uses a custom environment, say a thin wrapper around happy-dom that adds a BroadcastChannel polyfill for MSW 2.x, doctor skips the vmThreads and vmForks candidates without reporting why. That was issue #11192, fixed since.

If you hit it, point your config at the built-in environment, run doctor, then switch back:

// vitest.config.ts
export default defineConfig({
  test: {
    environment: 'happy-dom', // instead of './custom-happy-dom-environment.ts'
  },
})

Before you switch pools

Link to heading: Before you switch pools

vmThreads does not give each test file a fresh process:

  • code relying on process-level globals or native modules can break
  • memory leaks accumulate across files instead of dying with the process
  • a suite that leaks between files will start failing in new ways. detectAsyncLeaks finds those before you commit to the switch

Do not merge a doctor recommendation without a full CI run. A fast green run can still hide new flakiness.

Sources: