QA How-To
How to Use Playwright video recording (2026)
Learn Playwright video recording for tests and library scripts, including retry modes, sizing, annotations, CI artifacts, and failure diagnosis for QA teams.
22 min read | 2,846 words
TL;DR
Enable Playwright video recording in `playwright.config.ts` with `use: { video: 'retain-on-failure' }` or `video: 'on-first-retry'`. Videos are finalized when the page or browser context closes and appear with test output. For library code, create a context with `recordVideo: { dir, size }`, close the page or context, then use the page's `Video` object to get, save, or delete the recording.
Key Takeaways
- Configure test-runner video under `use`, with `retain-on-failure` or `on-first-retry` as practical CI starting points.
- Current video modes can target the original run, all retries, failed attempts, or the complete failure-and-retry chain.
- Close the page or browser context before expecting a complete video file from a manually created context.
- Set viewport and video size together so scaling and letterboxing do not hide small UI details.
- Use action and test annotations to make recordings easier to review, while keeping Trace Viewer for deeper technical evidence.
- Upload videos conditionally, protect sensitive screen data, and delete recordings that have no diagnostic value.
Playwright video recording captures what the browser viewport displayed during a test and stores the result with the test artifacts. In Playwright Test, enable it under use with a retention mode such as retain-on-failure or on-first-retry. In a Playwright Library script, create a browser context with recordVideo, then close the page or context so the file is finalized.
Video is especially useful for motion, overlays, scrolling, drag interactions, and the sequence visible to a user. It is not the richest debugging artifact for most CI failures, because it lacks the DOM, actionability, network, and console detail available in Trace Viewer. A mature test pipeline records video selectively and uses it alongside traces, screenshots, logs, and backend correlation IDs.
TL;DR
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
video: 'retain-on-failure',
},
});
| Goal | Recommended mode or API | Result |
|---|---|---|
| Keep only failed attempts | retain-on-failure |
Passing-run videos are removed |
| Record first retry only | on-first-retry |
Low-volume retry video |
| Preserve original failure only | retain-on-first-failure |
Captures run index 0 if it fails |
| Compare failure and retries | retain-on-failure-and-retries |
Keeps failed runs and all retries |
| Record a library context | browser.newContext({ recordVideo }) |
One video for each page |
| Save a chosen page video | await video.saveAs(path) after closing page |
Copies finalized recording |
The default test option is off. Choose a mode from your diagnostic need, not from a desire to archive every execution.
1. What Playwright video Recording Captures
Video recording captures the visible viewport of each page in a video-enabled browser context. It shows the sequence of visual frames: navigations, clicks, scrolling, dialogs, animations, loading states, overlays, and page changes. It does not record the entire desktop, the Playwright runner UI, terminal output, or browser chrome. Popups and additional pages are separate pages and can have their own associated video objects.
In Playwright Test, the runner owns the browser context lifecycle and attaches retained recordings to the corresponding test result. Files normally live below the configured outputDir, which defaults to test-results. The HTML report exposes the video alongside errors and other attachments.
A recording is finalized when its page or browser context closes. This lifecycle matters in library code. Reading or saving a complete video while the page remains open can wait for completion. If a script exits without awaiting context closure, the file may be missing or incomplete.
Video provides user-perspective evidence, but it cannot explain every failure. A button may appear visible while a transparent overlay blocks pointer events. A spinner may remain without revealing the 500 response behind it. Pair video with Playwright Trace Viewer debugging when you need actions, DOM snapshots, network, console, and source context.
2. Enable Playwright video Recording in Test Config
The simplest runner configuration sets video under use:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
outputDir: 'test-results',
reporter: [['html', { open: 'never' }]],
use: {
video: 'retain-on-failure',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
retain-on-failure records every attempt but removes a recording when that attempt passes. If an initial test fails and a retry passes, the initial failed run's video remains. This makes it a useful choice when you need the exact visible first failure. The cost is that successful attempts still incur recording work before their files are pruned.
on-first-retry avoids recording initial runs. It records retry index 1 and always keeps that recording. Combine it with at least one configured retry, or no retry will exist to record. A common CI policy is video on first retry and trace on first retry, while ordinary local runs have no retries.
Options in use may also be overridden by project or file. Keep the suite-level policy easy to predict, and document any exception for a high-risk workflow.
3. Choose the Right Video Mode
Current Playwright Test video modes mirror the trace mode family. The initial test execution is the first run, and later executions caused by retries are retry attempts.
| Mode | Records video on | Keeps video when | Practical use |
|---|---|---|---|
off |
Never | Never | Fast runs with other evidence |
on |
Every run | Always | Short focused investigation |
retain-on-failure |
Every run | That run failed | Exact failed-attempt evidence |
retain-on-first-failure |
Initial run only | Initial run failed | Preserve original failure only |
retain-on-failure-and-retries |
Every run | Run failed or is a retry | Compare complete retry chain |
on-first-retry |
First retry only | Always | Lean CI retry evidence |
on-all-retries |
Every retry | Always | Compare all retries without initial video |
Suppose retries: 2. A clean pass has only index 0. A flaky result might fail at index 0 and pass at index 1. A final failure can execute indexes 0, 1, and 2. on-first-retry keeps only index 1 in both retry scenarios. retain-on-first-failure keeps index 0 if it failed. retain-on-failure-and-retries keeps index 0 when failed plus all retry videos, even a passing retry.
Start with the smallest mode that answers your common diagnostic question. For an intermittent animation defect reproduced during retry, on-first-retry may be enough. For a state-changing checkout flow, the initial failure is often more valuable. Use the richer chain temporarily when attempts differ in a way the team cannot yet explain.
4. Configure Video Size and Viewport
Use the object form to set mode and output dimensions. If video size is not specified, Playwright derives it from the viewport and scales it down to fit within 800 by 800. Without an explicit viewport, the default video size is 800 by 450. The page image is placed at the top-left and scaled down as necessary to fit the recording size.
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
viewport: { width: 1280, height: 720 },
video: {
mode: 'retain-on-failure',
size: { width: 1280, height: 720 },
},
},
});
Matching a 16:9 viewport to a 16:9 recording avoids unexpected empty space and preserves detail, though it produces larger artifacts than a smaller video. Choose dimensions that keep relevant UI readable at normal review size. A very small recording can make text, hit targets, and subtle layout shifts impossible to inspect. A needlessly large recording costs storage and upload time.
For mobile projects, configure device emulation and a suitable video size at the project level. Do not reuse one landscape size for every portrait device without checking scaling. Record a representative test and inspect the result before standardizing.
Video size is not a substitute for responsive assertions. It affects evidence resolution, while the context viewport affects application layout. Keep both explicit.
5. Add Action and Test Annotations
Current Playwright video configuration can annotate interacted elements and display test information. Action annotations outline the target and show an action title. Test annotations can show file, test, or step-level context. These overlays make a recording easier to follow when the reviewer does not have the trace open.
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
video: {
mode: 'on-first-retry',
size: { width: 960, height: 540 },
show: {
actions: {
duration: 600,
position: 'top-right',
fontSize: 16,
},
test: {
level: 'step',
position: 'top-left',
fontSize: 13,
},
},
},
},
});
Supported positions include top-left, top, top-right, bottom-left, bottom, and bottom-right. Select positions that do not cover important application controls. Action duration controls how long the annotation remains visible, and step-level test information works best when the test uses meaningful test.step() names.
Annotations are evidence overlays, not application pixels. They should not be used for visual screenshot baselines. Review one sample from each viewport because a large label that is harmless on desktop may obscure most of a narrow phone recording.
For privacy-sensitive tests, the test title or step text itself may reveal customer or domain data. Keep test names descriptive but synthetic, and apply the same access rules to annotated videos as other reports.
6. A Runnable Test and Failure Workflow
This self-contained test records a simple interaction. It passes as written, so retain-on-failure will delete its video. Change the expected status temporarily in a local learning branch to observe failure retention, then revert the change.
// tests/video-demo.spec.ts
import { test, expect } from '@playwright/test';
test('records the visible save flow', async ({ page }) => {
await page.setContent(`
<main>
<label>Display name <input value='QA Engineer'></label>
<button>Save profile</button>
<p role='status'>Not saved</p>
</main>
<script>
document.querySelector('button').addEventListener('click', () => {
document.querySelector('[role=status]').textContent = 'Saved';
});
</script>
`);
await test.step('save profile', async () => {
await page.getByRole('button', { name: 'Save profile' }).click();
});
await test.step('verify confirmation', async () => {
await expect(page.getByRole('status')).toHaveText('Saved');
});
});
Run it with npx playwright test tests/video-demo.spec.ts and open npx playwright show-report. Under video: 'on', the pass has a video. Under retain-on-failure, a clean pass does not keep one. If a local altered expectation fails, the report shows the retained recording with that failed result.
Do not keep intentional failures in the production suite. Their purpose is to validate that configuration, report linkage, and CI artifact upload work before a real incident.
7. Record Video With the Playwright Library
Outside Playwright Test, enable recording when creating the context. Each page in the context has its own Video object. This Node.js script uses only documented library APIs:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
recordVideo: {
dir: 'videos/',
size: { width: 1280, height: 720 },
},
});
const page = await context.newPage();
await page.goto('https://example.com');
await page.getByRole('heading', { name: 'Example Domain' }).waitFor();
const video = page.video();
await page.close();
if (video) {
await video.saveAs('videos/example-domain.webm');
}
await context.close();
await browser.close();
page.video() returns null when video was not enabled. Video.saveAs() waits for the recording to finish and copies it to the requested location. In this example the page is closed first, so finalization can complete. Video.path() returns the generated file path but throws when connected remotely. Video.delete() waits if necessary and removes the recording.
If you close the context rather than the page, retain the Video reference before closure and use it afterward. Always await context.close() because videos are guaranteed to be written when the context closes. Abrupt process termination can lose evidence.
The library API does not apply test-runner retention modes automatically. Your script owns naming, cleanup, failure decisions, parallel-path uniqueness, and artifact upload.
8. Handle Popups, Multiple Pages, and Parallel Tests
A video-enabled context creates a video for each page. If a click opens a popup, capture the page event before the click and remember that the popup has its own recording:
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open receipt' }).click();
const popup = await popupPromise;
await popup.waitForLoadState();
const popupVideo = popup.video();
In Playwright Test, let the runner attach retained page videos to the result. In library code, close each page or the whole context before saving. Give copied videos unique names that include a run, worker, and business-safe identifier. Two parallel processes writing failure.webm can overwrite or confuse evidence.
Avoid creating manual browser contexts inside a test unless the scenario needs them. The built-in page fixture belongs to a runner-managed context and follows configured retention. A manually created context needs its own recordVideo option and must be closed by the test. If teardown is skipped due to a coding error, the video may never finalize.
For multi-tab flows, video can reveal which page the user saw, but Trace Viewer or explicit popup assertions remain necessary to explain page ownership and events. Use Playwright popup and new tab handling for reliable synchronization.
9. Use Video, Trace, and Screenshots Together
Each artifact answers a different question:
| Artifact | Strongest question | Limitation |
|---|---|---|
| Video | What visible sequence did the user experience? | Weak technical detail and no inspectable DOM |
| Trace | Which actions, page states, requests, and errors occurred? | Larger structured artifact, canvas replay can be limited |
| Failure screenshot | What did the final page look like? | One frame without sequence |
| Console or server log | What technical error was emitted? | May lack user-visible context |
| Network correlation ID | What happened in backend services? | Requires access to external observability |
A sensible CI combination is trace: 'on-first-retry', video: 'retain-on-failure', and screenshot: 'only-on-failure', adjusted to suite economics. The initial failed attempt gets video and screenshot, while a first retry gets a trace. That provides complementary views, though the artifacts belong to different attempts and must not be conflated.
For a difficult motion defect, temporarily retain both trace and video for the full retry chain. For an ordinary locator timeout, a trace may be enough. Measure storage and review value, then remove redundant permanent recording. More artifacts do not automatically create a better diagnosis.
When a screenshot regression is the requirement, use Playwright toHaveScreenshot examples rather than comparing video frames manually.
10. Upload and Retain Videos in CI
Upload test evidence even when the test step fails. A GitHub Actions pattern is:
- name: Run Playwright tests
run: npx playwright test
- name: Upload Playwright report and videos
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-evidence-${{ github.run_attempt }}
path: |
playwright-report/
test-results/
if-no-files-found: ignore
retention-days: 14
The result path contains only recordings retained by the configured mode. Artifact upload cannot recover videos that the runner deliberately removed after a pass. Make artifact names unique for browser matrices and shards. Confirm the paths against outputDir and reporter configuration, and validate the entire chain with a controlled local or branch-only failure.
Treat recordings as potentially sensitive. They can expose names, emails, account balances, messages, tokens rendered on screen, internal URLs, and test data. Use synthetic accounts, avoid production environments, restrict artifact downloads, and set a retention period based on the investigation window. Do not upload videos to a public issue tracker by default.
Large videos also cost time and storage. Reduce unnecessary recording modes before crushing resolution so far that evidence becomes unreadable. Delete obsolete manual recordings and rely on CI retention automation for normal runner output.
11. Debug Common Video Problems
If no video appears, first confirm that video is under use, the selected mode should keep this result, and the test actually ran in the expected project. A passed test under retain-on-failure produces no retained video. on-first-retry produces nothing when retries are zero or the initial run passes.
If a manual recording is incomplete, confirm that the page or context was closed and that closure was awaited before process exit. Store the Video reference before closing when you plan to call saveAs(). Ensure the target directory is writable and each parallel page uses a unique destination.
If the image is tiny or surrounded by empty space, align viewport and video aspect ratio. Inspect object configuration and device project inheritance. If annotations cover the target, move their position, reduce font size, or disable them for that project.
If video shows the symptom but not the cause, stop replaying it and open the trace. Check network, console, DOM snapshots, and locator logs. Correlate with backend identifiers for service failures. A video of a spinner is evidence that loading persisted, not evidence of why.
The Playwright timeout troubleshooting guide provides a causal workflow when the recording ends at a waiting action.
Interview Questions and Answers
Q: How do you enable video recording in Playwright Test?
I set the video option under use in playwright.config.ts. For CI, I usually start with retain-on-failure or on-first-retry rather than on. The selected mode determines which attempts are recorded and retained.
Q: When is a Playwright video file finalized?
The recording is finalized when the page or its browser context closes. In library code, I always await page or context closure before expecting the complete artifact. I retain the Video object first if I need to save or inspect its generated path afterward.
Q: What is the difference between retain-on-failure and on-first-retry?
retain-on-failure records every attempt and keeps a video only for an attempt that fails. on-first-retry does not record the initial run; it records and keeps retry index 1. The first mode captures the exact failure, while the second reduces recording on initial passes.
Q: Why would you use video when trace is available?
Video is convenient for continuous visible behavior such as scrolling, drag interactions, animation, and transient overlays. Trace is stronger for action, DOM, network, and console analysis. I use them as complementary evidence and avoid retaining both everywhere without demonstrated value.
Q: How do you record video with Playwright Library?
I pass recordVideo: { dir, size } to browser.newContext(), then create and use pages normally. Each page has a Video object. I close the page or context, then call saveAs(), path(), or delete() as required.
Q: How do video annotations help debugging?
Action annotations identify the interacted element and action, while test annotations display file, test, or step context. They make a recording easier to follow without cross-referencing the trace. I position and size them so they do not cover the UI under investigation.
Q: What security concerns apply to test videos?
Videos can reveal any information rendered in the viewport, including personal data and internal application details. I use synthetic data, restrict access, avoid production capture, and apply short retention. Shared clips are reviewed or redacted according to policy.
Common Mistakes
- Putting
videoat the configuration root instead of underuse. - Using
on-first-retrywith zero retries and expecting a recording. - Assuming
retain-on-failurekeeps video from successful attempts. - Exiting a library script without awaiting
browserContext.close(). - Awaiting
video.saveAs()while the page remains open and wondering why completion waits. - Recording every suite attempt permanently when only failure evidence is needed.
- Using a small or mismatched recording size that makes the relevant UI unreadable.
- Giving parallel manual recordings the same destination filename.
- Treating a video of a symptom as a complete root-cause analysis.
- Confusing an initial-failure video with a trace recorded on a later retry.
- Uploading recordings with customer or credential data to broadly accessible artifacts.
- Adding annotations that cover important mobile controls or reveal sensitive test titles.
Conclusion
Playwright video recording is straightforward to enable, but useful recording requires deliberate mode, lifecycle, sizing, and retention choices. In Playwright Test, configure video under use and let the runner manage attachments. In library scripts, create a video-enabled context, close it reliably, and manage each page's Video object yourself.
Start with failure or first-retry retention, verify one artifact at readable resolution, and pair it with a trace when technical cause matters. Keep only evidence that helps engineers act, and protect every recording as carefully as the application data visible inside it.
Interview Questions and Answers
Explain Playwright Test video retention modes.
The modes decide which initial or retry attempts are recorded and kept. `retain-on-failure` keeps failed attempts, `on-first-retry` records index 1, and `retain-on-first-failure` preserves a failed index 0. Current modes also support all retries and the failure-plus-retry chain.
Why must a BrowserContext be closed for video?
The encoder finalizes recordings when pages or the context close. Awaiting context closure guarantees that videos are written to the filesystem. Abrupt process exit or missing teardown can leave no complete artifact.
How would you choose video dimensions?
I align the recording aspect ratio with the explicit viewport, keep text and targets readable, and balance resolution against storage and upload cost. I verify desktop and mobile projects separately because one size rarely fits both well.
How do you access a page video in library code?
I call `page.video()` after creating the page in a video-enabled context and retain the returned `Video` object. After closing the page or context, I can call `saveAs()`, `path()`, or `delete()`. I handle the possible null when recording is disabled.
What is the advantage of annotated video?
It can outline interacted elements and display action or test-step titles, so reviewers can follow the intended sequence quickly. I keep annotations away from important controls and avoid exposing sensitive values in test names. They complement rather than replace trace detail.
How do you manage video artifacts in CI?
I use a selective retention mode, upload report and result directories even after failure, give matrix artifacts unique names, and set a bounded retention period. Access is restricted and tests use synthetic data. I validate paths with a controlled failure.
When would you not record test videos?
I skip video when traces and screenshots already answer the likely failures, when the surface has no meaningful motion, or when privacy and storage cost outweigh diagnostic value. Artifact policy should be risk-based rather than universal.
Frequently Asked Questions
How do I turn on Playwright video recording?
In Playwright Test, set `use: { video: 'retain-on-failure' }` or another supported mode in the config. In Playwright Library, create a context with `recordVideo: { dir: 'videos/' }` and close the context to finalize files.
Where are Playwright test videos saved?
Runner-managed videos are stored with test artifacts below the configured output directory, commonly `test-results`, and linked from the HTML report when retained. Library recordings go to the `recordVideo.dir` directory unless you copy them with `Video.saveAs()`.
Why is my Playwright video missing?
The mode may have removed a passing attempt, no retry may have occurred, or a manual context may not have been closed. Check mode semantics, retries, selected project, output paths, and awaited page or context closure.
How do I record video only when a Playwright test fails?
Use `video: 'retain-on-failure'`. Playwright records attempts and removes the video for each attempt that passes, while keeping recordings for failed attempts.
Can Playwright record video only on retry?
Yes. Use `on-first-retry` for retry index 1 or `on-all-retries` for every retry. Configure at least one retry at the runner level or no retry video can be created.
How do I set Playwright video resolution?
Use the object form, for example `video: { mode: 'retain-on-failure', size: { width: 1280, height: 720 } }`. Set a compatible viewport and inspect scaling, readability, and artifact size.
Is Playwright video better than trace?
Neither is universally better. Video is strong for visible sequence and motion, while trace adds actions, DOM snapshots, network, console, and source context. Use the least expensive combination that answers the failure.