Stable Placeholder Images for Playwright Visual Comparisons

Published March 11, 2026

Playwright's visual comparison API compares screenshots pixel-by-pixel. A single changed pixel triggers a diff. When pages contain dynamic images fetched from external sources, every test run risks a false positive. Deterministic placeholder fixtures freeze the image layer so only real UI changes produce diffs.

How Playwright visual comparison works

Playwright's `toHaveScreenshot()` assertion captures a screenshot and compares it against a stored baseline. If the pixel difference exceeds the configured threshold, the test fails and generates a diff image highlighting the changed regions.

This is powerful for catching CSS regressions, layout shifts, and rendering bugs. But it only works when the non-code parts of the page — images, fonts, animations — are identical between runs.

Replacing dynamic images with fixtures

The cleanest approach is to intercept image requests in your Playwright test setup and respond with committed fixture files. Playwright's `page.route()` API makes this straightforward.

Generate placeholder images in PlacePack at the exact dimensions your components expect. Store the files in a `tests/fixtures/images/` directory. In your test setup, route image requests to the fixture files.

Playwright route intercept

import { test, expect } from "@playwright/test";
import { readFileSync } from "fs";

test.beforeEach(async ({ page }) => {
  await page.route("**/api/images/**", (route) => {
    const body = readFileSync("tests/fixtures/images/product_600x600.png");
    route.fulfill({ body, contentType: "image/png" });
  });
});

test("product page layout", async ({ page }) => {
  await page.goto("/products/1");
  await expect(page).toHaveScreenshot("product-page.png");
});

Covering multiple viewport sizes

Responsive layouts render different image sizes at different viewports. Generate a PlacePack fixture for each breakpoint and route the correct fixture based on the requested dimensions or URL pattern.

For example, a hero image might be 1440x600 on desktop, 768x400 on tablet, and 375x280 on mobile. Generate all three in one PlacePack session and map each to the corresponding viewport test.

Organizing baselines and fixtures in CI

Commit both the fixture images and the Playwright baseline screenshots to version control. When a layout changes, update the fixture if the image dimensions changed, then run Playwright locally to capture a new baseline. Commit both in the same pull request.

This keeps the fixture set and the baselines in sync. Code reviewers can see exactly which images and screenshots changed, making it easy to verify that the update is intentional.

Per-state color coding for visual clarity

Use PlacePack's per-item color overrides to distinguish component states in your visual tests. A red placeholder for error states, green for success, and amber for loading makes it immediately obvious which state a screenshot captures.

This convention helps when reviewing Playwright reports — you can identify the state at a glance without reading test names or hovering over diff regions.

FAQ — Frequently asked questions

Does Playwright support SVG fixture images?
Yes. Route fulfillment accepts any content type. Use contentType: 'image/svg+xml' when serving SVG fixtures.
How do I update baselines when fixtures change?
Run `npx playwright test --update-snapshots` after replacing the fixture files. Commit the new baselines alongside the updated fixtures.
Can I use the PlacePack API instead of local files?
You can, but local files are more reliable for CI. The API introduces a network dependency that can cause timeouts. Generate fixtures once and commit them.

Related guides

Ready to generate placeholder images?

Open the generator with the right preset pre-loaded and download your pack in seconds.