Skip to content

Browser mode: Playwright Request/Route dispatchers accumulate across test files → route.fulfill: The object has been collected to prevent unbounded heap growth #10819

Description

@jon301

Describe the bug

In a large @vitest/browser-playwright suite, runs intermittently fail with a global unhandled rejection (no test in the stack — every test passes):

Error: route.fulfill: The object has been collected to prevent unbounded heap growth.
❯ …/@vitest/browser-playwright/dist/index.js

The provider serves modules (and vi.mock module mocks) via context.route(predicate, route => route.fulfill(...)). All test files in a session run through one persistent browser context on a single Playwright connection, and the Request/Route/Response dispatchers created per intercepted request are never disposed between files.

Playwright caps each dispatcher "bucket" at 10,000 (maxDispatchersForBucket in playwright-core, ?? 1e4, no public override) and, once exceeded, evicts the oldest to bound memory (DispatcherConnection.maybeDisposeStaleDispatchers). When a still-live module-serving Route later .fulfill()s a request whose Request dispatcher was evicted, sendMessageToServer throws because object._wasCollected is true — surfacing as route.fulfill: … collected to prevent unbounded heap growth.

The overflow is deterministic (it happens every run once the suite is large enough); the thrown error is probabilistic on top of it (only when an evicted Route/Request is the one later fulfilled), which is why it presents as a flake.

Evidence

Instrumenting playwright-core's dispatcher registry during a full run of a ~330-file browser-mode suite (one chromium instance):

  • The Request bucket climbs monotonically with no per-file reset: 1000 → 2000 → … → 10000, then evicts the oldest 1000 repeatedly (77 evictions in one full run; ~85k Request dispatchers created total).
  • A --shard=1/6 subset (54 files) alone reaches the 10,000 cap.
  • NODE_OPTIONS=--max-old-space-size does not help — the ceiling is dispatcher count, not V8 heap.

The provider (createContext(sessionId)) caches one context per session and reuses it for the whole run; reopening the page per file does not dispose the context's accumulated Request/Route/Response dispatchers.

Reproduction

Any browser-mode suite with enough files/vi.mock calls that cumulative intercepted requests exceed ~10,000 on one connection. Roughly: files × (module requests + mocks per file) > 10_000. In our repo it reproduces on ~10–15% of CI runs; instrumentation shows the bucket crossing 10k on every run.

Expected behavior

The provider should keep the per-connection dispatcher count bounded across files — e.g. dispose request/route dispatchers per file, recycle the browser context every N files, or otherwise release the module-serving Route/Request objects once a file completes — so a long suite never trips Playwright's GC eviction on live objects.

Suggested directions

  • Dispose the per-file module-mock routes and their associated Request/Response dispatchers when a test file finishes (context.unroute already runs, but the already-created Request dispatchers persist).
  • Optionally recycle the BrowserContext every N files under isolate: true.
  • Or surface a config to raise Playwright's dispatcher cap (currently setMaxDispatchersForTest is internal / not on playwright-core's public exports).

Workaround

We ship a pnpm patch raising playwright-core's per-bucket cap (env-tunable, default 1e6), which takes the observed peak from "10k + evicting" to "~85k with 0 evictions" and eliminates the failure. It's an interim — the real fix belongs in the provider's per-file dispatcher lifecycle.

Related issues (searched; this is not a duplicate)

Version / environment

  • vitest 4.1.8
  • @vitest/browser 4.1.8, @vitest/browser-playwright 4.1.8
  • playwright-core 1.61.0 (chromium, headless, one instances entry)
  • Node 24.16, pnpm 11.9.0

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