Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The reliable way to benchmark Puppeteer is to measure one precisely defined browser task under a fixed environment, repeat it, report a distribution, and use tracing and page.metrics() to explain outliers. A single page.goto() time is not a universal Puppeteer score: it changes with readiness criteria, cache state, network, browser build, machine load, and whether diagnostics are enabled.

1. Define exactly what “performance” means

Start with a question that has one start boundary, one end boundary, and one representative workload. Examples include:

  • Navigation: elapsed time from issuing page.goto() until a named application-ready selector appears.
  • Interaction: elapsed time from clicking a control until the resulting UI reaches a stable state.
  • Rendering: browser work and visual activity during a defined interval, diagnosed with a trace.

Do not compare a run that ends at the load event with one that ends when an application-specific selector appears. Those are different experiments. Write down whether the result represents website work, the automation workflow, or both. Keep navigation, waits, interactions, and output generation identical in every comparison.

2. Pin and record the test environment

Within a comparison, hold the environment constant and record it beside every result. Puppeteer releases are tightly bundled with browser revisions to preserve protocol compatibility; replacing the bundled browser is done at the user’s risk. Record the exact Puppeteer package version and browser version rather than saying only “Chrome.”

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
MixPad Free Multitrack Recording Studio and Music Mixing Software [Download]
  • Create a mix using audio, music and voice tracks and recordings.
  • Customize your tracks with amazing effects and helpful editing tools.
  • Use tools like the Beat Maker and Midi Creator.
  • Work efficiently by using Bookmarks and tools like Effect Chain, which allow you to apply multiple effects at a time
  • Use one of the many other NCH multimedia applications that are integrated with MixPad.
  • Operating system, CPU and memory class, and whether other CPU-heavy jobs are running.
  • Puppeteer and browser versions (including the browser revision used by Puppeteer).
  • Headless or headful mode, viewport dimensions, device scale factor, and any device emulation.
  • Network path, proxy, DNS conditions, and whether CPU or network throttling is active.
  • Concurrency: number of browser processes, pages, workers, and other tests sharing the host.
  • Cache and storage policy: cold first visit, warm repeat visit, or a deliberately reset profile.

Chrome guidance notes that extensions can add noise. Use a clean profile unless extensions are part of the workload. Clearing storage models first-visit behavior; preserving storage models repeat visits. Decide whether warm-up runs are excluded before looking at the results, and apply that rule to every variant. DevTools CPU throttling is relative to the host computer, not an exact simulation of a phone’s processor, so report the setting and do not label it as a specific mobile device.

3. Build a repeatable Node.js harness

The following script measures an application-ready navigation, captures supporting browser metrics, and writes one JSON record per run. Replace the URL and selector with the task you actually want to compare.

const puppeteer = require('puppeteer');

const URL = 'https://example.com';
const READY_SELECTOR = 'body';
const RUNS = 10;

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const results = [];

  try {
    for (let i = 0; i < RUNS; i++) {
      const page = await browser.newPage();
      await page.setViewport({ width: 1365, height: 768, deviceScaleFactor: 1 });

      const start = process.hrtime.bigint();
      await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 90000 });
      await page.waitForSelector(READY_SELECTOR, { visible: true, timeout: 90000 });
      const end = process.hrtime.bigint();

      const metrics = await page.metrics();
      results.push({
        run: i + 1,
        elapsed_ms: Number(end - start) / 1e6,
        metrics
      });
      await page.close();
    }
  } finally {
    await browser.close();
  }

  console.log(JSON.stringify(results, null, 2));
})();

process.hrtime.bigint() is a monotonic Node-side clock, suitable for end-to-end elapsed time. The timed interval includes navigation and the readiness wait, so it answers “how long did this automated task take?” It does not claim to isolate page rendering from Puppeteer orchestration.

If the task is an interaction, place the start clock immediately before the action and the end clock after the stable-state condition. Avoid arbitrary sleeps when a selector, application signal, or network-idle rule can express readiness. If a delay is genuinely part of the user workflow, keep it identical in all runs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Use page.metrics() as diagnostics, not a speed score

Puppeteer’s metrics API reports cumulative browser values for the sampled page. Depending on the browser revision, fields can include:

Metric What it helps answer Important limitation
TaskDuration How much browser task time accumulated Not wall-clock test duration
ScriptDuration How much JavaScript execution accumulated Interpret with the workload and trace
LayoutDuration and RecalcStyleDuration How much layout and style recalculation accumulated Do not identify the responsible code by themselves
Documents, Frames, Nodes, JSEventListeners Structural complexity during the sample Counts are not direct measures of perceived speed
JSHeapTotalSize and JSHeapUsedSize JavaScript heap size at collection time Values depend on page state and garbage collection
LayoutCount and RecalcStyleCount How often layout or style recalculation occurred Frequency alone does not establish user impact

The Timestamp value is monotonic seconds from an arbitrary point, not a wall-clock date. Durations are browser-reported cumulative values; compare like with like and keep the exact sampling point consistent. A high TaskDuration with a high ScriptDuration suggests scripting work worth investigating. High layout or style values suggest rendering work, but a trace is needed to locate the cause.

5. Repeat runs and summarize the distribution

Run enough repetitions to reveal variability, then publish the run count, raw observations when practical, a median, and a spread measure such as minimum–maximum or percentile values. There is no official Puppeteer rule that prescribes a particular repetition count or statistic, so state your methodology instead of presenting one as a standard.

Do not report only the fastest run. A slow run may reflect contention, a cache miss, a transient network delay, or a real page problem. Define invalid-run rules before examining results: for example, treat a navigation timeout as a failed run and report its count rather than silently deleting it. If you exclude an initial warm-up, say how many were excluded and apply that choice to every candidate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A small Node summarizer for the JSON produced above can calculate median and range:

function median(values) {
  const sorted = [...values].sort((a, b) => a - b);
  const middle = Math.floor(sorted.length / 2);
  return sorted.length % 2
    ? sorted[middle]
    : (sorted[middle - 1] + sorted[middle]) / 2;
}

const times = results.filter(r => Number.isFinite(r.elapsed_ms))
                     .map(r => r.elapsed_ms);
console.log({
  runs: times.length,
  median_ms: median(times),
  min_ms: Math.min(...times),
  max_ms: Math.max(...times)
});

Keep the raw data with the report. A median without the number of runs, environment, readiness condition, and cache policy cannot be reproduced or fairly compared.

6. Trace a separate diagnostic run

When timings change, tracing shows where browser activity occurred. Keep profiled runs separate from headline timing runs because collecting detailed diagnostics can alter the workload. Puppeteer permits one active trace per browser and can write trace bytes to a file.

const fs = require('fs');
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.setViewport({ width: 1365, height: 768, deviceScaleFactor: 1 });

  await page.tracing.start({
    path: 'puppeteer-trace.json',
    screenshots: false,
    categories: ['devtools.timeline', 'disabled-by-default-devtools.timeline']
  });
  await page.goto('https://example.com', {
    waitUntil: 'domcontentloaded',
    timeout: 90000
  });
  await page.waitForSelector('body', { visible: true, timeout: 90000 });
  await page.tracing.stop();

  await browser.close();
  console.log('Trace written to puppeteer-trace.json');
})();

Open the trace in Chrome DevTools or a compatible timeline viewer. Look for long scripting, rendering, network, or idle intervals and correlate them with your task boundaries. The DevTools Performance panel also supports User Timing marks and measures, runtime recordings, and throttling. Add marks around meaningful phases when a single elapsed number is not enough to explain a regression.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

7. Compare the right cases

Comparison Hold constant or disclose Question it can answer
Cold versus warm Profile reset and storage policy First-visit or repeat-visit behavior?
Unthrottled versus throttled Exact CPU/network settings and host hardware How does the workflow react to constrained conditions?
Headless versus headful Browser build, viewport, and all waits Does display mode alter this workload?
Automation stacks Same task, readiness condition, browser, and host Which orchestration fits this workload, not which is universally faster?
Raw versus profiled Whether tracing or other instrumentation is enabled What changed, and what diagnostic overhead was introduced?

Puppeteer’s stated design principle is “almost zero performance overhead over an automated page.” That is a project goal, not a measured guarantee for every workload; do not turn it into an overhead percentage without your own controlled experiment. Likewise, a local synthetic benchmark describes your selected machine and conditions, not all real users. Where available, compare laboratory findings with field data rather than treating one as a substitute for the other.

8. Troubleshooting slow or unstable benchmarks

Every run times out

Check that the URL is reachable from the benchmark host, raise the timeout only when the task legitimately needs it, and verify that the readiness selector exists in the tested state. Log the navigation error and classify the run as failed instead of converting it to an unusually large duration.

Results vary widely

Look for concurrent jobs, background updates, shared browser processes, changing cache state, extensions, proxy variance, and network contention. Use a clean profile, isolate the host, and separate cold and warm experiments.

Metrics look high but elapsed time is normal

Metrics are cumulative browser diagnostics, not wall-clock duration. Confirm that you sampled at the same boundary, then use a trace to determine whether scripting, layout, style recalculation, or repeated tasks explain the values.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Headless and headful numbers disagree

That is a different experiment. Keep mode fixed for a focused comparison, or report the mode change explicitly along with viewport, display environment, and browser version.

Tracing fails to start

Only one trace can be active per browser. Stop an existing trace before starting another, and ensure the output path is writable. Keep tracing out of the headline timing loop if it changes results.

A throttled test does not resemble a phone

DevTools throttling scales relative to the current host and cannot reproduce a phone’s processor architecture. Report the host and throttle settings, and avoid claiming device-equivalent results.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

9. Automate reporting and preserve evidence

Store each run’s elapsed time, success or failure, browser and Puppeteer versions, environment settings, cache policy, and diagnostic metrics. Record the exact script, URL revision or test data, readiness condition, and concurrency. A useful report lets another engineer rerun the same workload and distinguish a page regression from host noise. When comparing browser versions or environments, either keep the rest fixed or label the environment change as part of the experiment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If your goal is repeatable website captures rather than measuring Puppeteer itself, ScreenshotNeo provides a single HTTP request that returns 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. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

See the ScreenshotNeo API documentation for all options. A minimal cURL call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The equivalent Python request is:

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 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 also includes an MCP server so Claude, Cursor, and other MCP clients can call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account to try it.

FAQ

Is there an official number of Puppeteer runs I must perform?

No. Official documentation does not prescribe a universal repetition count or summary statistic. Choose enough repetitions to expose variability and document the choice.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can page.metrics() replace a trace?

No. Metrics summarize cumulative browser values; tracing supplies a timeline that can locate the activity responsible for a change.

Should I benchmark with the browser bundled by Puppeteer?

For a controlled comparison, yes, or document any replacement precisely. Puppeteer bundles browser revisions for protocol compatibility, and substituting another browser is at the user’s risk.

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.