Why your tests are flaky, and how to actually fix them
Flaky tests aren't bad luck. They have specific, recurring causes, and once you know them, most flakiness is preventable.
A flaky test is one that passes and fails on the same code, seemingly at random. One or two feel like a nuisance. A suite full of them is a slow-motion disaster, because flakiness destroys the one thing a test suite exists to provide: trust. Once a red build might just be flakiness, every red build becomes a judgement call, and under delivery pressure those calls always resolve toward "just re-run it" or "just merge it." At that point the suite has stopped protecting you.
The good news: flakiness isn't random bad luck. It comes from a short list of specific causes, and each has a concrete fix.
Cause 1: waiting wrong
This is the big one. Tests interact with an application that isn't instantly ready, elements load, requests resolve, animations finish. A test that doesn't wait correctly for the right condition races the application and loses sometimes.
The worst fix is the common one: a fixed sleep ("wait 2 seconds"). It's flaky by design, too short on a slow run, wastefully slow on a fast one. The right approach is waiting for a specific condition: wait until the element is actionable, until the request has resolved, until the expected state exists. Modern tools like Playwright auto-wait for actionability out of the box, which eliminates a whole category of this flakiness, but you still have to wait for *your* application's real readiness signals, not an arbitrary timer.
Cause 2: shared state between tests
When tests share state, a database record, a user account, a bit of global config, they stop being independent. Test B passes or fails depending on whether Test A ran first, or whether it ran at all. Run them in a different order or in parallel and the whole thing becomes unpredictable.
The fix is isolation: each test sets up its own data and cleans up after itself, so it runs correctly alone, in any order, and in parallel. Independent tests are stable tests. This discipline also unlocks parallelism, which is how you keep a growing suite fast.
Cause 3: testing at the wrong level
A lot of flakiness comes from testing through the UI things that aren't really about the UI. Driving a browser through five screens to verify a business rule is slow and exposes you to five screens' worth of timing and rendering fragility, when an API test could verify the same rule directly, faster and far more reliably.
The fix is the test pyramid: push verification to the lowest level that can actually test the thing. Reserve end-to-end UI tests for genuine end-to-end journeys, and let API and unit tests carry the logic. Less UII testing means less flakiness, almost for free.
Cause 4: brittle selectors
Tests that find elements by fragile paths, deep CSS chains, auto-generated class names, text that changes, break every time the UI shifts slightly. That's not quite flakiness in the random sense, but it produces the same effect: unpredictable red builds that erode trust.
The fix is stable, intentional selectors, dedicated test identifiers or accessible roles, so tests target what they mean to target and survive cosmetic changes.
How to dig out of a flaky suite
If you're already drowning in flakiness, the recovery is methodical, not heroic:
- Quarantine, don't ignore. Move flaky tests out of the blocking gate so they stop poisoning everyone's builds, but into a visible quarantine, not oblivion.
- Fix the root cause, by category. Most flaky tests fall into the causes above. Attack them by type, not one random test at a time.
- Set a quarantine budget. A quarantine that only grows is a slow gate failure. Cap it, and treat fixing flakes as real, prioritised work.
- Prevent regressions. Once stable, keep it stable: enforce the patterns (proper waits, isolation, right level, stable selectors) in review so new flakiness doesn't creep back.
Flakiness is often cited as a reason automation "doesn't work." It's really a reason to build it properly. A suite with disciplined waits, isolated state, the right test levels, and stable selectors stays green when it should and goes red when it matters, which is the entire point of having one.
Key takeaways
- →Flaky tests destroy trust: once red might just mean flaky, every red build gets rationalised away and the suite stops protecting you.
- →The main causes are specific and fixable: waiting wrong (use condition-based waits, not sleeps), shared state (isolate each test), testing at the wrong level (push down the pyramid), and brittle selectors (use stable identifiers).
- →Recover methodically: quarantine flaky tests out of the blocking gate, fix root causes by category, cap the quarantine, and enforce the patterns in review.
- →Flakiness is a reason to build automation properly, not a reason to avoid it.
Sources & further reading
- 1.Nullius Technologies, flaky-test triage and stability hardening across enterprise suites
Related service
QA & Test Automation
Turn slow, flaky, manual QA into fast, reliable automated coverage that ships with every release.