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

Use Puppeteer to render a page and capture its pixels, Jest to run the test, and jest-image-snapshot to compare the screenshot buffer with a saved image baseline. The first run creates the baseline; later runs fail when the rendered image differs beyond the comparison settings. Review the resulting diff before updating a baseline.

What screenshot tests check—and what Jest snapshots check

Jest’s ordinary snapshot tests serialize values, such as component output, into text. Screenshot-based visual regression tests compare rendered images instead. Jest describes visual regression tools as taking web-page screenshots and comparing the images pixel by pixel: Jest Snapshot Testing.

The tools have separate jobs: Puppeteer controls a browser and captures the page; Jest runs the test and reports its result; jest-image-snapshot adds an image matcher that compares the screenshot buffer with a stored baseline. These approaches can complement each other: text snapshots are useful for serialized output, while image comparisons can catch visual changes in a rendered page.

Install and register the image matcher

Install the matcher as a development dependency:

npm install --save-dev jest-image-snapshot

The package README documents a Jest peer-dependency range of >=20 and <=29. Check the version selected in your lockfile and the package metadata before using it with a newer Jest release; general Jest snapshot support does not establish that this matcher is compatible with Jest 30. See the jest-image-snapshot README.

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

Register the matcher in a Jest setup file or in the test module:

const { toMatchImageSnapshot } = require('jest-image-snapshot');
expect.extend({ toMatchImageSnapshot });

If using a Jest setup file, configure it through your project’s Jest setup options and ensure the setup runs before the test. Keeping registration in one shared setup file avoids repeating it across test files.

Write a Puppeteer visual test

This CommonJS example assumes your application is already running at the target URL and your project supplies a compatible Puppeteer setup. Replace the URL and readiness condition with those used by your application:

const puppeteer = require('puppeteer');
const { toMatchImageSnapshot } = require('jest-image-snapshot');

expect.extend({ toMatchImageSnapshot });

describe('home page visual appearance', () => {
  let browser;

  beforeAll(async () => {
    browser = await puppeteer.launch({ headless: true });
  });

  afterAll(async () => {
    await browser.close();
  });

  it('matches the approved image baseline', async () => {
    const page = await browser.newPage({
      viewport: { width: 1280, height: 800 },
      deviceScaleFactor: 1,
    });

    try {
      await page.goto('http://localhost:3000', { waitUntil: 'networkidle0' });
      await page.waitForSelector('[data-testid="home-ready"]');

      const image = await page.screenshot({ fullPage: true });
      expect(image).toMatchImageSnapshot();
    } finally {
      await page.close();
    }
  });
});

The matcher documentation’s basic pattern is to call page.screenshot() and pass its returned buffer to toMatchImageSnapshot(). The example above adds a fixed viewport, page readiness check, full-page capture, and browser/page cleanup; these details must reflect your application’s lifecycle and Puppeteer version. Avoid relying on networkidle0 alone when the page maintains persistent network connections or loads content after network activity settles.

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

Capture a stable state

  • Set the viewport and device scale factor explicitly, and keep them consistent between baseline creation and CI.
  • Load fixed test data and use predictable dates or time-dependent values.
  • Wait for a meaningful selector or application-ready signal rather than guessing with a fixed delay.
  • Disable or finish animations if motion is not what the test is intended to verify.
  • Use a consistent browser and operating environment. The Think Company example project uses Docker to improve local/CI parity; Docker is an implementation choice, not a requirement for every project.

Capture a page or a specific element

Use page.screenshot() for a page capture. If the behavior under test belongs to one component, use Puppeteer’s element screenshot API on a selected element instead. Keep the capture region consistent: a change in viewport, scroll position, or element size changes the image being compared, even when the component itself has not changed.

Generate and maintain image baselines

On the first run, jest-image-snapshot stores a baseline image under __image_snapshots__ by default. Later runs compare new screenshots with that reference. The README documents options for a custom snapshot directory, diff output, comparison thresholds, and baseline updating.

  1. Run the test locally to create the initial image.
  2. Open the saved baseline and verify that it shows the intended state, not a loading screen, error, or uninitialized page.
  3. Commit the baseline with the test so reviewers and CI use the same reference. Jest recommends keeping snapshot artifacts in version control and reviewing changes alongside the code: Jest Snapshot Testing.
  4. When a later test fails, inspect the baseline, received image, and diff. Decide whether the change is a defect, environmental noise, or an intentional design change.
  5. Update only the affected baseline after reviewing and approving the new appearance. Do not use a broad update to silence failures without checking what changed.

Jest’s standard snapshot guidance says it does not automatically write snapshots in CI unless an update option is explicitly supplied. Treat image baseline updates with the same care: review the visual output, then update deliberately.

Choose comparison settings without hiding regressions

jest-image-snapshot uses pixelmatch by default and also documents SSIM as a comparison method. Its README lists a default per-pixel threshold of 0.01 and a default overall failure threshold of zero. These are library defaults, not universal recommendations.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Per-pixel sensitivity: how much a pixel’s color can differ and still count as a match.
  • Overall failure threshold: how much of the image may differ before the test fails.
  • Comparison method: pixel-by-pixel comparison or structural similarity (SSIM).
  • Diagnostics: whether and where baseline, received, and diff images are written.
  • Noise policy: whether to stabilize the page, mask dynamic regions, or tolerate small rendering differences.

A more permissive threshold can reduce noisy failures, but it can also let a real visual regression pass. Tune settings against representative pages and inspect actual diffs. The package documentation exposes these choices but does not establish one correct threshold for every application.

Control dynamic content carefully

Timestamps, rotating promotions, ads, user-specific content, and consent banners can make otherwise sound tests inconsistent. Prefer deterministic fixtures or a controlled test environment. If a changing region is irrelevant to the behavior under test, remove or mask it before capture; do not hide content whose layout or behavior the test is meant to protect.

The matcher README demonstrates removing banner elements with Puppeteer before taking a screenshot. For example, adapt this only to a known test-only selector:

await page.evaluate(() => {
  document.querySelector('[data-testid="rotating-banner"]')?.remove();
});

Removing an element can shift surrounding content. If layout is part of the test, use a stable fixture or controlled banner state instead of deleting the region.

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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common failures

Symptom Likely cause What to check
Matcher import or Jest setup error The matcher was not registered before the test, or package versions are incompatible. Confirm expect.extend({ toMatchImageSnapshot }) runs before the assertion and check the installed matcher’s peer dependency range against the lockfile.
First run captures a blank or loading page The screenshot was taken before the application reached its intended state, or the route failed to load. Check navigation errors, use an application-ready selector, and inspect the received image before accepting it as a baseline.
Repeated failures with apparently unchanged code Viewport, browser environment, fonts, data, animations, or external content varies between runs. Fix viewport and device scale, use predictable data, control animation and time, and reduce reliance on network resources. Consider a consistent containerized environment.
Diff shows changes in banners or other transient regions Dynamic content is being captured. Control the content or remove/mask only the irrelevant region, ensuring that the adjustment does not conceal behavior the test should cover.
Test passes despite a visible change Thresholds are too permissive, the comparison method is unsuitable for the target, or the changed area is not in the capture. Inspect the capture bounds and diff, then tune the per-pixel and overall thresholds using representative pages.
Test is slow or intermittently times out The page waits on persistent network activity, unavailable external services, or an overly broad readiness condition. Wait for the application’s actual ready signal, control external dependencies, and avoid treating network-idle as the only readiness test.

Or skip the browser setup

If you need a screenshot file rather than a Jest baseline comparison, ScreenshotNeo can return a screenshot or PDF from one GET request. It accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.

Example using cURL (see the ScreenshotNeo API documentation):

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

ScreenshotNeo has 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000. It does not replace Jest’s baseline matcher when you need an automated visual regression test inside your test suite. Learn more at ScreenshotNeo, or sign up free for 1,000 screenshots a month with no card.

Frequently asked questions

Can I use this setup to test a page running locally?

Yes. Start the application in the test environment and point Puppeteer at its local URL. The test must ensure the server is ready before navigation; the example assumes the server lifecycle is handled separately.

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.

Should I use an image snapshot instead of a regular Jest snapshot?

Use the type that matches the risk you want to catch. Text snapshots compare serialized values; an image baseline checks the rendered appearance. A project may use both for different assertions.

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.