Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A Playwright component screenshot that appears shifted, resized, or generally misaligned is usually caused by the capture target or rendering conditions—not by a need for a looser pixel threshold. Fix it in this order: assert on the locator returned by mount(), make the baseline and comparison environments identical, explicitly align viewport and device-pixel settings, stabilize capture state, inspect the expected/actual/diff images, and update the golden only after an intentional UI change has been reviewed.

1. Confirm that the assertion captures the component, not the page

Component tests run inside a component-testing gallery. Asserting on page can include gallery markup, padding, or other content that is unrelated to the component. Playwright’s component-testing guide recommends taking the screenshot from the root locator returned by mount() (Playwright component testing).

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

test('primary button visual state', async ({ mount }) => {
  const component = await mount(<Button variant="primary">Save</Button>);
  await expect(component).toHaveScreenshot('primary.png');
});

If your test uses a story or component name rather than JSX, the same rule applies:

const component = await mount('components/Button/Primary');
await expect(component).toHaveScreenshot('primary.png');

For several states, call mount() for each state. Fresh mounts navigate independently, so each screenshot starts from the state you specify instead of inheriting a previous component.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Register routes before mounting

Mounting navigates to the component. Install network handlers first; otherwise the component may render a loading or error layout when the screenshot is taken. The component-testing documentation describes this ordering (route setup and mounting).

test('loaded card', async ({ page, mount }) => {
  await page.route('**/api/card/42', route =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ title: 'Example' })
    })
  );

  const component = await mount('components/Card');
  await expect(component).toHaveScreenshot('card-loaded.png');
});

2. Reproduce the baseline rendering environment

Playwright documents that screenshots can vary with the host operating system, browser version, browser settings, hardware, power source, and headless mode. A mismatch that looks like a one-pixel offset may actually be a font rasterization or browser-rendering change. Generate and compare snapshots in the same project, browser version, operating-system image, and headless configuration used to create the references (Playwright visual comparisons).

  • Run the same Playwright project in both jobs; do not compare a Chromium reference with a Firefox run unless that is intentional.
  • Pin the browser binaries and Playwright version in CI.
  • Use the same operating-system image and installed fonts. A missing font can alter glyph widths, line wrapping, and therefore every downstream coordinate.
  • Keep headless/headed mode consistent where your workflow depends on it.
  • Record the project, browser, viewport, and device scale factor in CI logs so a failing image can be tied to its inputs.

Do not change component CSS until these inputs match. Environment consistency is a prerequisite for diagnosing a real layout regression.

3. Make viewport and device scale explicit

Viewport size controls the CSS layout coordinate system; device scale factor controls how CSS pixels are rasterized. Playwright documents a default context viewport of 1280 × 720 and a default device scale factor of 1 (Browser API). A viewport of null follows the host window and is non-deterministic, so avoid it for visual baselines (TestOptions).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { defineConfig } from '@playwright/experimental-ct-react';

export default defineConfig({
  use: {
    viewport: { width: 1280, height: 720 },
    deviceScaleFactor: 1
  }
});

Also search for overrides in test.use(), browser.newContext(), and page.setViewportSize(). A test-level value silently wins over a project default. Ensure responsive breakpoints are identical between baseline and comparison; a one-pixel width change can switch a flex or media-query layout.

Choose the screenshot scale deliberately

toHaveScreenshot() supports scale: 'css' and scale: 'device' (LocatorAssertions). CSS scale produces one output pixel per CSS pixel. Device scale produces one output pixel per device pixel, so a high-DPI context creates a larger image. Keep this option fixed when generating and comparing snapshots.

await expect(component).toHaveScreenshot('button.png', {
  scale: 'css'
});

If the image dimensions changed, inspect both deviceScaleFactor and assertion scale; changing only one can make an otherwise identical component appear misaligned.

4. Stabilize the state before comparing pixels

Playwright’s screenshot assertion takes repeated captures and waits for two consecutive screenshots to match before comparing them. Its options cover animation handling, screenshot scale, and difference thresholds (PageAssertions; LocatorAssertions).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Control animation and transient content

Screenshot assertions disable animations by default. If your project overrides that behavior, restore deterministic settings or explicitly configure the test:

await expect(component).toHaveScreenshot('menu-open.png', {
  animations: 'disabled',
  caret: 'hide'
});

Freeze clocks, random IDs, rotating carousels, timestamps, blinking carets, and live counters when those values are not the behavior under test. Prefer test data and application-level controls over hiding broad regions. Screenshot style or mask options can filter volatile content, but use them only when excluding that content is part of the test’s purpose; masking a layout element can conceal a genuine alignment bug.

Wait for the layout you intend to test

Waiting for a selector, a stable application state, or a known response is better than an arbitrary sleep. If a font or image changes the component’s dimensions after first paint, wait for the application’s loaded state before the assertion. Keep route fulfillment deterministic and avoid external resources in a baseline test.

5. Read the diff instead of guessing

On failure, preserve the expected, actual, and diff images. A uniform outline around the component usually indicates a geometry or viewport change; text-only speckling often points to fonts or rasterization; a whole-page displacement suggests the wrong capture target or a page-level layout shift.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Expected and actual have different dimensions: check viewport, device scale factor, screenshot scale, and full-page versus element capture.
  • Everything is offset by the same amount: verify the locator’s bounding box, gallery padding, scroll position, and whether the assertion was made on page instead of the component root.
  • Only text differs: compare OS fonts, browser version, font loading, and anti-aliasing conditions.
  • Differences move between runs: investigate animations, network data, time, randomness, lazy images, and unhandled requests.

Playwright UI mode and Trace Viewer expose screenshot diffs and metadata such as browser and viewport size. Use those records to identify which input changed before modifying the test.

6. Treat tolerances as a final, narrow control

maxDiffPixels, maxDiffPixelRatio, and color thresholds define how much difference is accepted; they do not correct a shifted layout. Increasing them first can turn a real regression into a passing test. Set a tolerance only after you can explain the remaining variation and it is acceptable for this component.

await expect(component).toHaveScreenshot('icon.png', {
  maxDiffPixelRatio: 0.001
});

Keep tolerances local and documented. A global threshold broad enough to hide a one-component alignment failure weakens every visual check in the project.

7. Update a snapshot only after an intentional change

If the design change is deliberate, reviewed, and correct, regenerate the reference:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npx playwright test --update-snapshots

Review every changed image, remove accidental updates, and commit the snapshot directory with the code change. Updating a golden records a new rendered state; it is not a repair for an unexplained mismatch. Playwright’s visual-comparison guide covers this workflow (snapshot updates).

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

8. A practical diagnosis checklist

  1. Confirm the assertion uses the locator returned by mount().
  2. Install route handlers before mount() and verify the component is not in a loading state.
  3. Compare browser/project, OS, browser version, fonts, hardware conditions, and headless mode with the baseline job.
  4. Set an explicit viewport; never rely on viewport: null for a golden.
  5. Match deviceScaleFactor and screenshot scale.
  6. Disable or control animation, caret, time, randomness, and volatile network data.
  7. Inspect image dimensions and the expected/actual/diff triplet.
  8. Use a narrowly justified tolerance only for understood residual variation.
  9. Update snapshots only for a reviewed visual change.

Or skip the browser setup

For a standalone page image, ScreenshotNeo provides a single HTTP request. It accepts the cookie or consent banner before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. It also offers an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools.

See the complete parameter list in the ScreenshotNeo documentation. This call captures Stripe as WebP:

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

The same request in Python:

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)

And Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo supports full-page and element captures, dark mode, device presets or custom viewports, retina scale, PDF output, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Parameters used by other screenshot APIs also work, easing migration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Plans include 1,000 screenshots per month free with no card, then Starter at $5 for 3,000, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000. Yearly billing provides two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to start.

FAQ

Why does a component screenshot include unrelated content?

The assertion is probably targeting page or a broad selector. Assert on the root locator returned by mount() so the component gallery is excluded.

Should I set the viewport to null to use my desktop size?

No for deterministic visual tests. Playwright documents null as host-window dependent and non-deterministic; specify width and height instead.

When is a larger pixel-difference allowance justified?

Only when the residual variation is understood, repeatable, and outside the behavior the test is intended to protect. A geometric shift should be fixed at its source.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.