PLAYWRIGHT UIBEGINNER

Auto-Waiting in Playwright: Why You Don't Need sleep() Anymore

How Playwright's actionability checks and web-first assertions eliminate manual waits, race conditions, and flaky tests.

iff Solution Academy July 6, 2026 8 min read Updated July 6, 2026
Playwright Auto-waiting Flaky Tests

The Waiting Problem

Legacy frameworks force you to sprinkle sleep(2000) everywhere. Sleeps are either too short (flaky) or too long (slow). Playwright removes almost every case where you'd need one.

Actionability Checks

Before performing an action, Playwright waits for the target element to be attached, visible, stable, enabled, and receiving events. If any check fails within the timeout, the action retries.

ts
// No manual wait needed — Playwright retries until the button is actionable.
await page.getByRole('button', { name: 'Save' }).click();

Web-First Assertions

expect() with a Locator auto-retries the assertion until it passes or times out. This replaces polling loops.

ts
await expect(page.getByRole('alert')).toHaveText('Saved!');
await expect(page.getByRole('list')).toHaveCount(5);

When You Do Need to Wait

For non-DOM signals (network, custom events), Playwright still has explicit waiters:

ts
await page.waitForURL('**/dashboard');
await page.waitForResponse((r) => r.url().includes('/api/user') && r.ok());
await page.waitForLoadState('networkidle');

Anti-Patterns to Avoid

  • page.waitForTimeout(N) in production tests — brittle.
  • Manual polling loops — expect(locator) already polls.
  • waitForSelector followed by click — the click already waits.

Get Playwright tutorials in your inbox

Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.

Recommended Next Articles