How to Install Puppeteer in Visual Studio Code for Screenshot Automation
Install Puppeteer in VS Code, configure Chrome, capture full-page screenshots, debug failures, and compare a hosted ScreenshotNeo workflow.
Direct answer: Install Node.js first, open a fresh Visual Studio Code terminal, create a project with npm init -y, then run npm i puppeteer. Puppeteer downloads a compatible Chrome for Testing browser. Create a JavaScript file that launches the browser, waits for the page, saves page.screenshot(), and closes the browser.
VS Code is the editor. Node.js runs the script, and npm installs Puppeteer and its browser. If you want to manage Chrome yourself, install puppeteer-core instead and provide an executable path.
1. Install Node.js and prepare VS Code
Install a current Node.js release for your operating system. Then open a new VS Code window and a new integrated terminal so the updated PATH is loaded. VS Code documents the integrated terminal and Node workflow in its Node.js tutorial.
node --version
npm --version
If either command is not recognized, install Node.js and open another terminal. Existing terminals may not have the updated PATH.
Create or open a project
mkdir puppeteer-screenshots
cd puppeteer-screenshots
code .
npm init -y
If the folder already contains a package.json, skip npm init -y.
2. Install Puppeteer
The normal installation is:
npm i puppeteer
The puppeteer package downloads a compatible Chrome for Testing build and a headless shell. Puppeteer stores downloaded browsers in its cache, normally under $HOME/.cache/puppeteer. See the official installation guide.
Use puppeteer-core when your project owns the browser installation, connects to a remote browser, or must use a particular system Chrome:
npm i puppeteer-core
| Package | Browser ownership | Use it when |
|---|---|---|
puppeteer |
Puppeteer downloads a compatible browser | You want the simplest reproducible setup |
puppeteer-core |
You supply the browser | You manage Chrome centrally, use a remote browser, or require a specific binary |
If the browser was not downloaded
Some package managers or security settings block dependency install scripts. Install the browser explicitly:
npx puppeteer browsers install
If your package manager has a setting that ignores install scripts, allow Puppeteer’s installation script, then rerun the browser installation command.
3. Create a working screenshot script
Create screenshot.mjs in VS Code:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'screenshot.png', fullPage: true });
} finally {
await browser.close();
}
Run it from the integrated terminal:
node screenshot.mjs
The script waits until network activity has mostly stopped, captures the complete document, and writes screenshot.png beside the script. Puppeteer’s screenshots guide documents Page.screenshot() and element screenshots.
CommonJS alternative
If your project does not use ES modules, create screenshot.cjs:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'screenshot.png', fullPage: true });
} finally {
await browser.close();
}
})();
4. Choose the right capture scope and wait condition
Viewport screenshot
await page.screenshot({ path: 'viewport.png' });
Full-page screenshot
await page.screenshot({ path: 'full-page.png', fullPage: true });
Use fullPage when the document itself is the subject. Long or virtualized pages can still need application-specific scrolling or readiness logic.
One element
const card = await page.waitForSelector('.pricing-card');
await card.screenshot({ path: 'pricing-card.png' });
Wait for an application signal
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-ready="true"]');
await page.screenshot({ path: 'dashboard.png', fullPage: true });
networkidle2 is useful for pages that finish loading after their requests settle. It can wait indefinitely on applications with analytics, polling, or streaming requests. In those cases, use domcontentloaded plus a selector, or add a deliberate delay only when the page has no better readiness signal.
5. Configure viewport, device, and output
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({
path: 'desktop.png',
type: 'png',
fullPage: true
});
For a retina-style image, increase deviceScaleFactor. Puppeteer can return bytes instead of writing a file:
const bytes = await page.screenshot({ type: 'png' });
await import('node:fs/promises').then(fs => fs.writeFile('capture.png', bytes));
With encoding: 'base64', page.screenshot() returns a base64 string. Use JPEG or WebP when smaller files matter:
await page.screenshot({ path: 'capture.jpg', type: 'jpeg', quality: 85 });
await page.screenshot({ path: 'capture.webp', type: 'webp' });
6. Use a custom Chrome or Chromium binary
With puppeteer-core, provide executablePath. The same option can select a custom browser with the full package:
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.launch({
executablePath: '/absolute/path/to/chrome'
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'custom-browser.png' });
} finally {
await browser.close();
}
Browser-download configuration changes require rerunning Puppeteer’s browser installation command. Keep the executable path environment-specific rather than committing a path that only exists on one machine.
7. Debug Puppeteer inside VS Code
VS Code can debug Node scripts without leaving the editor. Set a breakpoint beside page.goto() or page.screenshot(), press F5, and inspect variables and exceptions. The JavaScript Debug Terminal and auto attach are useful when starting scripts from the terminal.
A minimal .vscode/launch.json for the ES module example is:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Run screenshot",
"program": "${workspaceFolder}/screenshot.mjs"
}
]
}
During diagnosis, log the URL, viewport, and readiness step. Capture a diagnostic screenshot before closing the browser when navigation succeeds but the page content is wrong.
8. Reliability and performance practices
- Always close the browser in a
finallyblock so failed navigations do not leave Chrome processes running. - Reuse one browser for multiple pages or URLs when processing a batch; create and close pages per job.
- Set a navigation timeout appropriate to the target, and catch errors around each URL so one failure does not discard a batch.
- Choose a readiness signal tied to the application rather than adding a large fixed delay.
- Limit concurrency to the CPU and memory available. Each browser page consumes resources, and many simultaneous full-page captures can exhaust a runner.
- Cache browser downloads in CI rather than downloading Chrome for every build.
- Keep screenshots in a predictable output directory and include the target URL and timestamp in filenames when generating archives.
page.setDefaultNavigationTimeout(60000);
try {
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#main-content', { timeout: 30000 });
await page.screenshot({ path: outputPath, fullPage: true });
} catch (error) {
console.error(`Capture failed for ${targetUrl}:`, error);
}
Puppeteer’s browser download is large: its documentation gives approximate Chrome for Testing sizes of about 170 MB on macOS, 282 MB on Linux, and 280 MB on Windows. Account for that storage and download time in CI images and ephemeral runners.
9. Troubleshooting
| Error or symptom | Cause | Fix |
|---|---|---|
node or npm is not recognized |
Node.js is missing or the terminal predates the PATH update | Install Node.js and open a fresh VS Code terminal |
Could not find Chrome |
The install script was blocked or the browser was never downloaded | Run npx puppeteer browsers install and permit the install script |
| Custom browser does not launch | The executable path is wrong, inaccessible, or incompatible | Use an absolute path, verify permissions, and select a compatible Chrome or Chromium build |
| Screenshot is blank | Capture happened before the app rendered, or the page failed a bot check | Wait for a meaningful selector, inspect console and network errors, and save a diagnostic screenshot |
| Screenshot is incomplete | The viewport was captured instead of the document, or lazy content was not loaded | Use fullPage: true and wait for the content that must appear |
| Script never finishes | networkidle2 is unsuitable for polling or streaming pages |
Use domcontentloaded plus a selector or bounded delay |
| Memory usage keeps growing | Pages or browsers are not closed, or concurrency is too high | Close pages, retain the finally block, and reduce parallel jobs |
| Breakpoints are ignored | The script was started outside the debugger or the wrong file is configured | Use F5, the JavaScript Debug Terminal, auto attach, or correct launch.json |
10. When a hosted screenshot API is simpler
Local Puppeteer gives you full browser control, but every runner must carry Chrome, fonts, dependencies, timeouts, and cleanup code. For a URL-to-image workflow, ScreenshotNeo provides one GET request that returns PNG, JPEG, WebP, or PDF.
Or skip the browser setup
Use the ScreenshotNeo API documentation for the complete option list. A basic request is:
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}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. 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 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and start with 1,000 screenshots per month at no charge.
FAQ
Should I install Puppeteer or puppeteer-core?
Choose puppeteer when Puppeteer should download its compatible browser. Choose puppeteer-core when your team supplies Chrome or a remote browser.
Where does Puppeteer store Chrome?
The default cache is under $HOME/.cache/puppeteer; the exact location can be changed through Puppeteer’s configuration.
Can I capture only a component?
Yes. Wait for the component selector, obtain its element handle, and call the handle’s screenshot() method.
Why does a full-page capture differ from what I see in the browser?
Viewport size, device scale, fonts, lazy loading, animations, consent overlays, and application readiness can all change the rendered result. Make those conditions explicit in the script.
Is Puppeteer required for every screenshot job?
No. If you only need hosted URL capture and do not want to maintain Chrome on each runner, use ScreenshotNeo’s API or MCP server.


