Puppeteer screenshot on macOS: install and configure Chromium
Install Puppeteer and its bundled Chromium on macOS, configure a browser when needed, and capture reliable screenshots with runnable examples.
Direct answer: install a current Node.js version that meets Puppeteer’s requirement, then run npm install puppeteer. Puppeteer downloads a compatible Chrome for Testing browser and headless shell when its install script runs. Launch the browser, open a page, navigate to a URL, and call page.screenshot(). On macOS, Puppeteer documents Node.js 22.12 or newer and lists the Chrome for Testing download at about 170 MB. See the official installation guide and system requirements; version requirements can change.
1. Install Node.js and Puppeteer
Use a Node.js release that satisfies the current Puppeteer system requirements. In a new project:
mkdir puppeteer-shot
cd puppeteer-shot
npm init -y
npm install puppeteer
The puppeteer package normally downloads Chrome for Testing and chrome-headless-shell during installation. Puppeteer’s default browser cache is $HOME/.cache/puppeteer. The macOS browser download is substantial: the installation guide estimates approximately 170 MB.
If your project uses another package manager, install the same package with its usual command:
yarn add puppeteer
pnpm add puppeteer
bun add puppeteer
Use a project-local install so the script and its browser dependency are reproducible with the project. You do not need to install Chrome separately for the standard bundled-browser setup.
2. Capture a screenshot with the bundled browser
Save this as screenshot.mjs. It sets a viewport, waits for navigation, writes a PNG, and closes the browser even if navigation or capture fails.
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1080, height: 1024 });
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60_000 });
await page.screenshot({ path: 'screenshot.png', fullPage: true });
console.log('Saved screenshot.png');
} finally {
await browser.close();
}
Run it with:
node screenshot.mjs https://example.com
The essential sequence is launch, create a page, set the viewport, navigate, capture, and close. Puppeteer’s getting-started guide documents the launch and page setup; the screenshot API documents capture behavior and options.
3. Choose when the page is ready
The navigation wait condition affects completeness and reliability. Select a condition that matches the site:
| Condition | Use it when | Tradeoff |
|---|---|---|
load |
The page’s load event is a reasonable readiness signal. | Late client rendering or lazy content may still be missing. |
domcontentloaded |
You want an early capture and can wait for specific content yourself. | Images, styles, and application data may not be ready. |
networkidle0 |
The page becomes fully quiet and the site does not keep connections open. | Analytics, polling, and streaming can prevent the condition from completing. |
networkidle2 |
You want a quiet page while tolerating a small number of active connections. | It still may time out on pages with persistent network activity. |
For pages with persistent traffic, wait for a meaningful selector after a less strict navigation wait:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForSelector('[data-page-ready="true"]', { timeout: 15_000 });
await page.screenshot({ path: 'ready.png', fullPage: true });
Replace the selector with an element that appears only when the content you need is rendered. If there is no reliable selector, wait for a known application state or use a short explicit delay as a last resort; fixed delays slow every capture and can still be too short.
4. Configure the screenshot
page.screenshot() accepts capture options. These are common choices for a local screenshot workflow:
| Option | What it controls | Example |
|---|---|---|
path |
Output file. The extension selects a default image type when type is omitted. |
path: 'page.png' |
type |
Image format: PNG, JPEG, or WebP where supported by the installed API/browser. | type: 'jpeg' |
quality |
Lossy image quality for JPEG or WebP; it does not apply to PNG. | quality: 80 |
fullPage |
Captures the full scrollable page rather than only the viewport. | fullPage: true |
clip |
Captures a specified rectangle of the page. | clip: { x: 0, y: 0, width: 800, height: 600 } |
omitBackground |
Allows transparency in formats that support it, such as PNG. | omitBackground: true |
captureBeyondViewport |
Controls capture outside the current viewport for applicable capture modes. | Check the installed version’s API reference. |
For stable dimensions, set the viewport before navigation or before the page performs responsive layout:
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 2 });
await page.goto(url, { waitUntil: 'load' });
await page.screenshot({ path: 'retina.png', fullPage: false });
A larger device scale factor produces more pixels and can increase memory use and file size. For JPEG output, provide a quality value and use a matching extension:
await page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 82 });
For a transparent PNG, remove the page background at capture time:
await page.screenshot({ path: 'transparent.png', omitBackground: true });
Check the current screenshot API reference for the complete option set supported by your installed Puppeteer version. Capture options and defaults are version-sensitive.
5. Install a browser separately or use a Chrome channel
The bundled browser is the simplest and most predictable choice: Puppeteer guarantees compatibility with the browser it downloads. If you need to manage the browser yourself, use puppeteer-core and provide an executable path or select a supported browser channel. The full puppeteer package can also launch a system Chrome channel.
Use a managed executable with puppeteer-core
npm install puppeteer-core
import puppeteer from 'puppeteer-core';
const executablePath = process.env.CHROME_PATH;
if (!executablePath) {
throw new Error('Set CHROME_PATH to the installed Chrome executable');
}
const browser = await puppeteer.launch({ executablePath, headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'managed-browser.png' });
} finally {
await browser.close();
}
Set CHROME_PATH to the executable that exists on your machine. Avoid copying a path from another developer’s Mac: installation location can vary by architecture, installation method, and user configuration. A separately updated browser can drift from the version Puppeteer expects; the compatibility guarantee applies to Puppeteer’s bundled browser. See the launch options and configuration interface.
Use the installed Chrome channel with puppeteer
With the full package, you can ask Puppeteer to use a locally installed stable Chrome channel:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ channel: 'chrome', headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'system-chrome.png' });
} finally {
await browser.close();
}
Use the channel name supported by your installed Puppeteer version and the browser available on the Mac. This is useful when browser version or installation is centrally managed, but you take responsibility for compatibility.
6. Control browser installation and cache configuration
If the install script did not download Chrome, run Puppeteer’s browser installer from the project:
npx puppeteer browsers install
Some package-manager configurations block dependency install scripts. Allow Puppeteer’s install script for the project, or run the browser installation command after package installation. The official installation guide documents the recovery path.
Puppeteer configuration can control the browser cache directory, default browser, executable path, and whether downloads are skipped. Use the supported configuration interface instead of relying on a path copied from another machine. If downloads are intentionally skipped, make sure a compatible browser is already available and that launch configuration points to it. Refer to Puppeteer configuration for current names and behavior.
The system requirements list unzip as necessary to unpack the Chrome archive unless the optional yauzl dependency is installed. If browser extraction fails, check that requirement and available disk space.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Could not find Chrome (ver. ...) |
The install script was skipped or the browser cache is missing. | Run npx puppeteer browsers install; check whether package-manager settings block install scripts. |
| Browser download or extraction fails | Network access, disk space, archive extraction, or required unzip support is unavailable. |
Retry with working network access, free disk space, and confirm unzip is installed or the optional yauzl dependency is available. |
Failed to launch or executable does not exist |
A configured executable path is invalid, or a browser download was skipped. | Use the bundled installation, rerun the browser installer, or set executablePath to a real browser executable on this Mac. |
| Navigation times out | The site remains active, loads slowly, or never satisfies a strict network-idle condition. | Use domcontentloaded or load, then wait for a page-specific selector. Set a considered timeout rather than waiting indefinitely. |
| Screenshot is blank or incomplete | The page was captured before client rendering, fonts, images, or lazy content finished. | Wait for a readiness selector or relevant asset, scroll lazy content into view if needed, then capture. Confirm the viewport and screenshot dimensions. |
| System Chrome works differently from bundled Chrome | The browser version is outside Puppeteer’s tested compatibility pairing. | Prefer Puppeteer’s bundled browser or align the managed browser version with the Puppeteer release. |
| Very tall full-page image is slow or large | Full-page capture and high device scale factor require more pixels and memory. | Capture only the needed region, lower the scale factor, or split a very long page into sections. |
8. Performance, reliability, and cost
Browser startup and rendering dominate a single screenshot. For a one-off local task, launch and close one browser as shown. For repeated captures in a long-running process, keep a browser process open and create a fresh page per job, then close each page; restart the browser periodically if your application needs predictable resource recovery. Do not launch an unbounded number of browsers or pages at once.
Reliability comes from choosing a browser version deliberately, using explicit navigation and readiness conditions, setting finite timeouts, and closing browser resources in finally. Sites may vary by time, geography, authentication, animation, or network conditions, so use consistent viewport, locale, timezone, and input state when comparing captures.
Local Puppeteer has no per-screenshot API fee, but each run consumes CPU, memory, storage, network bandwidth, and the time needed to maintain a browser installation. The bundled browser download is about 170 MB per the current installation documentation and is cached for reuse. A separately managed browser avoids Puppeteer’s browser download but adds version-management work.
Or skip the browser setup
If you need a screenshot without installing and maintaining Chromium on a Mac, ScreenshotNeo is a website screenshot API and MCP server for developers. It accepts one GET request with a URL and returns PNG, JPEG, WebP, or PDF. The API supports full-page capture, CSS selector element capture, viewport and device presets, retina scale, waits, custom CSS and JavaScript, cookies and headers, and more. See the ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 Bun.write('shot.webp', res);
Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets; each step can be turned off. Bot checks, blank pages, and failed loads are never billed. AI agents can use the MCP server’s take_screenshot, get_page_info, and capture_pdf tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is available on every plan.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
FAQ
Does Puppeteer install Chromium on macOS?
The current package downloads Chrome for Testing and a headless shell by default. The browser is Chromium-based; Puppeteer’s docs describe the download as Chrome for Testing.
Can I use Puppeteer with Apple silicon?
The published requirements list Chrome for Testing support on macOS arm64 as well as x64. Use a current supported Node.js version and the package-managed browser for the standard setup.
Should I choose puppeteer or puppeteer-core?
Choose puppeteer when you want Puppeteer to download its compatible browser. Choose puppeteer-core when another system manages the browser or you connect to a remote browser, and configure the connection or executable yourself.
Can I save a screenshot without writing it to disk?
The screenshot API can return image data when you omit a file path; check the installed version’s TypeScript/API definitions for the returned value and supported options.


