How-To/Speed Up Flaky Playwright Tests: Fix & Stabilize Fast

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/4

Cache 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