Overview
Playwright can capture screenshots and record videos of your tests, providing visual evidence of test execution and helping debug failures.
Screenshots
Full Page Screenshot
Capture a screenshot of the entire page:
import { test, expect } from '@playwright/test';
test('capture full page', async ({ page }) => {
await page.goto('https://example.com');
await page.screenshot({
path: 'screenshot.png',
fullPage: true
});
});
Element Screenshot
Capture a screenshot of a specific element:
test('capture element', async ({ page }) => {
await page.goto('https://example.com');
const element = page.locator('.main-content');
await element.screenshot({ path: 'element.png' });
});
Screenshot Options
await page.screenshot({
path: 'screenshot.png',
fullPage: true,
type: 'png', // 'png' or 'jpeg'
quality: 100, // 0-100 (jpeg only)
});
Visual Regression Testing
Compare Screenshots
Playwright includes built-in visual comparison:
import { test, expect } from '@playwright/test';
test('visual regression', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.png');
});
Update Baseline Screenshots
When intentional UI changes are made, update baselines:
npx playwright test --update-snapshots
Customize comparison tolerance in playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
toHaveScreenshot: {
maxDiffPixels: 100,
maxDiffPixelRatio: 0.1,
threshold: 0.2,
},
},
});
Video Recording
Enable video recording in your configuration:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
video: 'on', // Record all tests
// video: 'retain-on-failure', // Only failed tests
// video: 'on-first-retry', // Record on retry
videoSize: { width: 1280, height: 720 },
},
});
Video Recording Options
Always record
Records video for all tests. Record failures only
use: {
video: 'retain-on-failure',
}
Only keeps videos of failed tests.Record on retry
use: {
video: 'on-first-retry',
}
Records when tests are retried.Never record
Disables video recording.
Access Video Path
Get the video path programmatically:
import { test } from '@playwright/test';
test('with video', async ({ page }) => {
await page.goto('https://example.com');
// ... test actions ...
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status === 'failed') {
const videoPath = await page.video()?.path();
console.log('Video saved to:', videoPath);
}
});
Screenshot on Failure
Automatic Screenshots
Configure automatic screenshots for failed tests:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
screenshot: 'only-on-failure',
// screenshot: 'on', // Always capture
// screenshot: 'off', // Never capture
},
});
Custom Failure Handler
Implement custom screenshot logic:
import { test } from '@playwright/test';
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== testInfo.expectedStatus) {
const screenshot = await page.screenshot();
await testInfo.attach('screenshot', {
body: screenshot,
contentType: 'image/png'
});
}
});
test('example test', async ({ page }) => {
await page.goto('https://example.com');
// Test actions...
});
Artifacts Location
Configure where screenshots and videos are saved:
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Output directory for test artifacts
outputDir: 'test-results/',
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
Practical Examples
Debug Failed Tests
import { test, expect } from '@playwright/test';
test('checkout flow', async ({ page }) => {
await page.goto('https://example.com/shop');
// Take screenshot before critical action
await page.screenshot({ path: 'before-checkout.png' });
await page.click('text=Add to Cart');
await page.click('text=Checkout');
// Take screenshot after critical action
await page.screenshot({ path: 'after-checkout.png' });
await expect(page.locator('.success-message')).toBeVisible();
});
Visual Testing Workflow
import { test, expect } from '@playwright/test';
test.describe('homepage visual tests', () => {
test('desktop view', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage-desktop.png');
});
test('mobile view', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage-mobile.png');
});
test('dark mode', async ({ page }) => {
await page.emulateMedia({ colorScheme: 'dark' });
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage-dark.png');
});
});
Hide Dynamic Content
test('stable screenshot', async ({ page }) => {
await page.goto('https://example.com');
// Hide elements that change frequently
await page.screenshot({
path: 'stable.png',
mask: [
page.locator('.timestamp'),
page.locator('.ad'),
page.locator('.random-content'),
],
});
});
CI/CD Integration
Store artifacts in CI pipelines for easy access:
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 20
- run: npm ci
- run: npm run build
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Best Practices
Selective Recording: Use retain-on-failure for videos to save disk space while still capturing failures.
- Use visual regression for stable UIs: Only apply visual testing to components with predictable rendering
- Mask dynamic content: Hide timestamps, ads, and random content to reduce false positives
- Set appropriate tolerances: Configure pixel difference thresholds based on your needs
- Store artifacts in CI: Upload screenshots and videos to CI artifacts for easy debugging
- Clean up old artifacts: Implement retention policies to manage disk space
Video recording increases test execution time and storage requirements. Use it judiciously in CI/CD pipelines.
Troubleshooting
Screenshots appear blank
Wait for content to load before capturing:
await page.goto('https://example.com');
await page.waitForLoadState('networkidle');
await page.screenshot({ path: 'loaded.png' });
Visual comparison failing unexpectedly
Increase tolerance or update baselines:
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixels: 500,
threshold: 0.3,
});
Videos not being saved
Ensure video path is accessible and verify configuration:
test.afterEach(async ({ page }, testInfo) => {
const video = page.video();
if (video) {
const path = await video.path();
console.log('Video path:', path);
}
});