Speed Up Flaky Playwright Tests: Fix & Stabilize Fast
Flaky Playwright tests waste CI time and erode trust in your test suite. Follow these 7 steps to identify, fix, and prevent flaky E2E tests while keeping run times fast.
Prerequisites
- Playwright 1.40+ installed in your project
- An existing E2E test suite running in CI
- Access to CI logs and test reports
- Node.js 18+ (LTS recommended)
Flaky Playwright tests — those that pass and fail without code changes — are one of the most frustrating problems in E2E testing. Timing assumptions, ambiguous locators, shared state between tests, and CI environment variability all contribute to instability. Each flaky test erodes confidence in the entire suite and wastes developer time on false positives.
In this guide you'll eliminate flakiness at the root while keeping run times fast. Each step builds on the previous one, from diagnosis through prevention.
Identify Flaky Tests with a Retry Report
Before fixing flakiness, you need to know which tests are flaky. Run your suite with Playwright's retry logic enabled and collect the retry report. Tests that pass on retry are your flaky candidates.
# Run with 2 retries and generate a JSON report npx playwright test --retries=2 --reporter=json # Extract flaky tests (passed on retry) npx playwright test --retries=2 --reporter=html
Replace Hard Waits with Auto-Waiting Assertions
page.waitForTimeout() introduces arbitrary delays that either waste time (too long) or cause flakiness (too short). Playwright's auto-waiting assertions retry until the expected condition is met.
// Flaky or slow
await page.waitForTimeout(2000);
await page.click('button.submit');
// Auto-waiting
await page.getByRole('button', { name: /submit/i }).click();Scope Locators to Avoid Ambiguous Selectors
CSS selectors like .btn-primary or div > button:nth-child(2) are brittle and can match multiple elements as the DOM evolves. Playwright's semantic locators are stable and unambiguous.
// Scoped locator prevents matching the wrong element
const form = page.getByRole('form', { name: /login/i });
const email = form.getByLabel('Email');
await form.getByRole('button', { name: /sign in/i }).click();Isolate Tests with Independent State
Shared state is the #1 cause of flaky tests. Use beforeEach to set up fresh test data andafterEach to clean up. Use test.use() for isolated browser contexts and fixtures so no test leaks data into another.
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: /sign in/i }).click();
});
test.afterEach(async ({ page }) => {
// Clean up test data created during the test
await page.evaluate(() => localStorage.clear());
});Parallelise Across Workers and Shards
By default, Playwright runs test files in parallel but tests within a file sequentially. EnablefullyParallel: true to run every test in parallel. Then shard across CI machines to cut total suite time dramatically.
// playwright.config.ts
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 4 : undefined,
});
# CI matrix: shard 1 of 4
npx playwright test --shard=1/4Cache Authentication State to Skip Repeated Logins
If every test logs in from scratch, you're adding 3–10 seconds of wasted time per test. Log in once in a setup project, save the storage state with storageState when creating new browser contexts.
// login.setup.ts
export default async function setup({ page }) {
await page.goto('https://example.com/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: /sign in/i }).click();
await page.context().storageState({ path: 'auth.json' });
}
// Use in every test
test.use({ storageState: 'auth.json' });Monitor Flakiness Trends in CI with desplega.ai
Fixing flakiness is an ongoing process. desplega.ai tracks flakiness rate, rerun cost, and trends over time. Set up alerts so a sudden spike triggers an investigation before flaky tests reach production.
# Install the desplega.ai CLI npm install -g @desplega/cli # Upload test results after each CI run desplega upload --results test-results/report.json # Check flakiness trend desplega monitor --suite e2e --days 14
Related Guides
Issues
Track and manage test failures and issues effectively. Learn how to identify, categorize, and resolve testing issues in your workflow.
Vibe QA Extension
Power your vibe coding workflow with AI-powered QA testing. The Desplega.ai Vibe QA Extension integrates seamlessly with Lovable, bringing enterprise-grade testing directly into your development workflow.
Personas
Test your application with different user personas to ensure comprehensive coverage. Learn how to create and use personas in your testing strategy.