Key takeaways
- Never use fixed sleeps as the first fix for flaky tests.
- Actions wait for visible, stable, enabled and receiving-events states.
- Web-first assertions retry; plain value assertions do not.
- Use expect.poll or expect.toPass for conditions that are not locator based.
- Check network, data and test isolation before raising timeouts.
What is Playwright auto-waiting and assertions?
What does auto-waiting do?
Before Playwright clicks or types, it checks that the element is attached, visible, stable (not animating), able to receive events (not covered by another element) and enabled. For typing it also checks that the field is editable. If a check fails it waits and retries until the action timeout. This replaces most manual waits found in older automation code.
Which assertions retry?
Web-first assertions, written as await expect(locator).toHaveText('Done'), repeat the check until it passes or the expect timeout ends. Common ones are toBeVisible, toBeHidden, toHaveText, toContainText, toHaveValue, toHaveCount, toBeEnabled, toHaveURL and toHaveTitle. Compare this with expect(await locator.textContent()).toBe('Done'), which reads once and fails if the page is still updating.
How do I write a stable check?
await page.getByRole('button', { name: 'Place order' }).click();
await expect(page.getByText('Order confirmed')).toBeVisible();
await expect(page).toHaveURL(/confirmation/);
await expect(page.getByRole('row')).toHaveCount(3);
Each line waits for the state it needs. Put the assertion that proves the action finished right after the action.
What are the default timeouts?
Playwright has separate settings: a test timeout for the whole test, an action timeout for clicks and fills, a navigation timeout and an expect timeout for assertions. They are set in the configuration file or per test. Defaults are reasonable for most pages. If a test needs more time, find out why before raising the number: a slow API, missing test data or heavy animation are common causes.
When do I need polling or toPass?
Some conditions are not about a locator, such as a value in a database or a response from an API. Use await expect.poll(async () => getStatus()).toBe('ready') or wrap several steps in await expect(async () => { ... }).toPass(). These helpers retry a block until it succeeds, with clear timeouts, and are better than loops with sleeps.
What are soft assertions?
expect.soft(locator).toHaveText('...') records a failure but lets the test continue, so you see several problems in one run. Use them for checks on a summary page where independent fields are verified. Do not use them when later steps depend on the earlier result.
How do I fix flaky tests?
Work through causes in order: weak locators, assertions that read once, shared test data, tests depending on each other, third-party scripts, animations and slow environments. Run with --repeat-each to expose flakiness, open the trace to see the page state and fix the cause. Add retries in CI only as a safety net and track which tests need them.
What should I avoid?
Avoid page.waitForTimeout except for debugging, avoid asserting on text that includes dynamic dates and avoid broad timeouts that hide real slowness. Keep tests independent and data predictable.
How do animations and loading states affect tests?
An element that is still moving fails the stable check, so Playwright waits until it settles. Long skeleton screens or spinners are best handled by asserting the final content instead of waiting for the spinner to vanish, since the end state is what the user needs. If an animation is purely decorative, tests can disable it with a reduced motion setting or a test-only style so that runs are faster and calmer.
How do I write meaningful failure messages?
Give custom messages to important assertions, for example expect(locator, 'cart badge should show 2 items').toHaveText('2'). When a run fails in CI at night, a clear message tells the reader what the test intended before they open a trace. Combine this with descriptive test titles and steps using test.step so that reports read like a short story of the user journey.
What is a quick checklist for stable waits?
Use locators instead of handles, assert the end state, avoid sleeps, wait on named events, keep data unique per test and read the trace before changing any timeout number.
How do I practise these ideas?
Take one flaky test from your project, run it twenty times with repeat-each, open a failing trace and change one thing at a time. Note which change removed the flakiness, and write it in a team wiki so others learn the pattern.
Download Playwright Auto-Waiting and Assertions PDF
Playwright Auto-Waiting and Assertions PDF
The PDF carries the same content as this page in a clean, print-ready layout, so you can read it offline and keep it for revision.
4 pages · 112 KB · PDF · Updated 5 Nov 2026