Traces, Screenshots & Videos

Capture everything for post-mortem debugging

8 min readNode.js

Traces, Screenshots & Videos

In the previous tutorial, we learned how to debug tests interactively. But what about when tests fail in CI at 3 AM? You can't step through with a debugger.

That's where traces, screenshots, and videos come in. They capture everything that happened so you can debug failures after the fact — like a flight recorder for your tests.

The Trace Viewer

"What exactly is a trace?"

Traces are Playwright's superpower for debugging. A trace captures:

  • Screenshot at every step
  • DOM snapshot at every step
  • Network requests and responses
  • Console logs
  • Action timings

It's like time-travel debugging, but for test runs you weren't watching.

Recording Traces

Configure tracing in playwright.config.ts:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    // Record trace for each test
    trace: 'on', // 'on' | 'off' | 'on-first-retry' | 'retain-on-failure'
  },
});

Trace options:

  • 'on' — always record (large files, good for debugging)
  • 'off' — never record
  • 'on-first-retry' — record only when retrying a failed test
  • 'retain-on-failure' — record always, but keep only for failed tests

For CI, 'retain-on-failure' is the sweet spot — you get traces when you need them without wasting storage.

Viewing Traces

After a test runs, traces are saved in test-results/. View them with:

npx playwright show-trace test-results/my-test-chromium/trace.zip

Or just run:

npx playwright show-trace

And pick from available traces.

What You See in Trace Viewer

The Trace Viewer opens in your browser with:

Timeline — shows every action, click, keystroke, navigation

Screenshot at each step — see exactly what the page looked like

Before/After DOM snapshots — compare state before and after actions

Network tab — all requests with timing, headers, and bodies

Console tab — browser console logs

Source tab — your test code, highlighted at each step

Click any action in the timeline to time-travel to that moment. How cool is that?

Recording Traces Programmatically

For more control, start and stop traces in your test:

test('debug with custom trace', async ({ page, context }) => {
  // Start tracing
  await context.tracing.start({ screenshots: true, snapshots: true });
  
  await page.goto('/checkout');
  await page.getByLabel('Email').fill('user@example.com');
  // ... more actions
  
  // Stop and save trace
  await context.tracing.stop({ path: 'checkout-trace.zip' });
});

This is useful when you want traces for specific complex flows.

Screenshots

Screenshots capture the page at a specific moment. Use them for debugging, visual regression testing, or documentation.

Automatic Screenshots on Failure

The easiest setup — Playwright takes a screenshot whenever a test fails:

// playwright.config.ts
export default defineConfig({
  use: {
    screenshot: 'only-on-failure',
  },
});

Screenshot options:

  • 'off' — no automatic screenshots
  • 'on' — screenshot after every test
  • 'only-on-failure' — screenshot only when test fails (recommended for CI)

Failed test screenshots land in test-results/ alongside the test.

Manual Screenshots

Take screenshots anywhere in your test:

test('screenshot examples', async ({ page }) => {
  await page.goto('/dashboard');
  
  // Basic screenshot
  await page.screenshot({ path: 'dashboard.png' });
  
  // Full page (scrolls and stitches)
  await page.screenshot({ path: 'full-page.png', fullPage: true });
  
  // Specific element only
  await page.locator('.chart').screenshot({ path: 'chart.png' });
  
  // With options
  await page.screenshot({
    path: 'custom.png',
    fullPage: true,
    animations: 'disabled', // Stop CSS animations
    mask: [page.locator('.user-id')], // Hide sensitive data
  });
});

Screenshot Comparison (Visual Testing)

"Can I catch visual regressions automatically?"

Playwright can compare screenshots against baselines:

test('visual regression', async ({ page }) => {
  await page.goto('/pricing');
  
  // Compares against saved snapshot
  await expect(page).toHaveScreenshot('pricing-page.png');
  
  // Element screenshot comparison
  await expect(page.locator('.pricing-table')).toHaveScreenshot('pricing-table.png');
});

First run creates the baseline. Subsequent runs compare against it. Differences fail the test.

Update baselines with:

npx playwright test --update-snapshots

Screenshot Options

await page.screenshot({
  path: 'screenshot.png',
  fullPage: true,
  clip: { x: 0, y: 0, width: 800, height: 600 }, // Specific region
  omitBackground: true, // Transparent background
  type: 'jpeg', // or 'png' (default)
  quality: 80, // For JPEG only
  scale: 'css', // 'css' or 'device'
});

Video Recording

"Can I record a video of my test running?"

Yep! Videos capture the entire test run. They're bigger than traces but sometimes easier to share with non-technical folks.

Configuring Video Recording

// playwright.config.ts
export default defineConfig({
  use: {
    video: 'retain-on-failure',
  },
});

Video options:

  • 'off' — no video
  • 'on' — always record
  • 'on-first-retry' — record when retrying failed tests
  • 'retain-on-failure' — record always, keep only for failures (recommended)

Video Size

Videos can get large. Control the size:

export default defineConfig({
  use: {
    video: {
      mode: 'retain-on-failure',
      size: { width: 1280, height: 720 }, // Smaller than default
    },
  },
});

Recording Video Manually

test('manual video recording', async ({ browser }) => {
  const context = await browser.newContext({
    recordVideo: {
      dir: './videos/',
      size: { width: 1280, height: 720 },
    },
  });
  
  const page = await context.newPage();
  await page.goto('/app');
  // ... test actions
  
  // Video is saved when context closes
  await context.close();
  
  // Get the video path
  const video = page.video();
  if (video) {
    console.log('Video saved:', await video.path());
  }
});

Configuring Artifacts for CI

Here's a production-ready config that captures everything you need when tests fail:

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  
  // Retry failed tests once in CI
  retries: process.env.CI ? 1 : 0,
  
  // Capture artifacts
  use: {
    // Trace on retry (captures the retry, not the original failure)
    trace: 'on-first-retry',
    
    // Screenshot when test fails
    screenshot: 'only-on-failure',
    
    // Video when test fails
    video: 'retain-on-failure',
  },
  
  // Where to store artifacts
  outputDir: 'test-results/',
  
  // Reporter configuration
  reporter: [
    ['html', { open: 'never' }], // HTML report
    ['list'], // Console output
  ],
  
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
  ],
});

Viewing Artifacts

Local Development

Artifacts go to test-results/ by default. Structure looks like:

test-results/
ā”œā”€ā”€ my-test-chromium/
│   ā”œā”€ā”€ trace.zip
│   ā”œā”€ā”€ test-failed-1.png
│   └── video.webm
└── another-test-firefox/
    └── trace.zip

View them with:

# Open trace
npx playwright show-trace test-results/my-test-chromium/trace.zip

# HTML report (includes screenshots, traces)
npx playwright show-report

GitHub Actions

Upload artifacts in your workflow:

# .github/workflows/tests.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Install Playwright browsers
        run: npx playwright install --with-deps
        
      - name: Run tests
        run: npx playwright test
        
      - name: Upload test artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: |
            playwright-report/
            test-results/
          retention-days: 7

When tests fail, download artifacts from the GitHub Actions run page. The HTML report includes traces you can view directly.

Playwright HTML Report

The HTML report is the easiest way to review failures. It includes:

  • Test results summary
  • Embedded screenshots
  • Trace viewer built-in
  • Video playback
  • Error messages and stack traces

Generate and view:

npx playwright test
npx playwright show-report

The report opens in your browser. Click any failed test to see its trace, screenshots, and video.

Combining Artifacts Strategically

Different situations call for different artifacts:

CI Debugging

// Focus on traces — most detail, reasonable size
use: {
  trace: 'retain-on-failure',
  screenshot: 'only-on-failure',
  video: 'off', // Save storage
}

Bug Reports / Demos

// Video is easiest to share with stakeholders
use: {
  trace: 'off',
  screenshot: 'on',
  video: 'on',
}

Development

// Traces give you everything for debugging
use: {
  trace: 'on',
  screenshot: 'off',
  video: 'off',
}

Storage Considerations

Artifacts add up. A single trace can be 5-20MB. Videos can be 50MB+.

Tips for managing storage:

  • Use 'retain-on-failure' in CI — only keep what you need
  • Set retention days on artifact uploads
  • Clean test-results/ before runs: rm -rf test-results/
  • Add test-results/ and playwright-report/ to .gitignore
# .gitignore
test-results/
playwright-report/

Quick Reference

ArtifactConfigBest For
Tracetrace: 'retain-on-failure'Detailed debugging
Screenshotscreenshot: 'only-on-failure'Quick visual check
Videovideo: 'retain-on-failure'Sharing with non-devs

What's Next?

You now have a complete debugging artifact toolkit:

  • Traces capture everything: screenshots, DOM, network, console
  • Use Trace Viewer to time-travel through test runs
  • Automatic screenshots on failure for quick debugging
  • Video recording for demos and sharing
  • Configure artifacts differently for CI vs local development
  • HTML report bundles everything in one place

Time for real-world patterns. Let's tackle one of the trickiest challenges in E2E testing: handling authentication. Let's go!