How to Use Playwright's Trace Viewer for Debugging Failed Tests
Playwright's trace viewer is a powerful debugging tool that provides a visual timeline of your test execution, making it easier to identify why tests fail. This comprehensive guide will show you how to enable tracing, capture traces, and use the trace viewer effectively to debug your Playwright tests.
What is Playwright's Trace Viewer?
The trace viewer is a GUI tool that allows you to explore recorded Playwright traces. It provides a timeline view of your test execution, showing actions, screenshots, network requests, and console logs. This makes it invaluable for debugging complex test scenarios and understanding exactly what happened during test execution.
Enabling Tracing in Your Tests
Basic Tracing Setup
To enable tracing in your Playwright tests, you need to configure it in your test setup:
// playwright.config.js
module.exports = {
use: {
// Enable tracing on retry attempts and first run
trace: 'on-first-retry',
// Alternative options:
// trace: 'on', // Always record traces
// trace: 'retain-on-failure', // Only keep traces for failed tests
// trace: 'off', // Disable tracing
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
};
Programmatic Tracing
You can also control tracing programmatically within your tests:
// test.spec.js
import { test, expect } from '@playwright/test';
test('example test with tracing', async ({ page, context }) => {
// Start tracing before creating the page
await context.tracing.start({
screenshots: true,
snapshots: true,
sources: true
});
try {
await page.goto('https://example.com');
await page.click('button#submit');
await expect(page.locator('.result')).toBeVisible();
} finally {
// Stop tracing and save it to a file
await context.tracing.stop({ path: 'trace.zip' });
}
});
Python Tracing Configuration
For Python users, here's how to enable tracing:
# conftest.py
import pytest
from playwright.sync_api import sync_playwright
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
return {
**browser_context_args,
"trace": "on-first-retry"
}
# test_example.py
import pytest
from playwright.sync_api import Page, expect
def test_with_tracing(page: Page, context):
# Start tracing
context.tracing.start(screenshots=True, snapshots=True, sources=True)
try:
page.goto("https://example.com")
page.click("button#submit")
expect(page.locator(".result")).to_be_visible()
finally:
# Stop tracing and save
context.tracing.stop(path="trace.zip")
Tracing Configuration Options
Trace Recording Options
When starting tracing, you can configure what information to capture:
await context.tracing.start({
screenshots: true, // Capture screenshots on every action
snapshots: true, // Capture DOM snapshots
sources: true, // Include source code in traces
title: 'My Test Trace' // Custom trace title
});
Global Tracing Settings
Configure tracing globally in your Playwright configuration:
// playwright.config.js
module.exports = {
use: {
trace: {
mode: 'on-first-retry',
snapshots: true,
screenshots: true,
sources: true
}
}
};
Viewing and Analyzing Traces
Opening the Trace Viewer
After your tests generate trace files, you can open them with the trace viewer:
# Open a specific trace file
npx playwright show-trace trace.zip
# Open the trace viewer with the last test run
npx playwright show-trace test-results/
# Python equivalent
playwright show-trace trace.zip
Trace Viewer Interface
The trace viewer provides several key sections:
- Timeline: Shows the sequence of actions and their timing
- Actions: Detailed list of each action performed
- Screenshots: Visual snapshots at each step
- Network: All network requests and responses
- Console: Console logs and errors
- Source: Source code context for each action
Analyzing Failed Tests
When debugging failed tests, focus on these areas:
// Example of a failing test that benefits from tracing
test('debug failed login', async ({ page }) => {
await page.goto('https://example.com/login');
// This might fail - trace will show why
await page.fill('#username', 'testuser');
await page.fill('#password', 'wrongpassword');
await page.click('#login-button');
// Assertion that might fail
await expect(page.locator('.dashboard')).toBeVisible();
});
Advanced Tracing Techniques
Conditional Tracing
Enable tracing only for specific test conditions:
test('conditional tracing', async ({ page, context }) => {
const shouldTrace = process.env.DEBUG === 'true';
if (shouldTrace) {
await context.tracing.start({
screenshots: true,
snapshots: true
});
}
try {
await page.goto('https://example.com');
// Test logic here
} finally {
if (shouldTrace) {
await context.tracing.stop({ path: 'debug-trace.zip' });
}
}
});
Multiple Trace Segments
Break down complex tests into multiple trace segments:
test('multi-segment tracing', async ({ page, context }) => {
// First segment: Login
await context.tracing.start({ title: 'Login Flow' });
await page.goto('https://example.com/login');
await page.fill('#username', 'user');
await page.fill('#password', 'pass');
await page.click('#login');
await context.tracing.stop({ path: 'login-trace.zip' });
// Second segment: Dashboard interaction
await context.tracing.start({ title: 'Dashboard Flow' });
await page.click('#dashboard-link');
await expect(page.locator('.dashboard')).toBeVisible();
await context.tracing.stop({ path: 'dashboard-trace.zip' });
});
Best Practices for Effective Debugging
1. Strategic Trace Placement
Place tracing around the most critical parts of your tests:
test('strategic tracing', async ({ page, context }) => {
await page.goto('https://example.com');
// Start tracing before the critical section
await context.tracing.start({ screenshots: true, snapshots: true });
// Critical test logic that might fail
await page.click('.complex-dropdown');
await page.selectOption('select#options', 'value');
await page.click('#submit');
// Stop tracing after the critical section
await context.tracing.stop({ path: 'critical-section.zip' });
});
2. Trace Organization
Organize traces with meaningful names and paths:
test('organized tracing', async ({ page, context }) => {
const testName = 'user-registration';
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const tracePath = `traces/${testName}-${timestamp}.zip`;
await context.tracing.start({ title: testName });
try {
// Test logic
await page.goto('https://example.com/register');
await page.fill('#email', 'test@example.com');
await page.click('#register');
} finally {
await context.tracing.stop({ path: tracePath });
}
});
3. Combining with Other Debugging Tools
Use tracing alongside other debugging techniques for comprehensive analysis. Similar to how you might handle authentication flows in Puppeteer, Playwright's tracing can help you understand complex authentication scenarios step by step.
Troubleshooting Common Issues
Trace File Size Management
Large trace files can slow down analysis. Optimize by:
// Minimal tracing for performance
await context.tracing.start({
screenshots: false, // Disable screenshots if not needed
snapshots: true, // Keep snapshots for DOM analysis
sources: false // Disable source code if not debugging code
});
Network Request Analysis
Use traces to debug network-related issues:
test('network debugging', async ({ page, context }) => {
await context.tracing.start({
screenshots: true,
snapshots: true,
sources: true
});
// Monitor network requests
page.on('request', request => {
console.log(`Request: ${request.url()}`);
});
page.on('response', response => {
console.log(`Response: ${response.url()} - ${response.status()}`);
});
await page.goto('https://example.com');
await context.tracing.stop({ path: 'network-trace.zip' });
});
Integration with CI/CD
Automatic Trace Collection
Configure automatic trace collection in CI environments:
# .github/workflows/tests.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run tests
run: npx playwright test
- name: Upload traces
uses: actions/upload-artifact@v3
if: failure()
with:
name: playwright-traces
path: test-results/
Conclusion
Playwright's trace viewer is an essential tool for debugging failed tests effectively. By enabling tracing strategically, analyzing traces systematically, and combining tracing with other debugging techniques, you can quickly identify and resolve test failures. The visual timeline, detailed action logs, and network analysis capabilities make it much easier to understand what went wrong in your tests.
Remember to use tracing judiciously in production environments, as it can add overhead to test execution. Focus on enabling tracing for failed tests or specific debugging scenarios to maintain optimal test performance while gaining valuable debugging insights.
For more complex scenarios involving dynamic content, you might also want to explore how to handle AJAX requests effectively, as understanding asynchronous operations is crucial for successful web automation and testing.