ScreenshotNeo

BlogHow-to

How to Record Performance Traces with Puppeteer

Record a bounded browser session with Puppeteer, save or return the trace, choose capture options, and inspect the result in a timeline viewer.

By the ScreenshotNeo team4 October 20267 min read

Use Puppeteer’s page.tracing.start() before the navigation or interaction you want to study, then call page.tracing.stop() when that activity is complete. Pass a path to save the trace as a file; without one, handle the returned trace buffer yourself. Open the resulting trace in Chrome DevTools or a compatible timeline viewer. Only one trace can be active per browser. Puppeteer Tracing API

1. Install Puppeteer and record a trace

This runnable example launches Puppeteer’s downloaded Chrome, records navigation and a short post-load interval, then closes the browser. Save it as trace.mjs and run node trace.mjs.

import puppeteer from 'puppeteer';

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

  await page.tracing.start({ path: 'trace.json' });
  try {
    await page.goto('https://example.com', {
      waitUntil: 'networkidle2',
      timeout: 60_000,
    });

    // Include any interaction you want to investigate here.
    // For example: await page.locator('a').first().click();

    // Keep any intentional post-load observation interval inside the trace.
    await new Promise(resolve => setTimeout(resolve, 1_000));
  } finally {
    await page.tracing.stop();
  }

  console.log('Wrote trace.json');
} finally {
  await browser.close();
}

Install with npm install puppeteer. The puppeteer package downloads a compatible Chrome during installation. If you use puppeteer-core, install or provide a browser separately and configure the launch or connection for it. See the Puppeteer getting started guide.

The important boundary is the pair of tracing calls: start immediately before the work under investigation, and stop immediately after it. Starting before navigation includes navigation and rendering; starting after navigation isolates later work such as a click, route change, or scripted interaction.

2. Choose what the trace contains

page.tracing.start(options) accepts a TracingOptions object. Use only options relevant to the question you are diagnosing; a trace that captures a short, representative interval is easier to inspect and less likely to create unnecessary data.

Option Purpose Behavior
path Write trace data to a file. If omitted, data is not written to disk automatically; obtain it from stop().
categories Select tracing categories to include or exclude. Pass category names as strings. Prefix a category with - to exclude it. The default categories come from Puppeteer’s implementation.
screenshots Include screenshots in the trace timeline. Defaults to false. Enable when visual frames help correlate page appearance with timeline activity.
bufferSize Set the trace buffer size in kilobytes. When unspecified or zero, Chromium uses a documented default of 200 MB (200,000 KB).

These options and defaults are documented in the TracingOptions reference. A focused example with screenshots enabled and one category excluded:

await page.tracing.start({
  path: 'interaction-trace.json',
  screenshots: true,
  categories: ['-toplevel'],
});

try {
  await page.locator('[data-testid="open-menu"]').click();
  await page.locator('[data-testid="menu-panel"]').wait();
} finally {
  await page.tracing.stop();
}

Category names determine which events are recorded. If you specify categories, select them for the diagnostic you need and consult the Chromium/Puppeteer documentation for the category names supported by your installed version. Avoid assuming that a category set copied from another project or version is complete.

3. Return the trace as data instead of saving it

When path is absent, tracing.stop() can return trace bytes as a Uint8Array. Write those bytes yourself, pass them to a storage layer, or process them in memory.

import { writeFile } from 'node:fs/promises';
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.tracing.start({ screenshots: false });
  try {
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  } finally {
    const trace = await page.tracing.stop();
    if (!trace) {
      throw new Error('Puppeteer did not return trace data');
    }
    await writeFile('trace.json', trace);
  }
} finally {
  await browser.close();
}

The API types the result as Promise<Uint8Array | undefined>; check it before using it. With a path, Puppeteer writes the trace to that path. See Tracing.stop().

4. Capture a specific interaction

For an interaction trace, navigate to a known state first, then start tracing just before the action. Wait for the condition that defines completion, and stop the trace. This avoids mixing unrelated startup activity into the measurement.

await page.goto('https://example.com/dashboard', {
  waitUntil: 'domcontentloaded',
});
await page.locator('[data-testid="dashboard-ready"]').wait();

await page.tracing.start({ path: 'filter-change.json' });
try {
  await page.locator('[data-testid="filter"]').select('month');
  await page.locator('[data-testid="results-updated"]').wait();
} finally {
  await page.tracing.stop();
}

Use a selector or other explicit condition that represents the user-visible result. A fixed delay can be useful when the delay itself is part of the behavior being studied, but it is a weak substitute for a real completion condition because page timing varies.

5. Open and read the trace

  1. Locate the generated trace.json file (or the file you wrote from the returned buffer).
  2. Open it in Chrome DevTools’ performance timeline or another compatible timeline viewer.
  3. Inspect the time range around the navigation or interaction you captured.
  4. Use the timeline to identify where time is spent, then form a hypothesis and make a targeted follow-up capture.

A trace is evidence for performance diagnosis; collecting one does not automatically identify or fix a bottleneck. Keep the capture window narrow enough to make the relevant activity easy to find. Puppeteer describes traces as files that can be opened in Chrome DevTools or a timeline viewer. Tracing class reference

6. Reliability, performance, and cost considerations

  • One active trace per browser: Do not start a second trace in another page while one is active in that browser. Serialize trace captures or use separate browser instances when independent captures must run concurrently.
  • Bound the capture: Keep unrelated setup outside the tracing interval. Stop tracing in a finally block so an exception during navigation or interaction does not leave the trace running.
  • Buffer and artifact size: Screenshots and broad category selection can increase trace data. The documented default buffer is 200 MB; traces still consume memory and disk, so collect only the interval and detail needed.
  • Repeatability: Page content, network conditions, cache state, and browser version can affect a capture. For comparisons, keep the steps, viewport, browser setup, and waiting condition consistent, and label trace files with the scenario or run.
  • Cost: Puppeteer is a browser automation library, and this workflow runs a browser you provision. Resource use comes from browser execution and retaining trace artifacts; the cited Puppeteer tracing documentation does not state a per-trace fee.
  • Version matching: Puppeteer documentation pages can describe different package versions. Check the docs matching your installed version before depending on a signature or default. The core start/capture/stop workflow is documented across the API references.

7. Common errors and fixes

Symptom Likely cause Fix
No trace file appears. path was omitted, the path points somewhere unexpected, or the process cannot write there. Set an explicit writable path, or save the Uint8Array returned by stop(). Check the process working directory and filesystem permissions.
The returned trace value is empty or undefined. The code assumes that stop() always returns bytes, or the trace was configured to write to a path. Check the return value before writing it. When using path, use the file output; without it, handle the returned buffer.
Starting a trace fails because another trace is active. Another page in the same browser already has an active trace. Ensure every capture reaches stop() in a finally block and serialize captures within that browser.
The capture misses the slow work. The trace started after the work began or stopped before the work completed. Move start immediately before the action and stop after a meaningful completion condition.
The trace is hard to interpret or very large. The time window is too broad, screenshots are enabled unnecessarily, or too many categories are collected. Narrow the interval; leave screenshots off unless useful; choose categories deliberately.
Navigation times out before tracing stops. The page did not satisfy the chosen navigation condition within the timeout. Choose a navigation condition appropriate to the page, set a deliberate timeout, and ensure tracing cleanup runs in finally. Do not treat a timeout as proof that the page is fully loaded.
Chrome cannot launch. The environment lacks a compatible browser or required system dependencies, commonly when using puppeteer-core. Use puppeteer for its downloaded compatible Chrome, or configure puppeteer-core with an installed browser and satisfy the platform requirements.

8. Trace recording is different from screen recording

A performance trace is structured timeline data for analysis. Puppeteer’s separate page.record() API is experimental and uses Chrome’s screen-recording protocol to produce an MP4 video stream. Use tracing when you need performance timeline data; use the recording API when you need a visual video. Puppeteer Page API

Or skip the browser setup

If you need a screenshot artifact rather than a performance trace, ScreenshotNeo returns a screenshot or PDF from one GET request. It does not produce Puppeteer performance trace data.

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 request options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up free for 1,000 screenshots a month.

FAQ

Can I record two pages at once in one browser?

No. Puppeteer documents one active trace per browser. Use sequential captures or separate browser instances.

Does tracing capture screenshots by default?

No. The documented screenshots default is false; set it to true when visual frames are useful.

Can I use the trace without writing a file?

Yes. Omit path and handle the Uint8Array that tracing.stop() can return.

Is a performance trace an MP4?

No. Tracing produces timeline trace data. Puppeteer’s separate experimental page.record() feature produces an MP4 stream.