Root cause 1: Timing assumptions
Even with Playwright's auto-waiting, tests can fail when network conditions vary. Use waitForResponse and waitForSelector explicitly before assertions. Do not rely on page.waitForTimeout — it hides flakiness, it does not fix it.
Root cause 2: Brittle selectors
CSS class selectors that change frequently are the #1 cause of Playwright flakiness. Use getByRole, getByText, and getByTestId instead. Data-testid attributes are your safest bet for stable selectors.
Root cause 3: Shared state
Tests that depend on each other or share browser context will fail intermittently. Use test.describe.configure({ mode: 'serial' }) intentionally, and prefer isolated test fixtures with fresh state.
Root cause 4: CI environment differences
A test that passes locally but fails in CI is usually an environment issue. Check headless mode differences, screen resolution, font availability, and CPU throttling. Use Playwright's --reporter=list with trace capture on failure.
Quick fix checklist
- Replace
page.waitForTimeoutwith explicit waits - Audit all selectors — prefer
getByTestId - Make every test fully isolated
- Run with
--repeat-each 5to surface intermittent failures