Capturing Screenshots and Video on Test Failure
When a test fails on CI you were not watching, the only way to understand it is the evidence the run left behind. Playwright can record a screenshot, a video, and a full trace for every test, but recording all three on every run is slow and fills disk, so the practical pattern is to capture them only when a test fails. Three use options — screenshot: 'only-on-failure', video: 'retain-on-failure', and trace: 'on-first-retry' — give you rich forensic artifacts at almost zero cost on the happy path. This page belongs to Reporters & Test Artifacts, part of the Debugging & Test Observability guide, and walks through configuring and attaching these artifacts.
The three artifact options
All three are set under use in playwright.config.ts, and each accepts a mode that ties capture to the outcome:
screenshot: 'only-on-failure'— a single full-page image saved when, and only when, a test fails.video: 'retain-on-failure'— Playwright records every test but discards the recording for passing tests, keeping only the videos of failures.trace: 'on-first-retry'— a complete replayable trace recorded on the second attempt of a test that failed once, which is exactly the run you want to inspect.
The configuration
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Retries are needed for trace: 'on-first-retry' to ever trigger.
retries: process.env.CI ? 2 : 0,
use: {
// Save one image only when a test fails.
screenshot: 'only-on-failure',
// Record every test but keep the video only for failures.
video: 'retain-on-failure',
// Record a full trace on the first retry of a failed test.
trace: 'on-first-retry',
},
// The HTML reporter attaches all captured artifacts automatically.
reporter: [['html', { open: 'never' }]],
});
Because trace: 'on-first-retry' only fires when a test is retried, it does nothing unless retries is above zero — the retry and timeout settings in Configuring Retries and Timeouts for Stable CI are a prerequisite.
Attaching custom artifacts
Beyond the automatic captures, you can attach your own files — a downloaded export, an API response, a manual screenshot — to the report with testInfo.attach(). They appear inline in the HTML report next to the test:
import { test, expect } from '@playwright/test';
test('attaches a manual screenshot to the report', async ({ page }, testInfo) => {
await page.goto('/dashboard');
// Capture a buffer rather than writing to disk first.
const shot = await page.screenshot();
// Attach it so it shows up inline in the HTML report for this test.
await testInfo.attach('dashboard-state', { body: shot, contentType: 'image/png' });
await expect(page.getByRole('heading', { name: 'Overview' })).toBeVisible();
});
Step-by-step setup
- Enable retries so traces can trigger. Set
retries: process.env.CI ? 2 : 0, becausetrace: 'on-first-retry'records nothing unless a failed test is actually retried. - Set screenshot to only-on-failure. Add
screenshot: 'only-on-failure'underuseso a full-page image is saved for failures and nothing is written for passing tests. - Set video to retain-on-failure. Add
video: 'retain-on-failure'so every test is recorded but only failure videos are kept, giving you the full sequence that led to the break without storing thousands of green recordings. - Set trace to on-first-retry. Add
trace: 'on-first-retry'so the second attempt of a failing test produces a complete replayable trace, the single most useful artifact for diagnosis. - Use the HTML reporter to surface artifacts. Configure
['html', { open: 'never' }]so all captured screenshots, videos, and traces attach to their tests automatically and the report does not pop open in CI. - Attach any custom evidence with testInfo.attach(). For files Playwright does not capture by default, call
await testInfo.attach(name, { body, contentType })inside the test so the artifact appears inline in the report.
Verification
Confirm capture works three ways. First, deliberately fail a test locally and check the test-results folder — a screenshot and video should appear for that test and for no passing test. Second, open the HTML report and verify the failed test shows its image, video, and a trace launcher inline. Third, force a retry on CI and confirm the trace from the second attempt downloads and opens in the Trace Viewer & Debugging guide. Finally, publish these artifacts from your pipeline as described in CI/CD Integration so every failed build links to its evidence.
Frequently Asked Questions
Why is no trace captured even though I set trace on-first-retry?
The on-first-retry mode only records a trace when a failed test is retried, so it never fires if retries is zero. Set retries above zero in your config — commonly process.env.CI ? 2 : 0 — or switch to trace: 'retain-on-failure' if you want a trace on the very first failure without a retry.
Does video: 'retain-on-failure' slow down passing tests?
Playwright records every test to produce the failure videos, so there is a small overhead even on passing runs, but the recording is discarded the moment a test passes. The cost is modest and usually worth the evidence; if it matters for a huge suite, scope video capture to a single project rather than the whole run.
How do I attach a file Playwright does not capture automatically?
Use testInfo.attach(name, { body, contentType }) inside the test, passing a buffer or a path and the correct MIME type. The attachment appears inline in the HTML report next to that test, which is the way to surface downloaded exports, API payloads, or manual screenshots alongside the automatic artifacts.