Anti-Patterns
Decision
Ranked by how often they cause flake or wasted CI minutes:
page.waitForTimeout(2000)as the default wait — always wrong outside debugging. Replace with web-first assertion orwaitForResponse- Tests that depend on each other's state — a test that only passes when the previous one ran is a bug. Each test creates its own state and cleans up.
test.describe.serialis a smell - Brittle CSS selectors instead of role/label/test-id —
.btn.btn-primary.mt-3 > span:nth-child(2)breaks on the next theme refactor - Missing
awaiton async calls —page.click(...)returns a Promise; withoutawait, the next line races. Turn oneslint-plugin-playwright - Asserting implementation details —
await expect(page.locator('.has-loaded')).toBeVisible()ties to CSS-class side effect. Prefer user-visible:await expect(page.getByText('5 results')).toBeVisible() - Test files >500 lines / mega-test asserting 20 things — split per journey; use
test.step()to keep readable expect(true).toBe(false)for sad-path — usethrow new Error('Unexpected branch')ortest.fail()- Overusing
page.evaluate()— bypasses auto-wait, breaks trace viewer's locator picker, Selenium-porting tell. Most calls have a Playwright API - Using
isVisible()/textContent()for assertions — return immediately, no auto-retry. Alwaysexpect(locator) - Disabling
fullyParallelto "stabilize" — hides flake; right answer is locator/auth/state isolation - Committing
playwright/.auth/*.json— leaks session cookies; gitignore - Trace viewer only for failures — open them on slow CI runs to find race conditions in green tests
.first()instead of.filter()— hides "two elements match" bugs. Pair.toHaveCount(1)if uniqueness matters- Mixing VR (
toHaveScreenshot) and functional E2E in the same test — different stability requirements; keep separate
See Also
- Assertions — correct auto-retry usage
- Locators — resilient locator priority order
- CI Patterns —
forbidOnly, retries, artifact strategy - Debugging — finding the root cause of flake