ThinkKit Works
All notes
QA Automation

Killing Flaky Tests: A Practical Playbook

Flaky tests quietly destroy trust in a test suite. A concrete playbook for finding the flakes, diagnosing the real cause, and stopping them from coming back.

2 min read
AutomationFlaky TestsReliabilityCI

A flaky test — one that passes and fails without any code change — is worse than no test at all. It trains the team to ignore red, and an ignored suite is a dead suite. Here is the playbook I use to kill flakes for good.

The moment people start saying “just re-run it,” the suite has lost its authority. Every flake you tolerate makes the next real failure easier to dismiss.

Step 1 — Make flakes visible

You cannot fix what you cannot see. Before anything else, start tracking which tests fail intermittently.

  • Record every test failure with its test name and commit
  • Flag any test that has both passed and failed on the same commit
  • Keep a running “flaky list” the whole team can see

A test that fails 1 in 20 runs is invisible day to day but poisons confidence over a month. Data makes it undeniable.

Step 2 — Quarantine, don’t ignore

A known flake blocking the pipeline pressures everyone to bypass CI. So move it out of the blocking path — but into a visible holding area, not oblivion.

  • Tag the test as quarantined so it runs but doesn’t fail the build
  • Give every quarantined test an owner and a ticket
  • Cap the quarantine — if the list grows unbounded, that is its own alarm

Quarantine is a hospital, not a graveyard. A test that sits quarantined for a month with no investigation should be deleted — a test nobody will fix is just noise.

Step 3 — Diagnose the real cause

Flakiness almost always traces to a handful of root causes. Work through them:

  • Timing / async — asserting before the app is ready. The number-one cause.
  • Test interdependence — one test leaks state that another depends on.
  • Shared/real data — tests fighting over the same records or environment.
  • Non-determinism — random data, time-of-day, ordering, timezones.
  • Resource contention — parallel tests hitting limits (connections, ports).

Step 4 — Fix the cause, not the symptom

The tempting “fix” is a retry or a sleep. Both hide the flake instead of removing it.

// Symptom-hiding: brittle and slow
await page.waitForTimeout(3000);
await expect(page.getByRole('heading')).toBeVisible();

// Cause-fixing: wait for the actual condition
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();

The second version waits for the state you care about, not an arbitrary duration. It is both faster and reliable. Apply the same idea everywhere: replace fixed waits with explicit waits, isolate test data, and reset state between tests.

Step 5 — Prevent regression

Once a flake is fixed, keep it dead:

  • The test isolates its own data and cleans up after itself
  • No fixed sleep/waitForTimeout — only condition-based waits
  • Tests pass when run in a random order
  • Tests pass under parallel execution
  • A quarantined test has an owner and a deadline

Why it’s worth the effort

A reliable suite is a force multiplier: green means ship, red means stop, and nobody argues. A flaky suite is a tax on every merge and a slow erosion of the one thing automation exists to provide — trust. Killing flakes is not cleanup work; it is protecting the value of everything else you automated.

Related

More in QA Automation