ScreenshotNeo

BlogHow-to

How to Read Arguments from Puppeteer Console Messages

Capture browser console arguments with Puppeteer, read serializable values and objects, and handle serialization and JSHandle cleanup correctly.

By the ScreenshotNeo team4 October 20267 min read

Listen for the page’s console event, then call msg.args() to access the values passed to the browser console call. Each value is a Puppeteer JavaScript handle: use jsonValue() for serializable values, or evaluate() to inspect a particular property in the page context. Use msg.text() when a readable rendering is enough.

1. Capture console arguments

Browser-side console.log() runs in the page context. It does not automatically write to Node.js stdout. Register the listener before the page code runs so you do not miss early messages:

const puppeteer = require('puppeteer');

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

    page.on('console', async (msg) => {
      const handles = msg.args();
      try {
        const values = await Promise.all(handles.map((handle) => handle.jsonValue()));
        console.log({
          type: msg.type(),
          text: msg.text(),
          values,
          location: msg.location(),
        });
      } catch (error) {
        console.error('Could not serialize console arguments:', error);
      } finally {
        await Promise.all(handles.map((handle) => handle.dispose().catch(() => {})));
      }
    });

    await page.evaluate(() => {
      console.log('user:', { id: 42, active: true });
    });

    // Allow the async event handler to finish before closing its page context.
    await new Promise((resolve) => setTimeout(resolve, 50));
  } finally {
    await browser.close();
  }
})();

This CommonJS example can be run with Node.js after installing Puppeteer with npm install puppeteer. If your project uses ES modules, replace require('puppeteer') with import puppeteer from 'puppeteer'. The short wait here is only to keep the example’s event handler alive through the demonstration; in an application, coordinate shutdown with the work your listener actually performs.

2. Choose how to read each value

Need Use What to expect
A readable console line msg.text() A display string, not the original argument array.
Primitive or JSON-serializable arguments msg.args(), then each handle’s jsonValue() The serializable value; special objects may not round-trip as full objects.
A few properties from an object A handle’s evaluate(value => value.property) The callback runs in the page context against the referenced value.
Message diagnostics msg.type(), msg.location(), msg.stackTrace() Message kind and source information to help locate the call.

Read serializable arguments

For calls such as console.log('user:', { id: 42, active: true }), retrieve the handles and serialize each one:

page.on('console', async (msg) => {
  const values = await Promise.all(msg.args().map((handle) => handle.jsonValue()));
  console.log(values);
});

The result preserves separate arguments: in this example, the label and object are separate array entries. This is useful when your code needs structured values instead of one formatted string.

Inspect an object without cloning it wholesale

Some values are awkward or impossible to represent as ordinary JSON. Evaluate a focused expression on the handle instead of assuming jsonValue() will produce a complete plain object:

page.on('console', async (msg) => {
  const handles = msg.args();
  try {
    const [labelHandle, objectHandle] = handles;
    const label = await labelHandle.jsonValue();
    const id = await objectHandle.evaluate((value) => value.id);
    console.log(label, id);
  } finally {
    await Promise.all(handles.map((handle) => handle.dispose().catch(() => {})));
  }
});

The callback passed to evaluate() executes in the page. It cannot access local Node.js variables by closure; pass any needed data as explicit evaluation arguments. Keep the callback focused on the property or transformation you need.

DOM nodes and special values

Returning a DOM node through an evaluation that serializes its result does not preserve the node as a normal Node.js object. Puppeteer’s execution guide documents that a DOM node can become an empty object when returned this way. For a console argument that is a node or another special value, use the handle to read useful information in-page:

page.on('console', async (msg) => {
  const handles = msg.args();
  try {
    const summary = await handles.map((handle) =>
      handle.evaluate((value) => {
        if (value instanceof Element) {
          return {
            tagName: value.tagName,
            id: value.id,
            className: value.className,
            text: value.textContent,
          };
        }
        return { type: typeof value, value: String(value) };
      })
    );
    console.log(summary);
  } finally {
    await Promise.all(handles.map((handle) => handle.dispose().catch(() => {})));
  }
});

Adapt the summary to the object you are debugging. Avoid returning large DOM subtrees or sensitive page data unless you need them.

3. Handle lifetime and listener reliability

A JSHandle keeps a reference to its browser-side object and can prevent that object from being garbage-collected. Dispose handles when you are done with them, especially in long-running listeners. Navigation or destruction of the associated execution context disposes them automatically, so an in-flight inspection can fail if the page navigates before it completes.

  • Wrap handle use in try/finally so cleanup happens after successful reads and errors.
  • Do not use a handle after calling dispose().
  • Catch rejected promises inside async event listeners; an event emitter does not necessarily await the listener or report its rejected promise where you expect.
  • If you need to finish processing before closing the page or browser, track the listener’s promises in your own application and await them during shutdown.
  • Keep processing lightweight. If messages arrive faster than your asynchronous inspections complete, queue or sample work deliberately rather than retaining an unbounded number of handles.

4. Troubleshooting

Symptom Likely cause Fix
Nothing appears in Node.js Page console output is in the browser context, or the listener was attached after the message. Attach page.on('console', ...) before navigation or evaluation, then forward the event data to Node.
The argument array is missing from the output You are using msg.text(), which gives a readable rendering rather than individual values. Use msg.args() and read each handle.
An object is empty, incomplete, or not JSON serializable The value may be a DOM node or special browser object, or only part of it is serializable. Use handle.evaluate(value => ...) to extract the specific fields you need in the page context.
Evaluation fails after navigation The handle’s execution context was destroyed along with the old page context. Catch the error, discard those handles, and inspect a new message from the current page.
Evaluation cannot see a Node variable The callback is converted and evaluated in the page; it does not close over Node’s lexical scope. Pass the value as an explicit argument to evaluate().
Memory use grows during a long capture Handles are being retained without disposal, or message processing is backing up. Dispose each message’s handles in finally and bound any processing queue.
Listener errors appear as unhandled rejections An async event handler threw or a handle operation rejected without a catch. Catch failures inside the handler and keep cleanup in finally.

5. Performance, reliability, and cost

Reading msg.text() is the simplest path when a display string is sufficient. Extracting every argument with jsonValue() or running evaluations adds browser protocol work, so inspect only what the application needs. Dispose handles promptly and avoid dumping large objects on every message.

Console events and page execution are asynchronous. A navigation can invalidate handles, and shutting down the browser can interrupt pending reads. Catch per-message failures and coordinate browser shutdown with any processing that must complete. Puppeteer does not publish a relevant benchmark in the documentation reviewed for this guide, so choose an extraction strategy based on your workload rather than an assumed speed figure.

These Puppeteer calls do not have a per-console-message fee in the cited API documentation; your practical costs come from running the browser and your own infrastructure. Keep captures bounded, close pages and browsers when finished, and avoid retaining handles or excessive log payloads.

6. Or skip the browser setup

If your goal is to inspect what a page looks like, ScreenshotNeo can return a screenshot or PDF with one GET request. It is a website screenshot API and MCP server for developers. See the ScreenshotNeo API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
  • An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
  • The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000; every feature is on every plan.

Sign up for 1,000 free screenshots a month with no card.

7. FAQ

Does msg.args() return ordinary JavaScript values?

No. It returns Puppeteer handles for values in the page context. Read serializable values with jsonValue() or inspect them through evaluate().

Should I use text() or args()?

Use text() for a readable line. Use args() when your code needs to distinguish and process the values passed to the console method.

Can I construct a ConsoleMessage myself?

No. The constructor is internal. Consume the message object Puppeteer sends to the page’s console event.

Can I inspect messages from a worker with this page listener?

This pattern covers messages dispatched through the page’s console event. For other targets, use the relevant target-specific Puppeteer events and APIs rather than assuming a page listener receives every browser context’s output.

Sources