</>

Technology

Playwright

Difficulty

Advanced

Interview Question

How do you handle flaky tests in Playwright?

Answer

Handling Flaky Tests in Playwright

A flaky test is one that passes and fails inconsistently without any code change. Playwright has excellent tools to identify and fix them.

Step 1 — Identify Flaky Tests

TypeScript
// playwright.config.ts
export default defineConfig({
  retries: 2,
  use: {
    trace: 'on-first-retry', // Capture trace when test needs to retry
  },
  reporter: [
    ['html'],
    ['json', { outputFile: 'results.json' }],
  ],
});

After a run, tests that "passed on retry" are flaky. Check the HTML report for these.

Common Causes and Fixes

Cause 1 — Hard-coded Sleep

TypeScript
// BAD — fixed wait doesn't adapt
await page.waitForTimeout(3000);
await page.click('#submit');

// GOOD — wait for actionable state
await page.getByRole('button', { name: 'Submit' }).click();
// Playwright auto-waits for visible, enabled, stable

Cause 2 — Race Condition with Animation

TypeScript
// BAD — clicking during animation
await page.locator('.animated-btn').click();

// GOOD — wait for animation to finish
await page.locator('.animated-btn').waitFor({ state: 'stable' });
await page.locator('.animated-btn').click();

// OR disable animations in config
use: {
  launchOptions: {
    args: ['--disable-animations']
  }
}

Cause 3 — Data Dependency Between Tests

TypeScript
// BAD — test B depends on test A's data
test('A: create user', async ({ page }) => { /* creates user with id=1 */ });
test('B: check user 1', async ({ page }) => { /* assumes user 1 exists */ });

// GOOD — each test creates its own data
test('check user', async ({ page, request }) => {
  // Create data needed for THIS test
  const user = await request.post('/api/users', {
    data: { email: `test-${Date.now()}@test.com` }
  });
  const { id } = await user.json();
  await page.goto(`/users/${id}`);
});

Cause 4 — Shared State Between Tests

TypeScript
// BAD — localStorage persists between tests (if not using fresh context)
// GOOD — each test gets a fresh BrowserContext automatically

// Or explicitly clear state
test.beforeEach(async ({ context }) => {
  await context.clearCookies();
  await page.evaluate(() => localStorage.clear());
});

Cause 5 — Strict Mode Violation (Multiple Elements)

TypeScript
// BAD — fails if more than one element matches
await page.locator('.btn').click(); // Error if multiple .btn found

// GOOD — be specific
await page.locator('.btn').first().click();
await page.locator('.modal .btn').click(); // Scope to parent

Cause 6 — Network Timing Issues

TypeScript
// BAD — navigate before API response arrives
await page.click('#load-data');
await expect(page.locator('.data-table')).toBeVisible();

// GOOD — wait for the network response
const [response] = await Promise.all([
  page.waitForResponse('**/api/data'),
  page.click('#load-data'),
]);
await expect(page.locator('.data-table')).toBeVisible();

Tools to Debug Flaky Tests

Bash
# Run a test many times to reproduce flakiness
for i in {1..20}; do npx playwright test login.spec.ts; done

# Run with trace for debugging
npx playwright test --trace on

# UI Mode — watch test run interactively
npx playwright test --ui

Follow AutomateQA

Related Topics