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

CheckclickfillcheckhoverselectOptionpress
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: true sparingly — it can hide real bugs where an element is covered.

Waiting for navigation

// Navigations triggered by a click
await Promise.all([
  page.waitForURL('**/dashboard'),    // wait for URL change
  locator.click(),
]);

// Or use the built-in navigation expectation
await locator.click();                 // Playwright waits for load automatically
await expect(page).toHaveURL('/dashboard');

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-patternBetter approach
await page.waitForTimeout(2000)await expect(locator).toBeVisible()
await page.waitForSelector('.el')await locator.waitFor()
Checking isVisible() in a loopawait expect(locator).toBeVisible()
networkidle for all pageswaitForURL + specific assertion
force: true to fix flakinessDebug why element is covered