QuestionsTesting

Flaky tests: causes and elimination

FlakinessHardTesting

What causes flaky tests, and how do you systematically hunt down and eliminate flakiness?

What it tests

Whether you can name the concrete sources of nondeterminism and fix root causes rather than paper over them.

Approach & answer

A flaky test passes and fails without any code change — the worst kind of test, because it destroys trust in the whole suite (people start re-running until green, then ignore real failures). Flakiness always comes from NONDETERMINISM, and the value is in naming the specific sources. (1) TIMING / async races: asserting before an async update lands, or a fixed sleep(500) that's usually-but-not-always enough. Fix: await the outcome (findBy/waitFor), never sleep a magic number. (2) TIME & RANDOMNESS: tests that depend on Date.now(), timezones, or Math.random(). Fix: inject/freeze the clock (fake timers), seed randomness, pin the timezone. (3) SHARED STATE / TEST ORDER: one test leaks state (a singleton, module cache, DB row, localStorage, un-reset mock) into another, so results depend on order or parallelism. Fix: isolate — fresh setup per test, reset mocks and global state, unique data per test; run with randomized order to surface it. (4) TEST INTERDEPENDENCE / resource contention: shared ports, files, or a shared DB across parallel workers. Fix: give each worker its own namespace/schema/tmp dir. (5) NETWORK / EXTERNAL SERVICES: hitting real APIs that are slow or occasionally down. Fix: stub at the boundary (MSW), don't call the real internet in unit/integration tests. (6) ANIMATIONS / rendering timing in e2e: asserting mid-transition. Fix: wait for a stable condition (element visible/enabled), disable animations, use auto-waiting locators. (7) IMPROPER WAITS in e2e: waiting for a fixed time instead of a condition. The systematic hunt: reproduce by running the test in a loop (--repeat), in random order, and in parallel; quarantine the flaky test (tag/skip) so it stops blocking CI, but track it — quarantine is triage, not a fix; then bisect the nondeterminism (does it fail alone? only after test X? only in CI?). Root-cause it into one of the buckets above and remove the source. Crucially, do NOT 'fix' flakiness with automatic retries as the primary strategy — retries hide real intermittent bugs and let flakiness accumulate; use them sparingly, if at all, and always with visibility into what retried and why.

Use this technique when

A test fails intermittently in CI; auditing suite reliability; setting a retry/quarantine policy.

Code

Source of nondeterminism        Root-cause fix
-------------------------------  -------------------------------------
timing / async race              await the outcome (findBy/waitFor), no fixed sleep
time & randomness                fake timers, seed RNG, pin timezone
shared state / order dependence  fresh setup, reset mocks+globals, unique data
resource contention (parallel)   per-worker db schema / port / tmp dir
real network                     stub at boundary (MSW), never hit the internet
animations / e2e waits           wait for a stable condition, not a duration

Hunt: run in a loop, randomized order, and in parallel to reproduce.
Quarantine to unblock CI, but that's triage. Retries HIDE bugs -> last resort.

References