ScreenshotNeo

BlogGuides

Puppeteer Screenshot Testing with Jest: A Setup Guide

Set up repeatable Puppeteer screenshots in Jest with a preset or custom browser lifecycle, stable capture patterns, and practical CI troubleshooting.

By the ScreenshotNeo team4 October 202610 min read

Direct answer: add Puppeteer to Jest through the jest-puppeteer preset for the quickest setup, or use Jest’s global setup, global teardown, and a custom test environment when you need control over how the browser starts and connects. In either case, navigate to a known application state, set a consistent viewport, and use Puppeteer’s page.screenshot() for a page or element.screenshot() for one element.

This guide covers both approaches, screenshot options, repeatability, CI considerations, and common failures. Package compatibility and browser launch requirements can change, so check the current package documentation before pinning versions. Jest’s Puppeteer guide currently shows Jest 30.5; Puppeteer’s screenshot reference showed version 25.12.0 when researched. Jest: Using with Puppeteer · Puppeteer: Screenshots · Puppeteer Page.screenshot API

1. Choose a Jest and Puppeteer integration

Approach Use it when Trade-off
jest-puppeteer preset You want a packaged configuration and browser/page access in tests. Less lifecycle code to maintain; verify the preset’s current compatibility with your Jest and Puppeteer versions.
Custom lifecycle and environment You need to control browser launch, connection, reuse, or cleanup. More configuration to own. Follow current Jest docs closely because environment APIs can change.

Neither option is universally better. Choose based on your project’s existing Jest setup and how much control you need over the browser lifecycle.

2. Quick setup with the jest-puppeteer preset

Install and configure

Install the integration as a development dependency. The preset’s example installation also includes Jest and Puppeteer; check its current README for supported package combinations.

npm install --save-dev jest puppeteer jest-puppeteer

Add the preset to the Jest configuration. For a CommonJS project, create jest.config.cjs:

module.exports = {
  preset: 'jest-puppeteer',
  testMatch: ['<rootDir>/**/*.e2e.test.js'],
};

Then add a script in package.json:

{
  "scripts": {
    "test:e2e": "jest --config jest.config.cjs"
  }
}

The preset provides browser and page access to tests. Keep browser tests identifiable with a separate filename pattern or Jest project if you also have unit tests that should not launch a browser.

Write a screenshot test

Assuming the app is already running at http://127.0.0.1:3000, a minimal test is:

const fs = require('node:fs/promises');
const path = require('node:path');

beforeAll(async () => {
  await fs.mkdir(path.join(__dirname, 'artifacts'), { recursive: true });
  await page.setViewport({ width: 1365, height: 900, deviceScaleFactor: 1 });
  await page.goto('http://127.0.0.1:3000', { waitUntil: 'networkidle2' });
});

test('captures the home page', async () => {
  await page.screenshot({
    path: path.join(__dirname, 'artifacts', 'home.png'),
    fullPage: true,
    type: 'png',
  });
});

This illustrates the pattern; adapt page access to your integration and wait for your app’s actual ready condition. A network-idle event is not always a reliable readiness signal, especially for pages with polling, analytics, or long-lived connections.

3. Custom browser lifecycle when you need more control

Jest supports a global setup module, a global teardown module, and a test environment. The broad lifecycle is: launch Puppeteer once in global setup, make the browser WebSocket endpoint available to test environments, connect each environment to that browser, and close the browser in global teardown. Jest’s current Puppeteer guide provides implementation examples; use those current examples rather than copying older snippets wholesale. See Jest’s current guide.

Configure the three hooks in Jest, for example:

module.exports = {
  globalSetup: '<rootDir>/test/puppeteer-setup.cjs',
  globalTeardown: '<rootDir>/test/puppeteer-teardown.cjs',
  testEnvironment: '<rootDir>/test/puppeteer-environment.cjs',
  testMatch: ['<rootDir>/**/*.e2e.test.js'],
};

The environment should expose a page to test files and close its page/browser connection in its own teardown. Global setup and teardown run outside ordinary test-file globals; a value assigned in global setup is not automatically available inside test suites. The endpoint handoff and environment implementation are the parts that require careful alignment with the current Jest environment API.

Use custom wiring if you need to reuse a browser across suites, apply custom launch options, or integrate browser startup with other test infrastructure. For ordinary screenshot checks, start with the preset and move to custom lifecycle code only when the preset’s controls do not fit.

4. Make screenshots repeatable

A screenshot test only says something useful when the page state is controlled. A consistent test should control:

  • Viewport and scale: set width, height, and device scale factor explicitly.
  • Application state: use stable fixtures or seeded data; avoid relying on changing production content.
  • Readiness: wait for a meaningful selector or application signal rather than assuming navigation means rendering is finished.
  • Fonts and images: wait for web fonts and relevant images before capture when they affect the expected output.
  • Animation: disable or finish transitions if the test compares pixels.
  • Runtime: keep browser and dependency versions consistent between local development and CI.
  • Scope: decide whether the assertion covers the viewport, the whole document, or one component.

For example, wait for an application-owned ready marker, then await fonts and image decoding:

await page.goto('http://127.0.0.1:3000', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-test="app-ready"]');
await page.evaluate(async () => {
  if (document.fonts?.ready) await document.fonts.ready;
  await Promise.all(
    [...document.images].map(image => {
      if (image.complete) return Promise.resolve();
      return new Promise(resolve => {
        image.addEventListener('load', resolve, { once: true });
        image.addEventListener('error', resolve, { once: true });
      });
    }),
  );
});

Here, the app-ready selector is an example; expose a readiness signal that matches your application. Resolving on image errors prevents a broken asset from hanging the test, but decide separately whether missing images should fail the test. Waiting for resources improves consistency; it does not guarantee pixel-identical output across operating systems, fonts, browser builds, or GPU environments.

5. Capture a page, full page, or element

Viewport and full-page captures

page.screenshot() captures the page. Set fullPage: true when the screenshot should include content beyond the current viewport. Without it, the capture reflects the viewport. Full-page capture can produce tall files and may expose lazy-loading or sticky-header behavior that differs from an ordinary viewport capture.

await page.screenshot({ path: 'artifacts/viewport.png', type: 'png' });
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true, type: 'png' });

Capture one element

Use ElementHandle.screenshot() to capture a selected component. Puppeteer can scroll an off-screen element into view before taking its screenshot.

const card = await page.waitForSelector('[data-test="pricing-card"]');
if (!card) throw new Error('Pricing card was not found');
await card.screenshot({ path: 'artifacts/pricing-card.png', type: 'png' });

Useful screenshot options

Option What it controls When it helps
path Writes the screenshot to a file. Use a deterministic artifact path; create its directory first.
type Image format such as PNG, JPEG, or WebP where supported. PNG is useful for crisp visual comparison; lossy formats reduce file size.
fullPage Captures the full document rather than only the viewport. Use for page-length documentation or layout checks.
clip Captures a specified rectangular region. Use when a fixed region matters independently of a DOM element.
omitBackground Omits the default background in supported formats. Useful when transparency is part of the expected output.
captureBeyondViewport Controls capture outside the viewport in relevant cases. Use when working with clipping or viewport bounds; check the API details for version-specific behavior.

See the Puppeteer screenshot options reference for exact option behavior in your installed version. Page screenshots, clipping, and full-page screenshots are different capture scopes; choose intentionally instead of treating them as interchangeable.

6. Compare screenshots without making the test flaky

Saving a screenshot is not itself a visual assertion. You can retain it as a CI artifact for review, compare it to a committed baseline with a visual comparison tool, or assert non-visual page properties alongside it. If you add image-diff tooling, define an acceptable pixel threshold deliberately and review changed baselines rather than automatically accepting every difference.

  • Keep baseline images tied to the browser and operating environment used to generate them.
  • Use deterministic test data and disable motion when motion is not under test.
  • Ignore or mask genuinely variable regions such as timestamps only when they are outside the purpose of the test.
  • Prefer element captures for component-level visual tests; use full-page images when document layout is what matters.
  • Store screenshots as CI artifacts on failure so you can inspect the page the browser actually rendered.

These practices improve repeatability, but they cannot guarantee identical pixels across different machines. Browser version, operating system, font availability, rendering stack, and page state can all affect output.

7. Run screenshot tests in CI

  1. Install project dependencies from the lockfile so Jest, Puppeteer, and the preset resolve consistently.
  2. Make sure the application server is listening before the test navigates to it. Start it in the CI job or in a managed test setup hook.
  3. Use an explicit local URL and a readiness check; avoid arbitrary short sleeps as the only synchronization.
  4. Ensure the CI environment can launch the browser required by your Puppeteer package. If launch fails, check the package’s current installation and platform guidance.
  5. Write artifacts into a created directory and upload them from CI when tests fail.
  6. Close browser resources even after errors so a failed suite does not leave processes running.

Use a reasonable Jest timeout for browser navigation and rendering, but do not use a larger timeout to conceal a page that never reaches its expected state. Browser launch and navigation duration depend on the project and CI environment; no universal timing target is implied here.

8. Troubleshooting

Symptom Likely cause Fix
page is not defined The preset is not enabled, or the test assumes globals that its integration does not expose. Confirm preset: 'jest-puppeteer' and use the documented access pattern for the installed integration. For custom setup, expose the page from the test environment.
Browser executable or launch error Puppeteer’s browser is unavailable, dependencies are missing, or the environment blocks launch. Follow the installed Puppeteer package’s current browser installation and platform instructions; check the CI image and launch configuration.
Navigation timeout The page is waiting for a load condition that never occurs, or the app server is unreachable. Confirm the server URL is reachable. Choose a navigation condition suitable for the page and wait for a specific application-ready selector when network activity continues.
Screenshot is blank or incomplete The capture happened before rendering or required data arrived. Wait for an app-owned ready signal, required selector, fonts, and relevant image loads before capturing.
Screenshot differs between runs Uncontrolled data, viewport, animation, font, browser, or runtime differences. Stabilize those inputs and keep local/CI browser versions consistent. Capture the failing output to identify the variable region.
Output path error The directory does not exist or the process cannot write to it. Create the directory before capture and use a writable workspace path.
Jest hangs after a test A page, browser, server, or other handle was not closed. Close resources in teardown hooks and ensure cleanup runs when a test fails.
Coverage omits code used through Puppeteer evaluation Jest documents that coverage for test files is currently not possible when Puppeteer’s page.$eval, page.$$eval, or page.evaluate executes the passed function outside Jest’s scope. Keep this limitation scoped to those calls; do not assume it applies to every browser coverage workflow. See the caveat in the Jest Puppeteer guide.

9. Performance, reliability, and cost

Browser screenshot tests are integration tests: they launch or connect to a real browser, load your app, and render pixels. Keep the suite focused on important flows and components so browser startup and rendering do not dominate ordinary unit-test runs. Reusing a browser can reduce repeated startup work, but shared browser state can also make tests interfere with each other; isolate pages and reset application state.

For reliability, prefer explicit readiness conditions and cleanup over fixed delays. For cost, the main expense is usually CI time and the maintenance effort of visual baselines; avoid capturing every route on every change unless those captures answer a real regression question. No benchmark or runtime guarantee is implied: measure your own suite in its actual CI environment.

10. Or skip the browser setup

If you only need a screenshot from a URL and do not need a Jest assertion around your own app’s browser interaction, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF output. The API supports options such as full-page capture, element selectors, viewport/device settings, custom CSS and JavaScript, waits, and request blocking. See the ScreenshotNeo API documentation for parameters.

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}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

For a script using top-level await, run it as an ES module. The API also has cURL and Python examples in the documentation. ScreenshotNeo accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can be turned off. Bot checks, 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 for screenshots, page information, and PDF capture. Free includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card required.

11. Frequently asked questions

Can I use Jest’s default jsdom environment for Puppeteer?

Puppeteer drives a separate browser, so tests that need its browser and page should use the Puppeteer preset or a Puppeteer-aware test environment. Jest’s jsdom environment is a browser-like DOM implementation, not the launched Puppeteer page.

Should every screenshot test use fullPage: true?

No. Use full-page capture when content beyond the viewport is part of the test. Use a viewport screenshot for above-the-fold behavior and an element screenshot for a component.

Does a saved screenshot prove the page is correct?

No. It records the rendered output. Add a visual comparison or assertions that reflect the behavior you want to protect.

Will screenshots be pixel-identical on every developer machine?

Not necessarily. Control the browser, operating system, fonts, viewport, and page state to reduce differences, but rendering environments can still produce variation.