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.