Prefer role-based locators such as `page.getByRole('button', { name: 'Submit' })` over CSS selectors to align with the accessibility tree and survive minor DOM refactors.
Use `getByLabel` for form inputs, `getByPlaceholder` for unlabelled inputs, and `getByTestId` when the team controls test-id attributes — each targets a stable semantic attribute.
Chain locators with `.filter({ hasText: '...' })` or `.nth(0)` to narrow results when a role matches multiple elements.
Call action methods (`click`, `fill`, `check`) directly on the locator; Playwright automatically waits for the element to be visible, stable, enabled, and not obscured before acting — no explicit `waitForSelector` needed.
Use `expect(locator).toBeVisible()` and other web-first assertions from `@playwright/test`; they retry until the condition is met or the timeout expires, eliminating most race conditions.
Known gotchas
Locators are lazy — they re-query the DOM on every action. Storing a locator reference and reusing it is safe; storing an `ElementHandle` is not, because it can go stale after navigation.
`getByRole` matches the computed ARIA role, which may differ from the HTML tag. A `<div role='button'>` matches `getByRole('button')`, but a plain `<div>` without a role does not — inspect the accessibility tree in DevTools when a role locator finds nothing.
Auto-waiting respects the `actionability` checks (visible, stable, enabled, editable) but does NOT wait for network requests to finish. If an action depends on an in-flight API response, add `page.waitForResponse` or use `expect` assertions that poll for the resulting DOM change.
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp