ScreenshotNeo

BlogHow-to

How to Configure JavaScript Coverage in Puppeteer

Collect Puppeteer JavaScript coverage across page loads and interactions, configure all four options, and handle navigation, anonymous scripts, and reporting.

By the ScreenshotNeo team4 October 20266 min read

Puppeteer JavaScript coverage records which parts of scripts ran while you exercise a page. Start it before navigation or the interactions you want to measure with page.coverage.startJSCoverage(), then call page.coverage.stopJSCoverage() to get the entries. The four options control block or function granularity, anonymous scripts, raw V8 data, and navigation resets. For reliable coverage across documents, stop collection before navigating away, start it again on the next document, and merge the reports; setting resetOnNavigation: false does not guarantee that Chrome will preserve coverage.

1. Collect JavaScript coverage in Puppeteer

This runnable ES module example starts coverage before loading the page, exercises a button if one exists, and prints a byte-based summary. Install Puppeteer with npm install puppeteer, save this as coverage.mjs, and run node coverage.mjs.

import puppeteer from 'puppeteer';

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

  await page.coverage.startJSCoverage({
    resetOnNavigation: true,
    reportAnonymousScripts: false,
    includeRawScriptCoverage: false,
    useBlockCoverage: true,
  });

  await page.goto('https://example.com', { waitUntil: 'networkidle2' });
  // Add the interactions your test needs before stopping coverage.
  const entries = await page.coverage.stopJSCoverage();

  let totalBytes = 0;
  let usedBytes = 0;
  for (const entry of entries) {
    totalBytes += entry.text.length;
    for (const range of entry.ranges) {
      usedBytes += range.end - range.start - 1;
    }
  }

  console.log({
    scripts: entries.length,
    totalBytes,
    usedBytes,
    byteCoveragePercent: totalBytes
      ? (usedBytes / totalBytes) * 100
      : 0,
  });
} finally {
  await browser.close();
}

The percentage here follows Puppeteer’s documented byte-coverage example. It is a useful summary of covered source bytes, not a universal measure of test quality. Read the startJSCoverage reference and your installed Puppeteer release’s API docs when relying on version-specific behavior.

2. Choose the coverage options

All four options can be omitted; these are the documented defaults. Choose explicitly when the output needs to be reproducible or when a particular kind of script matters.

Option Default Effect Use it when
useBlockCoverage true Collects block-level coverage. Set to false for function-level collection. You need to distinguish executed code blocks, or want coarser function-level results.
reportAnonymousScripts false Includes scripts without a URL, such as scripts created with eval or new Function. Your application or test harness generates scripts dynamically.
includeRawScriptCoverage false Adds the raw V8 script coverage object to each applicable entry. Your downstream tooling needs the underlying V8 data as well as Puppeteer’s ranges.
resetOnNavigation true Resets coverage on navigation. You want the normal per-document behavior. Do not depend on setting it to false to retain data from a previous document.

For anonymous scripts, reported URLs generally begin with debugger://VM. A //# sourceURL=... comment gives a generated script a source URL, so it can be reported as a named script. Turn on anonymous reporting only if these scripts are relevant to the measurement; generated code can add noise to reports. See the JSCoverageOptions reference.

3. Capture coverage across navigation

Coverage collection belongs to a page’s JavaScript execution environment. Chrome may discard that environment and its coverage data when a page navigates, so resetOnNavigation: false does not promise that earlier data survives. To measure multiple documents, stop before leaving each one, start collection for the next, and combine the resulting entries in your reporting pipeline.

async function collectDocumentCoverage(page, url, exercise) {
  await page.coverage.startJSCoverage();
  await page.goto(url, { waitUntil: 'networkidle2' });
  await exercise(page);
  return await page.coverage.stopJSCoverage();
}

const firstEntries = await collectDocumentCoverage(
  page,
  'https://example.com/',
  async () => {},
);
const secondEntries = await collectDocumentCoverage(
  page,
  'https://example.com/next',
  async () => {},
);

const allEntries = [...firstEntries, ...secondEntries];

This example treats each navigation as a separate collection. If the same script appears in both reports, decide in your reporting layer whether to preserve both observations or consolidate by URL and script content. The appropriate merge depends on the report format and what you intend to measure.

4. Read, export, and interpret entries

stopJSCoverage() returns an array of entries. Each entry represents a script and includes its text and coverage ranges; raw V8 coverage is included when requested. The byte summary in the first example totals the script text and used range lengths. An empty result can be legitimate if no scripts ran during the collection window or the page did not load as expected.

For Istanbul-compatible output, Puppeteer’s Coverage reference points to the separate puppeteer-to-istanbul package. The following shows the collection and conversion shape; install the converter separately and consult its package documentation for the API supported by the version you use.

import puppeteer from 'puppeteer';
import pti from 'puppeteer-to-istanbul';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.coverage.startJSCoverage();
  await page.goto('https://example.com');
  const coverage = await page.coverage.stopJSCoverage();

  pti.write(coverage);
} finally {
  await browser.close();
}

See the official Coverage class reference, stopJSCoverage reference, and JSCoverageEntry reference. (The stop method URL is https://pptr.dev/api/puppeteer.coverage.stopjscov erage.)

5. Troubleshoot common coverage problems

Symptom Likely cause Fix
No entries or unexpectedly empty coverage Collection started after the scripts ran, the page did not load, or the relevant code was never exercised. Start before navigation, confirm the navigation succeeded, and perform the user actions that execute the target code before stopping.
Scripts from before a navigation are missing Chrome discarded the previous document’s execution environment and coverage during navigation. Stop before navigating away, start on the next page, and merge reports yourself.
Generated scripts do not appear Anonymous scripts are excluded by default. Set reportAnonymousScripts: true, or add a //# sourceURL to generated code where appropriate.
Coverage includes little code despite a successful page load Coverage records executed code, not every available branch or route. The test may not have triggered the relevant UI or lazy behavior. Exercise the interactions and states whose execution you want measured, then stop collection afterward.
Raw V8 field is absent includeRawScriptCoverage is false by default. Enable it when the downstream consumer needs raw V8 data.
Results are coarser than expected Function-level mode was selected. Set useBlockCoverage: true for block-level data; this is also the default.
Converter output is missing or incompatible Istanbul conversion is handled by an additional package, whose interface may vary by version. Install and check the version of puppeteer-to-istanbul, then follow its API for the installed version.

6. Performance, reliability, and cost

Coverage adds instrumentation and produces data that grows with the scripts and execution observed. Keep collection scoped to the page activity you need, avoid raw V8 data unless a consumer needs it, and write or process results after stopping. Wait conditions such as networkidle2 affect when your script proceeds; they do not change the coverage options. Choose a wait condition appropriate for the site, since pages with long-lived network activity may not reach an idle state promptly.

For repeatable results, keep the Puppeteer version fixed, use the same navigation and interaction sequence, and record whether collection is block-level or function-level. Coverage describes the execution captured in that run; it does not establish that unexecuted code is defective, nor does a high percentage alone demonstrate that behavior is correct. Puppeteer coverage itself has no separate per-capture fee; compute and storage costs depend on the environment and reporting pipeline you choose.

7. Or skip the browser setup

If your goal is a visual record of a page rather than JavaScript execution coverage, ScreenshotNeo captures a website with one GET request. It does not produce Puppeteer coverage data. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.

cURL:

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)
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 request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

See the ScreenshotNeo API documentation, then sign up for 1,000 free screenshots a month with no card.

8. FAQ

How do I configure JavaScript coverage in Puppeteer?

Call page.coverage.startJSCoverage(options) before the page activity to measure, then call page.coverage.stopJSCoverage() and process its returned entries.

Why is my coverage empty after navigation?

Coverage may be reset or discarded with the old document. Start collection before the navigation you want to measure, and stop before leaving a document when you need its data.

Does Puppeteer coverage automatically create an Istanbul report?

No. Use a separate converter such as puppeteer-to-istanbul to produce Istanbul-compatible output.

How do I include dynamically generated scripts?

Set reportAnonymousScripts: true to include scripts without a URL. A generated script can also use a //# sourceURL comment to provide a name.