Skip to content

perf(browser): reduce headless run overhead#10713

Open
sheremet-va wants to merge 2 commits into
perf/improve-loading-perffrom
perf/browser-mode
Open

perf(browser): reduce headless run overhead#10713
sheremet-va wants to merge 2 commits into
perf/improve-loading-perffrom
perf/browser-mode

Conversation

@sheremet-va

@sheremet-va sheremet-va commented Jul 5, 2026

Copy link
Copy Markdown
Member

Description

Reduces the fixed and per-file overhead of headless browser-mode runs with the playwright provider.

Adaptive session scaling

BrowserPool no longer opens min(maxWorkers, files.length) tabs upfront. Sessions open one at a time while the remaining work justifies another tab: the projected queue drain time (queue length × measured per-file cost, split across open sessions) has to exceed twice the measured session-open cost. Every tab pays a context + page + full module graph bring-up that competes with already-running sessions for the same Vite server, so fast suites finish sooner with fewer tabs, while suites with slower files still scale up to maxWorkers (without a per-file signal the pool keeps opening sessions like before).

Sourcemap serving policy

In headless runs (with the inspector off), Vitest no longer generates and inlines sourcemaps for its own pre-built modules — their stack frames are hidden from stack traces anyway. Serving them multiplied the bytes each browser context downloads by ~5 and burned server CPU on regenerating maps for minified code on every run.

A new browser.dependencySourcemaps: false option (also --browser.dependencySourcemaps=false) extends this to all of node_modules for projects that don't step into dependency code in devtools. It respects server.sourcemapIgnoreList, so workspace code that resolves to a node_modules path (e.g. with resolve.preserveSymlinks) can be opted back in. Sourcemaps of user source files are always served.

Source-mapped stacks for pre-bundled dependencies

Error stack frames pointing into pre-bundled dependencies are now mapped using the sourcemaps stored in the optimizer cache. Previously the transform pipeline map resolved back into the optimizer cache, so these frames were reported at positions in the optimized chunk (e.g. deps/react.js?v=…:468:41) instead of the dependency sources:

❯ Object.only …/react/cjs/react.development.js:789:16

This works regardless of the dependencySourcemaps setting, so reported test errors stay readable either way.

Framework assets cached as immutable

Vitest's own pre-built modules only change with the Vitest version and browser contexts don't outlive the run, so they are now served with Cache-Control: immutable instead of being revalidated by every tester iframe (skipped when persistentContext is enabled, since its disk cache survives upgrades). The immutable and the optimized-deps no-cache policies live in a single middleware.

Warm browser startup

Browser providers gain an optional prewarm(project) hook, invoked right before the browser Vite server is created. The playwright provider uses it to start import('playwright') and the browser launch concurrently with server startup. The launch options are resolved by the same code as the real launch and compared at adoption time — on any mismatch the speculative browser is discarded and the normal launch path runs. Prewarming is skipped for connectOptions, persistentContext, and inspector flows.

Fewer per-file round trips

  • The tester HTML is passed through transformIndexHtml once per server instead of on every iframe request.
  • The test file's sourcemap is requested as soon as the file finishes importing (it's needed after collection), and the server warms the file's transform at dispatch time, so neither sits on the run's critical path.
  • The __vitest_viewport command round trip is skipped when the viewport size didn't change between test files (playwright only — its per-page viewport can't drift externally; window-resizing providers re-pin every file).

The console logging specs now run with an explicit default reporter and assert on Vitest's own console forwarding instead of incidental log output.

Benchmark

Wall clock of vitest run, trivial test files (2 tests each), chromium headless, warm .vite cache, min/median of 5 runs, 10-core machine. Baseline is perf/improve-loading-perf, measured against a built worktree of the merge-base:

files isolate before (ms) after (ms) Δ
1 true 740/762 646/671 −12.7%
6 true 1208/1247 834/874 −31.0%
20 true 1623/1794 1015/1052 −37.5%
40 true 1959/1976 1313/1570 −33.0%
40 false 1468/1484 858/958 −41.6%
120 true 3713/3851 2419/2494 −34.9%

--browser.dependencySourcemaps=false saves ~5% more at 40 files.

- open browser sessions adaptively instead of min(maxWorkers, files)
  tabs upfront: sessions open one at a time while the projected queue
  drain time justifies another tab, based on the measured per-file cost
  and session open cost
- stop generating and inlining sourcemaps for Vitest's own pre-built
  modules in headless runs (their frames are hidden from stack traces);
  add `browser.dependencySourcemaps` to optionally do the same for all
  dependencies
- map error stack frames of pre-bundled dependencies using the
  sourcemaps stored in the optimizer cache: previously they pointed at
  the optimized chunk instead of the dependency sources
- skip the `__vitest_viewport` command round trip when the viewport
  size did not change between files (playwright only)
- serve Vitest's own pre-built modules with immutable cache headers
  so tester iframes stop revalidating them on every test file
- transform the tester HTML once per server instead of on every
  iframe request
- request the test file sourcemap as soon as the file finishes
  importing and warm its transform on the server at dispatch, so
  neither waits on the run's critical path
- add an optional `prewarm` hook to browser providers, called before
  the vite server is created; the playwright provider starts
  importing playwright and launching the browser concurrently with
  server startup and adopts the launch only if the resolved launch
  options match
- run the console logging specs with an explicit `default` reporter
  and assert on Vitest's own console forwarding
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant