</>

Technology

Playwright

Difficulty

Intermediate

Interview Question

Your Playwright test is intermittently failing (flaky). Walk me through how you would debug and fix it.

Answer

Scenario: Debugging a Flaky Playwright Test

This is one of the most common real-world QA challenges. Here is a systematic approach.

Step 1 — Enable Trace on First Retry

TypeScript
// playwright.config.ts
export default defineConfig({
  retries: 2,
  use: {
    trace: 'on-first-retry',       // Capture trace when flakiness occurs
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
});

Now when the test fails and retries, you get a full trace.

Step 2 — Reproduce Locally

Bash
# Run the test 10 times to reproduce the flakiness
for i in {1..10}; do npx playwright test flaky.spec.ts; done

# OR use UI mode to watch it fail
npx playwright test flaky.spec.ts --ui

Step 3 — Analyze the Trace

Bash
npx playwright show-report
# Click on the failed test → open Trace

Look at:

  • Which step failed (the red step in the timeline)
  • DOM at that moment (did the element exist? Was it visible?)
  • Network tab (was an API call slow or failing?)
  • Console tab (JavaScript errors?)
  • Timing (was there an animation or transition?)

Step 4 — Common Causes and Fixes

Cause A — Hard-coded Timeout

TypeScript
// BAD
await page.waitForTimeout(2000);
await page.click('#submit');

// GOOD — wait for actual state
await page.locator('#submit').waitFor({ state: 'visible' });
await page.locator('#submit').click();

Cause B — Strict Mode: Multiple Elements Match

TypeScript
// BAD — fails if page has more than one '.btn'
await page.locator('.btn').click();
// Error: "strict mode violation: locator('.btn') resolved to 3 elements"

// GOOD — be specific
await page.locator('.modal .btn.primary').click();
await page.getByRole('button', { name: 'Submit' }).click();

Cause C — Race Condition (No Wait for Network)

TypeScript
// BAD — clicks before dynamic content loads
await page.goto('/products');
await page.locator('.product-card').first().click();
// Cards may not have loaded yet!

// GOOD — wait for dynamic content
await page.goto('/products');
await page.locator('.product-card').first().waitFor();
await page.locator('.product-card').first().click();

Cause D — Shared Test Data (Tests Not Isolated)

TypeScript
// BAD — test B assumes test A created this user
// If tests run in different order, it fails

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

  await page.goto(`/users/${id}/edit`);
  // Now test is fully self-contained
});

Cause E — Animation Interference

TypeScript
// Button slides in with CSS animation — not immediately clickable
await page.locator('.animated-cta').waitFor({ state: 'visible' });
// Still fails because it's mid-animation

// Fix: disable animations globally or wait for stable
await page.addStyleTag({ content: '* { animation: none !important; transition: none !important; }' });

Step 5 — Verify the Fix

Bash
# Run 20 times to confirm it's no longer flaky
for i in {1..20}; do npx playwright test fixed.spec.ts --retries=0; done

Zero failures = flakiness resolved.

Follow AutomateQA

Related Topics