Global Infrastructure & Construction Group · 2024–25 · Frontend developer
Cutting a test suite nobody trusted down to one that runs
A flaky 40-minute end-to-end suite, rebuilt around fixtures and role-based selectors and sharded to under five minutes.
- Suite duration
- 40m → 4m
- Flake rate
- ~18% → <1%
- E2E specs
- 310 → 120
Stack
- Playwright
- TypeScript
- Vitest
- GitHub Actions
The problem
A suite that takes forty minutes and fails about a fifth of the time for no reason is worse than no suite at all: it trains the team to re-run rather than investigate, so genuine failures get dismissed with everything else.
The approach
Three changes did most of the work.
Fixtures replaced page objects. The existing page-object hierarchy had become a second application with its own inheritance tree and its own bugs. Playwright fixtures are just setup functions that tests declare in their arguments — no inheritance, no shared mutable state, and setup cost paid only by tests that need it.
Role-based selectors replaced CSS selectors. Anything reaching into DOM structure breaks during refactors without finding real bugs. getByRole("button", { name: "Save" }) breaks only when the button stops being a button called Save — and it doubles as an accessibility assertion, because an element you can't select by role and name is one a screen reader user can't find either.
Every arbitrary wait was deleted. waitForTimeout was the single largest flake source, and flaky in the worst direction: green locally, red on a loaded runner. Each instance turned out to be either a missing assertion or a genuine race condition in the application — several real bugs surfaced this way.
Being ruthless about the pyramid
The bigger duration win was deleting tests. Roughly two thirds of the E2E suite was unit-level logic wearing a browser as a costume. Moved down the pyramid, those cases ran in milliseconds, and the remaining 120 specs describe actual user journeys — which is the only thing E2E is uniquely good at.
Sharding across runners handled what was left. trace: "on-first-retry" was added at the same time: free on green runs, and it turns a CI failure into a DOM snapshot and a timeline instead of a guess.
The outcome
Four minutes, under 1% flake. The number that mattered wasn't either of those — it was that failures started being investigated again.