# Playwright Best Practices Use this reference when writing or reviewing locator, assertion, isolation, or synchronization logic. Official sources: - https://playwright.dev/docs/best-practices - https://playwright.dev/docs/locators - https://playwright.dev/docs/test-assertions - https://playwright.dev/docs/browser-contexts ## What Matters Most ### 1. Keep scenarios isolated Playwright's model is built around clean browser contexts so one test does not leak into another. Apply it like this: - do not depend on another scenario having run first - keep scenario state in the runner's scenario-owned context rather than module globals - model special authentication or session setup through explicit per-scenario fixtures rather than shared mutable state ### 2. Prefer user-facing locators Playwright recommends built-in locators that reflect what users perceive on the page. Preferred order: 1. `getByRole` 2. `getByLabel` 3. `getByPlaceholder` 4. `getByText` 5. `getByTestId` when an explicit test contract is the most stable option Avoid raw CSS/XPath selectors unless no stable user-facing contract exists and adding one is not practical. Also remember: - repeated content usually needs scoping to a stable container - exact text matching is often too brittle when role/name or label already exists - `getByTestId` is acceptable when semantics are weak but the contract is intentional - when a real UI region, card, status, or icon lacks an accessible name, prefer adding that semantic contract in product code before falling back to `getByTestId` ### 3. Use web-first assertions Playwright assertions auto-wait and retry. Prefer them over manual state inspection. Prefer: - `await expect(page).toHaveURL(...)` - `await expect(locator).toBeVisible()` - `await expect(locator).toBeHidden()` - `await expect(locator).toBeEnabled()` - `await expect(locator).toHaveText(...)` Avoid: - `expect(await locator.isVisible()).toBe(true)` - custom polling loops for DOM state - `waitForTimeout` as synchronization If a condition genuinely needs custom retry logic, use Playwright's polling/assertion tools deliberately and keep that choice local and explicit. Use `expect.poll` for non-DOM truth such as API state, backend eventual consistency, generated resources, or captured browser events. For DOM state, use locator assertions so Playwright can apply actionability and web-first retry semantics. ### 4. Let actions wait for actionability Locator actions already wait for the element to be actionable. Do not preface every click/fill with extra timing logic unless the action needs a specific visible/ready assertion for clarity. Good pattern: - assert a meaningful visible state when that is part of the behavior - then click/fill/select via locator APIs Bad pattern: - stack arbitrary waits before every action - wait on unstable implementation details instead of the visible state the user cares about ### 5. Match debugging to the active harness Playwright supports traces, screenshots, page snapshots, and browser logs. Configure artifact capture at the runner boundary instead of adding parallel diagnostics to individual scenarios. ## Review Questions - Would this locator survive DOM refactors that do not change user-visible behavior? - Is this assertion using Playwright's retrying semantics? - Is any explicit wait masking a real readiness problem? - Does this code preserve per-scenario isolation? - Is a new abstraction really needed, or does it bypass the runner's scenario-owned context and lifecycle?