Flaky Tests Are Lying to You — And Your Team Has Stopped Listening
Photo: Official GDC, CC BY 2.0, via Wikimedia Commons
There's a particular kind of learned helplessness that sets in when your CI pipeline goes red for the fourth time this week on the same test — a test that, when you re-run it, just... passes. No code changes. No explanation. Just vibes.
At first, developers investigate. They dig into logs, check for race conditions, wonder if it's a timing issue. After the tenth time, they stop investigating. They just hit "re-run" and go get coffee. After the fiftieth time, they stop caring about red pipelines altogether.
That's the real cost of flaky tests. Not the wasted CI minutes. Not the delayed deploys. It's the erosion of trust in your entire testing infrastructure — and once that trust is gone, it's genuinely hard to get back.
The Psychology of the "Re-Run" Button
Here's a scenario that probably sounds familiar: a developer opens a pull request, CI fails on a test that has nothing to do with their changes. They know it's flaky. Everyone knows it's flaky. So they click re-run, it passes, and the PR gets merged.
What just happened there? The team collectively decided that a failing test doesn't mean anything. And they're not wrong — in that specific case. But the habit that's forming is dangerous. Because now the mental model is "red doesn't necessarily mean broken." And that mental model will eventually let a real bug through.
Research in behavioral psychology calls this "alarm fatigue" — it's the same phenomenon that causes hospital staff to start ignoring monitor alarms when false positives are too frequent. The signal gets buried in noise. Your flaky tests are creating the same effect in your engineering org.
What Makes a Test Flaky in the First Place?
Flakiness isn't random. It has causes, and those causes are usually findable once you know what to look for.
Timing and async issues are the most common culprit. Tests that rely on setTimeout, animations, network responses, or database writes completing in a specific order will behave differently depending on system load, network latency, or just the mood of the CI runner that day.
Shared state between tests is another big one. If Test A leaves something in a database, a cache, or a global variable that Test B depends on — or accidentally poisons — you've got order-dependent tests. Run them in a different sequence and suddenly everything breaks.
External dependencies are a quiet killer. Tests that make real HTTP calls to third-party APIs, hit a shared staging database, or depend on a specific service being up introduce failure modes that have nothing to do with your code.
Resource contention on CI runners is often overlooked. Parallel test execution on underpowered runners creates race conditions that would never appear on a developer's laptop. Your tests pass locally every time. CI fails intermittently. The environment is the variable.
The Operational Tax Nobody Budgets For
Let's talk numbers for a second, because this is where flakiness stops feeling like a code quality issue and starts feeling like a business problem.
If a flaky test causes your CI pipeline to re-run twice a day, and each pipeline run takes 20 minutes, that's 40 minutes of CI time wasted daily. Across a team of 10 developers, each of whom briefly loses context while waiting — you're looking at a non-trivial productivity drain every single week.
But that's just the visible cost. The invisible cost is the slowdown in deployment confidence. Teams with high flakiness rates tend to batch their releases more, because nobody trusts a green pipeline enough to ship immediately. That means longer feedback loops, bigger diffs per release, and higher blast radius when something does go wrong.
A 2023 analysis by Google's engineering productivity team (which has studied flakiness extensively given their scale) found that flaky tests can account for up to 16% of all CI failures in large codebases. That's not a rounding error. That's a structural problem.
Tools Teams Are Actually Using to Fight Back
The good news: flakiness is solvable. Not always quickly, but systematically. Here are the tools and approaches that are actually making a difference:
Flakiness detection and quarantine — Tools like Buildkite's Test Analytics, Datadog CI Visibility, and GitHub Actions' built-in retry mechanisms can automatically track which tests fail intermittently over time. Some teams implement a "quarantine" system where flagged flaky tests are moved to a non-blocking suite while they're investigated, so they don't hold up deployments but also don't get ignored.
Playwright and Cypress for UI test stability — Both frameworks have invested heavily in auto-waiting and retry logic that reduces timing-related failures in end-to-end tests. If you're still using Selenium with manual sleep() calls scattered through your test code, that's a migration worth prioritizing.
Test isolation tooling — Libraries like jest-environment-jsdom with proper teardown, or database transaction rollbacks between tests (common in Rails and Django test suites), help eliminate shared-state issues without requiring a full rewrite.
Retry-with-logging strategies — Rather than silently retrying, some teams configure their CI to retry once on failure but flag the result as "passed with retry" in their dashboards. This makes flakiness visible without blocking the pipeline — and creates a paper trail that helps prioritize fixes.
Building a Culture Where Flakiness Is Treated Like a Bug
Tools help, but the cultural piece is what makes the difference long-term.
The teams that successfully reduce flakiness treat flaky tests the same way they treat open bugs: tracked, assigned, and prioritized. They don't let flaky tests linger in quarantine indefinitely — that's just a slower version of ignoring them.
Some teams implement a "flakiness budget" — a threshold above which new features can't ship until the flakiness rate comes back down. It sounds strict, but it keeps the problem from compounding.
The goal isn't a perfect test suite overnight. It's a test suite your team trusts enough to act on. When green means green and red means red, everything downstream — deploys, release decisions, incident response — gets faster and more confident.
At TryChec, we think a lot about what it means to test smarter. And smarter testing isn't just about coverage numbers or framework choices. It's about building a signal your team actually believes in. Flaky tests are noise. The job is to turn down the noise until the signal is clear enough to act on.