ScreenshotNeo

BlogHow-to

How to Set Up Screenshot Comparison for a React Website with Playwright

Set up Playwright visual comparisons for a React site, create and review baselines, and reduce noisy failures in CI.

By the ScreenshotNeo team4 October 20267 min read

To set up screenshot comparison for a React website with Playwright, use Playwright Test’s built-in toHaveScreenshot() assertion. Run the test once to create a reference image, review and commit that baseline, then run the test in a consistent environment so future captures can reveal unintended visual changes. The assertion works on the rendered page; there is no React-specific screenshot comparison package to add.

1. Install Playwright Test and start your React site

If your project does not already use Playwright Test, add it as a development dependency and install its browser:

npm install --save-dev @playwright/test
npx playwright install chromium

Set up a test command in package.json:

{
  "scripts": {
    "test:e2e": "playwright test"
  }
}

The test needs a running site at a URL that the browser can reach. You can start your React development server separately, or configure Playwright to start it for the test run. This example uses a local URL; replace it with the URL and startup command your project actually uses.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: 'http://127.0.0.1:3000',
    browserName: 'chromium',
    viewport: { width: 1280, height: 800 },
  },
  webServer: {
    command: 'npm run dev -- --host 127.0.0.1',
    url: 'http://127.0.0.1:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Adjust the server command and port to match your app. If your app needs environment variables, seeded data, or a test-specific configuration, provide those before the server starts. Keep credentials out of source control.

2. Add a visual comparison test

Create tests/home.visual.spec.ts. Put the page in the state you want to protect before capturing it: wait for required content, establish test data, and handle authentication or consent deliberately.

import { test, expect } from '@playwright/test';

test('home page matches its visual baseline', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
  await expect(page).toHaveScreenshot('home.png');
});

Run the test:

npx playwright test tests/home.visual.spec.ts

On the first run, Playwright reports that the expected screenshot is missing and writes the capture as the baseline. Later runs compare the current capture with that reference. Screenshot assertions wait until two consecutive captures are identical before comparing, which helps with transient rendering changes, but it does not replace making the page state deterministic. See the official Playwright screenshot comparison guide and page assertion reference.

3. Review and commit baselines

Playwright stores screenshot snapshots alongside a directory associated with the test file. Commit those reference images to version control so a developer or CI run compares against the same reviewed baseline.

  1. Run the test and inspect the generated image.
  2. Confirm it shows the intended page, viewport, and state.
  3. Commit the snapshot with the test and relevant application code.
  4. When the design intentionally changes, regenerate using npx playwright test --update-snapshots, inspect the replacement images, then commit them with the design change.

Do not update snapshots automatically just to make a failing build green. The expected, actual, and diff images are evidence: review them to decide whether the difference is an intended design change, a regression, or rendering environment drift. The official update guidance describes updating and reviewing screenshot references.

4. Make captures deterministic

Visual comparison is only useful when unrelated rendering variation is controlled. Playwright lists host operating system, browser version, settings, hardware, power conditions, and headless mode as sources of screenshot differences. Generate and compare baselines in the same environment where possible, particularly in CI. See Playwright’s notes on visual comparisons.

  • Fix the viewport. Set it in Playwright configuration or in the test. Keep dimensions consistent for baseline creation and CI.
  • Use a stable browser build and operating system. Avoid creating references on one machine and comparing them in a substantially different rendering environment.
  • Control page state. Use known test data and deterministic authentication. Wait for the particular content needed by the test instead of relying on arbitrary sleeps.
  • Handle animation and volatile UI intentionally. Playwright screenshot options include animation handling and a stylesheet hook. Use a stylesheet only to suppress genuinely irrelevant volatility, not content whose appearance is part of the behavior you want to test.
  • Choose the capture scope deliberately. A full-page screenshot catches broad layout changes. An element screenshot focuses the test on a stable component and avoids unrelated page regions.

For example, if the header is the behavior under test, compare that element instead of the entire page:

test('header matches its visual baseline', async ({ page }) => {
  await page.goto('/');
  const header = page.getByRole('banner');
  await expect(header).toBeVisible();
  await expect(header).toHaveScreenshot('header.png');
});

5. Set tolerances with care

Small pixel differences can result from rendering variation. Playwright lets you set a maximum number of differing pixels with maxDiffPixels or adjust per-pixel color sensitivity with threshold. Start with strict comparisons; introduce a tolerance only after inspecting actual diffs and understanding the noise. A permissive threshold can conceal real regressions. The values below are examples, not universal recommendations. Refer to the screenshot assertion options for supported settings.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  expect: {
    toHaveScreenshot: {
      maxDiffPixels: 100,
      threshold: 0.2,
    },
  },
});

You can also set comparison options on an individual assertion when one page has a justified, known source of minor variation:

await expect(page).toHaveScreenshot('dashboard.png', {
  maxDiffPixels: 100,
});

Choose a tolerance based on the observed diff and the risk of the page. For a checkout total, even a small text or alignment change may matter; for a broad decorative background, a small amount of known rendering noise may be acceptable.

6. Run comparisons in CI

Use the same Playwright configuration and dependency lockfile in local and CI runs. Install the browser version expected by the installed Playwright package, start the app at the configured address, and run the visual test command. Keep snapshot updates as a deliberate review step rather than a routine CI action.

npm ci
npx playwright install --with-deps chromium
npm run test:e2e

On failure, retain or inspect Playwright’s expected, actual, and diff artifacts. A consistent CI image and browser reduce environment drift; a stable application URL and test data reduce application-state drift. If the page relies on external services or content that can change independently, stub or otherwise control that data for repeatable comparisons.

7. Common errors and fixes

Symptom Likely cause Fix
Missing expected screenshot on first run No baseline has been created yet. Inspect the generated capture and commit it if it represents the intended state.
Screenshot differs on every run Dynamic content, animation, time-dependent data, or inconsistent rendering environment. Control data and page state, wait for the relevant UI, keep browser and OS consistent, and suppress only irrelevant volatility.
Test times out before comparison The app is unavailable, navigation is slow, or the awaited UI never appears. Check the server URL and startup command, then verify the locator and app state. Increase timeouts only when the slower behavior is expected.
CI has broad visual diffs while local runs pass CI uses a different OS, browser build, fonts, headless mode, or rendering settings. Generate and check baselines in a matching environment and use the Playwright browser installation associated with the lockfile.
Updating snapshots removes a meaningful failure The baseline was accepted without reviewing the actual and diff captures. Restore the reference, inspect the difference, and update only when the change is intentional.
TypeScript cannot find Playwright imports The package is missing, dependencies were not installed, or the test is outside the project’s expected setup. Install @playwright/test, run the package manager install, and execute through the Playwright Test runner.

8. Performance, reliability, and cost

Each visual test launches or uses a browser page, navigates to the application, and captures pixels for comparison. Keep suites efficient by testing representative pages and important components, avoiding duplicate captures of identical states, and using stable local or preview environments. Element captures can reduce unrelated page content, while full-page captures provide broader coverage at the cost of larger snapshots and more areas that can vary.

Reliability depends on reproducible browser rendering and predictable page content. Snapshot files also add review work: changes should be inspected and committed intentionally. Playwright’s screenshot comparison runs inside your test environment; costs depend on the infrastructure and execution time you choose. The cited Playwright documentation does not prescribe a universal runtime or infrastructure cost.

Or skip the browser setup

If you need a clean screenshot rather than a version-controlled visual regression assertion, ScreenshotNeo is a website screenshot API and MCP server. Its API documentation covers the request options and response behavior. Here is a one-call capture in each common shell or language:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card required.

FAQ

Does this require a React-specific Playwright integration?

No. Playwright drives a browser page and compares the rendered site. Use the same test setup for a React page as for any other website.

Should screenshot baselines be committed?

Yes. Committing reviewed snapshots lets local and CI runs compare against the same reference and makes visual changes reviewable with the code.

Can I compare a component instead of a full page?

Yes. Use a locator’s screenshot assertion when the component is the visual behavior you want to protect.

Is a screenshot comparison the same as checking accessibility?

No. It detects rendered visual differences; it does not replace semantic, keyboard, or accessibility checks.