Locators And Waits

1 min read
Mid-level1 min read
Rapid overview

Locators And Waits

TL;DR

The goal: stable selectors + web-first assertions. If you have to “sleep”, something is missing in the test design.

How it works

Core principles

Trust Playwright’s auto-waiting

// GOOD: actions auto-wait for actionability
await page.getByTestId('submit').click();

// BAD: redundant manual waits (often hides real problems)
await page.waitForSelector('[data-testid="submit"]');
await page.click('[data-testid="submit"]');

Prefer web-first assertions

// GOOD: retries until timeout
await expect(page.getByTestId('success')).toBeVisible();

// BAD: single snapshot check
expect(await page.getByTestId('success').isVisible()).toBe(true);

Locator strategy (priority order)

  1. getByTestId / [data-testid="..."] (fast + stable)
  2. getByRole (semantic + accessibility-friendly)
  3. getByLabel, getByPlaceholder (forms)
  4. getByText (only if unique and stable)
  5. Complex CSS selectors (last resort)

Recommended:

page.getByTestId('login-button')
page.getByRole('button', { name: 'Sign in' })
page.getByLabel('Email')

Avoid:

// brittle: DOM structure coupling
page.locator('.form > div:nth-child(2) button.primary')

// brittle: index-based selection
page.locator('button').nth(2)

What to wait for (instead of sleeping)

ScenarioBest wait
element appearsawait expect(locator).toBeVisible()
element disappearsawait expect(locator).not.toBeVisible()
text changesawait expect(locator).toHaveText(...)
URL changesawait expect(page).toHaveURL(...)
request finishesawait page.waitForResponse(...)

Parallel wait pattern

await Promise.all([
  page.waitForURL(/\\/dashboard/),
  page.getByTestId('login-submit').click(),
]);

Fast default:

await page.goto('/dashboard', { waitUntil: 'commit' });
await expect(page.getByTestId('dashboard-root')).toBeVisible();

Avoid networkidle unless you fully understand why it’s needed.

Common pitfalls

  • page.waitForTimeout(...)
  • waitUntil: 'networkidle' as a default navigation policy
  • .or(...) locator chains (slow + hides selector problems)
  • “check then assert” (double work): if (await locator.isVisible()) expect(locator).toBeVisible()

See also