Tests that don't lie: how to kill flaky tests
A flaky test is worse than no test: it teaches your whole team to ignore the colour red. Here's why tests flake and how to make them deterministic.
There's a quiet killer in test suites, and it isn't low coverage. It's flakiness — tests that pass and fail randomly with no code change. One flaky test feels harmless. But it slowly trains everyone to shrug at a red build, and the day red actually means something, nobody looks.
A flaky test doesn't just fail to catch bugs. It actively destroys trust in every other test you have.
Why tests flake
Almost all flakiness comes down to a few causes, and most are about time:
- Waiting for time instead of state — sleep(2000) and hope the page loaded. It usually did. Until the CI runner was slow.
- Shared state between tests — one test leaves data behind that another trips over, depending on order.
- Real network or real clocks — anything you don't control will eventually betray you.
- Animations and async UI — asserting before the interface has settled.
How to make them honest
Wait for state, never for time: assert that the element is visible, the request finished, the text appeared — modern tools like Playwright auto-wait for exactly this. Isolate every test so it sets up and tears down its own data. Mock the things you don't own (clocks, third-party APIs). And quarantine a flaky test the moment you spot it — a known-broken test out of the way beats a random one poisoning the whole suite.
A green build should mean “ship it.” Protect that meaning ruthlessly.