ScreenshotNeo

BlogHow-to

Best Way to Debug a Website Screenshot That Looks Different from the Browser

Match the page state, browser environment, viewport, device pixel ratio, and capture mode to find why a website screenshot differs from the browser.

By the ScreenshotNeo team4 October 20268 min read

The best way to debug a website screenshot that looks different from the browser is to make the two captures comparable first. Match the URL and page state, browser and version, operating system or runner, viewport dimensions, device pixel ratio (DPR), and capture area. Then capture again. If the mismatch remains, inspect the changed region and verify device-specific behavior on the real target hardware.

A screenshot is the output of a rendering environment, not a context-free picture of a page. Browser version, host operating system, settings, hardware, power source, and headless mode can all affect rendering. Playwright recommends keeping the environment that creates a visual baseline consistent with the environment that later checks it. Playwright documents these sources of rendering variation.

1. Confirm both captures show the same page state

Before changing CSS or adjusting screenshot tolerances, check that the images represent the same content and moment. Compare:

  • The full URL, route, query parameters, and redirects.
  • Login state, cookies, local storage, and other session data.
  • Dynamic content such as timestamps, rotating banners, random data, or live prices.
  • Scroll position and whether the page had finished loading, including images and fonts.
  • Any interaction state: open menus, expanded accordions, hover, focus, or selected tabs.

If the page state differs, reproduce the same state before investigating rendering. A visual diff cannot tell whether a changed pixel came from a browser difference or simply different content.

2. Match the rendering environment

Record the browser name and exact version, operating system or CI/container image, headed or headless mode, and relevant browser settings for each capture. If the mismatch is between a local browser and CI, run both captures in the same environment if possible. For regression tests, generate and compare baselines in the same browser and runner configuration.

Keep a short capture manifest alongside each baseline. For example:

URL: https://example.com/pricing
Browser: Chromium 130
Runner: Linux container image used by CI
Mode: headless
Viewport: 1440 × 900 CSS pixels
DPR: 1
Capture: viewport only
State: signed out, consent accepted, menu closed

Use the actual browser and runner versions from your project; the values above are illustrative, not a recommended fixed setup.

3. Match viewport, DPR, and capture area

Set the same viewport width and height in CSS pixels. Responsive breakpoints can change layout when the width differs by even a small amount. Also align DPR and screenshot scale: a screenshot may store one image pixel per CSS pixel or one pixel per device pixel. On a high-DPI display, device-pixel output can have larger pixel dimensions even when the CSS layout is the same.

In Playwright, the screenshot option supports scale: 'css' and scale: 'device'. Check the API you are using rather than assuming its default: Playwright documents different defaults for screenshot capture and screenshot assertions. Screenshot option details and the PageAssertions documentation describe the relevant controls.

Finally, compare like with like. A viewport screenshot shows the visible frame; a full-page screenshot captures the scrollable document. Chrome DevTools and Playwright both support these capture modes, but they answer different questions. Chrome DevTools device mode lets you set viewport and DPR while inspecting responsive behavior.

4. Reproduce the capture with Playwright

This runnable Node.js example fixes the viewport and DPR, waits for the page to load, and writes a viewport screenshot. Install Playwright with npm install -D playwright and install its Chromium browser with npx playwright install chromium.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    viewport: { width: 1440, height: 900 },
    deviceScaleFactor: 1,
  });
  const page = await context.newPage();
  await page.goto('https://example.com/pricing', {
    waitUntil: 'networkidle',
    timeout: 60000,
  });
  await page.screenshot({
    path: 'page.png',
    fullPage: false,
    scale: 'css',
    animations: 'disabled',
  });
  await browser.close();
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

networkidle can be unsuitable for pages that keep network connections open or poll continuously. In that case, wait for a meaningful selector or a known app-ready condition instead. For example, replace the navigation wait with waitUntil: 'domcontentloaded', then use await page.locator('[data-testid="page-ready"]').waitFor() for an application-specific signal. Use a selector that actually exists on your page.

To test full-page output, set fullPage: true and use the same mode for the reference. To emulate a device, set its viewport and deviceScaleFactor explicitly or create a context from a Playwright device preset. Presets include device parameters, but emulation does not reproduce every property of physical hardware. See Playwright emulation options.

5. Stabilize the image and inspect the difference

Capture only after the page has reached a repeatable state. Disable or wait out animations, wait for fonts and important images, and avoid arbitrary short sleeps where a page-ready signal is available. For Playwright visual assertions, toHaveScreenshot() waits for consecutive screenshots to stabilize before comparing against the expected image.

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

test('pricing page matches its visual baseline', async ({ page }) => {
  await page.setViewportSize({ width: 1440, height: 900 });
  await page.goto('https://example.com/pricing', {
    waitUntil: 'domcontentloaded',
  });
  await page.locator('[data-testid="page-ready"]').waitFor();
  await expect(page).toHaveScreenshot('pricing.png', {
    fullPage: false,
    animations: 'disabled',
    scale: 'css',
  });
});

Install the test runner with npm install -D @playwright/test, then run the test with npx playwright test. On the first run, Playwright may need to create the expected snapshot with its documented snapshot-update workflow.

When a diff remains, inspect its shape before changing thresholds:

  • Text edges or wrapping: check fonts, font loading, browser version, and viewport width.
  • Whole sections shifted: check responsive breakpoint, page state, CSS, and viewport dimensions.
  • Different image or blank image area: check network completion, lazy loading, permissions, and image URL availability.
  • Only dynamic widgets differ: stabilize their data or mask the smallest known variable region.
  • Colors differ: inspect browser color handling, color scheme, and environment settings.

Playwright supports masks and pixel or color-difference tolerances. Use them only for known variation after reviewing the diff; a wide tolerance can conceal an actual layout regression. See visual comparison guidance.

6. Verify device-specific differences on real hardware

Chrome DevTools device mode can reproduce viewport sizes, responsive breakpoints, and selected device properties such as DPR. It cannot simulate every behavior of a real phone. If the remaining difference appears only on a specific mobile device, browser version, font renderer, or hardware feature, check on that target device using remote debugging or a direct capture. Chrome explicitly notes that some mobile-device aspects cannot be simulated in DevTools.

Screenshot settings to align

Axis What to match Why it matters
Page state URL, session, content, interactions, scroll Different inputs produce different page content.
Browser and host Browser/version, OS or container, headed/headless mode Rendering can vary across environments.
Viewport Width and height in CSS pixels Breakpoints and line wrapping depend on available space.
Pixel scale DPR and CSS/device screenshot scale Changes output dimensions and can affect comparisons.
Capture area Viewport, element, or full page These images include different regions of the document.
Stability Ready condition, animations, variable regions Transient changes create noisy diffs.
Device fidelity Emulator or real target hardware Emulation cannot model every device behavior.

Common problems and fixes

Symptom Likely cause Fix
Screenshot dimensions differ despite similar layout DPR or screenshot scale differs Set the same device scale factor and choose the same CSS or device scale output.
Mobile navigation or columns differ Viewport crosses a responsive breakpoint Set the exact CSS viewport width and height; confirm the active media query in DevTools.
Local image matches but CI image does not Different browser, OS/container, browser settings, or headless mode Capture and compare in the same runner environment and pin the relevant versions.
Text wraps or looks slightly different Fonts are missing or not loaded, or rendering environment differs Wait for fonts to load, ensure the same font files are available, and align browser and OS.
Full-page image appears to have extra or missing content One capture is viewport-only or lazy content has not loaded Use the same capture mode and ensure the page has loaded the content being compared.
Visual test fails intermittently Animation, live content, or capture before the page is ready Wait on a meaningful ready condition, disable animations, and mask only understood dynamic regions.
Device emulation still does not match a phone Emulation cannot reproduce all hardware and browser behavior Reproduce on the actual target device and browser.

Performance, reliability, and cost notes

For repeatable local or CI checks, keep the browser and runner fixed and reuse a small set of explicit viewport/DPR configurations. This reduces avoidable variation and makes a changed baseline easier to explain. Full-page captures can be larger and slower than viewport captures, so use them only when the page below the fold is part of the check. Waiting for network idle can also delay or hang on apps with persistent requests; a specific ready selector is often more predictable.

Visual tolerance is a maintenance setting, not a diagnosis. Keep masks narrow, review baseline updates, and investigate broad differences before accepting them. The cited documentation describes controls and sources of variation but does not quantify how frequently any one cause occurs or provide a universal performance benchmark.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single GET request returns an image or PDF; the API accepts common screenshot parameter names to make switching straightforward. Use this call to capture the example page:

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for request options. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots.

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

FAQ

Why does my Playwright screenshot look different from my browser?

Usually the captures use a different browser or host environment, viewport, DPR, page state, or capture mode. Align those settings first, then inspect the remaining diff.

Should I use CSS scale or device scale?

Use the same scale as the reference and test configuration. CSS scale produces one output pixel per CSS pixel; device scale follows the device pixel ratio.

Can DevTools device mode prove how a real phone will render?

No. It is useful for viewport and responsive checks, but some mobile behavior requires testing on the actual device.

Should I increase the screenshot diff threshold until the test passes?

Only after identifying a known, acceptable source of pixel variation. A larger threshold can hide a real visual regression.