Lesson 4 of 9 · 20m
End-to-End Tests Without the Flake
The smallest possible e2e suite — the golden path, real auth, seeded data — and the patterns that keep it reliable enough to block merges.
E2e tests are the most expensive and most valuable per test — they exercise the real system a real user would touch. The mistake is treating them like more unit tests: covering every edge case in a 30-second browser run instead of 3 fast ones.
What e2e should cover
- The golden path: sign up → create → edit → share → sign out. One test per user-critical journey, not per feature.
- The risky integrations: OAuth callback, payment webhook, file upload — the paths that can't be unit-tested.
- The assertion that "the whole system works" — not that every button does.
If an e2e test can be written at the integration level (same confidence, less flake), write it there. E2e exists to catch stack failures — a misconfigured proxy, a CORS header, a build that bundles wrong.
Flake sources, ranked
- Timing —
waitForSelectorraces, fixedsleep(500)delays. Always wait on a condition (expect(locator).toBeVisible()), never a duration. - Shared state — tests mutating the same database row. Use isolated test users or fresh data per test.
- Browser/environment drift — viewport size, timezone, network latency. Set them in the test fixture explicitly.
- Async nondeterminism — a render that usually finishes in 200ms but occasionally takes 2s. Wait for the element, not the clock.
The runner matters
Playwright, Cypress, or Selenium all work — the differences are in the fixtures, parallelism, and how they handle waiting. What matters is: each test runs in a clean browser context, tests run in parallel without sharing state, and the suite runs in CI before merge, not nightly.
Field notes
- Keep the e2e suite under 10 tests for a typical web app — 10 slow tests beat 100 flaky ones.
- Seed data via API or direct DB insert — never via UI automation.
- An e2e test that's "sometimes red" gets rerun tolerance in CI until it's fixed — don't let it erode trust in green.