| Role-based locators | getByRole, getByText, getByLabel | Preferred over CSS/XPath selectors |
| Page Object Model | Classes in tests/pages/ | Encapsulate all page-specific locators and actions |
| Authentication | storageState + setup projects | Authenticate once, reuse across tests |
| Visual regression | expect(page).toHaveScreenshot() | Mask dynamic content to prevent flakes |
| Accessibility audit | AxeBuilder from @axe-core/playwright | .withTags() + .analyze() on key flows |
| Trace debugging | trace: 'on-first-retry' | Full DOM snapshots, network logs, and timeline |
| Network mocking | page.route() | Stable tests without third-party dependencies |
| HAR replay | page.routeFromHAR() | High-fidelity mocks from recorded traffic |
| Image blocking | page.route('**/*.{png,jpg}', abort) | Speed up tests by skipping images |
| CI sharding | --shard=1/4 | Split suite across parallel CI machines |
| Blob reports | --reporter=blob + merge-reports | Merge sharded results into a single report |
| Test tags | { tag: ['@smoke'] } | Filter tests by category with --grep |
| Test steps | test.step('name', async () => {}) | Group actions in trace viewer and reports |
| Changed tests only | --only-changed=$GITHUB_BASE_REF | Run only test files changed since base branch |
| Native a11y checks | toHaveAccessibleName, toHaveRole | Lightweight alternative to full axe-core scans |
| Git info in reports | captureGitInfo reporter option | Link test reports to commits for CI debugging |
| Web-first assertions | expect(element).toBeVisible() | Auto-wait instead of waitForTimeout |
| Fixture composition | mergeTests() / mergeExpects() | Combine 3+ fixture modules into one test |
| Auto fixtures | { auto: true } on test.extend() | Run for every test (logging, screenshots) |
| Fixture options | { option: true } on test.extend() | Configurable via config or test.use() |
| Data-driven tests | for...of loop generating test() calls | Parameterized tests from arrays or CSV files |
| Context emulation | browser.newContext() options | locale, timezone, colorScheme, offline, isMobile |
| Two roles in one test | Concurrent browser.newContext() calls | Separate storageState per context |