Locators And Waits

1 min read
Rapid overview

Locators + Waiting (Best Practices)

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

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.

Anti-patterns

  • 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()