Debugging Like a Pro

Find and fix test failures quickly with Playwright's debugging tools

9 min readNode.js

Debugging Like a Pro

In the previous tutorial, we organized our tests with the Page Object Model. Now let's prepare for the inevitable: tests will fail. It happens to everyone.

The good news is Playwright has some of the best debugging tools out there. You can step through tests, inspect elements, time-travel through test runs, and more. Let's learn how to find and fix problems fast.

Quick Debugging Commands

Here's a cheat sheet you'll use constantly:

# Run with Playwright Inspector (step-by-step debugging)
npx playwright test --debug

# Run specific test with debugging
npx playwright test login.spec.ts --debug

# Open UI Mode (visual debugging)
npx playwright test --ui

# Run headed (see the browser)
npx playwright test --headed

# Slow down execution
npx playwright test --headed --slowmo 500

Playwright Inspector

The Inspector is your best friend for debugging. It pauses test execution and lets you step through each action, inspect elements, and see what's happening.

Launching the Inspector

# Debug all tests
npx playwright test --debug

# Debug a specific test
npx playwright test --debug -g "login flow"

# Debug a specific file
npx playwright test tests/login.spec.ts --debug

Or use the PWDEBUG environment variable:

PWDEBUG=1 npx playwright test

What the Inspector Shows

When the Inspector opens, you get:

  • Browser window — shows your app
  • Inspector panel — shows test code, locators, and controls

The Inspector pauses before each action. You can:

  • Step Over — run the current line and pause on the next
  • Play — continue running until the next page.pause() or test end
  • Pick Locator — click elements to get their locator

Pick Locator Tool

"How do I figure out the right locator for an element?"

This is incredibly useful. Click the "Pick Locator" button, then click any element in the browser. The Inspector shows you the best locator for that element:

Picked locator: getByRole('button', { name: 'Submit' })

Copy it directly into your test. No more guessing at locators.

Explore Mode

Click "Explore" to test locators without running a test:

  1. Type a locator in the input box
  2. See matching elements highlighted in the browser
  3. Tweak until you get exactly what you want

UI Mode

UI Mode is like the Inspector on steroids. It's a full visual interface for running, debugging, and exploring your tests.

Launching UI Mode

npx playwright test --ui

This opens a browser window with:

  • Test list — all your tests, organized by file
  • Timeline — visual representation of test steps
  • Watch mode — auto-run tests when files change
  • DOM snapshot — see the page at any point in time

Time Travel Debugging

"Can I see what the page looked like at step 5?"

This is the killer feature. After a test runs:

  1. Click any step in the timeline
  2. See exactly what the page looked like at that moment
  3. Inspect the DOM, see what was visible, check element states

Failed on step 5? Click step 4 to see what the page looked like right before. How cool is that?

Watch Mode

Enable watch mode, and UI Mode will automatically re-run tests when you save changes. Edit your test, save, see results — no manual re-running.

Filtering Tests

Click the filter icon to:

  • Run only failed tests
  • Run tests matching a search query
  • Run tests from a specific file

VS Code Integration

If you use VS Code, the Playwright extension is a must.

Installing the Extension

  1. Open VS Code
  2. Go to Extensions (Cmd+Shift+X or Ctrl+Shift+X)
  3. Search "Playwright Test for VS Code"
  4. Install the official Microsoft extension

Running Tests from VS Code

Once installed, you'll see:

  • Green play buttons next to each test
  • Testing sidebar with all your tests
  • Output panel showing test results

Click the play button to run a test. Click the debug icon to run with debugger attached.

Setting Breakpoints

  1. Click the gutter next to a line number to set a breakpoint
  2. Right-click the play button and choose "Debug Test"
  3. Test pauses at your breakpoint
  4. Use VS Code's debug panel to inspect variables, step through, etc.
test('debugging with breakpoints', async ({ page }) => {
  await page.goto('/login');
  
  // Set a breakpoint on this line
  await page.getByLabel('Email').fill('user@example.com');
  
  // Inspect `page` object, locators, etc.
  await page.getByRole('button', { name: 'Login' }).click();
});

Show Browser

In the Testing sidebar, check "Show Browser" to see the browser while tests run. Great for watching what's happening.

Console Debugging

Sometimes you just need good old console.log.

Logging in Tests

test('debug with console', async ({ page }) => {
  await page.goto('/products');
  
  const productCount = await page.locator('.product-card').count();
  console.log('Found products:', productCount);
  
  const firstProduct = await page.locator('.product-card').first().textContent();
  console.log('First product:', firstProduct);
  
  // Check if element exists
  const banner = page.getByRole('banner');
  console.log('Banner visible:', await banner.isVisible());
});

page.pause()

Insert a pause anywhere in your test to stop and open the Inspector:

test('stops midway', async ({ page }) => {
  await page.goto('/checkout');
  await page.getByLabel('Email').fill('user@example.com');
  
  // Test pauses here — Inspector opens
  await page.pause();
  
  // You can step through from here
  await page.getByRole('button', { name: 'Continue' }).click();
});

This is perfect when you want to debug a specific point in a longer test.

Logging Page Console

Capture what's logged in the browser console:

test('capture browser logs', async ({ page }) => {
  // Log all console messages from the page
  page.on('console', msg => {
    console.log('PAGE LOG:', msg.type(), msg.text());
  });
  
  // Log errors specifically
  page.on('pageerror', error => {
    console.log('PAGE ERROR:', error.message);
  });
  
  await page.goto('/app');
  // Now you'll see any console.log, errors, etc. from your app
});

Screenshots for Debugging

Take screenshots at any point to see what the page looks like:

test('debug with screenshots', async ({ page }) => {
  await page.goto('/dashboard');
  
  // Take a screenshot
  await page.screenshot({ path: 'debug-1.png' });
  
  await page.getByRole('button', { name: 'Load Data' }).click();
  
  // Take another after interaction
  await page.screenshot({ path: 'debug-2.png' });
  
  // Full page screenshot (scrolls to capture everything)
  await page.screenshot({ path: 'debug-full.png', fullPage: true });
  
  // Screenshot a specific element
  await page.locator('.chart').screenshot({ path: 'debug-chart.png' });
});

Debugging Locators

Can't find an element? Here's how to debug locators:

Check if Element Exists

test('debug locator', async ({ page }) => {
  await page.goto('/products');
  
  const button = page.getByRole('button', { name: 'Add to Cart' });
  
  // How many match?
  const count = await button.count();
  console.log('Matching elements:', count);
  
  if (count === 0) {
    // Try a broader search
    const allButtons = await page.getByRole('button').all();
    console.log('All buttons on page:');
    for (const btn of allButtons) {
      console.log(' -', await btn.textContent());
    }
  }
});

Evaluate in Browser Context

Run JavaScript in the browser to inspect elements:

test('evaluate in browser', async ({ page }) => {
  await page.goto('/app');
  
  // Get all elements matching a selector
  const elements = await page.evaluate(() => {
    return Array.from(document.querySelectorAll('button'))
      .map(el => ({
        text: el.textContent,
        visible: el.offsetParent !== null,
        classes: el.className
      }));
  });
  
  console.log('Buttons found:', elements);
});

Use Strict Mode Violations

When a locator matches multiple elements and you expected one, Playwright throws an error. Use this to your advantage:

// This throws if multiple buttons match
await page.getByRole('button', { name: 'Submit' }).click();

// Error: strict mode violation: getByRole('button', { name: 'Submit' })
// resolved to 3 elements

Fix by making the locator more specific:

// Target the one in the form
await page.locator('form#checkout')
  .getByRole('button', { name: 'Submit' })
  .click();

Debugging Timeouts

Tests timing out? Figure out what's waiting:

test('debug timeout', async ({ page }) => {
  await page.goto('/slow-page');
  
  // This might timeout - but why?
  try {
    await page.getByText('Data loaded').waitFor({ timeout: 5000 });
  } catch (error) {
    // Take screenshot to see current state
    await page.screenshot({ path: 'timeout-state.png' });
    
    // Log page content
    console.log('Page content:', await page.content());
    
    throw error;
  }
});

Or catch network requests:

test('debug slow loads', async ({ page }) => {
  // Log all network requests
  page.on('request', req => console.log('→', req.method(), req.url()));
  page.on('response', res => console.log('←', res.status(), res.url()));
  
  await page.goto('/app');
  // Now you can see what's loading and what might be slow/failing
});

Common Issues & Solutions

ProblemHow to Debug
Element not foundUse Pick Locator in Inspector
Multiple elements matchMake locator more specific, scope to parent
Timeout waiting for elementScreenshot before the wait, check if element exists
Wrong element clickedUse { force: true } to skip visibility check, or fix locator
Test passes locally, fails in CIRun headed in CI with video recording
Flaky testAdd traces, look for race conditions

Debugging Checklist

When a test fails:

  1. Read the error message — Playwright's errors are descriptive
  2. Run with --debug — step through and see where it breaks
  3. Use UI Mode — time travel to see what happened
  4. Add page.pause() — stop right before the failure
  5. Take screenshots — capture state at different points
  6. Check locators — use Pick Locator to verify
  7. Log network requests — see if APIs are failing
  8. Check for race conditions — is something loading async?

What's Next?

You're now a debugging ninja:

  • --debug flag and Playwright Inspector for step-by-step debugging
  • UI Mode for visual debugging and time travel
  • VS Code integration with breakpoints
  • page.pause() to stop mid-test
  • Screenshots and console logging
  • Debugging locators and timeouts

When debugging alone isn't enough, you need traces and artifacts. Learn about traces and screenshots for post-mortem debugging. Let's go!