A reliable test waits for a meaningful condition, not an estimated number of seconds. Good waits handle normal variation without hiding real performance problems.

Auto-waiting

Playwright waits before actions until the locator resolves to one element and the element is visible, stable, enabled and able to receive events as required.

await page.getByRole('button', { name: 'Pay' }).click(); await expect(page.getByText('Payment complete')).toBeVisible();

The assertion retries. A plain immediate check does not.

Explicit waits

Use an explicit wait when the next step depends on an event that auto-waiting cannot express.

NeedExample
Navigationawait page.waitForURL('**/dashboard')
API responseawait page.waitForResponse(r => r.url().includes('/save') && r.ok())
Downloadconst download = await page.waitForEvent('download')
Element stateawait expect(locator).toBeEnabled()
const responsePromise = page.waitForResponse( r => r.url().includes('/orders') && r.status() === 201 ); await page.getByRole('button', { name: 'Submit' }).click(); await responsePromise;
Start the wait first: otherwise a fast event may finish before the test starts listening.

Polling

Poll when checking an external system or an eventually consistent result.

await expect.poll(async () => { const response = await request.get('/jobs/123'); return (await response.json()).status; }, { timeout: 30_000 }).toBe('complete');

Poll a specific condition at a sensible interval. Stop when it passes or reaches a clear timeout.

Timeouts

  • Test timeout: maximum time for the whole test.
  • Expect timeout: maximum retry time for an assertion.
  • Action timeout: optional limit for an individual action.
  • Navigation timeout: optional limit for navigation.
export default defineConfig({ timeout: 30_000, expect: { timeout: 5_000 } });

Set limits from realistic service expectations. A very long global timeout makes genuine hangs slow to diagnose.

Why fixed sleeps cause flaky tests

// Avoid await page.waitForTimeout(5000); // Prefer a user-visible outcome await expect(page.getByRole('status')).toHaveText('Saved');
  • If the app is fast, the sleep wastes time.
  • If the app is slower, the test still fails.
  • The test does not explain what it expected.
  • Large suites multiply the wasted delay.
Debugging only: a short sleep may help you observe behaviour locally. Remove it before committing the test.

Useful links