Flaky Tests Don’t Lie.
Every time a flaky test breaks your CI pipeline for the fourth time this week, someone in the standup says, “It’s a Playwright issue.” It’s…
Flaky Tests Don’t Lie. Your Design Does. After 3+ years of building test automation at scale, here’s the hard truth nobody wants to hear.
Every time a flaky test breaks your CI pipeline for the fourth time this week, someone in the standup says, “It’s a Playwright issue.” It’s not. I’ve been writing test automation for over a decade and I’ll tell you straight — the framework almost never is.
I’ve watched teams rip out Selenium for Cypress, rip out Cypress for Playwright, and still end up with the same fragile, mood-dependent test suites. The tool changes. The instability doesn’t. That’s a design problem, not a dependency problem.
Flakiness is a symptom. The disease lives in how the test was conceived, structured, and wired to the application it’s supposed to verify. Let’s tear into the real causes — the ones nobody talks about in the GitHub issues thread.
The Synchronization Trap
The first thing junior automation engineers reach for when a test breaks intermittently is a wait. Not a smart wait — a dumb wait. A waitForTimeout(3000) stuffed in like a band-aid over a wound that needs stitches.

Hard waits work on your laptop, on a good day, with a fast network. They fail on CI runners, on degraded environments, on Mondays. Playwright already has auto-waiting baked into its assertions. The moment you override that with a fixed delay, you’re betting three seconds of build time on vibes.
Proper synchronization means attaching your test to something deterministic — a network response, a DOM state change, a specific element attribute. Not a wall clock.
Shared State: The Silent Killer
Here’s a scenario I’ve seen destroy otherwise stable pipelines. Two tests share a database user. Test A creates some records. Test B validates the count of those records. Individually they pass every time. In parallel — chaos.
“The most dangerous test is the one that only fails when something else runs before it. It means your test isn’t testing the system — it’s testing the leftover state of someone else’s test.”
Test isolation isn’t just a best practice — it’s the foundational contract of a meaningful test. Every test should own its data from setup to teardown. No shared users. No shared sessions. No “well, the previous test logs us in anyway” shortcuts.
In Playwright this means using storage state files per test, resetting database state via API before each run, and never depending on execution order. If your test can only pass when run third in the file, it’s not a test — it’s a prayer.
The Locator Problem Nobody Admits To
Fragile locators are responsible for more flakiness than any async timing issue. I’d bet on it. When developers change a class name or refactor a component, every locator targeting that CSS quietly breaks. Tests that worked fine last sprint start hemorrhaging on the next deployment — and the team blames the framework.

The getByRole, getByLabel, and getByText APIs aren't just good practices — they're structurally resilient. They bind to what the user sees, not how the engineer wrote the CSS. And when you own the component, a deliberate data-testid is a contract you maintain intentionally, not a side-effect you discover is broken.
Validate the API, Not the Spinner
One pattern that’s made my suites dramatically more stable: stop relying purely on UI state to verify that something happened. A loading spinner disappears. A success toast fades. DOM transitions are timing-sensitive by nature. But the API response? That’s binary. It either returned 200 or it didn’t.
Unnecessary UI Interactions Multiply Risk
Every UI interaction is a potential failure point. Every page navigation, every form fill, every modal dismiss adds latency, timing sensitivity, and surface area for something to go sideways. If you’re filling out a registration form just to get a user into the system so you can test something else entirely — stop.
Create your user via the API in test setup. Seed your database in before-hooks. Use Playwright’s storageState to replay authenticated sessions rather than re-logging in through the UI for every test. The less unnecessary UI surface your test touches, the less opportunity entropy has to introduce variance
Engineering Discipline Is the Fix
None of this requires a better framework. Playwright is already exceptional — its auto-waiting, network interception, and parallel execution model are genuinely well-engineered. The test failures you’re chasing aren’t Playwright’s fault.
They’re the result of tests written quickly, under sprint pressure, without enough thought about isolation, determinism, or what exactly is being verified. The fix is discipline, not a migration.
Audit your hard waits and delete every one. Make every test own its data from first line to last. Pick locators that survive refactors. Verify behavior through the API when UI timing is unreliable. Reduce your test’s footprint to only what it actually needs to touch.
Do those five things consistently and your suite won’t just be less flaky. It’ll be something your team actually trusts — which is the only thing that makes automated testing worth doing in the first place.
메타데이터
- post_id
- b4bcc09ea432
- slug
- flaky-tests-dont-lie-b4bcc09ea432
- url
- https://medium.com/@savithasjois2020/flaky-tests-dont-lie-b4bcc09ea432
- canonical_url
- https://medium.com/@savithasjois2020/flaky-tests-dont-lie-b4bcc09ea432
- author_url
- https://medium.com/@savithasjois2020
- status
- ok
- fetched_at
- 2026-06-10 08:17:25