Skip to content
Testing

Fix Flaky Playwright Tests with Auto-Waiting and Retrying Assertions

A Save button becomes clickable before the save result appears. Your test passes locally and fails on a busy runner because it checks the result too early. The useful repair is to wait for the right observable condition. The example separates action readiness from application completion so you can check both.

By Published

4 min readEditorial analysisUpdated
  • Playwright
  • Testing
  • Debugging
  • QA interviews
What to remember

Use locators for actions and awaited retrying assertions for eventual UI results. Investigate selectors, state and isolation before extending timeouts or adding retries.

Write the success condition before choosing a wait

Success means the status reads Saved after clicking Save. A successful click alone cannot establish that. Fixed pauses guess completion time: short ones fail on slow runs; long ones delay every run. Record the action, observed status and expected status first.

Keep test data independent: another test changing the same account or draft invalidates the starting state. Prefer locators with a clear role and accessible name. If several elements match, identify the intended target rather than selecting an arbitrary first result.

Choose a wait that matches the thing you need

The table separates an action, an eventual UI observation and a custom condition. A string already extracted from the page is a snapshot. Comparing that string cannot make the browser update it. Keep the locator attached to a retrying assertion when the UI may still change.

Waiting decisions for a save flow
NeedUseWhat it establishes
Click Savelocator.click()Actionability
Status becomes Savedawait expect(locator)Expected UI condition
Custom value changesawait expect.poll(...)Polled condition
Compare a known valueexpect(value)Current snapshot

Run a fixture with two independent delays

Save this as a spec in an existing Playwright Test project. The HTML simulates readiness after 300 milliseconds and completion 400 milliseconds after the click. Those timers belong to the simulated application; the test itself contains no guessed sleep. Expected result: the button is clicked after enabling, and the assertion finishes when the status becomes Saved.

Replace the final line with expect(await page.getByRole('status').textContent()).toBe('Saved'). It can read Draft and fail; a slow enough read can also hide the race. Restore the locator assertion, vary both delays within the configured timeout, and rerun. This experiment demonstrates why awaiting a read is different from awaiting an eventual condition.

import { test, expect } from '@playwright/test';

test('save waits for readiness and completion', async ({ page }) => {
  await page.setContent(`
    <button disabled>Save</button>
    <p role="status">Draft</p>
    <script>
      const button = document.querySelector('button');
      setTimeout(() => { button.disabled = false; }, 300);
      button.addEventListener('click', () => {
        setTimeout(() => {
          document.querySelector('[role="status"]').textContent = 'Saved';
        }, 400);
      });
    </script>
  `);

  await page.getByRole('button', { name: 'Save', exact: true }).click();
  await expect(page.getByRole('status')).toHaveText('Saved');
});

Understand what auto-waiting can establish

For click, Playwright checks a unique target, visibility, stability, receiving pointer events and enabled state. Different actions have different checks. These checks concern whether the action can be performed, not whether the application's save completed. An enabled button can still have no useful handler during incomplete hydration: expose genuine readiness in the application instead of assuming click checks can detect it.

Likewise, forcing a click can bypass relevant checks without fixing the underlying condition. If an overlay unexpectedly covers Save, inspect why it exists. Make the test wait for the intended state, or fix the product behavior when the overlay should not be there.

Use a failed trace to decide what to change

Review action logs, DOM snapshots and network activity at the failure. Did the locator match wrongly, the status never appear, or a request fail? Extend a timeout only when the correct condition legitimately needs more time; it cannot fix a wrong expectation.

Retries can expose a recurring flaky pattern, but a retry-pass is still evidence to investigate. Preserve the first failure's trace and starting data. Also consider fixture reuse or shared state when outcomes depend on which test ran first.

A 15-minute debugging exercise

Start with the immediate text comparison from the example. Explain the race, fix it, then add a second Save button to the fixture. Resolve the ambiguity through a meaningful container or distinct accessible name, not first(). Finally make the application never publish Saved and confirm that the corrected test fails with a useful condition timeout. A test that always passes is not a repaired test.

Review locators and isolation in the linked overview. Rehearse what you observed, which assumption failed and what the repaired assertion proves.

Quick answers

Frequently asked questions

Does Playwright automatically wait for every application operation?

No. Action checks establish readiness for a particular action. Express the eventual result separately, such as an awaited locator assertion for the save status.

Why is expect(await locator.textContent()) flaky?

textContent returns a value at that moment. A generic comparison of that value does not retry the page read. Use await expect(locator).toHaveText(...) when text may arrive later.

Should I add retries to fix a flaky test?

Use retry outcomes as diagnostic evidence. Fix the specific selector, state, race or isolation problem and retain meaningful failure checks; repeated attempts do not correct a wrong test.

Source notes

References and review policy

Information checked on October 4, 2026. Section links identify sources for factual claims and technical explanations. Interpretations, practice scenarios and preparation recommendations are RecallDeck’s editorial work.

From reading to recall

Practice the full interview loop.

RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.

Start studying

Keep going