Attach a listener to the page’s console event before navigation or any action that can log output. Pyppeteer will then deliver each browser-side message as a ConsoleMessage; print msg.text for a readable line, inspect msg.type for filtering, and convert msg.args when you need structured values.
Capture console output with the page event
This complete example captures messages from the initial page load and from JavaScript evaluated afterward:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch()
page = await browser.newPage()
# Register before goto so early messages are not missed.
page.on("console", lambda msg: print(f"[{msg.type}] {msg.text}"))
await page.goto("https://example.com")
await page.evaluate("console.log('hello', 42, {foo: 'bar'})")
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
Run it with Python after installing Pyppeteer and allowing it to use a compatible Chromium binary. A successful run prints a line for the evaluated message, such as [log] hello 42 [object Object] (the exact object rendering depends on the browser protocol’s text conversion).
The important detail is timing: page.on("console", handler) must execute before goto, a click, a form submission, or an evaluate call that emits the message. The handler remains active until you remove it or close the page.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errors#1 Best Overall
Understand what Pyppeteer sends to your handler
Pyppeteer’s API describes ConsoleMessage objects as being dispatched by the page through the console event. The object provides three fields useful for different diagnostics:
| Field | Use it for | What you receive |
|---|---|---|
msg.text |
Readable logs, CI output and simple files | A text representation assembled from the console arguments |
msg.type |
Routing or filtering | The browser message type, such as log, warning or error |
msg.args |
Structured or multiple arguments | A list of JavaScript handles, one for each original argument |
Use text when the output is primarily for people. Use args when an object, array or other value must retain its structure. Because the entries are JavaScript handles rather than ordinary Python values, you must explicitly request a JSON-compatible value or inspect properties. A non-serializable value may require string conversion or targeted property access instead.
Filter errors and warnings
For a test run, printing every informational message can hide the failure you need. Filter by msg.type while retaining the original text:
def on_console(msg):
if msg.type in {"error", "warning"}:
print(f"BROWSER {msg.type.upper()}: {msg.text}")
page.on("console", on_console)
Attach this function before navigation. Filtering does not prevent the browser from producing other messages; it only controls what your Python process emits. Keep an unfiltered handler during initial debugging if you are unsure which level a site uses.
Recommended Free Tools
Rank #2
Preserve structured arguments
msg.text is convenient but intentionally lossy. A call such as console.log("user", {"id": 7, "roles": ["admin"]}) contains a structured object that may be flattened in the text field. Iterate over msg.args and ask each handle for a JSON value when it is serializable:
async def on_console(msg):
values = []
for handle in msg.args:
try:
values.append(await handle.jsonValue())
except Exception:
# Functions, DOM nodes and other non-JSON values need inspection
# or string conversion instead of jsonValue().
values.append("<non-serializable>")
print({"type": msg.type, "text": msg.text, "args": values})
page.on("console", on_console)
Event callbacks may be asynchronous. If your installed Pyppeteer version does not await an async callback automatically, wrap it in a task or use a synchronous callback that schedules work on the running event loop. For simple line logging, the synchronous lambda in the first example avoids that issue.
Release handles when you perform long-running or high-volume captures, and avoid attempting to serialize cyclic objects. For a diagnostic snapshot, selecting only the properties you need is safer than recursively converting an unknown object.
Capture messages during real interactions
Console output often occurs after an interaction rather than during the first load. Register once, then perform the action:
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated 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 matchimport asyncio
from pyppeteer import launch
async def main():
browser = await launch()
page = await browser.newPage()
def report(msg):
print(f"[{msg.type}] {msg.text}")
page.on("console", report)
await page.goto("https://example.com", {"waitUntil": "networkidle2"})
# Replace with a selector that exists on your page.
# await page.click("button#save")
# await page.waitForSelector(".result")
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
Keep the listener on the same Page instance that performs the navigation and interaction. A listener attached to a different tab cannot receive this page’s events. If you create multiple tabs, install a handler on each one whose output matters.
Why console.log does not appear in your terminal
- Browser and Python are separate contexts. A page’s
console.logwrites to the browser console, not automatically to the host process. Theconsoleevent is the bridge. - The listener was installed too late. Messages emitted during document creation can be gone before a handler exists. Attach it immediately after
newPage(), beforegoto(). - You are watching the wrong page. Confirm the handler and the triggering call use the same
pageobject. - Your filter excludes the message. Some sites use
info,debugor another level. Temporarily print everymsg.type. - The value is in a worker. Pyppeteer’s normal page-console path excludes log entries whose source is
worker; worker diagnostics require separate target and worker-lifecycle handling.
Workers, service workers and separate targets
Page scripts and worker scripts are different execution targets. The implementation listens for Chrome DevTools Protocol runtime console events and also processes log entries, but it emits a page console message only when a log entry is not sourced from a worker. Therefore a service worker or dedicated worker may log successfully while nothing arrives at your page’s handler.
When worker output is the subject of the investigation:
- Confirm the code really runs in a worker, rather than in the page’s main world.
- Observe target creation and worker attachment in your Pyppeteer version.
- Keep worker lifecycle handling separate from page-level assertions; a worker can start after navigation and terminate before a later check.
- Use page messages to record the hand-off (for example, a page callback that receives a worker result) when you need a single CI log.
Exact worker APIs vary with the installed Pyppeteer release and its bundled Chromium, so verify behavior against those versions when a worker is central to a test.
Build a reliable console collector
Fail tests on browser errors
browser_errors = []
def collect_errors(msg):
if msg.type == "error":
browser_errors.append(msg.text)
page.on("console", collect_errors)
await page.goto("https://example.com")
# Perform test actions here.
if browser_errors:
raise AssertionError("Browser console errors:n" + "n".join(browser_errors))
Decide whether third-party script errors should fail your test before enabling this policy. Many production pages log recoverable errors, so a warning-only report can be more appropriate than an unconditional failure.
Keep output deterministic
- Install handlers before every operation that can emit output.
- Prefix lines with the message type and, if you collect several pages, the page or test name.
- Store raw
msg.textfor quick review and structured arguments only for messages that need them. - Close the browser in a
finallyblock in production scripts so a failed assertion does not leave Chromium running. - Set navigation and action timeouts deliberately; a timeout is not a console message and should be reported as a separate failure category.
Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| No output at all | Handler attached after navigation or to another page | Register immediately after newPage() and verify the same object performs the action. |
| Only some levels appear | Filtering by msg.type |
Print all types first, then narrow the set. |
| Objects are unreadable | Relying only on msg.text |
Iterate through msg.args and call jsonValue() where supported. |
jsonValue() fails |
Argument is a DOM node, function, cyclic object or other non-serializable handle | Inspect selected properties or use string conversion; do not assume every handle is JSON. |
| Page logs appear but worker logs do not | Worker source is excluded from the normal page-console emission path | Track the worker target separately or relay results through page code. |
| Behavior differs between machines | Different Pyppeteer or Chromium versions | Record both versions and reproduce with the same pair. |
Or skip the browser setup
If your actual goal is a clean image or PDF of a page rather than browser-console diagnostics, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and whether the shot was billed.
For a screenshot, see the ScreenshotNeo documentation and use:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
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 failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also exposes an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. Its API supports full-page and element captures, device and viewport settings, retina scale, dark mode, PDF controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call and a usage API. Every feature is included on every plan. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Frequently asked questions
Can I remove a console listener?
Yes. Keep the same callback object and pass it to the page’s listener-removal method supported by your Pyppeteer version. Anonymous lambdas are convenient for short scripts but harder to remove later.
Best Value
Does the console event include JavaScript stack traces?
The documented message fields are type, text and args. A stack trace is not guaranteed by this event interface; capture additional page error or protocol information when a trace is required.
Why can a message order look different from my source code?
Console calls run with the page’s asynchronous event loop. Network callbacks, timers and framework queues can interleave their output, so assert on collected facts or message categories rather than assuming every line arrives in source order.
Frequently Asked Questions
Can I remove a console listener?
Keep a reference to the callback and pass that same reference to the listener-removal method supported by your Pyppeteer version.
Does the console event include JavaScript stack traces?
The documented fields are type, text and args; a stack trace is not guaranteed, so collect additional page-error or protocol data when you need one.
Why can message order differ from source order?
Asynchronous timers, network callbacks and framework queues can interleave console calls; test collected categories or values instead of assuming strict source order.
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.

