ScreenshotNeo

BlogHow-to

How to Capture a Scrolling Web Page with Chromium Headless

Learn when Chromium’s headless CLI is enough, how to capture a full page with Puppeteer, and how to control capture with the DevTools Protocol.

By the ScreenshotNeo team4 October 20267 min read

For a full scrolling-page screenshot, use Puppeteer’s page.screenshot({ fullPage: true }). Chromium’s documented --headless --screenshot command captures a screenshot at the selected window size, but its CLI reference does not describe it as a full-page capture. Use the CLI when the visible viewport is enough; use Puppeteer for a straightforward full-page capture, or the Chrome DevTools Protocol (CDP) when you need lower-level control. Chrome Headless CLI reference, Puppeteer screenshot options, CDP Page domain.

1. Choose the capture method

Method Use it when What it captures
Chrome headless CLI You need a fixed-size viewport image or a simple command-line capture. A screenshot at the configured window dimensions. The CLI reference does not document a full scrolling-page option.
Puppeteer You need a scripted full-page screenshot with a high-level API. Set fullPage: true in screenshot options.
Direct CDP You need protocol-level capture controls or are integrating directly with Chromium. Page.captureScreenshot includes captureBeyondViewport; layout metrics are available through Page.getLayoutMetrics.

2. Capture a viewport with Chromium’s headless CLI

This is the documented CLI pattern for a viewport screenshot:

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

Chrome saves screenshot.png in the current working directory. Change 412,892 to the viewport width and height you want. The window size does not turn the capture into a full-page screenshot: a tall page can continue below the captured area.

Settle time-dependent content

The CLI also documents --timeout and --virtual-time-budget. These affect when the capture happens, not how much of the page is captured:

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

--timeout sets a maximum wait. --virtual-time-budget advances time-dependent scripts as though the specified amount of time passed. Select values based on the page you are capturing; neither option guarantees that application data, images, or content triggered by scrolling has finished rendering.

3. Capture the full page with Puppeteer

Install Puppeteer in your Node.js project, then save this as an ES module, for example capture.mjs. The example navigates to a page, waits for the networkidle2 navigation condition, captures the full page, and closes the browser even if an operation fails.

import puppeteer from 'puppeteer';

const url = process.argv[2] ?? 'https://example.com/';
const browser = await puppeteer.launch({ headless: true });

try {
  const page = await browser.newPage();
  await page.goto(url, {
    waitUntil: 'networkidle2',
    timeout: 60_000,
  });

  await page.screenshot({
    path: 'page.png',
    fullPage: true,
  });
} finally {
  await browser.close();
}

Run it with node capture.mjs https://example.com/. The package must be installed and its browser available in the environment. Puppeteer documents fullPage as capturing the full page. Its screenshot options also include captureBeyondViewport, but for the usual full-page goal, start with fullPage: true.

Choose a readiness condition deliberately

  • waitUntil: 'networkidle2' is a possible navigation wait condition, not proof that every page has finished rendering. Pages with polling or long-lived network connections may never become idle, while application content may appear after network activity has stopped.
  • If a known element signals readiness, wait for it explicitly with page.waitForSelector('YOUR_SELECTOR') before the screenshot. Replace the placeholder with a selector that exists on the target page.
  • For a page that animates or loads content after navigation, use a deliberate delay only when you know it is needed. A delay does not guarantee that all content is complete.
  • Lazy-loaded images or sections triggered by scrolling may not be loaded just because navigation completed. Verify the output on the target page. Puppeteer’s full-page option describes the capture extent; it does not establish universal behavior for every site’s lazy-loading logic.

4. Use CDP for lower-level screenshot control

The Chrome DevTools Protocol exposes Page.captureScreenshot and its captureBeyondViewport parameter. This is useful when you are already speaking CDP or need protocol-level control. For a new implementation, Puppeteer is usually simpler because it provides browser lifecycle, navigation, and screenshot APIs together.

At a high level, a direct CDP workflow is:

  1. Launch a Chromium build with remote debugging enabled and connect a CDP client.
  2. Attach to a page target and enable the Page domain.
  3. Inspect layout dimensions with Page.getLayoutMetrics if your capture logic needs them.
  4. Call Page.captureScreenshot with the desired format and captureBeyondViewport setting, then decode its base64 image data.

CDP is a protocol, not a stable cross-version promise: the cited reference is tip-of-tree and warns that protocol details may change without backward-compatibility guarantees. Check the protocol supported by the Chromium build you deploy. The CDP reference documents the method and parameter; it does not make one invocation a universal recipe for every Chromium version or page.

5. Verify full-page completeness

A screenshot can be technically full-height and still omit content that the page has not loaded. When completeness matters, make a small verification run against the actual target and Chromium build.

  • Check that the bottom of the document appears in the image.
  • Check images and content that normally load only after scrolling.
  • Check sticky headers, fixed elements, and content inside nested scrolling regions.
  • Check the captured page at the intended viewport width; responsive layouts can change page height and content.
  • Repeat after changing readiness logic or browser versions.

There is no single guarantee for every operating system, Chromium version, iframe, nested scroll container, or site-specific lazy-loading implementation in the cited documentation.

6. Troubleshooting

Symptom Likely cause What to try
Only the visible area is in the image. The capture used the CLI viewport screenshot, or Puppeteer did not use full-page options. For Puppeteer, pass fullPage: true. The documented CLI command is a viewport capture pattern, not a documented scrolling-page command.
The screenshot is blank or shows an incomplete page. Capture happened before the page rendered or its required content became available. Wait for a meaningful selector, review the navigation wait condition, and inspect the page at capture time.
Navigation times out on a page that appears usable. The chosen navigation condition may not resolve for a page with persistent network activity. Try a less strict navigation condition, then wait for a page-specific selector that signals the content you need.
Some images or sections are missing. They may load only when scrolled into view, or after a site-specific event. Check whether the page needs scrolling or additional readiness logic before capture. Verify the result; full-page capture alone does not establish that lazy content was loaded.
CLI says the Chrome command is unavailable. The executable is not on PATH, or its installed name differs. Use the installed Chromium or Chrome executable path for your system and confirm that the process can launch in headless mode.
CDP command or parameter is rejected. The Chromium build may implement a different protocol version. Check the protocol supported by that build and its documentation; the tip-of-tree reference can change.
The output file is not where expected. Relative paths resolve from the process working directory. Use an explicit output path and ensure the process has permission to write there.

7. Performance, reliability, and cost

Full-page images can be much taller and larger than viewport images, so capture, memory use, and output size can grow with document dimensions. Keep the viewport at the smallest width that represents the layout you need, avoid unnecessary waits, and choose PNG or another suitable output format based on your downstream use. The cited references do not provide universal performance numbers; measure against your pages and deployment environment.

For reliability, close browser instances in a finally block, set a navigation timeout, and record which URL and readiness condition were used when a capture fails. Long-running or resource-heavy pages may need operational limits in your own service. Browser automation has no per-shot API price in this example, but it does require you to provide and maintain the runtime and browser environment.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request can return an image or PDF; its full-page capture option loads lazy images. Cookie banners are accepted and removed before the shot, along with known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status.

Here is a cURL request:

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

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
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}`);

See the ScreenshotNeo API documentation for parameters and configuration. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

How do I take a full-page screenshot in headless Chrome?

Use Puppeteer’s page.screenshot({ fullPage: true }). The documented Chrome CLI screenshot command is for a window-sized capture.

Why does --screenshot only capture the visible area?

The CLI reference documents the screenshot flag and window dimensions, but not a full scrolling-page capture option. Use a browser automation API or CDP for full-page capture.

Does network idle mean all lazy content is ready?

No. Network idleness is a navigation wait condition. Content that depends on scrolling or page-specific events may need separate handling and verification.

Should I use Puppeteer or CDP?

Use Puppeteer for a high-level full-page workflow. Use direct CDP when you need lower-level protocol control and can account for version differences.