How to Stop JavaScript Coverage in Puppeteer
Stop Puppeteer JavaScript coverage with `page.coverage.stopJSCoverage()`, collect its reports, and avoid losing data across navigations.
Stop JavaScript coverage in Puppeteer with await page.coverage.stopJSCoverage(). Call it after the interactions you want to measure; it resolves with an array of coverage reports. Coverage must first be started on that page with startJSCoverage(). The returned reports contain script text and ranges that were executed. Puppeteer Coverage API.
Minimal working example
This Node.js example starts coverage, loads a page, waits for it to settle, and stops collection. Replace the URL and interaction with the journey you want to inspect.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.coverage.startJSCoverage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
// Perform the actions whose JavaScript usage you want to measure.
await page.locator('body').wait();
const reports = await page.coverage.stopJSCoverage();
console.log(`Collected ${reports.length} script reports`);
for (const report of reports) {
console.log(report.url, report.text.length, report.ranges);
}
} finally {
await browser.close();
}
If your Puppeteer version does not support page.locator(), omit that illustrative wait or use a page wait appropriate to your application. The essential sequence is start, navigate and interact, then stop.
What stopJSCoverage returns
The result is an array with a report for each reported script. Each report includes the script URL, script text, and executed ranges. Anonymous scripts are excluded unless requested when coverage starts. The stop call is both the end of collection and the point where you receive the collected entries.
You can estimate used bytes from the ranges. Puppeteer’s example sums range.end - range.start - 1 for each range and compares that number with the script text length. A script may contain several ranges; sum all of them rather than treating each script as one uninterrupted block.
function summarizeCoverage(reports) {
return reports.map(({ url, text, ranges }) => {
const usedBytes = ranges.reduce(
(total, range) => total + range.end - range.start - 1,
0,
);
return {
url,
scriptBytes: text.length,
usedBytes,
unusedBytes: Math.max(0, text.length - usedBytes),
};
});
}
This is a byte-range-oriented summary following the API example, not a complete source map or a guarantee that the result reflects every possible user journey. Exercise the interactions you care about before stopping.
When to stop collection
- Start coverage before the navigation or interaction you intend to measure.
- Load the page and perform the relevant actions, including opening menus or dialogs if those paths matter.
- Stop coverage as soon as that measurement window is complete.
- Persist or process the returned reports before closing the browser or discarding the result.
Coverage reports describe the execution observed during that collection window. Stopping too early omits later interactions. Stopping after a navigation may be too late if Chrome has already discarded the prior page’s execution context.
Navigation and multi-page journeys
resetOnNavigation defaults to true. Setting it to false does not guarantee that coverage data survives navigation: Chrome may discard the previous page’s JavaScript execution environment and its coverage data. For a journey across pages, stop before leaving each page, start a fresh collection on the next page, and merge the returned reports in your own application. startJSCoverage options.
await page.coverage.startJSCoverage();
await page.goto('https://example.com/first');
// Exercise the first page.
const firstPageReports = await page.coverage.stopJSCoverage();
await page.coverage.startJSCoverage();
await page.goto('https://example.com/second');
// Exercise the second page.
const secondPageReports = await page.coverage.stopJSCoverage();
const journeyReports = [...firstPageReports, ...secondPageReports];
Appending arrays preserves each page’s report entries. If your downstream report needs one entry per URL, define a merge policy deliberately: two page visits can have the same script URL but different executed ranges. The API does not merge those visits automatically.
Include anonymous scripts when needed
Scripts without an associated URL, including code produced by eval or new Function, are omitted by default. Enable reportAnonymousScripts at start time to include them. Puppeteer documents generated URLs in the debugger://VM form unless the script supplies a //# sourceURL comment.
await page.coverage.startJSCoverage({
reportAnonymousScripts: true,
});
await page.goto('https://example.com');
// Exercise code that may create anonymous scripts.
const reports = await page.coverage.stopJSCoverage();
The relevant documented choices are resetOnNavigation and reportAnonymousScripts. The navigation option is not a preservation guarantee; use explicit stop and restart boundaries when the result must be retained across pages.
Exporting or combining results
Keep the raw array if you need to choose an export format later. Puppeteer’s API documentation points to puppeteer-to-istanbul as a path for converting coverage to an Istanbul-consumable form. CSS coverage is separate: starting or stopping JavaScript coverage does not start or stop CSS coverage. Use the corresponding CSS coverage methods when measuring stylesheets. Coverage API and examples.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The stop call fails because coverage is not active | Coverage was not started on this page, or it was already stopped. | Pair each stop with a successful startJSCoverage() call and avoid stopping the same collection twice. |
| Reports are empty or missing expected scripts | The page may not have executed scripts during the collection window, or anonymous scripts are excluded by default. | Start before the relevant work, perform the target interactions, and set reportAnonymousScripts: true if URL-less scripts matter. |
| Coverage from an earlier page is missing | Navigation can discard the old execution environment even when resetOnNavigation is false. |
Stop before navigation, start again on the next page, and combine the returned arrays in application code. |
| The report omits code behind a menu or dialog | That code path was not run during collection. | Exercise the menu, dialog, or other conditional behavior before stopping. |
| Expected CSS entries are absent | Only JavaScript coverage was collected. | Use Puppeteer’s separate CSS coverage methods for stylesheet coverage. |
Runtime, reliability, and cost considerations
Coverage collection is instrumentation around browser execution, so keep the collection window limited to the page and interactions you intend to analyze. Puppeteer’s cited API reference does not publish a general performance overhead figure; measure the effect in your own workload if timing or resource use matters.
For reliable multi-page results, stop and save at each navigation boundary. A setting that disables reset cannot recover data the browser has discarded. Store raw reports before closing the browser, and make repeated visits and duplicate script URLs explicit in your merge strategy. Puppeteer itself is software rather than a per-screenshot service; any infrastructure or browser runtime cost depends on where you run it.
Or skip the browser setup
If your goal is a screenshot rather than JavaScript execution coverage, ScreenshotNeo returns a screenshot or PDF from one GET request. Its capture options include full-page shots, CSS selector capture, waits, viewport and device settings, and image formats. See the ScreenshotNeo API documentation.
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}`);
- 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 cost nothing, and response headers report the page verdict and billing status.
- An MCP server lets AI agents, including Claude and Cursor, take screenshots.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Does stopJSCoverage return a file?
No. It returns report objects in memory. Save or convert those reports with your own code or an export tool.
Can I use the returned reports to find unused JavaScript?
They show executed ranges for the run you measured. They can help identify code not used in that run, but one journey cannot establish that code is unused for every route or user interaction.
Does stopping JavaScript coverage also stop CSS coverage?
No. JavaScript and CSS coverage use separate methods and report collections.


