ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team1 October 20267 min read

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 finally block 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.