Playwright Cheatsheet
Auto-waiting
Use this Playwright reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
What auto-waiting covers
Playwright automatically waits before every action and assertion. You almost never need waitForTimeout or manual sleeps.
Actions — checks performed before executing
| Check | click | fill | check | hover | selectOption | press |
|---|---|---|---|---|---|---|
| Attached to DOM | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Visible | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Stable (no animation) | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Enabled | ✓ | ✓ | ✓ | ✓ | ||
| Editable | ✓ | |||||
| Receives events | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Assertions — auto-retry
expect(locator).* polls continuously until the matcher passes or times out.
Default assertion timeout: 5 000 ms (configurable via expect.timeout).
Overriding timeouts
// Per-action await locator.click({ timeout: 10_000 }); // Per-assertion await expect(locator).toBeVisible({ timeout: 15_000 }); // Per-test test('long test', async ({ page }) => { test.setTimeout(60_000); }); // Global in config export default defineConfig({ timeout: 30_000, expect: { timeout: 10_000 }, });
Bypassing auto-waiting
await locator.click({ force: true }); // skip actionability checks await locator.click({ noWaitAfter: true }); // don't wait for navigation/network idle after
Use
force: truesparingly — it can hide real bugs where an element is covered.
waitForURL
await page.waitForURL('https://example.com/home'); await page.waitForURL('**/home'); // glob await page.waitForURL(/\/home$/); // regex await page.waitForURL('**/home', { waitUntil: 'networkidle' }); await page.waitForURL('**/home', { timeout: 10_000 });
waitForLoadState
await page.waitForLoadState('load'); // default — fires on DOMContentLoaded + load await page.waitForLoadState('domcontentloaded'); await page.waitForLoadState('networkidle'); // ≤2 connections for ≥500ms (avoid in fast apps) await page.waitForLoadState('commit'); // HTTP response headers received
waitForSelector (legacy — prefer locator)
await page.waitForSelector('.spinner', { state: 'hidden' }); await page.waitForSelector('.toast', { state: 'visible' }); await page.waitForSelector('#result', { state: 'attached' }); await page.waitForSelector('#old', { state: 'detached' });
waitForFunction — custom JS condition
await page.waitForFunction(() => window.appReady === true); await page.waitForFunction( count => document.querySelectorAll('.item').length >= count, 5 // argument passed into page context ); await page.waitForFunction(() => document.readyState === 'complete', null, { timeout: 10_000, polling: 200, // check every 200ms (default: 'raf') });
waitForEvent
const [popup] = await Promise.all([ page.waitForEvent('popup'), page.locator('a[target="_blank"]').click(), ]); const [download] = await Promise.all([ page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click(), ]); const [request] = await Promise.all([ page.waitForEvent('request', req => req.url().includes('/api/data')), page.locator('#load').click(), ]);
Waitable events (page)
close, console, crash, dialog, domcontentloaded, download, filechooser, frameattached, framedetached, framenavigated, load, pageerror, popup, request, requestfailed, requestfinished, response, websocket, worker
waitForResponse / waitForRequest
const [response] = await Promise.all([ page.waitForResponse('**/api/users'), page.locator('#load-users').click(), ]); console.log(await response.json()); const [response2] = await Promise.all([ page.waitForResponse(res => res.url().includes('/api') && res.status() === 200), page.locator('#submit').click(), ]);
expect.poll — async retry loop
await expect.poll(async () => { const res = await page.request.get('/status'); return (await res.json()).phase; }, { message: 'Deployment should be ready', intervals: [1000, 2000, 5000, 10_000], timeout: 60_000, }).toBe('ready');
Common anti-patterns
| Anti-pattern | Better approach |
|---|---|
await page.waitForTimeout(2000) | await expect(locator).toBeVisible() |
await page.waitForSelector('.el') | await locator.waitFor() |
Checking isVisible() in a loop | await expect(locator).toBeVisible() |
networkidle for all pages | waitForURL + specific assertion |
force: true to fix flakiness | Debug why element is covered |