Skip to content
SDETRole
SDETRole

Playwright interview question

How does Playwright auto-waiting work, and when is it not enough?

intermediate 2–5 yrs #auto-waiting#actionability#assertions#flaky-tests

Short answer

Before acting, Playwright waits for actionability checks (attached, visible, stable, receives events, enabled, editable as relevant). Web-first assertions retry until timeout. It is not enough for app-level state like background jobs or data that changes without a DOM signal.

Answer

Playwright waits before every action until the target element is actionable, and its web-first assertions retry until the expected state appears. Together, these remove most manual waits. Auto-waiting only knows about what is observable in the page, though, so it is not enough when the condition you care about lives in the backend, in a non-DOM value, or in a UI that looks ready before it really is.

Actionability checks

When you call an action on a locator, Playwright resolves the locator to exactly one element (strictness) and waits until the relevant checks pass:

Check Meaning Applies to (examples)
Attached element is in the DOM all actions
Visible non-empty bounding box, not visibility:hidden click, fill, hover, check
Stable not animating; same bounding box for two consecutive animation frames click, hover, check
Receives events not obscured; the hit target at the click point is the element click, hover, check
Enabled not disabled (including via a disabled fieldset or aria-disabled) click, fill, check, selectOption
Editable enabled and not readonly fill, clear

If a check fails it re-tries until the action timeout (by default no separate limit, so bounded by the test timeout of 30 seconds) and then fails with a log explaining which check never passed. { force: true } skips the non-essential checks; using it routinely hides real problems such as an overlay covering a button.

Navigation has its own waiting: page.goto waits for the load event by default (configurable with waitUntil). For navigations triggered by a click, assert on the destination with await expect(page).toHaveURL(...) or use page.waitForURL(...).

Web-first assertions

await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('status')).toHaveText('Saved');

The assertion re-checks until it passes or the expect timeout (5 seconds by default) runs out. Snapshot reads such as await locator.isVisible() or await locator.textContent() do not wait.

When it is not enough

1. State that has no DOM signal yet. An order is placed, but fulfilment runs as a background job. The UI shows “Order received” instantly; the thing you want to verify happens seconds later. Poll the source of truth:

await expect.poll(async () => {
  const res = await request.get(`/api/orders/${orderId}`);
  return (await res.json()).status;
}, { intervals: [500, 1_000, 2_000], timeout: 30_000 }).toBe('FULFILLED');

2. Several conditions that must hold together, or a check that needs a retry of an action:

await expect(async () => {
  await page.getByRole('button', { name: 'Refresh' }).click();
  await expect(page.getByTestId('report-status')).toHaveText('Ready', { timeout: 1_000 });
}).toPass({ timeout: 30_000 });

3. The UI is visible before it is interactive. For example, server-rendered HTML before hydration: the button is visible, enabled and stable, the click “succeeds”, but no handler is attached yet. Auto-waiting cannot know. Wait for a signal that the app is ready (an element that only renders after hydration, or a response the click depends on), and raise it with developers as a testability issue.

4. Waiting for a specific network response before asserting on data derived from it:

const saved = page.waitForResponse((r) => r.url().endsWith('/api/profile') && r.request().method() === 'PUT');
await page.getByRole('button', { name: 'Save' }).click();
expect((await saved).ok()).toBeTruthy();

Start waiting before the action that triggers the request.

5. Your assertion is the wrong one. Checking for a spinner to vanish passes instantly if the spinner has not appeared yet. Assert on the end state (the data is shown) instead.

What not to do

page.waitForTimeout(3000) is a fixed sleep: too short in CI, wasteful locally. The documentation discourages it outside debugging.

What interviewers look for

  • The actionability checks, roughly correct, and that they apply per action type.
  • Retrying web-first assertions versus non-retrying snapshot reads.
  • Specific limits: backend state, hydration, multi-step conditions.
  • Correct tools: expect.poll, toPass, waitForResponse, never fixed sleeps.

A strong answer covers

  • Lists actionability checks performed before actions
  • Mentions web-first assertions retrying until timeout
  • Identifies limits: app/back-end state with no UI signal
  • Uses explicit event waits like waitForResponse instead of fixed sleeps
  • Mentions timeouts: action, expect, test, navigation
Practise answering under time pressure

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →