Debugging Like a Pro
Find and fix test failures quickly with Playwright's debugging tools
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:
- Type a locator in the input box
- See matching elements highlighted in the browser
- 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:
- Click any step in the timeline
- See exactly what the page looked like at that moment
- 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
- Open VS Code
- Go to Extensions (Cmd+Shift+X or Ctrl+Shift+X)
- Search "Playwright Test for VS Code"
- 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
- Click the gutter next to a line number to set a breakpoint
- Right-click the play button and choose "Debug Test"
- Test pauses at your breakpoint
- 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
| Problem | How to Debug |
|---|---|
| Element not found | Use Pick Locator in Inspector |
| Multiple elements match | Make locator more specific, scope to parent |
| Timeout waiting for element | Screenshot before the wait, check if element exists |
| Wrong element clicked | Use { force: true } to skip visibility check, or fix locator |
| Test passes locally, fails in CI | Run headed in CI with video recording |
| Flaky test | Add traces, look for race conditions |
Debugging Checklist
When a test fails:
- Read the error message — Playwright's errors are descriptive
- Run with
--debug— step through and see where it breaks - Use UI Mode — time travel to see what happened
- Add
page.pause()— stop right before the failure - Take screenshots — capture state at different points
- Check locators — use Pick Locator to verify
- Log network requests — see if APIs are failing
- Check for race conditions — is something loading async?
What's Next?
You're now a debugging ninja:
--debugflag 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!