Compare Puppeteer and Playwright for Website Screenshots in Node.js
Compare Puppeteer and Playwright for Node.js screenshots, with runnable full-page and element capture examples, visual testing guidance, and fixes for common issues.
Short answer: Puppeteer and Playwright both take full-page and element screenshots from Node.js. For a one-off capture script, either is suitable; choose based on your existing project and the browser engines you need. If screenshots are part of a visual regression suite and you want Playwright Test’s built-in screenshot assertions, Playwright is the more direct fit. Keep the browser and rendering environment consistent so baseline differences are less likely to come from the machine instead of your site.
For managed captures without installing and maintaining a browser, ScreenshotNeo is the alternative to try first: it removes consent banners, popups, and chat widgets before capture, and only clean screenshots are billed.
At a glance
| Need | Use | Why |
|---|---|---|
| Simple Node.js screenshot | Either | Both expose a direct page screenshot API. |
| Full-page screenshot | Either | Both support fullPage: true. |
| Screenshot one element | Either | Puppeteer has element-handle screenshots; Playwright has locator screenshots. |
| Visual assertions in an existing Playwright Test suite | Playwright | toHaveScreenshot() provides a documented baseline and comparison workflow. |
| Chrome and Firefox automation in a Puppeteer stack | Puppeteer | Chrome for Developers documents Puppeteer automation for Chrome and Firefox. Check the exact supported setup and versions you deploy. |
| Chromium, Firefox, and WebKit projects | Playwright | Playwright documents these browser engines; verify your required browser versions. |
This is a workflow comparison, not a speed ranking: the official material reviewed does not establish a relevant performance benchmark or cost difference.
Take a full-page screenshot with Puppeteer
Install Puppeteer in your project, then save a full-page PNG. The example uses CommonJS and a public example URL; replace it with the page you control or are authorized to capture.
npm install puppeteer
// screenshot-puppeteer.cjs
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 30000,
});
await page.screenshot({ path: 'page-puppeteer.png', fullPage: true });
} finally {
await browser.close();
}
})();
Run it with node screenshot-puppeteer.cjs. The try/finally ensures the browser is closed even if navigation or capture fails. Puppeteer documents the page screenshot API and full-page option in its screenshots guide.
Capture one element with Puppeteer
const selector = '.hero';
await page.waitForSelector(selector, { visible: true, timeout: 10000 });
const element = await page.$(selector);
if (!element) throw new Error(`No element found for ${selector}`);
await element.screenshot({ path: 'hero-puppeteer.png' });
Use a selector that identifies exactly the intended region. Element screenshots capture that element’s rendered bounds; they are different from clipping an arbitrary rectangle from the page.
Take a full-page screenshot with Playwright
Install Playwright and its browser binaries. The package’s browser installation step is needed in a fresh environment; see the official Playwright installation guide for current commands.
npm init -y
npm install playwright
npx playwright install chromium
// screenshot-playwright.cjs
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1,
});
const page = await context.newPage();
await page.goto('https://example.com', {
waitUntil: 'load',
timeout: 30000,
});
await page.screenshot({ path: 'page-playwright.png', fullPage: true });
} finally {
await browser.close();
}
})();
Run it with node screenshot-playwright.cjs. Playwright documents page screenshots, full-page capture, and returned image bytes in its screenshots guide and Page API.
Capture one element with Playwright
const hero = page.locator('.hero');
await hero.waitFor({ state: 'visible', timeout: 10000 });
await hero.screenshot({ path: 'hero-playwright.png' });
Locators resolve against the page when used, which fits Playwright’s locator-based automation workflow. If the selector matches several elements, narrow it to the one intended for capture.
Choose based on the job
Choose Puppeteer when
- Your project already uses Puppeteer and needs a direct page or element capture.
- The browser targets and versions in your deployment fit your Puppeteer setup.
- You want its documented screenshot controls, such as output path, clipping, transparent background, quality, or image type. Check the installed version’s option names and constraints in the ScreenshotOptions reference.
Choose Playwright when
- Your suite already uses Playwright Test and the
expect(page).toHaveScreenshot()workflow is useful. - You need to run against the browser engines supported by your Playwright setup, including Chromium, Firefox, and WebKit as documented by Playwright.
- You want locator screenshots or its documented screenshot controls, including full-page capture, clipping, masking, and output behavior. Consult the Page API for the exact version you install.
Neither choice automatically produces a stable image. Browser version, operating system, settings, hardware, power source, and headless mode can change rendering. The browser APIs and options also evolve, so treat examples as a starting point and check the documentation matching your installed version.
Make screenshot output reproducible
- Pin the execution environment. Generate and compare baselines on the same operating system image, browser build, settings, and headless configuration. Playwright’s visual comparisons guide identifies environment differences as a source of screenshot variation.
- Set viewport and scale explicitly. Use a fixed viewport and device scale factor. Keep these values the same when capturing and comparing.
- Wait for the state you need. A navigation event alone may not mean client-side rendering, images, or fonts are ready. Wait for a meaningful selector or application-ready signal. Use network-idle waits only when they suit the page; analytics or long-lived requests can keep a page busy.
- Control dynamic content. Timestamps, rotating ads, animations, and personalized content can change pixels between runs. Playwright documents screenshot styling and masking options for volatile regions. Hide or mask content only when it is outside what the test is intended to validate.
- Use stable test data and page state. Keep locale, authentication, cookies, timezone, and data fixtures consistent where those affect rendering.
- Review diffs at the intended threshold. A visual diff is evidence of a pixel change, not a diagnosis. Inspect whether it is a product regression or an expected environment/content variation before updating a baseline.
Visual baseline assertion with Playwright Test
Install the test package and browser as described by the current Playwright setup guide, then use an assertion such as:
const { test, expect } = require('@playwright/test');
test('home page visual baseline', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('home.png', { fullPage: true });
});
On the first run, Playwright can create a reference image; later runs compare captures with that reference. Review Playwright’s visual comparisons documentation for baseline update and comparison behavior. The test runner is an additional workflow choice: a Puppeteer capture can also be paired with a separate image comparison tool.
Screenshot options and edge cases
| Requirement | What to consider |
|---|---|
| Full document | Set fullPage: true. This may produce a very tall image and take longer or consume more memory than a viewport capture. |
| Specific rectangle | Use a clip rectangle when you need fixed page coordinates. For content-driven bounds, capture an element instead. Check the installed API’s clipping coordinate and validation rules. |
| Transparent output | Puppeteer documents omitBackground for transparency. Confirm output format support and alpha handling in the installed version and image viewer. |
| JPEG or WebP / quality | Puppeteer documents type and quality options. Quality applies to lossy formats; the exact valid range and format constraints are version-specific. Playwright also has screenshot format controls in its Page API. |
| Mask or hide dynamic areas | Playwright provides masking and screenshot styling controls. For Puppeteer, hide unstable elements with page-side CSS or JavaScript when appropriate, and ensure the change does not mask behavior the test should catch. |
| Lazy-loaded content | Full-page mode is not a guarantee that every site’s lazy content has loaded. Scroll through the page or trigger the application’s loading behavior before capturing, then wait for expected content. |
| Very long pages | Large images increase memory, disk, and diff costs. Prefer element or viewport shots if the requirement does not need the entire document. |
| Authenticated pages | Establish the same session, cookies, and permissions before navigation. Avoid storing credentials in source files or baselines. |
Or skip the browser setup
ScreenshotNeo takes a screenshot through one HTTP request. It returns PNG, JPEG, WebP, or PDF, and its API accepts the parameter names other screenshot APIs use, which can make switching easier. See the ScreenshotNeo API documentation for request options.
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await require('node:fs/promises').writeFile('shot.webp', image);
For the same capture in cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
And Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
- Cookie banners and consent notices, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses identify the page verdict and billing status in headers.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for AI agents and MCP clients. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; all features are on every plan.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser launch fails in CI | Browser binary or system dependencies are missing, or the environment cannot launch the configured browser. | Install the browser required by the package version in your build image; check the official install guide and launch error for missing dependencies. |
| Navigation times out | The page is slow, a request never settles, or the chosen network-idle condition does not occur. | Set an intentional timeout and wait for a meaningful page selector or load state. Do not raise the timeout without checking what the page is waiting on. |
| Screenshot is blank or incomplete | Capture ran before client rendering or content loading completed, or the page failed to load. | Check navigation errors, wait for an app-ready selector, and verify expected text or element presence before capture. |
| Full-page image misses lazy content | Content is loaded only after scrolling or interacting. | Scroll the document in steps, wait for each relevant region, and then capture. Confirm the page has reached its expected bottom. |
| Element screenshot throws or is empty | The selector matched no visible element, was ambiguous, or the element was outside the expected state. | Wait for the selector to be visible, assert it exists, and narrow the selector to one target. |
| Visual test fails with small pixel changes | Rendering environment or dynamic content changed. | Compare browser, OS, settings, viewport, scale, fonts, and page data; mask genuinely irrelevant volatile regions and regenerate baselines only after review. |
| Different output type or quality than expected | An option may not be supported with that format or may have changed between versions. | Check the options reference for the installed package and inspect the actual output file format and dimensions. |
Performance, reliability, and cost
For both libraries, capture time depends on browser startup, page loading, scripts, assets, image dimensions, and the wait condition. Reuse browser processes for batches where appropriate rather than launching a fresh process for every page, while isolating page contexts when sessions or settings must differ. Close pages and browsers reliably, limit concurrency to available memory and CPU, and record failed navigations separately from valid screenshots.
Full-page captures and high device scale factors can create large images and consume more memory. Use a viewport or element capture when it satisfies the requirement. Pin package and browser versions for repeatability, and check each library’s current documentation for supported options and browser versions. The source material provides no comparative speed benchmark or price figure, so choose based on workflow and deployment needs rather than an assumed performance winner.
With ScreenshotNeo, the stated plans are Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free. Only clean shots are billed, and each response includes verdict and billing headers. For self-hosted Puppeteer or Playwright, budget for your own compute, browser maintenance, and storage; the exact cost depends on your infrastructure.
FAQ
Are Puppeteer and Playwright both suitable for a single screenshot?
Yes. Both provide direct page screenshot APIs in Node.js. Use the one already in your project unless a browser target or workflow requirement points elsewhere.
Does Puppeteer have visual regression assertions like Playwright Test?
The reviewed Puppeteer documentation covers screenshot capture, not an integrated visual assertion runner. Puppeteer can be paired with another test or image-diff tool; Playwright Test documents toHaveScreenshot().
Will the same page produce identical screenshots in both tools?
Do not assume pixel identity. Browser engine, browser build, host environment, settings, and timing can affect rendering. Keep those conditions controlled when comparing outputs.
Which should I use if I need WebKit?
Playwright documents WebKit support. Confirm the browser and version requirements for the exact environment you plan to deploy.
