How to Stop CSS Coverage in Puppeteer
Stop Puppeteer CSS coverage after the navigation and interactions you want to measure. Learn what the returned entries mean, how to calculate usage, and how to troubleshoot gaps.
Call await page.coverage.stopCSSCoverage() after the navigation and interactions you want to measure. It stops CSS coverage collection and returns an array of reports for stylesheets. It does not remove CSS for you, and one page run cannot establish that styles are unused across every route or interface state.
Minimal example
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.coverage.startCSSCoverage();
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
// Perform the interactions whose CSS usage should be measured.
const entries = await page.coverage.stopCSSCoverage();
console.log(entries);
} finally {
await browser.close();
}
})();
Start collection before the activity you want included and stop it afterward. If navigation fails or an earlier operation throws, execution may never reach the stop call; use a finally block or error handling suited to your script so the browser is still closed.
What the result contains
The method resolves to coverage entries for stylesheets. Each entry includes stylesheet information such as its URL, source text, and used ranges. The ranges identify portions observed during the captured run; they are input for analysis, not a safe-to-delete stylesheet plan. See Puppeteer’s stopCSSCoverage API reference and the Coverage class example.
Calculate observed CSS usage
Puppeteer’s documented example estimates used and total bytes from the returned text and ranges. This percentage describes the browser activity you captured; it is not proof that the remaining CSS can be deleted.
const entries = await page.coverage.stopCSSCoverage();
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;
}
}
const percentUsed = totalBytes === 0 ? 0 : (usedBytes / totalBytes) * 100;
console.log({ totalBytes, usedBytes, percentUsed });
The formula follows Puppeteer’s example. JavaScript string length counts UTF-16 code units, so interpret this as the documentation’s estimate rather than a compressed network-transfer measurement. A stylesheet can appear mostly unused because the run did not visit every route or exercise menus, dialogs, validation errors, responsive layouts, and other states that need its rules.
Measure the states that matter
- Create the page and start CSS coverage before navigating.
- Navigate to the target route.
- Exercise the controls, routes, and viewport conditions whose styles you want represented.
- Stop coverage after those actions and inspect the entries.
- Repeat with other representative routes or states before using the data to guide cleanup.
Coverage is collected for the activity between start and stop. If you need to understand navigation behavior, check the option documentation for your installed Puppeteer version: startCSSCoverage() documents resetOnNavigation, whose documented default is true. Set it explicitly when that behavior matters to your measurement, and verify it against the version you use.
Important limitation: injected style tags
Puppeteer documents that “CSS Coverage doesn’t include dynamically injected style tags without sourceURLs.” If a framework or script adds styles at runtime, some injected styles may therefore be absent from the report. Do not treat a missing entry as proof that the corresponding styling is irrelevant.
Common errors and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
stopCSSCoverage is not a function |
The value is not a Puppeteer Page, or the code is using a different API or incompatible setup. |
Call it on the page returned by browser.newPage() or another Puppeteer page, and check the installed Puppeteer documentation and version. |
| The result is empty or has few entries | Coverage may have started after navigation, the page may not have loaded stylesheets, or the run may have exercised little content. | Start before navigation, confirm the page loaded the expected stylesheets, and include the interactions and routes of interest. |
| Injected styles seem absent | Dynamic style tags without sourceURLs are not included by CSS coverage. | Account for this limitation separately; do not infer that those styles are unused from this report alone. |
| The usage percentage changes between runs | The page state, interactions, navigation behavior, or loaded content changed. | Make the capture steps repeatable, record the states exercised, and configure navigation reset behavior deliberately. |
| Coverage is never stopped after a failure | An earlier navigation or interaction threw before the stop call. | Use try/finally to close the browser reliably, and structure error handling so cleanup runs even when measurement fails. |
Reliability, performance, and safe cleanup
The report is only as representative as the routes and UI states exercised. For a site-wide cleanup, collect observations across the meaningful pages and states, including responsive variants where they matter. Keep the capture sequence consistent so changes in the report are easier to interpret.
Coverage collection adds measurement work to the browser run, but the cited API documentation provides no benchmark from which to promise a fixed overhead. Measure it in your own workflow if runtime matters. The API reference likewise does not establish a monetary price for using this Puppeteer method; practical costs depend on the browser infrastructure and runtime you choose.
Before removing a rule, validate the change across your site’s routes and interactive states. Treat coverage as evidence about one observed run, not a guarantee of safe deletion.
Or skip the browser setup
If you need a screenshot of the page while investigating its rendered appearance, ScreenshotNeo is a website screenshot API and MCP server. Its screenshot call is separate from Puppeteer’s CSS coverage: it captures an image or PDF, not stylesheet usage data.
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 require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
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. Its 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 for 1,000 free screenshots a month, with no card required.
FAQ
Does stopping coverage remove unused CSS?
No. It returns observed coverage data. Removing styles and validating the result are separate tasks.
Can one run identify all unused CSS on my site?
No. It reports the page activity that was captured. Other routes and interaction states may use styles that this run did not encounter.
Does CSS coverage include every dynamically added style?
No. Puppeteer’s documentation calls out dynamically injected style tags without sourceURLs as a limitation.
Should I use page.coverage.stopCSSCoverage() or CSSCoverage.stop()?
For ordinary page code, use the public page workflow shown in Puppeteer’s Coverage documentation. The internal Coverage class constructor is not the normal page API.


