What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

PhantomJS screenshots rarely match current Chrome pixel for pixel because they are produced by different rendering engines. PhantomJS uses the older QtWebKit engine; Chrome uses Blink. Align the viewport, crop, scale, fonts, browser environment and page-readiness state, then compare like with like. For new visual tests, use a maintained Chromium tool; if PhantomJS output is contractual, keep a PhantomJS baseline instead of treating Chrome as an exact replacement.

Why PhantomJS and Chrome render different pixels

They use different engines

PhantomJS renders through QtWebKit, while current Chrome renders through Blink. The engines can calculate different layout geometry, support different CSS and SVG features, rasterize text differently and handle edge cases in media queries, WebGL and replaced elements in different ways. A page can therefore have the same HTML and still produce different line breaks, element sizes or antialiased pixels.

PhantomJS development is suspended. Its WebKit implementation does not receive the platform and CSS fixes that current browsers do, so engine parity is not a realistic long-term goal. PhantomJS documentation also warns that comparing WebKit version strings is not a reliable feature test; test the feature you actually depend on.

Viewport and crop are separate settings

The CSS viewport determines responsive breakpoints and layout. In PhantomJS, page.viewportSize sets that viewport. The captured rectangle is controlled separately by page.clipRect. A 1280-pixel viewport with a 1024-pixel clip is not equivalent to a 1024-pixel viewport: the page may choose a different breakpoint before it is cropped.

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

Scale and zoom change the bitmap

Device scale factor (DPR) controls how CSS pixels become physical pixels. Zoom changes the rendered scale before rasterization. Two captures can have identical CSS geometry but different PNG dimensions and glyph edges if one uses DPR 2 and the other DPR 1, or if browser zoom differs. Set both explicitly and check the final image dimensions, not just the CSS width and height.

Fonts and operating systems affect layout

If a requested font is missing, the fallback font can be wider or narrower, changing wrapping and the height of every block below it. Even with the same font files, operating-system hinting and antialiasing can alter glyph pixels. Compare screenshots only on the same OS image with the same installed fonts, locale and font files.

Readiness is often nondeterministic

PhantomJS and Chrome may capture at different points in the page lifecycle. Web fonts, image decoding, JavaScript data, lazy-loaded images, animations and delayed API responses can all settle after navigation reports success. PhantomJS exposes JavaScript, image-loading and resource-timeout settings, but those settings do not automatically mean that an application is visually ready.

Background and output pipelines differ

PhantomJS renders through Qt’s image pipeline. If the document does not set a background, PhantomJS can leave it transparent rather than painting white. A browser capture with a composited white background will consequently differ over the entire image. Output format and compression add another source of differences: use PNG for lossless visual comparisons.

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

A deterministic fix, step by step

1. Choose what “correct” means

  • For new tests, prefer a maintained Chromium automation stack and establish a Chromium baseline.
  • If an existing product or approval process requires PhantomJS, treat its output as the contractual baseline and do not expect Chrome to become pixel-identical.
  • If you need to compare implementations, record the engine and exact version beside every screenshot.

2. Freeze the execution environment

Use a pinned container or virtual machine. Record the OS image, engine version, locale, timezone, installed fonts, shipped font files, user agent and capture options. Do not compare a developer laptop with a CI runner as though they were equivalent. A browser update, font-package update or operating-system patch can change antialiasing without any application-code change.

3. Set the viewport before navigation

Use the same CSS width and height in both implementations, and set them before loading the URL so responsive CSS is evaluated under the intended conditions.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

PhantomJS example:

var page = require('webpage').create();
page.viewportSize = { width: 1280, height: 800 };
page.clipRect = { top: 0, left: 0, width: 1280, height: 800 };
page.settings.javascriptEnabled = true;
page.settings.loadImages = true;
page.settings.resourceTimeout = 30000;
page.open('https://example.com', function (status) {
  if (status !== 'success') {
    console.error('open failed: ' + status);
    phantom.exit(1);
  }
  page.render('phantom.png');
  phantom.exit();
});

The example captures the viewport-sized rectangle. For a deliberate crop, change clipRect while leaving viewportSize at the layout size.

Equivalent Puppeteer setup:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.setViewport({width: 1280, height: 800, deviceScaleFactor: 1});
await page.goto('https://example.com', {waitUntil: 'networkidle0'});
await page.screenshot({
  path: 'chrome.png',
  type: 'png',
  fullPage: false,
  clip: {x: 0, y: 0, width: 1280, height: 800}
});
await browser.close();

4. Make scale and zoom explicit

Choose one DPR for every run, normally 1 for CSS-pixel visual tests unless you are intentionally testing a retina rendering. Keep browser zoom at its default and record it if your harness changes it. Verify that a 1280 by 800 CSS viewport produces the expected physical pixel dimensions; a DPR of 2 produces roughly twice as many pixels in each direction.

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

5. Wait for visual readiness, not just navigation

Use a bounded readiness sequence:

  1. Wait for navigation to complete.
  2. Wait for required selectors that prove the page’s main content exists.
  3. Wait for document.fonts.ready in a modern browser, or use an application signal after fonts are known to be loaded.
  4. Wait for images to be complete and decoded.
  5. Wait for an application-owned flag such as window.__VISUAL_READY__ = true.
  6. Fail when a required resource or signal is missing instead of taking a best-effort screenshot.

Puppeteer readiness helper:

await page.goto('https://example.com', {waitUntil: 'networkidle0', timeout: 60000});
await page.waitForSelector('#main-content', {timeout: 15000});
await page.evaluate(async () => {
  if (document.fonts) await document.fonts.ready;
  const images = Array.from(document.images);
  await Promise.all(images.map(img => img.complete
    ? (img.decode ? img.decode().catch(() => {}) : Promise.resolve())
    : new Promise(resolve => {
        img.addEventListener('load', resolve, {once: true});
        img.addEventListener('error', resolve, {once: true});
      })));
});
await page.waitForFunction(() => window.__VISUAL_READY__ === true, {timeout: 15000});

On a test-owned page, set the flag only after data binding, image decoding and layout-affecting transitions are complete. A fixed sleep can hide slow-resource failures and is less reliable than a bounded condition.

6. Normalize page state

Disable or finish animations and transitions in the test environment, set a known scroll position, freeze time and randomness when those values affect layout, and make lazy loading deterministic. Do not let a screenshot trigger a different personalized state because of cookies, locale or timezone. Supply the same headers, cookies and user agent when the application uses them to select content.

await page.addStyleTag({content: `
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    caret-color: transparent !important;
  }
`});
await page.evaluate(() => window.scrollTo(0, 0));

7. Define background, format and bounds

Set an explicit background in the page or capture options:

html, body { background: #fff; }

Use PNG when differences of one pixel matter. Keep full-page versus viewport capture consistent, and use the same clip rectangle. Full-page capture is not interchangeable with a viewport clip: it changes the document area and can trigger different lazy-loading behavior.

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

8. Diagnose in a fixed order

  1. Compare image dimensions and the CSS viewport.
  2. Compare DOM bounding boxes and computed styles for the first element that diverges.
  3. Inspect loaded fonts and failed resources.
  4. Check DPR, zoom, scroll position and crop bounds.
  5. Only after geometry matches, investigate antialiasing and compression.

Log the final URL and user agent for every capture. Redirects and user-agent-specific responses can make two apparently identical requests render different documents.

Comparison checklist

Axis What to make identical Typical symptom when it differs
Engine and version PhantomJS/QtWebKit or a pinned Chromium build Unsupported CSS, SVG or layout changes
CSS viewport Width and height before navigation Different responsive breakpoint or wrapping
Crop semantics Viewport, full-page mode and clip rectangle Different output dimensions or missing content
DPR and zoom Device scale factor and browser zoom Different physical size and text edges
OS and fonts OS image, font files, locale and hinting environment Line-height, wrapping and glyph-pixel changes
Readiness Fonts, images, JavaScript, network and visual-ready signal Missing images, fallback fonts or partial data
Background Explicit page background and transparency policy Transparent PhantomJS pixels versus white browser pixels
Format PNG versus JPEG/WebP and quality settings Compression halos or color differences

Common failures and precise fixes

Fonts wrap differently

Cause: a missing web font, different fallback, or capture before font readiness. Fix: install and pin the same font files, wait for font readiness, and compare computed font family, weight and line height.

The whole page is shifted at a breakpoint

Cause: viewport width was set after navigation, or the clip width was mistaken for the viewport width. Fix: set the CSS viewport before opening the page and keep the clip rectangle separate.

Images are blank or different sizes

Cause: image loading is disabled, a resource timed out, decoding is incomplete, or lazy loading depends on scroll position. Fix: enable image loading in PhantomJS, increase and monitor the resource timeout, wait for completion and decoding, and force a deterministic scroll/lazy-load state.

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

Only text edges differ

Cause: DPR, OS text rendering or font hinting differs after geometry has matched. Fix: run both captures on the same OS image with the same DPR and fonts. If that is impossible, use perceptual or region-tolerant comparison rather than a strict pixel threshold.

The PhantomJS image has transparent areas

Cause: the document never set a background. Fix: set explicit html and body backgrounds and keep the same alpha policy in the other capture tool.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Chrome shows content PhantomJS cannot

Cause: modern CSS, SVG, WebGL or JavaScript depends on capabilities absent from the suspended QtWebKit engine. Fix: migrate the capture to maintained Chromium, or preserve the PhantomJS baseline and mark the implementations as different rendering targets.

Runs pass locally but fail in CI

Cause: different browser binaries, fonts, OS libraries, timezone, locale, network timing or cache state. Fix: pin the CI image and browser, ship the required fonts, log environment metadata, control caches and wait on explicit readiness signals.

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.

When to migrate from PhantomJS

Migration is the practical choice for new visual tests because PhantomJS is suspended and cannot track current browser behavior. Use a maintained Chromium automation stack when your acceptance criteria are “what users see in a current browser,” when the page requires modern platform features, or when you need documented controls for viewport, scale, clipping and readiness.

Keep PhantomJS when an established artifact, downstream comparison or legal record specifically depends on its output. In that case, freeze its runtime and maintain a separate baseline. Do not mix PhantomJS and Chromium images in one threshold without documenting the engine boundary.

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

Or skip the browser setup

If you need repeatable hosted captures instead of maintaining browser binaries, ScreenshotNeo is the first service to try: it removes consent banners, newsletter popups and chat widgets before capture, bills only clean shots, and has a $5 paid plan for 3,000 shots.

One GET request returns PNG, JPEG, WebP or PDF. The API reports whether a result was clean, blocked or failed through X-Page-Verdict and whether it was billed through X-Billed. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing.

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

cURL

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

See the ScreenshotNeo API documentation for parameters and response handling.

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
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(`${res.status} ${res.statusText}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Controls for reproducible captures

ScreenshotNeo provides 63 options, including full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or a custom viewport, retina scale, PDF paper size/margins/landscape/page ranges, HTML/CSS-to-image, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits for selectors/delays/network idle, ad/tracker/request/resource blocking, custom headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, image resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work to simplify switching.

An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients, so an AI agent can request captures without your own browser harness.

Plans and cost

Plan Allowance Price
Free 1,000 shots/month $0, no card
Starter 3,000 shots/month $5
Growth 15,000 shots/month $15
Pro 60,000 shots/month $39
Scale 250,000 shots/month $99
Business 1,000,000 shots/month $249

Every feature is included on every plan, and yearly billing provides two months free. Start with 1,000 free screenshots a month with no card.

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

Frequently Asked Questions

Can a CSS reset make PhantomJS and Chrome identical?

A reset can remove author-style variation, but it cannot equalize QtWebKit and Blink layout algorithms, font rasterization or unsupported platform features. Engine and environment alignment still determine the baseline.

Should visual tests compare JPEG screenshots?

Use PNG for pixel-level assertions. JPEG introduces lossy blocks and ringing that can look like rendering defects; use JPEG only when delivery size matters more than exact comparison.

How should I handle unavoidable antialiasing differences?

After matching geometry, fonts, OS and scale, use a documented per-pixel tolerance or perceptual comparison for text edges. Keep strict checks for layout regions where deterministic pixels are achievable.

Why did a cache hit change my screenshot workflow?

A cached response can bypass a fresh page load and have different billing or readiness behavior. Record the cache state and choose a deliberate cache policy when captures are part of a test.

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.