How to Screenshot a Website with Headless Chrome
Capture a website with Chrome’s headless CLI, then use Puppeteer or Playwright for full pages, elements, and repeatable screenshots.
For a quick viewport screenshot, run Chrome in headless mode with --screenshot and set the viewport using --window-size:
chrome --headless --screenshot --window-size=1280,900 https://example.com/
Chrome writes screenshot.png to the current working directory. Use this command for a one-off image. For repeatable jobs, full-page captures, selected elements, readiness waits, or image bytes to process in code, use Puppeteer or Playwright.
1. Choose the capture you need
| Goal | Method | Notes |
|---|---|---|
| Visible viewport, one-off | Chrome CLI | Set viewport dimensions with --window-size. |
| Repeatable scripted capture | Puppeteer or Playwright | Control navigation, waits, output path, and errors. |
| Full scrollable page | Puppeteer or Playwright | Use the full-page option; long pages can produce large images. |
| Single element | Puppeteer or Playwright | Locate the element and capture its bounds. |
| Post-process image bytes | Puppeteer or Playwright | Return the screenshot as bytes or a buffer instead of saving a file. |
Decide whether you need the viewport, full page, or one element first. Also decide if you need a file or bytes, and whether the page needs a specific readiness condition before capture.
2. One-off screenshot with Chrome CLI
chrome --headless --screenshot --window-size=1280,900 https://example.com/
Run it in the directory where you want screenshot.png. Replace 1280,900 with the viewport width and height you need. The CLI example captures the viewport; it is not the same as a full-page screenshot.
Chrome’s --timeout flag sets a maximum wait before the screenshot is captured. Treat it as a cutoff, not a signal that all page content is ready: the capture may happen while the page is still loading. If the result is inconsistent, use an automation library and wait for a page-specific condition.
3. Scripted screenshots with Puppeteer
Install Puppeteer in a Node.js project, then save this as screenshot.mjs. The example navigates, waits for network activity to settle, and writes a viewport screenshot:
import puppeteer from 'puppeteer';
const url = 'https://example.com/';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 900 },
});
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60_000,
});
await page.screenshot({ path: 'screenshot.png' });
} finally {
await browser.close();
}
networkidle2 is a useful starting condition, not a universal definition of “ready.” Pages with polling, streaming, or delayed application rendering may never become idle or may become idle before the content you care about appears. In those cases, wait for a known selector or other application-specific signal.
Full-page and element captures
Set fullPage: true to capture the full scrollable page. For one element, wait for it to appear and use its element handle’s screenshot method:
await page.screenshot({ path: 'full-page.png', fullPage: true });
const card = await page.waitForSelector('[data-testid="product-card"]');
if (!card) throw new Error('Product card was not found');
await card.screenshot({ path: 'product-card.png' });
Puppeteer screenshot options include a file path, output type, clip rectangle, full-page mode, background transparency, and quality where the chosen format supports it. Its documented fullPage default is false. For an element capture, make sure the selector identifies one visible element and that any needed content has finished rendering.
4. Scripted screenshots with Playwright
Install Playwright and its browser binaries for your environment. This Node.js example returns a PNG buffer; it also shows how to capture the full page or a selected locator:
import { chromium } from 'playwright';
import { writeFile } from 'node:fs/promises';
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 900 },
});
await page.goto('https://example.com/', {
waitUntil: 'networkidle',
timeout: 60_000,
});
const png = await page.screenshot({ type: 'png' });
await writeFile('screenshot.png', png);
await page.screenshot({ path: 'full-page.png', fullPage: true });
await page.locator('main').screenshot({ path: 'main.png' });
} finally {
await browser.close();
}
When no path is supplied, Playwright returns screenshot bytes, which can be passed to an image-processing library or uploaded to storage. The full-page and locator calls save separate files in this example.
5. Readiness, dimensions, and output choices
Wait for the right condition
- Use a navigation wait condition for the document’s lifecycle or network activity.
- For content rendered after navigation, wait for a selector that represents the content you need.
- Use a fixed delay only when the page offers no better readiness signal; delays make jobs slower and can still be too short or unnecessarily long.
- For CLI captures, remember that
--timeoutis only a maximum wait before capture.
Viewport, full page, element, and clip
- Viewport: captures what fits in the configured viewport.
- Full page: captures the scrollable page and can create a tall image. Lazy-loaded content may require scrolling or another explicit loading strategy before capture.
- Element: captures the selected element’s rendered bounds. A missing, hidden, or ambiguous selector can cause failure or an unexpected result.
- Clip rectangle: captures a defined region when you know the desired coordinates and dimensions.
Format, quality, and transparency
Puppeteer supports screenshot output type and quality settings where they apply. Choose PNG for lossless output or when transparency matters; use a lossy format when smaller output is more important. Quality is relevant only to formats that support it. Configure a transparent background only when the output and page background behavior support the result you want.
6. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, and the API documentation lists its parameters. For this example, save the response as a WebP file:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Cookie and consent banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, inspect page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card required.
7. Reliability, performance, and cost
- Close the browser: use a
finallyblock so failed navigation or capture does not leave a browser process running. - Set timeouts deliberately: navigation and readiness waits need limits. A timeout helps a job fail predictably; it does not make an incomplete page ready.
- Keep captures bounded: full-page images can be tall and memory-intensive. Capture only the needed page or element when possible.
- Control concurrency: each browser page and image consumes resources. Start with modest parallelism and increase it only after observing memory and completion behavior in your environment.
- Make output deterministic: set the viewport, wait for the same page condition, and use stable selectors. Dynamic content, animations, and personalized page state can change pixels between runs.
- Account for browser setup: local automation requires compatible browser binaries and runtime resources. The CLI is simple for one-off use; a script adds setup and maintenance but gives more control.
The cited Chrome, Puppeteer, and Playwright documentation describes commands and API behavior, not performance benchmarks. Runtime and resource use depend on the page, browser version, machine, and capture extent.
8. Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| No screenshot file appears | The command ran from a different working directory, Chrome was not found, or the process failed before capture. | Check the command’s working directory and Chrome executable availability; inspect the process error output. |
| The screenshot is blank or incomplete | Capture happened before the page or its client-rendered content was ready. | Wait for a relevant selector or application-specific signal before capturing; do not rely on a maximum timeout as a readiness guarantee. |
| CLI capture differs from the requested dimensions | The desired size was not passed as the viewport dimensions. | Set --window-size=WIDTH,HEIGHT and check that the dimensions are in the intended width,height order. |
| Navigation times out | The page is slow, never reaches the chosen network condition, or keeps connections open. | Choose a condition suited to the site, wait for a specific selector when appropriate, and keep a bounded timeout. |
| Element screenshot fails | The selector matched nothing, the element is hidden, or the page replaced it during capture. | Wait for the selector, confirm it is visible, and use a stable locator or selector. |
| Full-page image omits lazy content | Content only loads after scrolling into view. | Trigger the page’s loading behavior before capture, then wait for the content to appear. |
| Script exits with browser not closed | An exception occurred before cleanup. | Put browser shutdown in a finally block, as in the examples. |
| Image is larger than expected | A full-page capture includes the entire scrollable document. | Use viewport or element capture, or a clip rectangle, if you only need part of the page. |
9. FAQ
Does headless Chrome need a visible desktop?
No. The command runs Chrome without opening a visible browser window.
Does the CLI screenshot the entire page by default?
The documented CLI example is a viewport capture. Use Puppeteer or Playwright’s full-page option for the scrollable page.
Can I save a screenshot to a different filename?
The documented CLI behavior saves screenshot.png in the current working directory. Use a scripting API when you need a chosen output path or screenshot bytes.
Which method should I start with?
Use the CLI for a single viewport image. Use Puppeteer or Playwright when you need scripted waits, full-page or element capture, or output bytes for further processing.


