How to Capture a Website Screenshot with Puppeteer on Windows Without Headless Errors
Capture website screenshots with Puppeteer on Windows, then diagnose Chrome launch failures with the right checks for browser setup, policies and permissions.
Puppeteer captures a website screenshot on Windows by launching Chrome, opening a page, navigating to the target URL, and calling page.screenshot(). To avoid headless launch errors, start with Puppeteer’s bundled Chrome for Testing, check the installed Node and Puppeteer versions, and match any fix to the actual error. For diagnosis, temporarily launch visible Chrome with browser logs enabled.
Capture a website screenshot with Puppeteer
Install Puppeteer in a project directory. Installing the puppeteer package downloads a compatible Chrome for Testing browser and a chrome-headless-shell binary. Puppeteer guarantees operation with its bundled browser; arbitrary system Chrome installations may not be compatible.
mkdir puppeteer-screenshot
cd puppeteer-screenshot
npm init -y
npm install puppeteer
Save this as screenshot.js:
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();
}
})();
Run it with node screenshot.js. The finally block closes Chrome even if navigation or screenshot capture throws an error. Replace the example URL with the page you need.
networkidle2 is a useful starting point, not proof that every dynamic page has finished rendering. A page may continue updating after network activity settles, or keep connections open and never become idle. If the screenshot misses content, wait for a known selector before capturing:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('main article');
await page.screenshot({ path: 'article.png', fullPage: true });
Choose a selector that appears when the content you need is ready. Puppeteer’s screenshot guide demonstrates saving a page image with Page.screenshot(); for a selected element, use an element handle’s screenshot() method. See the [Puppeteer screenshot guide](https://pptr.dev/guides/screenshots).
Capture one element
const article = await page.waitForSelector('main article');
if (!article) throw new Error('Article element was not found');
await article.screenshot({ path: 'article.png' });
The element screenshot method attempts to scroll a hidden element into view. If the selector matches multiple elements, use a more specific selector so the intended element is captured.
Save a full page or a viewport
Use { fullPage: true } to capture the full document; omit it for a viewport screenshot. Check the screenshot options supported by your installed Puppeteer version when adapting options from a different version. Long pages can produce large image files and may take more time and memory to render.
Check Windows and Puppeteer setup
Start by collecting the exact launch error, Puppeteer version, Node version, launch options, and whether Puppeteer uses its downloaded browser or an external executable. “Headless error” can refer to several different failures, so the message and environment determine the fix.
- Check the current requirements. The Puppeteer v25.12.0 system requirements page lists Node 22.12 or later and Windows x64 for Chrome for Testing. It also lists
tar.exeor PowerShell as extraction tools unless the optionalyauzldependency is installed. Requirements can differ for older project versions; consult the requirements for the version you use. See [Puppeteer system requirements](https://pptr.dev/guides/system-requirements). - Confirm the browser was downloaded. The standard
puppeteerinstallation downloads a compatible browser. If installation scripts were skipped or the browser cache is elsewhere, Puppeteer may have no browser to launch. See [Puppeteer installation](https://pptr.dev/guides/installation). - Check the browser cache location. Since Puppeteer v19, its default browser download directory is
~/.cache/puppeteer, resolved from the user’s home directory. SetPUPPETEER_CACHE_DIRif that location is unavailable or you need a different cache directory. See [Puppeteer configuration](https://pptr.dev/guides/configuration). - Use the bundled browser unless you need a managed one. If you intentionally use a separately installed browser, configure an explicit
executablePathorchanneland check compatibility. Puppeteer only guarantees operation with its bundled browser. See [LaunchOptions](https://pptr.dev/api/puppeteer.launchoptions) and [installation](https://pptr.dev/guides/installation).
Choose a headless mode
Puppeteer’s default is headless: true, which runs modern headless Chrome. For a visible window, set headless: false. Setting headless: 'shell' launches the separate chrome-headless-shell binary, corresponding to old headless mode. Puppeteer notes that shell does not completely match regular Chrome behavior, though it can be more performant when the full Chrome feature set is unnecessary. It is a mode choice, not a general repair for Windows launch errors. See [Puppeteer headless mode](https://pptr.dev/guides/headless-modes).
| Setting | What it does | When it helps |
|---|---|---|
Default or headless: true |
Runs modern Chrome without a visible window. | Normal automated capture. |
headless: false |
Opens a visible browser window. | Inspecting startup, navigation or page behavior while debugging. |
headless: 'shell' |
Uses the separate headless-shell binary. | Jobs that do not need the full regular Chrome behavior and where its behavioral differences are acceptable. |
Diagnose launch failures on Windows
Show Chrome and forward its logs
Try a visible launch and turn on browser process output. This helps establish whether Chrome starts and whether the problem occurs during browser startup, DevTools connection, or page navigation.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
dumpio: true
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'debug.png' });
} finally {
await browser.close();
}
})();
dumpio: true forwards the browser process’s standard output and error streams to Node’s output. Once the cause is clear, remove visible mode and extra logging if the job should run unattended. See [Puppeteer debugging](https://pptr.dev/guides/debugging).
Chrome policy enforces extensions
Puppeteer passes --disable-extensions by default. The troubleshooting guide documents that some Chrome policies enforce extensions and can prevent launch in this situation. If that policy applies to your machine, the documented workaround is to enable extensions:
const browser = await puppeteer.launch({ enableExtensions: true });
Use this only when the policy-related cause matches the environment. It does not address unrelated launch errors. See [Puppeteer troubleshooting](https://pptr.dev/troubleshooting).
Windows sandbox access denied
Chrome’s Windows sandbox needs suitable permissions on downloaded Chrome files. The troubleshooting guide says Puppeteer v22.14.0 and later attempts to configure these permissions by running Chrome’s setup.exe during browser installation. For an older version or a persistent access-denied error, the guide documents this example command:
icacls "%USERPROFILE%/.cache/puppeteer/chrome" /grant *S-1-15-2-1:(OI)(CI)(RX)
Check the actual cache directory first: a custom PUPPETEER_CACHE_DIR changes the path. The troubleshooting page cautions that high-security environments should use a more restrictive SID, such as one provided by the installer. Confirm your organization’s security policy before changing permissions; do not treat the example SID as a universal setting. See [Puppeteer troubleshooting](https://pptr.dev/troubleshooting).
Use an external Chrome only for a reason
If you need a separately managed Chrome installation, specify its executable path or a supported channel in launch options. Puppeteer warns that it only guarantees compatibility with its bundled browser, so an arbitrary system Chrome version can introduce launch or behavior problems. Check the exact browser version and launch configuration when diagnosing failures instead of assuming that changing headless mode will fix a version mismatch.
Common errors and fixes
| Symptom | Likely cause | What to check or do |
|---|---|---|
| Puppeteer cannot find Chrome or reports no executable. | The browser download is missing, the cache path differs, or a custom executable path is wrong. | Confirm installation completed, inspect PUPPETEER_CACHE_DIR and the configured path, and reinstall the intended Puppeteer package if its browser was not downloaded. |
| Chrome exits during launch or prints access denied. | Windows sandbox permissions on the downloaded Chrome files may be insufficient. | Check the Puppeteer version and actual browser cache path. Follow the documented permissions guidance only if the error fits this cause. |
| Launch fails on a managed or work computer. | A Chrome policy may enforce extensions while Puppeteer disables them by default. | Check whether that policy is present; use enableExtensions: true only for this documented case. |
| The browser window opens, but navigation times out. | The browser launched; the page may be slow, unreachable, or waiting on a loading condition that does not settle. | Separate launch from navigation in the logs. Check the URL and network access, then choose an appropriate navigation condition or wait for the page’s required selector. |
| The screenshot is blank or misses page content. | The capture may happen before the relevant content renders, or the target selector may not identify the expected element. | Wait for a content-specific selector and confirm the element exists before capture. A network-idle event alone is not a universal signal that dynamic content is ready. |
| It works locally but fails in a scheduled or service account. | The process may use a different user profile, home directory, cache location, or permissions context. | Record the account running Node, make the browser cache location explicit when needed, and verify that account can access the downloaded browser. |
| A custom Chrome path launches unreliably. | The external browser version may not be compatible with the installed Puppeteer version. | Try Puppeteer’s bundled browser as the compatibility baseline, or verify the exact external executable and version. |
Reliability, performance and cost
Make capture failures easier to diagnose
- Keep launch, navigation, readiness waits and screenshot capture as distinct steps so logs identify where failure occurred.
- Close the browser in a
finallyblock. For a long-running process that captures many pages, create pages deliberately and close them when finished. - Use a selector tied to the content you need when a page renders asynchronously. Use a navigation wait condition appropriate to the site rather than assuming one condition works everywhere.
- Record the Node version, Puppeteer version, browser source, launch options and exact error when a failure occurs.
- When using an external executable, pin and manage its version intentionally; the bundled browser is Puppeteer’s guaranteed compatibility baseline.
Manage capture time and image size
Headless shell can be more performant for automation when its behavior is sufficient, according to Puppeteer’s headless-mode documentation. It is not equivalent to regular Chrome in every respect, so compare output behavior for your target pages before choosing it. Full-page captures of long documents require rendering and encoding more pixels than viewport captures. Use a viewport capture when that is all the downstream task needs, and reserve full-page images for cases where the whole document matters.
Account for operational cost
Puppeteer is open-source software, but running captures still uses the machine’s CPU, memory, storage and network. Browser downloads consume disk space; full-page images and concurrent Chrome processes increase resource use. For repeated captures, reuse a browser process where appropriate, limit concurrency to available resources, and close pages and browsers cleanly. No fixed runtime or cost estimate applies across sites and Windows machines.
Or skip the browser setup
If you need a screenshot without installing and maintaining Chrome, [ScreenshotNeo](https://screenshotneo.com) provides a website screenshot API. See the [ScreenshotNeo API documentation](https://screenshotneo.com/docs/) for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
- Cookie banners are accepted and removed before capture; the service also removes known consent platforms, newsletter popups and chat widgets. Each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads and cache hits cost nothing. Responses include
X-Page-VerdictandX-Billedheaders. - An MCP server gives AI agents tools for screenshots, page information and PDF capture.
- The free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does Puppeteer require a visible Chrome window?
No. Puppeteer defaults to headless mode. Set headless: false when you need to inspect the browser during debugging.
Is networkidle2 always the right wait condition?
No. It is a documented example and a useful starting point, but dynamic pages can render content later or keep network connections active. Wait for the page state your capture actually needs.
Should I switch to headless: 'shell' to fix a launch error?
Not as a general fix. Shell selects a different binary and has behavioral differences from regular Chrome. Diagnose the launch message and environment first.
Can I use my installed Chrome instead of Puppeteer’s download?
You can configure an executable path or channel, but Puppeteer guarantees operation with its bundled browser. An external browser adds a compatibility variable.
Where is Puppeteer’s downloaded browser on Windows?
By default, Puppeteer v19 and later uses ~/.cache/puppeteer under the current user’s home directory. The cache can be configured, so check PUPPETEER_CACHE_DIR if the browser is not there.


