How to Start a Performance Trace with Puppeteer
Use Puppeteer’s tracing API to capture a page load or interaction, save the trace, and inspect it in Chrome DevTools. Includes options and troubleshooting.
Use Puppeteer’s page.tracing API: start tracing before the page load or interaction you want to measure, perform that work, then stop tracing. Pass a path to save a JSON trace file:
await page.tracing.start({ path: 'trace.json' });
await page.goto('https://example.com');
await page.tracing.stop();
Open the resulting file in Chrome DevTools or a timeline viewer. The Puppeteer Tracing API reference documents this start–action–stop sequence. Tracing options can evolve, so check the reference for the version installed in your project.
Complete runnable example
Install Puppeteer, save this script as trace-page.js, and run it with Node.js. Puppeteer launches a browser, records navigation, writes trace.json in the current directory, then closes the browser even if an error occurs.
npm install puppeteer
const puppeteer = require('puppeteer');
(async () => {
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: 'networkidle0',
timeout: 30000,
});
} finally {
// Stop tracing even when navigation fails, so the trace can be saved.
await page.tracing.stop();
}
console.log('Trace saved to trace.json');
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The networkidle0 wait condition waits for no active network connections; pages with persistent connections may never reach it. If that applies, use domcontentloaded or load, or wait for a page-specific selector before stopping the trace.
Trace a specific interaction
To investigate a click, route change, or other interaction, navigate to the starting state first, then bracket only the action you want to study. This keeps unrelated startup work out of the capture.
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.tracing.start({ path: 'interaction-trace.json' });
await page.click('#open-menu');
await page.waitForSelector('.menu-panel');
await page.tracing.stop();
Use a selector that represents the meaningful end state. A fixed delay can work for animations, but a selector or other explicit condition is generally easier to interpret and less sensitive to machine speed.
Save to a file or keep trace bytes
The path option writes trace data to disk. If you omit it, tracing.stop() returns the trace as a Uint8Array, which you can save, upload, or process yourself.
const traceBytes = await page.tracing.stop();
require('node:fs').writeFileSync('trace.json', traceBytes);
Choose file output for a straightforward local workflow. Use returned bytes when the trace should be sent directly to another system or stored under a dynamically chosen name. Do not assume a trace is small: detailed recordings can consume substantial memory and disk space.
Configure categories and screenshots
tracing.start() accepts optional settings. Categories determine which trace event groups are collected. A category prefixed with - is excluded. The screenshots option enables screenshot events in the trace.
await page.tracing.start({
path: 'trace.json',
categories: [
'devtools.timeline',
'v8.execute',
'disabled-by-default-devtools.screenshot',
],
screenshots: true,
});
Category names are Chromium tracing categories; use a focused set suited to the question you are investigating. More categories and screenshots can increase trace size and make interpretation noisier. See the Puppeteer tracing options reference for supported options, including buffer settings.
| Option | Purpose | Consideration |
|---|---|---|
path |
Writes the trace to a file | Ensure the process can write to the destination. |
categories |
Selects or excludes trace event groups | Start with the events needed for the investigation. |
screenshots |
Includes screenshots in the recording | Can make the trace larger. |
bufferSize |
Controls trace buffer capacity | Chromium’s documented default is 200 MB when omitted or zero; large captures can still run out of resources. |
Inspect the trace
- Finish the capture and confirm the output file exists and is non-empty.
- Open Chrome DevTools and use its Performance panel to load the saved trace, or use the timeline viewer referenced in the Puppeteer documentation.
- Inspect the recorded time range around the navigation or interaction. Look for long tasks, scripting, rendering, and the event sequence related to the behavior you are investigating.
- Repeat with the same start and stop boundaries after changing one variable at a time.
For lower-level tracing control, the Chrome DevTools Protocol Tracing documentation describes tracing start/end operations and trace data transfer modes. Most Puppeteer scripts should use page.tracing.
Reliability, performance, and cost
- One active trace: only one trace can be active per browser. Stop the current trace before starting another, including when handling exceptions.
- Capture boundaries matter: tracing too early or stopping too late adds unrelated events and makes the trace harder to read. Start immediately before the work under investigation.
- Resource use: traces use memory and produce files that may be large, especially with many categories or screenshots. Keep captures focused and close the browser when finished.
- Repeatability: network conditions, cache state, browser version, and page content can change between runs. Record the relevant setup when comparing captures; a trace is diagnostic evidence, not by itself proof of a universal performance result.
- Cost: Puppeteer tracing has no per-capture service fee, but browser execution consumes your machine or CI resources and trace storage. Manage retention and avoid keeping sensitive page data longer than needed.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Trace file is missing or empty | The trace was not stopped successfully, the path is not writable, or the process exited early. | Await page.tracing.stop(), use a writable path, and stop tracing in a finally block. |
| “Tracing is already started” or similar | A trace is already active in the browser. | Stop the existing trace before starting another; do not overlap captures across pages in the same browser. |
| Navigation hangs before trace stops | The chosen wait condition, often network idle, is not reached because of long polling or persistent connections. | Use a different waitUntil condition and wait for the specific page state you need. |
| Trace omits the event of interest | Tracing started after the event or stopped before it completed, or the relevant category was excluded. | Move the start/stop calls to bracket the event and review the category list. |
| Trace is too large or costly to inspect | The capture window is too broad, too many categories are enabled, or screenshots were included. | Narrow the capture and collect only needed categories; disable screenshots unless useful. |
| Returned trace value is not a file path | No path was supplied. |
Use the returned Uint8Array and write it with fs.writeFileSync, or specify a path at start. |
Or skip the browser setup
If your goal is a clean screenshot rather than a Chrome performance trace, ScreenshotNeo captures a URL with one API request. It does not produce Puppeteer trace data; use the DIY workflow above when you need a performance timeline. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
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 request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Can one Puppeteer browser trace multiple pages at once?
No. Puppeteer documents one active trace per browser, so serialize captures and stop each one before beginning the next.
Does a trace measure only page load performance?
No. You can record any bounded browser activity, including an interaction, by starting immediately before it and stopping after its meaningful completion.
Is a Puppeteer trace the same as a screenshot?
No. A trace records browser timeline events for performance analysis. A screenshot is a rendered image of the page at a moment or capture state.


