Write end-to-end tests using Playwright. Test user behavior, not implementation details. Also useful for non-test Playwright scripting.
Test what users see and do, never implementation details. Every selector choice should be semantic.
| Priority | Method | Use when |
|---|---|---|
| 1 | getByRole('button', { name: /submit/i }) | Any semantic element |
| 2 | getByLabel('Email') | Form inputs |
| 3 | getByPlaceholder(...) | Inputs without labels |
| 4 | getByAltText(...) | Images |
| 5 | getByTitle(...) | Icon buttons with no text |
| 6 | getByTestId(...) | Last resort only |
| 7 | getByText(...) | Non-interactive text only |
| ❌ | locator('.class') / XPath |
| Never |
await expect)await expect(page.getByRole('button', { name: 'Save' })).toBeVisible()
await expect(page.getByLabel('Email')).toHaveValue('[email protected]')
await expect(page.getByRole('checkbox')).toBeChecked()
await expect(page.getByRole('listitem')).toHaveCount(5)
await expect(page).toHaveURL('/dashboard')
Never use waitForTimeout() or waitForSelector() — await expect() auto-retries.
beforeEach for common setup within a suiteafterEach for cleanup (prefer API calls over UI)waitForTimeout() / arbitrary sleeps