Playwright Best Practices
Expert guidance for writing reliable, maintainable Playwright tests.
Quick Reference
| Concern |
Best Practice |
Avoid |
| Selectors |
getByRole, getByTestId, getByLabel |
CSS classes, DOM structure |
| Waits |
Auto-waiting, explicit assertions |
waitForTimeout, hardcoded delays |
| Accessibility |
getByRole, a11y checks |
Visual-only testing |
| Flaky tests |
Proper waits, stable selectors |
Timing-dependent assertions |
| Isolation |
Cleanup after each test |
Tests depending on each other |
| Parallel |
Independent tests |
Shared state |
What Do You Need?
- Selectors - Stable, semantic locator strategies
- Waits - Proper waiting, no hardcoded delays
- Accessibility - A11y assertions, keyboard nav
- Responsive - Testing different viewports
- Flaky prevention - Isolation, cleanup, retries
Specify a number or describe your testing concern.
Routing
| Response |
Reference to Read |
| 1, "selector", "locator", "getBy" |
selectors.md |
| 2, "wait", "timeout", "delay" |
waits.md |
| 3, "a11y", "accessibility", "keyboard" |
accessibility.md |
| 4, "responsive", "mobile", "viewport" |
responsive.md |
| 5, "flaky", "unstable", "retry" |
flaky-tests.md |
Essential Principles
Use semantic selectors: getByRole, getByLabel, getByTestId are stable. CSS classes and DOM structure change frequently.
Never waitForTimeout: Hardcoded delays make tests slow and flaky. Use auto-waiting and explicit assertions.
Test accessibility: getByRole ensures accessible markup. Keyboard tests verify a11y.
Isolate tests: Each test should work independently. Clean up test data after each test.
Responsive testing: Test mobile, tablet, desktop viewports.
Selector Best Practices
// ❌ Bad: Fragile selectors
page.click('div > div > button')
page.click('.btn-primary')
page.click('#submit-btn-123')
// ✅ Good: Stable, semantic selectors
page.getByRole('button', { name: 'Submit' })
page.getByTestId('submit-button')
page.getByLabel('Email address')
Wait Strategies
// ❌ Bad: Hardcoded waits
page.waitForTimeout(5000) // Flaky, slow
// ✅ Good: Explicit waits
await page.waitForURL('/dashboard')
await page.waitForSelector('[data-testid="success-message"]')
await expect(page.getByTestId('loading')).toBeHidden()
await page.waitForResponse(resp => resp.url().includes('/api/users') && resp.status() === 200)
Accessibility Testing
// Good: Semantic selectors enforce a11y
await page.getByRole('button', { name: 'Submit' }).click()
// Good: Keyboard navigation test
test('is keyboard navigable', async ({ page }) => {
await page.goto('/form')
await page.keyboard.press('Tab')
await expect(page.getByTestId('name-input')).toBeFocused()
})
// Good: A11y assertions (with axe-core)
await expect(page).toHaveAccessibleTree()
Responsive Testing
test.describe('Mobile', () => {
test.use({ viewport: { width: 375, height: 667 } })
test('shows mobile menu', async ({ page }) => {
await page.goto('/')
await expect(page.getByTestId('hamburger-menu')).toBeVisible()
})
})
Common Anti-Patterns
| Anti-Pattern |
Severity |
Fix |
| waitForTimeout |
Critical |
Use explicit waits/assertions |
| CSS class selectors |
High |
Use getByRole/getByTestId |
| Tests depending on each other |
High |
Make tests independent |
| No cleanup |
Medium |
Use fixtures with proper cleanup |
| Only desktop testing |
Low |
Test multiple viewports |
| Hardcoded test data |
Medium |
Use data factories |
Reference Index
Success Criteria
Tests are reliable when:
- No waitForTimeout in tests
- Selectors are semantic (getByRole, getByTestId)
- Tests run in isolation (independent)
- Test data cleaned up after each test
- Multiple viewports tested
- Accessibility assertions present
- Tests are deterministic (no randomness)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: playwright-best-practices-23description: Playwright best practices including selectors, wait strategies, accessibility testing, responsive design, and flaky-test prevention. Use when writing or improving Playwright E2E tests. Use when this capability is needed.4---56# Playwright Best Practices78Expert guidance for writing reliable, maintainable Playwright tests.910## Quick Reference1112| Concern | Best Practice | Avoid |13|---------|---------------|-------|14| Selectors | getByRole, getByTestId, getByLabel | CSS classes, DOM structure |15| Waits | Auto-waiting, explicit assertions | waitForTimeout, hardcoded delays |16| Accessibility | getByRole, a11y checks | Visual-only testing |17| Flaky tests | Proper waits, stable selectors | Timing-dependent assertions |18| Isolation | Cleanup after each test | Tests depending on each other |19| Parallel | Independent tests | Shared state |2021## What Do You Need?22231. **Selectors** - Stable, semantic locator strategies242. **Waits** - Proper waiting, no hardcoded delays253. **Accessibility** - A11y assertions, keyboard nav264. **Responsive** - Testing different viewports275. **Flaky prevention** - Isolation, cleanup, retries2829Specify a number or describe your testing concern.3031## Routing3233| Response | Reference to Read |34|----------|-------------------|35| 1, "selector", "locator", "getBy" | [selectors.md](./references/selectors.md) |36| 2, "wait", "timeout", "delay" | [waits.md](./references/waits.md) |37| 3, "a11y", "accessibility", "keyboard" | [accessibility.md](./references/accessibility.md) |38| 4, "responsive", "mobile", "viewport" | [responsive.md](./references/responsive.md) |39| 5, "flaky", "unstable", "retry" | [flaky-tests.md](./references/flaky-tests.md) |4041## Essential Principles4243**Use semantic selectors**: getByRole, getByLabel, getByTestId are stable. CSS classes and DOM structure change frequently.4445**Never waitForTimeout**: Hardcoded delays make tests slow and flaky. Use auto-waiting and explicit assertions.4647**Test accessibility**: getByRole ensures accessible markup. Keyboard tests verify a11y.4849**Isolate tests**: Each test should work independently. Clean up test data after each test.5051**Responsive testing**: Test mobile, tablet, desktop viewports.5253## Selector Best Practices5455```typescript56// ❌ Bad: Fragile selectors57page.click('div > div > button')58page.click('.btn-primary')59page.click('#submit-btn-123')6061// ✅ Good: Stable, semantic selectors62page.getByRole('button', { name: 'Submit' })63page.getByTestId('submit-button')64page.getByLabel('Email address')65```6667## Wait Strategies6869```typescript70// ❌ Bad: Hardcoded waits71page.waitForTimeout(5000) // Flaky, slow7273// ✅ Good: Explicit waits74await page.waitForURL('/dashboard')75await page.waitForSelector('[data-testid="success-message"]')76await expect(page.getByTestId('loading')).toBeHidden()77await page.waitForResponse(resp => resp.url().includes('/api/users') && resp.status() === 200)78```7980## Accessibility Testing8182```typescript83// Good: Semantic selectors enforce a11y84await page.getByRole('button', { name: 'Submit' }).click()8586// Good: Keyboard navigation test87test('is keyboard navigable', async ({ page }) => {88 await page.goto('/form')89 await page.keyboard.press('Tab')90 await expect(page.getByTestId('name-input')).toBeFocused()91})9293// Good: A11y assertions (with axe-core)94await expect(page).toHaveAccessibleTree()95```9697## Responsive Testing9899```typescript100test.describe('Mobile', () => {101 test.use({ viewport: { width: 375, height: 667 } })102103 test('shows mobile menu', async ({ page }) => {104 await page.goto('/')105 await expect(page.getByTestId('hamburger-menu')).toBeVisible()106 })107})108```109110## Common Anti-Patterns111112| Anti-Pattern | Severity | Fix |113|--------------|----------|-----|114| waitForTimeout | Critical | Use explicit waits/assertions |115| CSS class selectors | High | Use getByRole/getByTestId |116| Tests depending on each other | High | Make tests independent |117| No cleanup | Medium | Use fixtures with proper cleanup |118| Only desktop testing | Low | Test multiple viewports |119| Hardcoded test data | Medium | Use data factories |120121## Reference Index122123| File | Topics |124|------|--------|125| [selectors.md](./references/selectors.md) | getByRole, getByTestId, getByLabel |126| [waits.md](./references/waits.md) | Auto-waiting, explicit assertions |127| [accessibility.md](./references/accessibility.md) | A11y checks, keyboard navigation |128| [responsive.md](./references/responsive.md) | Viewports, devices, mobile testing |129| [flaky-tests.md](./references/flaky-tests.md) | Isolation, retries, debugging |130131## Success Criteria132133Tests are reliable when:134- No waitForTimeout in tests135- Selectors are semantic (getByRole, getByTestId)136- Tests run in isolation (independent)137- Test data cleaned up after each test138- Multiple viewports tested139- Accessibility assertions present140- Tests are deterministic (no randomness)141142---143> Converted and distributed by [TomeVault](https://tomevault.io/claim/jovermier) — claim your Tome and manage your conversions.144<!-- tomevault:4.0:skill_md:2026-04-13 -->