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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Most Playwright component screenshot “alignment” failures are caused by what was captured or how it was rendered, not by a one-pixel CSS bug. Assert against the locator returned by mount(), make the browser, viewport, device scale factor and capture state identical to the baseline run, inspect the expected/actual/diff images, and only then change CSS, tolerances or snapshots.

Start with the smallest reproducible component screenshot

Component tests should mount the state you intend to compare and screenshot the component’s root locator. Playwright’s component-testing guide recommends this approach because asserting on page can include the component gallery or other page content. See Playwright component testing.

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

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

If the failure disappears when you change expect(page) to expect(component), the mismatch was capture scope rather than component geometry. Each mount() starts a fresh navigation, so separate stories or states do not inherit accidental page content.

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.

Register routes before mounting

Mounting navigates the component-test page. Install any response handlers first, otherwise the component can render a loading, error or empty state while the screenshot is taken.

test('loaded card', async ({ page, mount }) => {
  await page.route('**/api/card/42', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ title: 'Example' })
    });
  });
  const component = await mount(<Card id='42' />);
  await expect(component).toHaveScreenshot('card-loaded.png');
});

Use a fixed rendering environment

Playwright documents visual variation from the host operating system, browser version, browser settings, hardware, power source and headless mode. Generate and compare snapshots in the same project and environment whenever possible; otherwise a font rasterization or layout change can look like an offset in your component. The visual-comparison guidance is at playwright.dev/docs/test-snapshots.

Make the project explicit

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

export default defineConfig({
  projects: [
    {
      name: 'chromium-linux',
      use: {
        ...devices['Desktop Chrome'],
        browserName: 'chromium',
        headless: true,
        viewport: { width: 1280, height: 720 },
        deviceScaleFactor: 1
      }
    }
  ]
});

Pin the browser binaries used by CI and local baseline generation, and avoid comparing a baseline created on one operating system with a run on another unless you have deliberately accepted that rendering difference. A failure that moves text or changes antialiasing is often an environment mismatch, not a changed margin.

Check viewport and device pixel ratio separately

Playwright’s default browser-context viewport is 1280 by 720 and its default device scale factor is 1. These are independent controls. A CSS viewport determines responsive layout; the device scale factor determines how CSS pixels are rasterized. Review project use settings, test.use(), browser.newContext() and any page.setViewportSize() call. References: Browser API, Emulation and TestOptions.

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

Avoid a null viewport for visual baselines

viewport: null makes the size depend on the host window. Playwright identifies that mode as non-deterministic, so a laptop, container and CI runner can select different breakpoints. Set width and height explicitly in the project or test.

Keep screenshot scale consistent

toHaveScreenshot() accepts scale: 'css' or scale: 'device'. CSS scale emits one image pixel per CSS pixel; device scale emits one per device pixel and can produce a larger high-DPI image. Keep the assertion scale, context device scale factor and baseline generation settings unchanged. The assertion options are documented in PageAssertions and LocatorAssertions.

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

If the image dimensions differ, check viewport and scale before changing component CSS. A consistent two-pixel displacement at a high device scale can simply be a different raster grid.

Stabilize the state before diagnosing pixels

Screenshot assertions take repeated captures and wait for two consecutive screenshots to match before comparing them. Playwright’s screenshot options also control animation handling and comparison thresholds. Let the assertion settle, but make the inputs deterministic so it is not repeatedly settling on different content.

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

Control animation and volatile UI

  • Use the documented animation behavior for screenshot assertions, and disable or fast-forward animations when motion is not part of the visual contract.
  • Provide stable fixture data and route responses before mount().
  • Hide a clock, rotating ad or live counter only when that content is intentionally outside this test’s purpose. A style filter that removes a real layout element can conceal a regression.

Wait for the component’s real ready condition

const component = await mount(<Dashboard />);
await component.locator('[data-testid="dashboard-ready"]').waitFor();
await expect(component).toHaveScreenshot('dashboard.png');

Prefer a semantic ready selector, a known network-idle condition or a bounded delay required by the component. Do not add an arbitrary long delay as a first fix; it increases test time without proving that the layout is stable.

Read the diff before changing thresholds

Open the expected, actual and diff images in Playwright UI mode or the trace viewer. The pattern usually identifies the class of defect:

Diff pattern Likely cause Next check
Entire component shifted by a constant amount Wrong locator ancestor, viewport breakpoint or page padding Assert on the returned root locator; print its bounding box and verify viewport dimensions
Text edges differ while boxes align Operating system, browser, font or device-scale rendering Compare browser project, OS image, installed fonts and DPR
Only images or late content differ Unmocked request, lazy loading or unstable response Register routes before mount and wait for the loaded state
Diff changes on every retry Animation, clock, caret or live data Freeze or mask the specific volatile input, then rerun
Image dimensions changed Viewport or scale mismatch Compare CSS viewport, device scale factor and assertion scale

Do not raise maxDiffPixels, maxDiffPixelRatio or the color threshold to make a geometric shift pass. Those settings redefine what is accepted; they do not repair the layout. Apply a tolerance only after you can name the remaining, acceptable rendering variation.

Fix the underlying component or test setup

When the locator is wrong

Use the locator returned by mount(), or a deliberate child locator when the test is specifically for that child. Avoid page.screenshot() for a component assertion unless the full page is the subject of the test. Check that a wrapper used by the component-test harness is not being mistaken for the component root.

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

When layout inputs are wrong

Make width, height, locale, color scheme, timezone and any responsive feature flags explicit. A breakpoint crossing can change flex direction, font size or wrapping while leaving the component code untouched. Keep those settings identical in the baseline project and the comparison project.

When the component really changed

Fix the CSS or markup if the diff is unintended. Re-run the focused test and inspect the new diff. Only after review should you record a new golden image.

Update snapshots only after an intentional change

For a reviewed design change, run:

npx playwright test --update-snapshots

Review every changed reference, remove accidental files, and commit the snapshot directory with the test change. Updating a golden image records a new expected rendering; it does not explain an unexplained alignment failure. Keep the baseline-generation environment documented so a future runner does not recreate the mismatch.

Common failures and precise fixes

Symptom Cause to verify Fix
Local passes, CI fails with a one-pixel edge Different OS, browser build, fonts or headless mode Use the same container/browser project for both runs
Only mobile story fails Implicit or null viewport selected a different breakpoint Set an explicit mobile width and height
Screenshot is twice as large Device scale factor or scale: 'device' changed Restore the intended DPR and screenshot scale
Component screenshot contains gallery chrome Assertion targets page Assert on the locator returned from mount()
Expected data is missing Route handler was installed after navigation Register page.route() before mount()
Every retry differs Animation or live content never settles Stabilize only the relevant animation, clock or data source
Large diff passes after a tolerance change Threshold masked a real geometry regression Revert the threshold and fix or review the visual change

Performance, reliability and cost considerations

Repeated screenshot capture and browser startup make visual tests slower than ordinary assertions. Keep the test focused on the smallest component that proves the behavior, reuse a fixed project configuration, and avoid unnecessary full-page images. Stable route fixtures reduce retries and make failures reproducible. A deterministic container may cost more CI setup initially but prevents teams from regenerating baselines for every runner.

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

Snapshot files are part of the test artifact. Store them with the test, review image diffs in code review, and retain trace output for failures. Do not trade away the signal by accepting broad pixel tolerances or masking large regions.

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

Or skip the browser setup:

For a deployed page or publicly reachable component showcase, ScreenshotNeo provides a single screenshot API request. It is not a substitute for a local Playwright component test when you need a mounted, isolated React/Vue/Svelte state, but it can remove browser orchestration for URL-level visual checks. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and whether it was billed. An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

See the full parameter reference in the ScreenshotNeo documentation. The API supports PNG, JPEG, WebP and PDF, and options include full-page capture with lazy images loaded, a CSS-selector element capture, dark mode, 12 device presets or a custom viewport, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS input, custom JavaScript and CSS, pre-capture clicks, hidden selectors, waits for selectors/delay/network idle, request and resource blocking, headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, resizing, TTL-based caching, signed public-image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting and an OpenAPI specification. Familiar parameter names from other screenshot APIs are accepted to ease migration.

cURL

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

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)

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}`);

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000 and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Sign up free for ScreenshotNeo to get the 1,000 monthly shots without a card.

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

FAQ

Can I use a ScreenshotNeo URL to test an uncommitted component?

Only if the page is reachable by ScreenshotNeo, such as a deployed preview or authenticated endpoint configured with suitable headers or cookies. For an in-memory component mounted by Playwright, keep the component test local.

Should a baseline be shared across operating systems?

Share it only when the rendering environment is deliberately standardized. Otherwise maintain references generated by the same OS, browser project and rendering settings as the comparison run.

What is the safest first change when a diff looks like an offset?

Confirm the assertion target and print the viewport and image dimensions before touching CSS or tolerance settings. Those checks distinguish capture-scope and raster-scale errors from genuine layout changes.

Frequently Asked Questions

Can I use a ScreenshotNeo URL to test an uncommitted component?

Only if the page is reachable by ScreenshotNeo, such as a deployed preview or authenticated endpoint configured with suitable headers or cookies. For an in-memory component mounted by Playwright, keep the component test local.

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

Should a baseline be shared across operating systems?

Share it only when the rendering environment is deliberately standardized. Otherwise maintain references generated by the same OS, browser project and rendering settings as the comparison run.

What is the safest first change when a diff looks like an offset?

Confirm the assertion target and print the viewport and image dimensions before touching CSS or tolerance settings. Those checks distinguish capture-scope and raster-scale errors from genuine layout changes.

The Bottom Line

Fix alignment failures in order: capture scope, rendering environment, viewport and device scale, deterministic state, diff interpretation, then intentional baseline updates. This preserves the regression signal instead of hiding a layout bug behind a tolerance.

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.

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