How to Install Puppeteer in Claude Code for Browser Screenshots
Install Puppeteer in your project Claude Code is helping with, capture browser screenshots, and fix common Chrome setup errors. Learn when an MCP server is needed.
Quick answer: Install Claude Code separately, then install Puppeteer inside the JavaScript project that will take screenshots. In that project run npm i puppeteer, save the script below as screenshot.mjs, then run node screenshot.mjs https://example.com. Puppeteer downloads a compatible Chrome for Testing browser by default. If you want Claude Code itself to control a browser through tools, configure a browser automation MCP server separately; installing the package alone does not add browser tools to Claude Code.
This guide covers both setups, a complete screenshot script, options for managing Chrome, and fixes for common installation and capture errors. For current requirements and commands, see Anthropic’s Claude Code setup guide and Puppeteer’s installation guide.
1. Understand what you are installing
There are two separate pieces:
- Claude Code is Anthropic’s coding assistant. It can work in a project directory and help write or run project code. The setup guide lists Node.js 18+ as a requirement and documents
npm install -g @anthropic-ai/claude-codeas a standard installation command. Do not usesudo npm install -g; Anthropic warns it can cause permission and security problems. - Puppeteer is a JavaScript browser automation library. Add it as a dependency of the project whose scripts need to drive a browser and take screenshots.
A project-level Puppeteer install does not automatically give Claude Code a browser-control tool. For direct tool-based browser interaction, configure a suitable MCP server in Claude Code. Anthropic documents MCP as the mechanism for adding external tools and data sources; choose a browser server only after checking its current maintainer, setup steps, and security model.
2. Install Claude Code and Puppeteer
Install Claude Code
If Claude Code is not installed, follow Anthropic’s current setup guide. Its documented npm method is:
npm install -g @anthropic-ai/claude-code
Authenticate as prompted, open a terminal in your JavaScript project, and start Claude Code there:
cd path/to/your-project
claude
Install Puppeteer in the project
In the project directory, create a package manifest if needed, then install Puppeteer:
npm init -y
npm i puppeteer
The regular puppeteer package downloads a compatible Chrome for Testing browser (and, in applicable releases, a headless shell). Puppeteer stores browser downloads in its cache by default. The download is substantial, so allow time and disk space for the first installation; subsequent runs can reuse the cached browser.
If your project does not use ES modules yet, either use the .mjs filename shown below or set "type": "module" in package.json. The .mjs extension works without changing the manifest.
3. Take a screenshot with a runnable script
Create screenshot.mjs in the project root. The script accepts a URL, sets a predictable viewport, navigates, saves a full-page PNG, and always closes the browser. Puppeteer’s screenshot API is Page.screenshot(); the documented examples also show waiting for networkidle2.
import puppeteer from 'puppeteer';
const target = process.argv[2] ?? 'https://example.com';
const output = process.argv[3] ?? 'screenshot.png';
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 1 });
await page.goto(target, {
waitUntil: 'networkidle2',
timeout: 60_000,
});
await page.screenshot({
path: output,
fullPage: true,
type: 'png',
});
console.log(`Saved ${output}`);
} finally {
await browser?.close();
}
Run it with:
node screenshot.mjs https://example.com example.png
The script is an illustrative starting point, not a guarantee that every site will finish loading under the same wait condition. Use an authorized target and adjust the readiness check to the page you are capturing.
Choose a wait condition deliberately
domcontentloadedwaits for the initial HTML document to be parsed. Use it when you have a more precise application-ready signal to wait for afterward.loadwaits for the page load event, including many dependent resources.networkidle2waits for a quiet network period under Puppeteer’s threshold. It can be useful for many mostly-static pages, but pages with polling, analytics, streaming, or long-lived requests may never become idle.
For a dynamic application, wait for a page-specific selector after navigation instead of treating network quiet as proof that rendering is complete:
await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForSelector('[data-page-ready="true"]', { timeout: 20_000 });
await page.screenshot({ path: output, fullPage: true });
Capture one element instead of the full page
Wait for a stable, unique CSS selector and use the element handle’s screenshot method:
const card = await page.waitForSelector('.report-card', { timeout: 15_000 });
if (!card) throw new Error('Report card was not found');
await card.screenshot({ path: 'report-card.png' });
Puppeteer’s element screenshot method scrolls an off-screen element into view when needed. Prefer a selector tied to your app’s stable markup rather than a position-dependent selector.
4. Configure viewport, output, and page state
| Need | Setting or method | Notes |
|---|---|---|
| Viewport dimensions | page.setViewport({ width, height }) |
Set before navigation when responsive layout depends on viewport size. |
| Retina-like output | deviceScaleFactor: 2 |
Produces more device pixels for the same CSS viewport and a larger image. |
| Whole document | fullPage: true |
Captures beyond the viewport; very long pages can use considerable memory and produce large files. |
| JPEG or WebP | type: 'jpeg' or 'webp' |
Choose a supported format and quality setting if file size matters; PNG is lossless and useful for text or pixel comparisons. |
| Transparent page | Launch with transparent background and capture a page configured without an opaque background | Transparency depends on page and browser settings; verify output in the intended viewer. |
| Page state | Interact with the page before capture | Fill forms, click controls, or set application state only as needed; wait for the resulting content to settle. |
For reproducible screenshots, keep the viewport, device scale factor, browser version, locale, timezone, fonts, and page data consistent. Animations, timestamps, rotating content, personalization, and third-party widgets can make otherwise identical captures differ. Where appropriate, disable animations with test-only CSS, use stable fixtures, and wait for a specific ready state.
5. Choose between puppeteer and puppeteer-core
| Package | Use it when | Browser setup |
|---|---|---|
puppeteer |
You want the standard project setup and a browser version selected to work with the library. | Installs a compatible browser unless installation scripts or downloads are disabled. |
puppeteer-core |
You manage Chrome yourself or connect to a remote browser. | Does not download Chrome. Configure an executable path, channel, or remote connection explicitly. |
For a locally installed Chrome with puppeteer-core, configure its executable path for your environment:
npm i puppeteer-core
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.launch({
executablePath: process.env.CHROME_PATH,
headless: true,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'screenshot.png' });
} finally {
await browser.close();
}
Set CHROME_PATH to a real Chrome or Chromium executable available in that environment. A path that works on a developer laptop may not exist in a CI container. For remote browsers, use the connection method and endpoint supplied by the browser provider rather than guessing a WebSocket URL.
6. Fix a missing Chrome download
If installation completed but launch reports Could not find Chrome (ver. ...), a common cause is a package manager that blocked Puppeteer’s install script. Install the browser manually from the project directory:
npx puppeteer browsers install
Alternatively, configure your package manager to allow Puppeteer’s install script, then reinstall the dependency. The exact setting depends on the package manager and its version; follow that manager’s current documentation. Avoid disabling install-script protections globally just to fix one dependency.
Puppeteer’s installation guide also documents configuration for skipping downloads and choosing a cache directory. For example, the PUPPETEER_SKIP_DOWNLOAD setting suppresses downloads; if it is enabled, provide a managed browser and explicit configuration instead. The cache directory can be changed with PUPPETEER_CACHE_DIR. Keep the browser cache available between CI jobs if you want to avoid downloading it on every run.
7. Give Claude Code browser tools with MCP (optional)
Use this path when you want Claude Code to interact with pages through browser tools during a session, rather than simply helping write or run your project’s Puppeteer script. MCP servers expose tools to Claude Code; a local Puppeteer dependency is not itself such a server.
- Choose a browser automation MCP server whose maintainer, current installation method, permissions, and network access you have reviewed.
- Follow that server’s current instructions to add it to Claude Code. Anthropic documents MCP setup in its Claude Code MCP guide; the CLI also provides
claude mcpfor configuring servers. - Restart or refresh the Claude Code session as its instructions require, then confirm the server’s tools are available before asking Claude Code to use them.
- Keep the server limited to the sites and actions needed. Browser tools may interact with authenticated pages, so treat credentials and page contents as sensitive.
There is no universal MCP install command: each server has its own package, configuration, and security model. Do not paste a command for an unverified server or assume an MCP server’s interface is interchangeable with Puppeteer’s JavaScript API.
8. Run screenshots reliably and control resource use
- Close every browser. Use
try/finallyas in the examples so errors do not leave Chrome processes running. - Set finite timeouts. Navigation and selector waits should fail clearly instead of hanging a job indefinitely.
- Reuse a browser for batches. For multiple pages, launch one browser and create a fresh page per capture, closing each page after use. This avoids repeatedly starting Chrome. Do not share one page concurrently across captures.
- Limit parallelism. Each browser and page consumes memory and CPU; start with modest concurrency and increase only while the host remains healthy.
- Plan for browser dependencies. Minimal Linux containers may lack system libraries or fonts Chrome needs. Use a compatible environment and install the required runtime dependencies according to the operating system and deployment image.
- Keep versions predictable. Lock the npm dependency version through the project lockfile and make CI install from that lockfile. Browser/library compatibility matters, especially when supplying your own Chrome.
- Use bounded retries. A transient navigation failure may be retried, but retries can repeat page actions. Make the capture workflow safe to repeat and record the URL, error, and attempt count.
Performance depends on the target site, browser startup, network, page assets, viewport, and full-page image size. The first run may include browser download and cache setup; later runs can reuse them. Full-page and high device-scale screenshots use more memory and storage. Puppeteer has no per-screenshot service fee, but your machine or CI provider still has compute, network, storage, and maintenance costs.
9. Troubleshooting common errors
| Symptom | Likely cause | Fix |
|---|---|---|
Could not find Chrome (ver. ...) |
Browser download did not run, was skipped, or the cache is unavailable. | Run npx puppeteer browsers install; check skip-download settings and preserve the configured cache. |
Failed to launch or missing shared libraries |
Host OS/container lacks Chrome runtime dependencies, or executable permissions are wrong. | Use an environment with the required browser dependencies; verify the executable exists and can run as the current user. |
| Navigation timeout | The site is slow, blocked, or never reaches the chosen lifecycle condition. | Use a realistic timeout, choose a suitable waitUntil, and wait for a specific selector when the app is ready. |
| Screenshot is blank or incomplete | Capture ran before client-side rendering, fonts, images, or application data finished loading. | Wait for an app-specific readiness selector; if needed, wait for fonts or images explicitly and inspect console/page errors. |
| Layout differs from the visible browser | Viewport, device scale, browser version, locale, fonts, authentication, or responsive breakpoints differ. | Match those inputs and authenticate through an appropriate test setup. Avoid relying on a developer’s existing browser profile. |
Script says Cannot use import |
Node treats the file as CommonJS. | Use the .mjs extension or add "type": "module" to package.json. |
| Process hangs or memory grows | A browser/page was not closed, or too many captures run at once. | Close resources in finally, cap concurrency, and avoid unbounded full-page captures. |
| MCP tools do not appear in Claude Code | Server configuration is invalid, server failed to start, or session has not refreshed. | Use the server’s current setup instructions, inspect Claude Code’s MCP status, and restart/refresh as documented. |
10. Or skip the browser setup
If you only need a screenshot from a URL, ScreenshotNeo is a website screenshot API and MCP server. Make one GET request with a URL to receive an image or PDF. See the ScreenshotNeo API documentation for request options and response details.
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}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
11. Frequently asked questions
Does Claude Code install Puppeteer for me?
Claude Code can help you add and run the project commands, but Puppeteer is a dependency of your JavaScript project. Install it from that project’s directory.
Can I use Puppeteer without downloading Chrome?
Yes. Use puppeteer-core and configure a browser executable or remote browser connection. You then own browser installation and compatibility.
Can Puppeteer save PDF files too?
Yes. Puppeteer supports page PDF generation in addition to screenshots. Use its PDF API and options when the output should be a document rather than a raster image.
Do I need an MCP server to create screenshots?
No, not for a project script. MCP is for exposing browser interaction as tools directly available to Claude Code.
Why does networkidle2 sometimes time out?
Some pages keep network requests active continuously. Wait for a page-specific ready selector or another condition that reflects the content you actually need.


