What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
In a Node.js script that uses Puppeteer, attach listeners before navigation or any interaction that can fail. Use the console event for messages emitted by console.*, pageerror for uncaught exceptions in page JavaScript, error for a page crash, and requestfailed for requests that fail at the network level. Keep those signals separate, record them with the URL and timestamp, and persist them in your test or CI logs.
The minimal Puppeteer solution
Install Puppeteer in a Node.js project, create a page, register every listener, and only then navigate:
npm install puppeteer
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
page.on('console', msg => {
console.log(`[browser console:${msg.type()}] ${msg.text()}`);
});
page.on('pageerror', error => {
console.error('[uncaught page exception]', error.name, error.message);
if (error.stack) console.error(error.stack);
});
page.on('error', error => {
console.error('[page crashed]', error.name, error.message);
});
page.on('requestfailed', request => {
console.error('[request failed]', request.method(), request.url(), request.failure());
});
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.close();
await browser.close();
})();
The listener must exist before goto(), clicks, form submissions, or scripts run. An event emitted before a listener is attached cannot be recovered. The console callback receives a Puppeteer console-message object; msg.type() identifies values such as error, warning, log, and info, while msg.text() gives a readable representation.
What each event actually tells you
| Event | Meaning | Typical use |
|---|---|---|
console |
A page called a console API, including console.error(), warnings, logs, and informational output. Puppeteer can also surface page errors or warnings through this stream. |
Collect application diagnostics and deliberately logged errors. |
pageerror |
An uncaught exception escaped page JavaScript. | Find failures that were thrown without a preceding console.error(). |
error |
The page crashed. | Detect a browser renderer or page crash, which is different from a JavaScript exception. |
requestfailed |
A request failed at the network or transport layer. | Identify DNS, connection, TLS, or aborted-resource failures. |
An HTTP 404 or 503 still produces an HTTP response, so it does not emit requestfailed. If HTTP status is part of your test, inspect the response returned by page.goto() or listen for responses separately. Do not treat a network failure, an HTTP error response, a page crash, and an uncaught exception as interchangeable evidence.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11#1 Best Overall
Capture structured records for CI and test reports
Printing a line is useful while debugging, but structured records are easier to archive, filter, and attach to a failed build. This example writes JSON Lines to standard output and preserves the event category, page URL, message, and stack when available.
const puppeteer = require('puppeteer');
function write(record) {
process.stdout.write(JSON.stringify({
time: new Date().toISOString(),
...record
}) + 'n');
}
(async () => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
page.on('console', msg => {
write({
kind: 'console',
level: msg.type(),
url: page.url(),
text: msg.text()
});
});
page.on('pageerror', error => {
write({
kind: 'pageerror',
url: page.url(),
name: error?.name,
message: error?.message ?? String(error),
stack: error?.stack ?? null
});
});
page.on('error', error => {
write({
kind: 'page-crash',
url: page.url(),
name: error?.name,
message: error?.message ?? String(error),
stack: error?.stack ?? null
});
});
page.on('requestfailed', request => {
write({
kind: 'requestfailed',
url: page.url(),
requestUrl: request.url(),
method: request.method(),
failure: request.failure()
});
});
const response = await page.goto(process.env.TEST_URL || 'https://example.com', {
waitUntil: 'networkidle2',
timeout: 30000
});
write({kind: 'navigation', status: response?.status() ?? null, url: page.url()});
await browser.close();
})().catch(error => {
write({kind: 'runner-error', name: error.name, message: error.message, stack: error.stack});
process.exitCode = 1;
});
Keep the original kind. Filtering only for console.error misses exceptions that are thrown directly, while recording every console message as a defect creates noise. A practical CI policy is to archive all records, fail a test on pageerror (if that is your application contract), and use a separate allowlist for known third-party warnings.
Exception payloads can vary with the Puppeteer version and execution context. Preserve the available name, message, and stack rather than assuming the value is always a native Error. The optional chaining and fallback above prevent the logger itself from crashing while handling an unusual payload.
Make navigation and interactions observable
Attach listeners once per page
Register listeners immediately after newPage(). If your test creates several pages or popups, attach the same handlers to each page, or centralize this in a helper:
Rank #2
function observePage(page, write) {
page.on('console', msg => write({kind: 'console', level: msg.type(), text: msg.text(), url: page.url()}));
page.on('pageerror', error => write({kind: 'pageerror', message: error?.message ?? String(error), stack: error?.stack ?? null, url: page.url()}));
page.on('error', error => write({kind: 'page-crash', message: error?.message ?? String(error), url: page.url()}));
page.on('requestfailed', request => write({kind: 'requestfailed', requestUrl: request.url(), failure: request.failure(), url: page.url()}));
}
Cover popups and new tabs
A listener on the original page does not automatically observe a newly opened tab. Listen for the browser’s targetcreated event, convert the target to a page, and install the handlers before driving it:
browser.on('targetcreated', async target => {
if (target.type() === 'page') {
const popup = await target.page();
observePage(popup, write);
}
});
The target callback is asynchronous, so coordinate it with your test’s popup wait when the first script in the new tab must not be missed. In many tests, Promise.all([browser.waitForTarget(...), click]) followed by immediate observation is clearer than relying on timing.
Exercise the failing code after instrumentation
Navigate, wait for the required selector, then perform the click or form submission. Set a suitable timeout and catch runner-level failures separately; a timeout from Puppeteer is not itself a page JavaScript exception.
await page.goto('https://example.com/app', {waitUntil: 'domcontentloaded'});
await page.waitForSelector('#save', {timeout: 10000});
await page.click('#save');
await page.waitForTimeout(500);
Use Chrome DevTools for interactive diagnosis
When you can reproduce the problem manually, open the DevTools Console. It displays error and warning stack traces and lets you filter by severity, script URL, and the selected JavaScript execution context. Enable “Preserve log” before reloading when the failure disappears during navigation. These controls are particularly useful for separating your bundle’s messages from extensions, analytics, and third-party frames.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsDevTools is an inspection tool, not a replacement for automation listeners: it helps a person reproduce and narrow a problem, whereas Puppeteer forwarding gives CI or a batch job durable records.
Playwright and lower-level CDP choices
If the project already uses Playwright
Use Playwright’s page events instead of adding Puppeteer solely for logging. The concepts remain the same: listen for console messages, page errors, crashes, and failed requests before navigation. Keeping one automation framework avoids duplicated browser lifecycle and timeout behavior.
If you need Chrome DevTools Protocol events
CDP exposes lower-level Runtime console API and Log event surfaces. The legacy CDP Console domain is deprecated in favor of Runtime or Log, so choose the current domain supported by the Chromium version you operate. Raw CDP is appropriate when you need protocol-level integration or are attaching to a browser owned by another process; it requires more lifecycle and payload handling than a framework page listener.
Attaching Playwright to an existing Chromium
Playwright’s chromium.connectOverCDP() can attach to an existing Chromium instance. The documented limitations are important: CDP attachment is Chromium-only and has significantly lower fidelity than Playwright’s normal protocol connection. If you control both browser ends and need advanced Playwright behavior, prefer the standard Playwright connection.
Rank #4
Common failures and precise fixes
- No browser messages appear: the listener was attached after
goto()or after the interaction. Move registration immediately afternewPage(). - Only
console.erroris recorded: an uncaught exception may never call the console API. Addpageerror. - Too many records: the
consoleevent includes normal logs and warnings. Store all levels but filter reports bymsg.type()and event kind. - A 404 is missing from request failures: that is expected. A 404 or 503 is an HTTP response; inspect response status separately.
- The browser process exits with no page error: distinguish a runner or browser-process failure from the page’s
errorcrash event and retain the outercatchrecord. - Stack is absent: preserve the message and name anyway. Minified production bundles, cross-origin frames, and protocol payload differences can limit stack detail.
- Popup errors are absent: install listeners on every newly created page, not only the first page.
- Logs from an iframe are confusing: include the page URL and, where your framework exposes it, the execution context or frame identity so messages can be attributed correctly.
- CI hangs: set navigation and action timeouts, close the browser in a
finallyblock, and keep logging independent of the assertion that may fail.
Performance, reliability, and data handling
Event listeners are lightweight, but logging every console message can become expensive on chatty applications. Prefer JSON Lines or another streaming format, cap message size, and send records to the test artifact rather than repeatedly rewriting one large file. Redact tokens, cookies, authorization values, and personal data before exporting logs. Do not evaluate arbitrary page objects merely to stringify them; msg.text() is safer for a basic record, while deeper remote-object inspection should be deliberate.
Use a stable browser version in CI and record the Puppeteer and Chromium versions with each run. This makes changes in exception serialization or console payloads explainable. Always close pages and browsers in cleanup code so a crashed renderer does not leave orphaned processes.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is a visual capture rather than debugging JavaScript behavior, ScreenshotNeo provides a single HTTP request that returns a PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Only clean shots are billed, while 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 server offers take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the parameter reference in the ScreenshotNeo documentation. A direct call is:
Free tools Windows power users keep installed
One-click scans. No signup required.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in 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)
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}`);
ScreenshotNeo includes full-page and selector captures, device and viewport controls, retina scale, dark mode, PDF options, custom CSS and JavaScript, click and wait conditions, request blocking, headers and cookies, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Every feature is on every plan: 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does console.error() catch every JavaScript error?
No. It captures an explicit console call. An exception can be thrown without any console call, so use pageerror for uncaught page exceptions.
Should a failed HTTP response be labeled a network failure?
No. A 404 or 503 is still an HTTP response. Record its status separately from requestfailed, which represents a request that failed before receiving a response.
When should I choose raw CDP?
Choose it when you need protocol-level Runtime or Log events or must attach to a browser managed elsewhere. For ordinary Puppeteer or Playwright tests, framework events require less plumbing.
Frequently Asked Questions
Can I capture errors from a page opened by a click?
Yes. Observe newly created targets or popups and attach the same listeners to the resulting Page before interacting with it.
Why is my error stack sometimes missing?
Stack availability varies with bundling, cross-origin execution contexts, and Puppeteer payloads. Always retain the event kind, name, and message as a fallback.
Quick 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.

