ScreenshotNeo

BlogHow-to

Playwright Screenshot Testing for React Apps with Vite

Build reliable visual regression tests for React and Vite with Playwright: set baselines, stabilize rendering, debug diffs, and run tests in CI.

By the ScreenshotNeo team4 October 20269 min read

Use Playwright Test’s expect(page).toHaveScreenshot() to catch visual changes across a React app served by Vite. The first run creates a reference image; later runs compare the rendered page with that baseline. For a focused component check, use Playwright component testing to mount a React component and compare its root locator. Keep the browser and operating system consistent between baseline generation and CI, stabilize dynamic page state, and review image diffs before updating snapshots.

This guide covers page-level visual regression tests and component screenshots. These checks compare rendered images; they complement functional assertions and do not replace them.

1. Choose the screenshot test boundary

Approach What it covers Use it when
Page screenshot The page rendered in a browser, including layout and composed components. You want to detect changes to a route, page layout, or integrated UI.
Component screenshot A mounted component and its rendered subtree. You want a focused visual check that excludes unrelated page or gallery content.

Start with page tests when the behavior under review depends on routing, layout, or interactions among multiple components. Use component testing for reusable components whose appearance can be checked independently. Playwright documents React component testing and using an existing Vite development server to serve the component gallery in its component testing guide.

2. Install Playwright Test and configure Vite

In an existing React and Vite project, install the test runner and its browser binaries:

npm init playwright@latest

Follow the installer prompts to add Playwright Test and choose the browsers your project needs. Check the generated playwright.config.ts and test directory. A minimal configuration for a Vite app can start its development server before tests:

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: 'html',
  use: {
    baseURL: 'http://127.0.0.1:5173',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
  webServer: {
    command: 'npm run dev -- --host 127.0.0.1',
    url: 'http://127.0.0.1:5173',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

This configuration assumes the Vite app runs on port 5173. If your project uses another port or a preview build, update both the server command and URL. The webServer setting lets Playwright start the app and wait for its URL; it avoids relying on a separately launched local process.

3. Write a page-level visual test

Create tests/home.spec.ts. The example checks a meaningful heading before capturing the page, so a failed or empty navigation does not silently become the intended screenshot:

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', {
    fullPage: true,
  });
});

Replace Welcome with content that identifies the ready state in your app. If the app loads asynchronously, wait for a stable, user-visible condition rather than adding an arbitrary sleep.

Create and review the baseline

  1. Run the test once with npx playwright test. Playwright reports a missing reference and writes the screenshot for comparison.
  2. Open the generated image and confirm it represents the intended page state, viewport, and data.
  3. Commit the reviewed reference image alongside the test. Treat it as test input and review changes to it in code review.
  4. Run the test again. Playwright captures the page and compares it with the committed reference.

When a visual change is intended, inspect the actual and diff images first, then update the reference deliberately with npx playwright test --update-snapshots. Review the resulting image change before committing it. Do not update snapshots just to make a failing test pass.

4. Make the page state deterministic

A screenshot records the pixels at capture time. Differences can come from a real UI regression or from changes in data, timing, fonts, rendering environment, and other inputs. Stabilize those inputs before relaxing the comparison.

  • Wait for readiness: navigate to the page and assert a meaningful element is visible. For a specific async region, wait for that region’s content or loading indicator to reach the expected state.
  • Control data: use fixed fixtures and deterministic API responses. Avoid tests that depend on live third-party data.
  • Control time and randomness: freeze or inject clocks and random values when they affect visible content.
  • Handle animation: screenshot assertions disable animations by default. If your UI still changes over time, establish a stable state before capturing.
  • Normalize only volatile areas: inject a screenshot-only stylesheet or mask a genuinely variable region, such as a timestamp or rotating ad. Keep important UI visible so regressions remain detectable.
  • Keep fonts and assets available: ensure the test server can load the same local fonts and images in local and CI runs.

Playwright’s toHaveScreenshot() waits until two consecutive screenshots match before it compares the final image. This helps with transient rendering, but it does not make uncontrolled application data deterministic. See the official PageAssertions API for screenshot assertion behavior and options.

5. Capture a React component with Vite

Playwright component testing runs a component in a browser through a component test harness. The component guide describes configuring the Vite development server to serve that gallery. A test can mount a component and compare its root locator, keeping the assertion focused on that component rather than a full application page.

Component testing setup depends on the project’s installed Playwright version and framework configuration. Use the current Playwright component testing guide to install and configure the React adapter for your project. A component test has this shape:

import { test, expect } from '@playwright/experimental-ct-react';
import { ProductCard } from './ProductCard';

test('product card appearance', async ({ mount }) => {
  const component = await mount(
    <ProductCard name="Notebook" price="$12" />,
  );

  await expect(component).toHaveScreenshot('product-card.png');
});

The import path and setup can vary with the Playwright component testing package version; follow the guide for the version installed in your repository. Keep the component props and any mocked dependencies fixed so a baseline represents a deliberate state. Test important variants separately, such as disabled, error, or long-content states, rather than relying on one screenshot to cover every component behavior.

6. Configure screenshot comparison carefully

toHaveScreenshot() accepts options for what is captured and how pixels are compared. Check the API documentation for the exact supported options in your installed Playwright version.

Option or technique Purpose Guidance
fullPage Capture the whole scrollable page rather than only the viewport. Use for page layout coverage; very long pages can make failures harder to diagnose.
scale Choose CSS-pixel or device-pixel screenshot scaling. Keep scale consistent with the baseline environment. The API documents CSS and device scale choices.
animations Control animation handling during capture. Screenshot assertions disable animations by default; avoid depending on a transient animation frame.
stylePath / style injection Apply screenshot-only CSS, for example to hide a volatile cursor or timestamp. Use narrowly so the screenshot still covers meaningful UI.
mask Cover selected locators whose content is inherently variable. Mask only the changing area; a mask can hide a real visual regression in that region.
threshold, maxDiffPixels, maxDiffPixelRatio Allow a defined amount of pixel or color difference. Begin with deterministic rendering. Increase tolerance only after inspecting the diff and understanding the rendering variation.

Playwright documents comparison tolerances in its SnapshotAssertions API. A tolerance can reduce noise from small rendering differences, but it can also allow actual changes through. Do not use a permissive threshold as a substitute for stable test inputs.

7. Run the tests consistently in CI

Playwright cautions that screenshot rendering can vary with the operating system, browser version, settings, hardware, power source, and headless mode. Generate baselines and compare them in a consistent environment. A practical setup is to pin the Playwright dependency, install its matching browser binaries in CI, and generate or update references using the same operating system and browser project used for comparison.

npx playwright test

Use the package lockfile and the repository’s normal dependency installation in CI. Ensure the CI job installs Playwright’s browser dependencies as required by the runner environment. Avoid creating reference images on one OS and expecting pixel-identical output from another without validating that workflow. If cross-browser coverage is required, maintain and review the references for each configured browser project.

8. Debug a screenshot diff

  1. Read the test failure and identify the page, browser project, and screenshot name.
  2. Open the expected, actual, and diff images. Find whether the change is localized or affects the entire page.
  3. Check the test trace and action history around the capture: verify navigation, data, and readiness assertions.
  4. Compare browser, operating system, viewport, device scale, fonts, and headless settings with the baseline generation environment.
  5. Fix nondeterministic inputs or an actual UI defect. If the new appearance is intended, update the baseline after review.

Playwright Trace Viewer can show screenshot film strips, action snapshots, logs, and source locations. UI Mode is also useful for inspecting tests and their screenshots interactively. Use these tools to understand the source of a diff before accepting it.

9. Troubleshooting

Symptom Likely cause Fix
The first run fails because no snapshot exists. No reference has been created yet. Run the test, inspect the generated screenshot, then commit the approved baseline.
The page is blank or incomplete in the screenshot. The app failed to load, an async request is pending, or the test captured before the page was ready. Check navigation and console or trace details. Wait for a meaningful ready-state assertion and control the relevant response.
Snapshots pass locally but fail in CI. Different OS, browser binary, font availability, viewport, scale, headless mode, or data. Align baseline and CI environments, install matching browser binaries, and remove uncontrolled inputs.
The entire screenshot differs. The wrong route or state loaded, app styling or assets did not load, or the rendering environment changed. Verify the URL, ready-state assertion, server logs, assets, viewport, and browser version before considering a baseline update.
A small region changes on every run. Timestamp, random content, animation, network data, or another volatile region. Make inputs deterministic; otherwise narrowly mask or normalize the truly variable element.
A component test cannot mount the component. Component testing adapter or Vite gallery configuration is missing or does not match the installed version. Follow the current Playwright component testing setup for React and verify its Vite configuration.
A threshold hides a meaningful change. The configured pixel or color tolerance is too permissive. Inspect the diff, reduce tolerance, and fix the rendering source instead of broadening the allowance.
Snapshot updates include unexpected files. Multiple projects, browsers, or test cases generated their own references. Review each changed reference and confirm project-specific snapshots are expected before committing.

10. Performance, reliability, and maintenance

Screenshot checks add browser rendering and image comparison work to a test run. Keep them focused: capture representative page states and important component variants instead of taking redundant screenshots after every assertion. Full-page captures cover more content but can take longer and make large diffs less focused.

Reliability depends on controlling the page and rendering environment. Fixed data, explicit readiness checks, consistent browser binaries, and reviewed snapshots reduce false failures. Retries can help collect diagnostic traces for intermittent failures, but retries should not conceal a flaky test. Keep reference images in version control so code review can evaluate visual changes alongside the implementation.

There is no single useful runtime or cost figure for this workflow: it depends on page complexity, number of tests, browser projects, runner hardware, and CI configuration. Playwright screenshot tests run in your browser test environment; account for browser execution and artifact storage in the project’s existing CI budget.

Or skip the browser setup

If you need a screenshot of a live URL without maintaining browser setup, ScreenshotNeo is a website screenshot API and MCP server. The one-call request returns an image or PDF, and the ScreenshotNeo docs describe its parameters.

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}`);
  • Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Should I use page screenshots or component screenshots?

Use page screenshots for route-level layout and integration. Use component screenshots when you want to isolate a reusable component and its states.

Do screenshot tests replace accessibility or functional tests?

No. A screenshot can reveal appearance changes, but it does not establish that controls work or that the page is accessible. Keep behavior and accessibility checks alongside visual comparisons.

Should I update the baseline every time a screenshot test fails?

No. Inspect the expected, actual, and diff images and determine whether the change is intended. Update only after review.