Visual comparisons, screenshots and video
Set up screenshot baselines that do not fail on every font update, mask the parts that legitimately change, and capture video only when it helps.
Baselines and expected diffs
test("renders the pricing table", async ({ page }) => {
await page.goto("/pricing");
await expect(page).toHaveScreenshot("pricing.png", {
fullPage: true,
maxDiffPixels: 120,
animations: "disabled",
caret: "hide",
});
});
test("card matches its design", async ({ page }) => {
const card = page.getByTestId("plan-pro");
await expect(card).toHaveScreenshot("plan-pro.png", {
mask: [card.getByTestId("current-price"), page.getByRole("timer")],
});
});npx playwright test visuals.spec.ts # compare
npx playwright test visuals.spec.ts --update-snapshots # accept the new baseline
# inspect the difference report
npx playwright show-reporttoHaveScreenshotwrites a baseline on first run and compares on every run afterwards.maxDiffPixelsallows a small tolerance;maxDiffPixelRatiodoes the same as a proportion of the image.maskpaints over regions that legitimately change - timestamps, avatars, randomised recommendations - which removes most false failures.animations: "disabled"freezes CSS animations and transitions so two runs capture the same frame.
⚠️
Screenshot baselines are platform-dependent. Text rendering differs between operating systems and browsers, so a baseline generated on macOS will not match Linux. Generate baselines in the same container the suite runs in, and commit the expected snapshot path per platform.
Configuration that keeps visual tests stable
// playwright.config.ts
export default defineConfig({
expect: {
toHaveScreenshot: {
maxDiffPixels: 100,
threshold: 0.2, // per-pixel colour tolerance
animations: "disabled",
caret: "hide",
scale: "css", // compare at CSS pixels, not device pixels
},
},
snapshotPathTemplate:
"{testDir}/__screenshots__/{projectName}/{testFilePath}/{arg}{ext}",
});| Setting | Effect | When to change it |
|---|---|---|
threshold | Per-pixel colour difference allowed | Slight anti-aliasing noise |
maxDiffPixels | Absolute count of differing pixels | Small legitimate changes |
maxDiffPixelRatio | Same, as a fraction | Varying image size |
scale: "css" | Compares at CSS pixels | High-DPI runners |
animations | Freezes or allows motion | Leave disabled |
fullPage | Captures beyond the viewport | Long pages |
- Keep the number of screenshot assertions small. Each one is a test that fails when a designer changes a margin.
- Prefer component screenshots over full-page ones: the diff is smaller and the cause is usually obvious.
- Never loosen the threshold to make a real change pass. If the change is intended, update the baseline and review it.
- Generate and update baselines in CI-like conditions, and review the new images in the pull request.
Screenshots and video on failure
use: {
screenshot: "only-on-failure", // or "on" / "off"
video: "retain-on-failure", // or "on-first-retry" / "off"
trace: "on-first-retry",
},// an ad hoc capture from inside a test
await page.screenshot({ path: "artifacts/step-1.png", fullPage: true });
await page.locator("#summary").screenshot({ path: "artifacts/summary.png" });
await page.video()?.saveAs("artifacts/run.webm");- Video is expensive - in CPU, in storage and in review time. Record only on failure, and only where a video answers a question a trace cannot.
- A trace is usually better than a video: it is smaller, it carries the DOM and network, and it can be stepped through.
- Upload artefacts with a short retention window; screenshots and videos contain whatever was on the page, including personal data.
- Set
video: "retain-on-failure"rather than"on"so a green run costs nothing.
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-artifacts
path: |
test-results/
retention-days: 7FAQ
How do I stop visual tests failing on every dependency update?
Mask the regions that legitimately change, keep assertions to the components that matter, allow a small pixel tolerance for anti-aliasing, and run the comparisons in the same container image every time. Font and renderer differences are the usual cause of wholesale diffs.
Should I use video or traces?
Traces for debugging. They include the DOM snapshots, console output and network activity, and they can be stepped through. Use video only when the failure is inherently about motion or timing and a sequence of snapshots does not convey it.
Related
Debugging with UI mode, codegen and inspector Multiple browsers, devices and emulation
Last refreshed 2026-09-18.