Flaky Tests Common Causes
Hunt timing, shared state, order dependence, and network flake — a practical checklist to stabilize frontend test suites.
- testing
- flaky-tests
A flaky test fails intermittently without code changes. Flakes destroy trust: teams retry CI until green or ignore failures. Fixing flake is product work.
Docs: Playwright flakiness, Testing Library async.
Top causes in frontend suites
1. Timing and races
// Bad
await user.click(button);
expect(screen.getByText('Saved')).toBeInTheDocument(); // not rendered yet
// Good
expect(await screen.findByText('Saved')).toBeInTheDocument();
Fixed sleep(500) still races on slow CI.
2. Shared mutable state
- Singleton stores not reset
localStorage/ cookies between tests- MSW handlers stacked without reset
- DB rows on shared staging
afterEach(() => {
localStorage.clear();
cleanup();
server.resetHandlers();
});
3. Order dependence
Test B passes only if test A left a user logged in. Always arrange full preconditions.
4. Network non-determinism
Real HTTP to staging: 500s, latency, rate limits. Prefer MSW or controlled test APIs.
5. Animation and transitions
Asserting mid-animation opacity. prefers-reduced-motion, disable animations in test CSS, or wait for final role/text.
6. Clock skew
Date-dependent UI (expires soon) without faking Date / timers.
7. Parallelism collisions
Two workers same account. Isolate with unique IDs per worker.
8. Selector ambiguity
getByText('Add') matches multiple. Use role + name; getAllBy* intentionally.
Debug protocol
- Reproduce with
--repeat-each=20or quarantine job. - Capture trace/video (Playwright).
- Check whether failure is assertion vs timeout.
- Log seed data IDs.
- Fix root cause; remove
retriesas the long-term strategy.
Retries policy
| Layer | Retries |
|---|---|
| Unit | 0 |
| Component | 0 |
| E2E | 0–1 only while triaging, then 0 |
Retries hide bugs.
Async RTL patterns
// wait for disappearance
await waitFor(() => {
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});
// findBy = getBy + wait
await screen.findByRole('alert');
Avoid manual loops.
Footguns
waitForwith always-true condition.- Snapshot updates on flake noise.
- Marking tests
.skippermanently. - Blaming “CI is slow” without traces.
Interview out-loud answer
“Flakes usually come from races, shared state, or real network. I use findBy/auto-wait, reset storage and MSW each test, isolate data, and treat retries as temporary. E2E gets traces; unit tests should never need sleeps.”
Quarantine with ownership
Move known flakes to a quarantined job that doesn’t block merge only with an owner and expiry date. Track counts; if a test is quarantined more than a sprint, delete or rewrite it. Silent quarantine forever is how suites rot.
Extra practice
Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.
Notes from real codebases
Teams that succeed here keep the rules mechanical: lint where possible, CI for the rest, and a short human checklist for what automation cannot see. Document exceptions with an owner name and a removal date so “temporary” escapes do not become permanent architecture.
Related on this site
- End-to-end testing tradeoffs
- Fake timers for debounce
- MSW mock service worker
- Testing Library
- Integration testing UI
Further reading
Related guides
- Accessibility Testing in CIWire axe and lint rules into CI without false confidence — what automation catches, what it misses, and a practical pipeline.
- Component Testing StorybookUse Storybook as a component workshop and optional test runner — stories as living specs, interaction tests, and a11y checks.
- Contract Testing APIsKeep frontend and backend agreements honest — schema contracts, Pact-style consumer tests, and OpenAPI-driven checks without brittle E2E.
- Coverage Metrics PitfallsWhy 100% line coverage can still ship bugs — gaming metrics, useless tests, and what coverage is actually good for.
- End-to-End Testing TradeoffsWhen E2E tests earn their keep, why they flake, and how to keep a small critical-path suite instead of a slow second frontend.