← Back to Docs
Recipe

Playwright Patterns

Reliable selectors, resilient waits, and clean test architecture for end-to-end flows.

Locator Strategy

Prefer getByRole andgetByTestId over fragile CSS paths. Roles mirror the accessibility tree and survive markup refactors.

await page.getByRole('button', { name: 'Checkout' }).click()
await page.getByTestId('cart-total').textContent()

Auto-Waiting

Playwright auto-waits for elements to be actionable. Avoid manualpage.waitForTimeout — lean on web-first assertions instead.

await expect(page.getByText('Order confirmed')).toBeVisible()
await expect(page.locator('.spinner')).toBeHidden()

Network Idle

For SPA transitions, wait for a specific API response rather thannetworkidle. This keeps tests deterministic under variable latency.

const resp = await page.waitForResponse(
  r => r.url().includes('/api/orders') && r.status() === 200
)

Fixtures & Isolation

Extend test with custom fixtures for shared setup. Each test gets a fresh context — no state leaks between cases.

const test = base.extend<{ authedPage: Page }>({
  authedPage: async ({ page }, use) => {
    await page.goto('/login')
    await page.getByLabel('Email').fill('u@nimbus.dev')
    await page.getByRole('button', { name: 'Sign in' }).click()
    await expect(page).toHaveURL('/dashboard')
    await use(page)
  }
})

Pro tip: Run with--trace onto capture DOM snapshots, network logs, and screenshots for every failure.