Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“Protocol error (Runtime.callFunctionOn): Target closed” means Puppeteer tried to use a page, frame, browser context, or DevTools session that had already become unavailable. The quickest check is teardown: find where your code closes the page or browser and make sure every required asynchronous operation is awaited and handled before that point. The same text can also appear with unusually large transfers or a remote browser that disconnected, so use the failing call and runtime logs to identify which path you have.

What the error actually tells you

Runtime.callFunctionOn is a Chrome DevTools Protocol method Puppeteer uses for work such as page.evaluate(), selector operations, and calls on JavaScript handles. “Target closed” is a lifecycle symptom, not a diagnosis: the target (usually a page or frame) or its session disappeared before the protocol request completed.

Do not treat the message as proof of one Chromium bug, a universal payload limit, or a missing launch flag. Historical issue reports from different Puppeteer, Node.js, browser, and hosting versions show several paths to the same text. Your first task is to classify the failure by timing and environment.

First fix: await work before closing the page

The simplest and best-supported cause is an operation left in flight while cleanup runs. A reported reproduction started page.waitForSelector() without awaiting it and then closed the page. A Puppeteer collaborator summarized the remedy: “You should await the calls before you close the page.” That advice applies to navigation, selector waits, evaluations, screenshots, downloads, and any other operation whose result or completion your code needs.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Dell PowerEdge R730xd Server 24B SFF 2U, 2X Intel Xeon E5-2690 v4 2.6Ghz (28-cores Total), 128GB DDR4 RAM, 4X 1.2TB 10K SAS 2.5” 12Gb/s HDD, H730P 2GB RAID, NIC 10Gb + I350 1Gb (Renewed)
  • Dell PowerEdge R730xd 24B SFF 2U Server
  • 2x Intel Xeon E5-2690 v4 2.6Ghz 14-Core (28-cores Total)
  • 128GB DDR4 RAM – 4x 1.2TB 10K SAS 2.5” 12Gb/s
  • Dell H730P mini 2GB 12Gb/s RAID
  • 2x 750W PSU - 2x 10Gb SFP+ 2x 1Gb (RJ45) NIC

Broken teardown

const pending = page.waitForSelector('#ready');
await page.close();
await pending; // The target is already gone

Safe teardown with try/finally

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  try {
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
    await page.waitForSelector('h1', { timeout: 15000 });
    const heading = await page.$eval('h1', el => el.textContent.trim());
    console.log(heading);
  } catch (error) {
    console.error('Page operation failed:', error);
  } finally {
    // All awaited work above has settled before this point.
    await page.close().catch(() => {});
    await browser.close().catch(() => {});
  }
})();

Audit every cleanup branch, including timeout handlers, signal handlers, rejected promises, and framework hooks. A browser.close() also invalidates its pages and contexts, so closing the browser while a page operation is pending has the same effect.

Promise.race does not cancel the losing operations

A common pattern waits for whichever selector or event occurs first:

const winner = await Promise.race([
  page.waitForSelector('.success'),
  page.waitForSelector('.failure')
]);
await page.close();

When the race resolves, the other promise is still running unless the underlying API supports cancellation and you invoke it. Closing the page can therefore make the losing wait reject with “Target closed.” Track the individual promises, use an abort mechanism where your Puppeteer version and operation support one, or keep the page alive until outstanding work has been safely settled and handled.

Rank #2
Dell Optiplex 7050 SFF Desktop PC Intel i7-7700 4-Cores 3.60GHz 32GB DDR4 1TB SSD WiFi BT HDMI Duel Monitor Support Windows 11 Pro Excellent Condition(Renewed)
  • Model: Dell OptiPlex 7050 Small Form Factor (SFF)
  • Processor: Intel Core i7-7700 3.60 GHz
  • Memory: 32GB DDR4 Ram
  • Storage: 1TB Solid State Drive (SSD) Fast Boot + Storage
  • Operating System: Windows 11 Pro (64-bit)

Track and settle both branches

const success = page.waitForSelector('.success', { timeout: 10000 });
const failure = page.waitForSelector('.failure', { timeout: 10000 });

try {
  await Promise.race([success, failure]);
} finally {
  // Consume both outcomes so late rejections do not become unhandled.
  await Promise.allSettled([success, failure]);
  await page.close();
}

This example waits for both promises to settle before closing. In production, choose timeouts and cancellation appropriate to your flow; settling two long waits can delay cleanup, so an explicit cancellation strategy may be preferable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check the exact call and the data crossing the protocol

If the stack points to page.evaluate, JSHandle.jsonValue(), page.setContent, or a similar bridge between Node.js and Chromium, inspect the size and shape of the value being transferred. Historical reports describe one attempted return of about 115 MB of base64 data from page.evaluate and another involving a very large generated HTML table passed to setContent. Those are workload-specific reports, not an established maximum protocol size.

Reduce the transfer

  • Return only fields the Node.js process needs instead of a complete DOM or image encoded as base64.
  • Process records inside the page and return aggregates, IDs, or paged slices.
  • For generated markup, build smaller sections and measure memory and timing at each step.
  • Test chunked transfer as a mitigation, but verify it under your workload; no cited report guarantees that chunking fixes every failure.

Prefer a narrow evaluation

// Expensive: serializes a potentially huge object.
const wholeTable = await page.evaluate(() => document.querySelector('table'));

// Narrow: return only the values needed by Node.js.
const rows = await page.$$eval('table tr', trs =>
  trs.slice(0, 1000).map(tr => [...tr.cells].map(cell => cell.textContent.trim()))
);

Measure the approximate byte size of generated HTML and returned strings, and log the operation name before and after it. That makes a transfer-related failure distinguishable from a teardown race.

Rank #3
Hewlett Packard Enterprise ProLiant MicroServer Gen11 Tower Server, Intel Xeon 6315P Processor, 16GB Memory, External 180W US Power Supply (HPE Smart Choice P86811-005)
  • MODEL P86811-005: HPE ProLiant MicroServer Gen11 preconfigured with Intel Xeon 6315P 2.80GHz 4-core processor, ideal for small business IT, edge workloads, and on-premise compute
  • WHISPER-QUIET & SPACE-SAVING: Ultra-compact mini tower design fits easily in small office spaces; supports wall, flat, or vertical placement for deployment flexibility
  • READY OUT OF THE BOX: Includes 16GB DDR5 UDIMM memory (expandable to 128GB), dedicated iLO-M.2 port kit, embedded Intel VROC SATA controller for Gen11 servers, 180w external power adapter and 1/1/1 year warranty for dependable plug-and-play server operation
  • EXPANDABLE DESIGN: Two PCIe slots (including PCIe 5.0) and four LFF-NHP drive bays provide robust options for storage and component scalability. Features new MR408i-p controller support for enhanced storage performance
  • INTEGRATED REMOTE MANAGEMENT: Comes with HPE iLO 6 and embedded TPM 2.0, enabling secure, remote administration through browser, command line, or API with shared port access

Remote browsers add a separate failure path

With browserless, Azure, a container service, or another remote endpoint, the page can disappear because the WebSocket or browser process ended. One historical report shows ECONNRESET in service logs, followed by a timed-out job and browser cleanup. That sequence is specific to its environment, but it illustrates what to compare:

  • Application timestamps around the failing Puppeteer call.
  • WebSocket disconnect and reconnect events.
  • Remote service timeout, quota, and job-cleanup messages.
  • Browser-process exit, container restart, or out-of-memory logs.
  • Your navigation and operation timeout values.

If the remote session is gone, changing the JavaScript order alone cannot restore it. Reconnect or retry by creating a new page/session, and make the operation idempotent so a retry does not duplicate a purchase, form submission, or other side effect.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A repeatable diagnostic workflow

  1. Capture the complete stack trace. Record the exact Puppeteer method that rejected, not only the final protocol line.
  2. Map lifecycle calls. Search for page.close(), browser.close(), browser-context closure, test teardown, and process-exit paths.
  3. Check awaits. Confirm every operation required before teardown is awaited. Look for promises created and ignored, especially in loops and callbacks.
  4. Inspect races. For Promise.race, identify losing promises and decide how they are cancelled or settled.
  5. Estimate payloads. Log the approximate input and output size for evaluations, handles, and setContent.
  6. Compare local and remote behavior. Run the smallest reproduction against a local browser. If only remote fails, correlate service and WebSocket logs.
  7. Record versions and configuration. Include Puppeteer, Node.js, Chromium/browser, operating system, container image, hosting service, and launch options. Historical examples include Puppeteer 1.11.0 with Node.js 11.3.0, Puppeteer 3.2.0 with Node.js 14.2.0, and Puppeteer 5.5.0 with Node.js 14.15.0; those 2018–2020 configurations are clues, not current compatibility guidance.
  8. Minimize. Reproduce with one page and one operation, then add navigation, waits, evaluation, and teardown one at a time.

Common symptoms and targeted fixes

What you observe Most useful check Action
Error appears immediately after a test or request finishes Cleanup runs before an ignored promise settles Await the operation; move closure into a finally after required work and handle rejection.
Error follows Promise.race Losing waits remain attached to the page Cancel where supported or await Promise.allSettled before teardown.
Failure occurs during evaluate or jsonValue with huge output Large serialization/transfer Return less data, page or aggregate it, and test chunking.
Only a hosted browser fails; logs show reset or timeout Session or browser process lifecycle Inspect service logs, increase an appropriate timeout, reconnect or retry a new session.
Adding random Chromium flags is the only attempted fix No evidence yet identifies a flag-related cause First establish whether teardown, payload, or remote lifecycle is responsible.

Make the automation more reliable

  • Use one owner for page and browser shutdown; avoid multiple independent cleanup routines closing the same target.
  • Keep navigation, waits, evaluations, and screenshots in an explicit sequential flow unless concurrency is intentional and cleaned up.
  • Set operation-specific timeouts and log start/end timestamps, URL, frame, and target identifier.
  • Use try/finally so failures still close resources, but do not put closure ahead of required asynchronous work.
  • For retries, create a fresh page or browser session after a disconnect and ensure side effects are safe to repeat.
  • Keep a minimal reproduction alongside dependency updates; a version change can alter timing without making the error message more specific.

Or skip the browser setup

If your goal is a clean image or PDF rather than browser automation, ScreenshotNeo provides a website screenshot API and MCP server. One GET request captures a URL without you managing Puppeteer, pages, or browser teardown.

Rank #4
Hewlett Packard Enterprise ProLiant MicroServer Gen11 Tower Server, Intel Pentium Gold G7400 Processor, 16GB Memory, 1TB HDD Storage, External 180W US Power Supply (HPE Smart Choice P74439-005)
  • MODEL P74439-005: Compact and affordable HPE ProLiant MicroServer Gen11 powered by Intel Pentium Gold G7400 3.7GHz processor, ideal for file sharing, NAS, and basic business workloads
  • READY OUT OF THE BOX: Includes 16GB DDR5 UDIMM memory (expandable to 128GB), one 1TB SATA 6G Business Critical HDD, embedded Intel VROC SATA, dedicated iLO-M.2 port kit, 180w external power adapter and 1/1/1 warranty for dependable plug-and-play server operation
  • WHISPER-QUIET & SPACE-SAVING: Ultra-compact mini tower design fits easily in small office spaces; supports wall, flat, or vertical placement for deployment flexibility
  • INTEGRATED REMOTE MANAGEMENT: Comes with HPE iLO 6 and embedded TPM 2.0 for secure, license-free remote server administration through shared port access
  • EXPANDABLE DESIGN: Two PCIe slots (including PCIe 5.0) and four LFF-NHP drive bays provide robust options for storage and component scalability. Features new MR408i-p controller support for enhanced storage performance
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for options and response headers. The same request in Python is:

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)

And in 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}`);

Before capture, ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to 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.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

FAQ

Is this error always caused by closing the page too soon?

No. Early teardown is the first and simplest check, but large protocol transfers and remote browser termination are separately reported contexts.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does Puppeteer impose an official 115 MB limit?

No such limit is established here. About 115 MB was one user’s reported base64 transfer, not an official ceiling or benchmark.

Best Value
HP Z4 G4 Workstation, Intel Xeon W-2133 (6-Core) up to 3.9GHz, 64GB DDR4, 512GB NVMe M.2 SSD + 2TB HDD, Nvidia Quadro P400 2GB, USB 3.1, Windows 11 Pro (Renewed)
  • HP Z4 G4 Workstation Tower
  • Intel Xeon W-2133 6-Core 3.6GHz (3.9GHz Turbo)
  • 64GB DDR4 Memory - Nvidia Quadro P400 2GB
  • 512GB NVMe M.2 SSD (boot) + 2TB HDD (storage)
  • Windows 11 Pro 64-bit

Should I downgrade Puppeteer or Node.js?

Do not choose a version change from the message alone. Capture your current versions and produce a minimal reproduction first; historical issue configurations are not compatibility recommendations.

Can I ignore the losing promise in a race?

No. A resolved race does not automatically cancel other operations. Arrange cancellation or safe settlement before the target is closed.

Frequently Asked Questions

Is this error always caused by closing the page too soon?

No. Early teardown is the first and simplest check, but large protocol transfers and remote browser termination are separately reported contexts.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does Puppeteer impose an official 115 MB limit?

No such limit is established here. About 115 MB was one user’s reported base64 transfer, not an official ceiling or benchmark.

Should I downgrade Puppeteer or Node.js?

Do not choose a version change from the message alone. Capture your current versions and produce a minimal reproduction first; historical issue configurations are not compatibility recommendations.

Can I ignore the losing promise in a race?

No. A resolved race does not automatically cancel other operations. Arrange cancellation or safe settlement before the target is closed.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.