“Protocol error (Page.captureScreenshot): Target closed” means the Chromium page target or its DevTools Protocol session disappeared before Puppeteer received the screenshot response. The message identifies a closed target, not the reason it closed. In practice, investigate lifecycle races first, then browser exits or disconnects, oversized captures, and version-specific behavior. A retry helps only when the browser and page are still usable; it cannot reopen a target that has already gone away.
What the error actually means
page.screenshot() ultimately sends the Chrome DevTools Protocol (CDP) Page.captureScreenshot command. Puppeteer’s CDP page implementation rejects its pending operation with TargetCloseError('Target closed') when the primary CDP session disconnects. Therefore, the failure is at the page-target or session layer.
The text alone does not prove that Chromium crashed, memory was exhausted, your timeout caused the problem, or Puppeteer contains a bug. Those are competing explanations. You need runtime evidence from the affected process, options and lifecycle.
Fix it in diagnostic order
1. Find code that closes the page or browser
Start with the most actionable possibility: another path closes the target while the screenshot is pending. Search for page.close(), browser.close(), browser.disconnect(), timeout callbacks, test teardown and finally blocks. Also inspect jobs sharing the same page or browser.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
Always await the capture before cleanup:
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.screenshot({path: 'shot.png'});
} finally {
await browser.close();
}
A common race looks like this:
// Risky: cleanup can run while the screenshot is still pending
const screenshot = page.screenshot({path: 'shot.png'});
await browser.close();
await screenshot;
Do not share one page between concurrent tasks unless you coordinate every navigation, close and capture. Give each job its own page, or serialize operations with a queue.
2. Compare a normal capture with the failing capture
Change one variable at a time. First capture the current viewport, then test fullPage: true, a smaller clip, a lower device scale factor and a smaller viewport. This distinguishes a lifecycle failure from a resource or dimension-related failure.
await page.setViewportSize?.({width: 1280, height: 800}); // use your framework's viewport API
await page.screenshot({path: 'viewport.png', fullPage: false});
await page.screenshot({path: 'full.png', fullPage: true});
Puppeteer itself does not publish a universal maximum screenshot size. A secondary troubleshooting report proposes oversized dimensions as one possible browser-crash cause; treat that as a hypothesis to verify, not a guaranteed diagnosis. If only large captures fail, reduce the viewport, clip, scale or page length, or capture the page in sections.
3. Check whether Chromium exited or CDP disconnected
Attach listeners and preserve browser-process diagnostics. These events tell you whether the target vanished because the browser went away.
Rank #2
browser.on('disconnected', () => console.error('Puppeteer disconnected'));
page.on('close', () => console.error('Page closed'));
const proc = browser.process();
if (proc) {
proc.on('exit', (code, signal) =>
console.error('Chromium exited', {code, signal}));
proc.stderr?.on('data', data =>
console.error('Chromium stderr:', data.toString()));
}
In containers and CI, also inspect the container or service logs for an OOM kill, supervisor restart, sandbox failure or an explicit termination signal. A browser that has exited cannot satisfy a pending CDP command.
4. Reproduce with the smallest possible page
Use a fresh browser, one page and a simple URL. Then add your real navigation, scripts, cookies, headers, viewport and capture options one at a time.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.goto('data:text/html,<h1>test</h1>', {waitUntil: 'load'});
await page.screenshot({path: 'minimal.png'});
} finally {
await browser.close();
}
})();
If the minimal case succeeds, compare the real page for heavy scripts, redirects, authentication, service workers, long-running requests and code that closes or replaces pages. If it fails too, retain the complete stack trace and process logs for the next steps.
5. Record versions and connection mode
Write down the exact Puppeteer version, Chrome or Chromium version, operating system, container image, Node.js version, launch versus puppeteer.connect(), viewport, device scale, clip and all screenshot options. Puppeteer’s official changelog records releases and Chrome rollups; check the entry matching your installed package before applying version-specific advice. No single release can be claimed as a universal fix for this error.
Free tools Windows power users keep installed
One-click scans. No signup required.
For a reproducible report, include the URL or a sanitized equivalent, whether the failure is viewport-only or full-page, parallelism, browser exit status, CDP disconnect events and the code surrounding every timeout and cleanup path.
6. Retry only when the target is known to be alive
A bounded retry is reasonable after a transient navigation or browser hiccup if browser.connected is true and the page has not emitted close. On a closed target, create a new page (and, if necessary, a new browser) before retrying. A loop that repeats page.screenshot() on the same closed page cannot repair the session.
async function screenshotWithFreshPage(browser, url, path) {
const page = await browser.newPage();
try {
await page.goto(url, {waitUntil: 'networkidle2', timeout: 60000});
await page.screenshot({path});
} finally {
if (!page.isClosed()) await page.close();
}
}
Capture-size and page-content edge cases
Full-page and lazy-loaded content
Full-page mode can create a much larger bitmap than the visible viewport. Test a viewport capture first. If the site continually appends content or lazy-loads images while scrolling, wait for the page to settle and consider a fixed clip or sectioned captures.
Device scale and clips
A high device scale factor multiplies the pixel dimensions and memory required for an image. Lower it temporarily, remove an unusually large clip, and restore settings incrementally after a successful baseline.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Rank #4
Navigation and teardown timing
Do not start a navigation, replace the page, or run teardown while the capture is pending. Keep timeout handlers from calling browser cleanup until the screenshot promise has been resolved or rejected, and make cleanup idempotent so two error paths do not close the same browser unexpectedly.
Common symptoms, likely explanations and fixes
| Symptom | What it suggests | Next action |
|---|---|---|
| Every screenshot fails immediately | Closed page, disconnected CDP session or dead browser | Check page.isClosed(), browser.connected, disconnect and process-exit logs; create a new page/browser. |
| Only full-page or huge clips fail | Capture dimensions may be stressing Chromium | Use viewport or smaller clips, lower scale, and inspect browser stderr and host memory. |
| Failure occurs during tests or job shutdown | Cleanup race | Await screenshot before finally, timeout cleanup or worker shutdown; isolate pages per job. |
| Only one Puppeteer/Chrome combination fails | Release or connection-mode interaction | Record both versions and consult the matching official changelog entry; test a controlled upgrade or rollback. |
| Browser process exits | Chromium crash, OOM kill, signal or environment failure | Read stderr, container logs and exit status; fix the environment before adding retries. |
What not to assume
- The error message does not establish a Chromium crash or out-of-memory condition.
- A timeout wrapper in a historical Puppeteer issue is an example worth inspecting, not proof that every timeout wrapper causes the failure.
- There is no evidence-based universal screenshot-size threshold to paste into configuration.
- Retries do not revive a closed CDP target.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server when maintaining Chromium lifecycle code is not the goal. One GET request returns PNG, JPEG, WebP or PDF. Its capture flow accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before the shot; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP tools—take_screenshot, get_page_info and capture_pdf—let Claude, Cursor and other MCP clients request captures.
For the complete parameter list and options, see the ScreenshotNeo API documentation. The following calls are runnable after replacing the key and target URL:
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
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)
Node.js
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 has 63 capture options, including full-page lazy-image loading, CSS-selector elements, dark mode, 12 device presets or custom viewports, retina scale, PDF paper and page ranges, custom CSS and JavaScript, clicks, selector waits, delays, network-idle waits, ad/tracker/request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous signed webhooks, 100-URL bulk calls, a usage API and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work, which can simplify migration.
The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try it.
Best Value
- Used Book in Good Condition
FAQ
Does this error come from the website being blocked?
Not necessarily. A blocked, blank or failed page can contribute to a failed workflow, but this specific message reports that Puppeteer lost the target or CDP session. Confirm the browser and page lifecycle before diagnosing site access.
Should I use page.screenshot({timeout: ...})?
A timeout can prevent an operation from waiting indefinitely, but it does not explain why the target disappeared. Ensure timeout handling does not close the page or browser while the capture is still running, and preserve the original error and process logs.
Is puppeteer.connect() more likely to produce this error than launch?
The connection mode is a diagnostic variable, not a proven cause. Record it, along with the remote browser’s ownership and shutdown policy, because another service may close a connected browser.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




