Taming Flaky Tests

Replace sleeps and races with deterministic waits, isolation, and quarantine.

  • flakiness
  • e2e
  • reliability

Problem

A test passes locally and fails one run in ten in CI. Flaky tests erode trust until the whole suite gets ignored — worse than having no suite at all.

Solution

Most flakiness is a hidden race: the test asserts before the app settles. Wait for the observable condition, never a fixed timeout. Control the clock, network, and randomness so each run is deterministic, and make every test set up and tear down its own data.

When you cannot fix a flaky test immediately, quarantine it (and track it) rather than retrying blindly — silent retries hide real bugs.

Snippet

Wait for the outcome, not the clock
// ❌ Arbitrary timing — slow and still racy
await page.waitForTimeout(2000);
expect(await page.locator('.toast').count()).toBe(1);

// ✅ Web-first, auto-retrying assertion
await expect(page.getByText('Saved')).toBeVisible();

Pitfalls

  • Bumping global retries to hide flakiness — it masks real product bugs and slows the suite.
  • Sharing mutable state (DB rows, logged-in session) across tests, creating order dependencies.
  • Asserting on timing-sensitive values (animations, "2 seconds ago") without faking the clock.