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.
// 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.
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:
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.