ScreenshotNeo

BlogHow-to

How to Take Screenshots with Headless Chrome

Capture a page with Chrome’s headless CLI or Puppeteer. Learn viewport sizing, full-page capture, timing, output options, and how to fix common failures.

By the ScreenshotNeo team4 October 20267 min read

Quick answer: For a one-off screenshot, run chrome --headless --screenshot --window-size=412,892 https://example.com/. Chrome writes screenshot.png to the current directory. For repeatable automation, use Puppeteer: navigate to the page, wait for the condition that fits its loading behavior, then call page.screenshot(). Chrome’s Headless CLI reference and the Puppeteer screenshots guide document these workflows.

1. Take a screenshot with Chrome’s headless command line

Install Chrome or Chromium and make sure its executable is available as chrome in your shell. Then run:

chrome --headless --screenshot --window-size=412,892 https://example.com/

The output is screenshot.png in the command’s current working directory. The window size sets the browser viewport in pixels; the page’s responsive layout will depend on those dimensions.

If your browser executable has another name or location, substitute its path, for example chromium or /path/to/chrome. Check the installed browser’s supported flags with its help output; flags can differ by version and packaging.

Bound how long Chrome waits

chrome --headless --timeout=10000 --screenshot --window-size=1280,800 https://example.com/

--timeout sets the maximum wait in milliseconds before screenshot capture. It bounds the wait; it does not guarantee that every asynchronous widget, image, or client-rendered section has finished.

For pages whose content depends on timers, Chrome’s CLI reference also documents --virtual-time-budget. For example:

chrome --headless --virtual-time-budget=42000 --screenshot https://example.com/

Choose the budget based on how the page works. Advancing virtual time can help with timer-driven content, but it is not a universal substitute for checking that the page reached the state you need.

chrome --headless --print-to-pdf=page.pdf --no-pdf-header-footer https://example.com/

This creates a PDF, not a PNG screenshot. Use --no-pdf-header-footer when you want to omit Chrome’s print header and footer.

2. Automate captures with Puppeteer

Puppeteer is useful when you need repeatable captures, control over the viewport, or logic around navigation and output. Install it in a Node.js project:

npm install puppeteer

Save this as screenshot.mjs and run it with node screenshot.mjs:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 1 });
  await page.goto('https://example.com/', { waitUntil: 'networkidle2', timeout: 30000 });
  await page.screenshot({ path: 'page.png' });
} finally {
  await browser.close();
}

The Puppeteer guide demonstrates waiting for networkidle2 during navigation. Pick a wait condition that matches the site: persistent connections can make network-idle waits unsuitable, while a short arbitrary delay can capture too early. If navigation reaches the timeout, inspect the page and choose an appropriate wait strategy rather than assuming that a longer timeout guarantees a complete render.

3. Choose viewport, full-page, or element capture

Set a consistent viewport

Set the viewport before navigation and keep it fixed when comparing runs. A viewport controls responsive breakpoints and the visible area; it is not the same as the full document height. In Puppeteer, use page.setViewport({ width, height, deviceScaleFactor }). In the CLI, use --window-size=WIDTH,HEIGHT.

Capture the entire page

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

fullPage is false by default. Set it to true when you need a capture beyond the initial viewport. Long pages can produce large images and take longer to render and save. Lazy-loaded images may need scrolling or other page-specific handling before capture; confirm that content below the fold has loaded.

Capture a selected element

const element = await page.$('main article');
if (!element) throw new Error('Could not find main article');
await element.screenshot({ path: 'article.png' });

Replace the selector with one that identifies the target element. If the element is missing, hidden, or outside the expected state, fix the selector or wait for the relevant content before capturing.

Capture a clipped region

await page.screenshot({
  path: 'region.png',
  clip: { x: 40, y: 80, width: 640, height: 360 }
});

clip defines a rectangle in page coordinates. Ensure its width and height are positive and that the region corresponds to the content you intend to save.

4. Select screenshot output options

Puppeteer’s ScreenshotOptions API documents these useful options:

Option What it controls Notes
path File destination A relative path is resolved from the current working directory.
type Image format PNG is the default. Use a supported format such as JPEG or WebP when appropriate.
quality Lossy image quality Applies to formats other than PNG. Choose a value supported by your Puppeteer version.
fullPage Full document capture Defaults to false.
clip Rectangle to capture Useful for a specific region of the page.
captureBeyondViewport Capture outside the viewport See the API and test behavior with your page and Puppeteer version.
omitBackground Background handling Can omit the default background for supported transparent output workflows.
await page.screenshot({
  path: 'page.webp',
  type: 'webp',
  quality: 80,
  fullPage: true
});

Use PNG for lossless output or when transparency matters. Lossy formats can reduce file size, with image quality depending on the chosen setting and page content. Verify format support in the installed Puppeteer version.

5. Make captures repeatable

  1. Fix the browser and viewport. Record the browser/Puppeteer version and set explicit width, height, and device scale factor.
  2. Use a stable target state. Navigate to a deterministic URL and choose a wait condition based on how its content loads.
  3. Keep output paths explicit. Write to a known directory and ensure the process has permission to write there.
  4. Check the artifact. Confirm the file exists, has nonzero size, and can be opened. For visual comparisons, keep the same viewport and capture timing.
  5. Close browser processes. Use a finally block so failures do not leave Chromium running.

Web pages can change over time because their content, ads, experiments, and network responses change. A consistent script reduces variation but cannot make a changing remote page identical on every run.

6. Troubleshoot common capture failures

Symptom Likely cause Fix
chrome: command not found Chrome is not installed or its executable is not on PATH. Install a browser or invoke its full executable path; check the package’s executable name.
Screenshot is blank or missing content Capture happened before client-rendered or delayed content appeared. Wait for a page-specific selector or state; review navigation timing and timeout behavior.
Navigation times out The page is slow, unreachable, or never satisfies the selected wait condition. Check network access and URL, then select a suitable wait condition and a reasonable timeout. Persistent connections can defeat network-idle waits.
Output file is not where expected Relative paths and CLI output use the current working directory. Check the process working directory or provide an explicit output path supported by the command/API.
Element capture fails The selector matched nothing or the element was not ready. Validate the selector and wait for the target element before calling its screenshot method.
Image dimensions differ from expectation Viewport size, full-page behavior, or device scale factor changed. Set viewport dimensions and scale factor explicitly; distinguish viewport capture from full-page capture.
PDF has headers or footers Print decorations are enabled. Use --no-pdf-header-footer with the CLI workflow.
Browser process remains after an error The script exited before closing the browser. Close it in a finally block, as in the runnable example.

7. Performance, reliability, and cost

Headless Chrome still has to launch a browser, load the page, render it, and write the image. Reusing a browser for multiple captures can avoid repeated startup work, while each page should still be managed and closed deliberately. Full-page screenshots and large viewports can require more memory and produce larger files than viewport captures. Choosing JPEG or WebP can reduce output size when lossless PNG is unnecessary.

For reliable automation, use explicit dimensions, deliberate readiness checks, bounded navigation timeouts, and cleanup in all success and error paths. A timeout limits how long a job waits; it cannot make a failed network request succeed or ensure that delayed content is present. Local browser automation has no per-shot API charge, but it does require you to operate the browser runtime and handle its resource use, failures, and updates.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. One GET request returns an image or PDF, and its parameter names also work with those used by other screenshot APIs. See the ScreenshotNeo API documentation.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
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}`);
await Bun.write('shot.webp', res);

Replace YOUR_API_KEY with your key. The Node.js example uses Bun’s file writer; with Node.js, save the response body with Buffer.from(await res.arrayBuffer()) and writeFile from node:fs/promises. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

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

9. FAQ

Does headless Chrome capture the full page by default?

No. The CLI example captures the page at its selected window size. In Puppeteer, set fullPage: true for a full-page image.

Does a screenshot timeout mean all page content is ready?

No. A timeout bounds waiting. Readiness depends on the page’s loading behavior and the condition your script uses.

Is --print-to-pdf a screenshot option?

It is a separate output mode that creates a PDF rather than an image.

Where does Chrome save the default screenshot?

In the current working directory, under the name screenshot.png.