A test suite takes 20 minutes and blocks every PR. How do you make it fast without losing confidence?
Diagnosing what makes a suite slow and applying parallelization, sharding, and selection without sacrificing coverage.
A slow suite is a real cost — it slows every merge, so people batch changes, skip running it locally, and lose the fast feedback tests exist to provide. Attack it in layers. First, DIAGNOSE: find the slow tests (runners report per-test/-file timing) and the slow LAYER. Usually the shape is wrong — too many slow e2e/integration tests doing work that unit tests could do (the ice-cream cone). Rebalancing toward the pyramid (push logic coverage down to fast unit tests, keep e2e to critical journeys) is the biggest structural win. Second, PARALLELIZE: unit/component runners (Jest, Vitest) already run test FILES across worker processes/threads on multiple cores — ensure that's on and workers are tuned to the machine; the prerequisite is ISOLATION (no shared state/ports/db rows), because parallelism exposes any hidden coupling as flakiness. Third, SHARD across MACHINES in CI: split the suite into N shards run on N runners in parallel (jest --shard=1/4, Playwright's built-in sharding), cutting wall-clock roughly linearly; combine with per-worker resource namespacing (own DB schema, own tmp dir, own port) so shards don't collide. Fourth, TEST SELECTION: run only what changed — Jest's --onlyChanged / --changedSince, or tooling that maps changed files to affected tests (Nx/Turbo affected graphs), so a one-file PR doesn't run 10k tests; keep the full suite for main/merge. Fifth, cut per-test OVERHEAD: avoid real network (MSW instead of live calls), fake timers instead of real waits, seed DB via fast fixtures/transactions with rollback instead of full migrations per test, reuse expensive setup with beforeAll where safe, and mock heavy modules you don't need. Sixth, CACHE: dependency and build caches, and transform caches (SWC/esbuild transforms are far faster than babel-ts) so startup isn't dominated by compilation. The guardrail throughout: speed must not come from deleting assertions or over-mocking until tests prove nothing — the goal is the same confidence, faster. Measure before and after, and watch that parallelization didn't introduce flakiness (the tax for hidden shared state).
CI is slow; scaling a growing suite; tuning parallel workers and sharding.
Lever How Watch out for
----------------------- ------------------------------------ --------------------------
rebalance the pyramid move coverage from e2e -> unit keep e2e for critical flows
parallelize (1 machine) Jest/Vitest workers across cores needs test isolation
shard (N machines) jest --shard=1/4, pw sharding per-worker db/port/tmp
test selection --onlyChanged / affected graph run full suite on main
cut per-test overhead MSW, fake timers, fast fixtures don't over-mock -> false green
caching deps + SWC/esbuild transform cache invalidate correctly
Rule: same confidence, faster. Speed must NOT come from weaker assertions.
Measure before/after; parallelism turns hidden shared state into flakiness.