Test Automation

How to Fix Flaky Playwright Tests: Wait on the Network, Not the Clock

Kulish KulshresthaOctober 17, 20265 min readUpdated October 17, 2026
How to Fix Flaky Playwright Tests: Wait on the Network, Not the Clock

The fix for flaky Playwright tests is to stop waiting on the clock and wait on the network: replace every fixed timeout with a wait for the specific response or visible state that proves the application has finished its work. A sleep is a guess about speed. A wait on the real event is correct on a slow day and on a fast one.

Most flaky Playwright tests are not a tool problem. They are a design problem in the suite: waits that guess, locators that depend on markup nobody promised to keep stable, and test data built inline in every spec. This guide covers the causes, the patterns that remove them, and a checklist you can run against your own suite. It draws on a real end-to-end test automation engagement on a multi-tenant commerce operations platform.

Why Playwright tests go flaky

When a nightly run goes red, either the product is broken or the test is. A flaky suite blurs the difference, and that is its real cost. Somebody has to open each failure and decide which it is, usually by hand and usually before the working day starts. After enough mornings like that, people stop trusting a red result and start rerunning it.

A few habits produce most of the noise.

  • Arbitrary timeouts. A fixed pause passes when the application is quick and fails when it is not. Raising the number makes the suite slower without making it correct.
  • Locators tied to implementation details. Identifiers generated by a forms library or a data grid change when the library does. The test fails for a reason no user would ever notice.
  • Test data built inline. When every spec invents its own records, with random values scattered across files, failures are hard to reproduce and the setup cannot be reused.

What to wait on instead of timeouts

Playwright already retries most assertions about what is visible on the page, so the starting rule is to lean on that and never add a pause on top of it. The harder case is an action whose outcome is not visible straight away: a save, a delete, or a bulk operation that talks to the backend.

For those, wait for the response of the exact request the action triggers. In a GraphQL application, that means waiting on the named operation rather than on the address, because many operations usually travel to the same endpoint. If you want to wait for a GraphQL response in Playwright reliably, key the wait on the operation name, then assert on what came back.

A save usually has more than one step, so wait for each in order: the mutation, the success notification, and then the refetch that refreshes the list. Waiting on the mutation proves the request returned. Waiting on the notification proves the interface reacted. Waiting on the refetch proves the screen now shows the saved state, so the next step starts from a settled page.

Keep the waits inside a Playwright page object model

Scattering network waits across specs trades one problem for another. A Playwright page object model gives each waiting rule a single home. The page object knows how to save and what "saved" means, so a spec reads as intent: create the record, confirm it appears.

Layering helps. A base page holds the shared behavior. Drawer and wizard classes extend it for the interface patterns that recur, and each can have a variant that is aware of the GraphQL operations behind it. When a rule about waiting changes, you change it once.

There is a trade-off, and it is worth stating plainly. Page objects that wait on operation names have to know those names, so a frontend engineer who renames a query breaks the tests loudly. That is the point. A loud break takes an hour to fix. A silent flake costs a team its trust in the suite, and that trust does not come back easily.

Use test data builders instead of inline data

Test data builders are small fluent objects that assemble a valid record one named step at a time. The same builder can seed data through the API before a test starts and fill the form during the test, so setup and interaction describe the same record. Builders pay off most in a suite that runs one generic create, update and delete scenario against many different entity drawers. Each entity declares its own metadata on the drawer class, and the shared scenario builds data through the builder instead of through scattered random-data helpers.

Pair builders with locators that survive a redesign. Prefer the role and accessible name that a user or assistive technology would see, and scope a step by its ARIA region name rather than by generated identifiers. These are among the Playwright best practices that turn a frontend migration into an annoyance instead of a rewrite.

What this looked like on a real engagement

On a multi-tenant commerce operations platform, features were shipping faster than they were being automated. The specs that did exist waited on arbitrary timeouts, built their data inline, and broke every time the frontend moved to a new forms library or data-grid version. Each nightly run mixed genuine defects with test-side timing failures, and somebody sorted one from the other by hand every morning.

We owned automation for several areas of the application and built the suite in layers: eighty-five end-to-end test cases and forty-eight page-object classes across forty-six files, with a base page, drawer and wizard classes, and a GraphQL-aware variant of each. Fluent data builders for schedules, item availability, order-type settings and store groups seeded data through the API and filled the interface. Every create, update and delete waits on the named GraphQL operation and its refetch, never on a sleep.

The decision that mattered was the trade-off described above. We made page objects know operation names on purpose, accepting that a renamed query would fail loudly, because a loud break is cheap and a silent flake is not.

The suite was built to survive two frontend migrations. Eighty-five test cases and forty-eight page-object classes remain in use on the integration branch. When a forms-library migration removed the old store-list query and its element identifiers, four bulk-wizard tests broke. Scoping the affected step by its ARIA region name brought all four back, and the fix survived the migration after that one too. For the full account, you can read the full case study, or browse the other Kaycore case studies.

A checklist for fixing flaky Playwright tests

  • No fixed sleeps anywhere. Every wait names a response, a notification or a visible state.
  • Every save waits for the mutation, then the success notification, then the refetch, in that order.
  • Locators use role and accessible name, scoped by region, rather than generated identifiers.
  • Page objects own the waiting rules, so specs describe intent and nothing else.
  • Test data comes from builders, and the same builder can seed through the API or fill the form.
  • Every red nightly failure is sorted into a product defect or a test fix, never left as rerun and hope.

Waiting is only part of nightly stability. The rest is what your tests leave behind in a shared environment, which we cover in flaky tests in CI and how to take over a red pipeline. If you would like to see how we report findings on an application, look at a sample quality audit, or see how we work with teams on our services page.

Questions

Frequently asked questions

Why are my Playwright tests flaky?

Usually the cause sits in the suite rather than in Playwright. The common culprits are fixed timeouts that guess how long the application needs, locators tied to generated identifiers that change when a library changes, and test data built inline in every spec. Replacing guesses with waits on real events removes most of the noise.

How do I wait for a GraphQL response in Playwright?

Wait for the response of the specific named operation that the action triggers, then assert on the result. For a save, wait for three things in order: the mutation, the success notification, and the refetch query that refreshes the screen. That way the next step starts from a settled page.

Should I ever use a fixed timeout in an end-to-end test?

Treat a fixed timeout as a signal that you have not yet found the event the test should wait for. There is nearly always a response, a notification or a visible state that proves the work is finished, and a wait on that event is correct whether the application is fast or slow.

Does waiting on operation names make tests brittle?

It makes them loud rather than brittle. If a frontend engineer renames a query, the tests break immediately and visibly, and the fix is quick. The alternative is a silent flake that costs the team its trust in the suite, which is much harder to win back.

Keep reading

Ship AI you can trust in production

Talk to our lead quality engineers about validating your AI systems. You keep the test suites, datasets, and results.

  • Senior engineers only
  • You keep the code and datasets
  • A lead architect reviews your request
  • Zero spam, no BDRs
Kaycore

Kaycore AI

Online • AI Assistant

Need human expertise?

Hi! I'm the Kaycore AI Assistant. We test AI, and everything AI builds. Ask me about our services, case studies, a sample quality audit, or KayHealth.