What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
To capture XHR or Fetch traffic from a headless browser, attach request and response listeners before navigating or triggering the action that makes the call. In Playwright, use passive listeners to observe traffic and page.waitForResponse() to coordinate with a click or form submission. In Puppeteer, listen for responses; enable request interception only when you need to change or block traffic, because every intercepted request must be explicitly completed.
Capture requests passively in Playwright
Playwright’s request event is useful for outgoing request details such as method and URL. Its response event gives you the response status and access to the associated request. Register both listeners before page.goto(); attaching them afterward risks missing requests made during initial page load.
import { chromium } from 'playwright';
const targetUrl = 'https://example.com';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
page.on('request', request => {
console.log('>>', request.method(), request.resourceType(), request.url());
});
page.on('response', response => {
const request = response.request();
const type = request.resourceType();
if (type === 'xhr' || type === 'fetch') {
console.log('<<', response.status(), response.url());
}
});
try {
await page.goto(targetUrl);
// Trigger any additional UI action here while the listeners remain attached.
} finally {
await browser.close();
}
The filter narrows output to requests Playwright classifies as xhr or fetch. Remove it temporarily if you are unsure how the application’s traffic is classified, or if you also need documents, scripts, images, or other resource types. A response with status 404 or 503 is still an HTTP response: log it as a response, not as a transport failure.
Free tools Windows power users keep installed
One-click scans. No signup required.
Wait for a request caused by a click
For a known API call, a waiter is more reliable than watching console output and guessing which response belongs to the action. Create the promise before clicking so a fast response cannot arrive before the waiter is armed. Match as narrowly as practical: a path alone might match several requests, so include the method or other stable details when useful.
#1 Best Overall
const apiResponsePromise = page.waitForResponse(response =>
response.url().includes('/api/data') &&
response.request().method() === 'GET'
);
await page.getByRole('button', { name: 'Load data' }).click();
const apiResponse = await apiResponsePromise;
console.log(apiResponse.status(), apiResponse.url());
const body = await apiResponse.json();
console.log(body);
waitForResponse() accepts glob, regular-expression, and predicate matching. The predicate form is convenient when the relevant condition combines URL and request method. If the application issues similar calls from other interactions, make the predicate more specific so the waiter does not resolve on the wrong response.
Choose observation or interception
Passive listeners report what the page sends and receives without intentionally changing traffic. Routing is for intervention: blocking, rewriting, fulfilling, or aborting requests. Do not enable routing just to get a log; it changes the request lifecycle and adds work to your handler.
| Need | Playwright approach | Scope and consequence |
|---|---|---|
| Log outgoing request details | page.on('request') |
Observe requests from that page without modifying them. |
| Log status and response details | page.on('response') |
Observe responses and inspect the associated request. |
| Wait for one action-triggered response | page.waitForResponse() |
Synchronize the test or script with a matching response; arm it before the action. |
| Modify traffic for one page | page.route() |
Matching requests pause until the handler continues, fulfills, or aborts them. |
| Modify traffic across pages in a context | browserContext.route() |
Use when the same routing rule should apply across every page in that context. |
When both page and context routes match, the page route takes precedence. Install routes before navigation so they can affect requests from the beginning of the load.
await context.route('**/analytics/**', route => route.abort());
await context.route('**/api/data', async route => {
const response = await route.fetch();
const json = await response.json();
json.debug = true;
await route.fulfill({ response, json });
});
The first handler aborts matched analytics requests. The second fetches the original response, changes its JSON, then fulfills the browser request with the modified response. Keep such rules narrow: an overbroad route can change application behavior or prevent the page from loading correctly.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Capture background responses in Puppeteer
For observation, attach a response listener before navigation; request interception is not required just to record responses. The example below logs API-like response URLs while leaving requests untouched.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
page.on('response', response => {
if (response.url().includes('/api/')) {
console.log(response.status(), response.url());
}
});
try {
await page.goto('https://example.com');
// Trigger the relevant control here; the response listener stays active.
} finally {
await browser.close();
}
})();
If you need to abort, continue, or fulfill requests, enable interception and make sure every handler path resolves each intercepted request. An unanswered request stalls. If multiple handlers may process the same request, use Puppeteer’s interception-resolution guard pattern so a second handler does not try to resolve an already handled request.
await page.setRequestInterception(true);
page.on('request', request => {
if (request.resourceType() === 'image') return request.abort();
return request.continue();
});
page.on('response', response => {
if (response.url().includes('/api/')) {
console.log(response.status(), response.url());
}
});
This example blocks images and continues all other requests. It is an intervention, not a neutral logging setup. Add it only when image loading is unnecessary for the task, and ensure all matching requests are resolved even when your conditions branch.
Why a request may be missing
The listener was attached too late
Navigation can initiate requests immediately. Register listeners before page.goto(), and create a waitForResponse() promise before clicking or submitting the form that triggers the call.
Rank #3
A Service Worker handled the traffic
Playwright warns that page and context routing do not intercept requests handled by a Service Worker. If a route misses traffic that the page otherwise uses, create the browser context with serviceWorkers: 'block' and check whether routing then sees the request. Blocking workers changes the page’s environment, so use it when it suits the capture or test. If the goal is to inspect Service Worker activity itself, use the framework’s Service Worker support rather than assuming page routes will expose it.
const context = await browser.newContext({ serviceWorkers: 'block' });
const page = await context.newPage();
// Register routes or listeners before navigating.
The filter or match condition is too narrow
A listener filtered to xhr and fetch will omit other resource types. A response waiter for one path and method will not resolve for a different endpoint or method. Temporarily log all request URLs and resource types, then refine the filter after identifying the traffic that matters.
The page has a response, but it is an error status
Do not treat every unsuccessful status as a missing request. HTTP errors such as 404 and 503 are responses and appear in the response lifecycle. A transport-level failure instead emits requestfailed rather than the successful-response sequence of request, response, and request-finished. Record both response statuses and failures if you need to distinguish server replies from calls that did not complete at the transport level.
Interception stalled the page
Once Puppeteer interception is enabled, every request waits until it is continued, fulfilled, or aborted. Audit every handler branch for a resolution call. In Playwright, matching routed requests likewise remain pending until the route handler continues, fulfills, or aborts them. If you only wanted to inspect traffic, remove routing and use passive listeners instead.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
What to record and how to keep captures useful
A durable request log should let you associate each request with its response and understand what happened without retaining unnecessary sensitive data. For each relevant call, consider recording:
- Request URL, method, resource type, and timestamp.
- Response status and selected response headers.
- Selected request headers when needed to diagnose the call.
- Redirect relationships and an identifier linking the request to its response.
- A bounded request or response body only when the use case requires it.
Redact cookies, authorization headers, and personal data before writing logs to persistent storage. Keep request and response identifiers together: redirects and retries can otherwise look like duplicate API calls. Limit body capture by size and relevance; retaining every body increases storage, exposes more data, and can make logs harder to inspect.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance and reliability trade-offs
Start with passive observation and filter the output in your own handler. When you have identified the traffic that matters, add only the narrow interception or blocking rules needed for the task. Every intercepted request adds handler work and can affect page behavior if it is delayed or resolved incorrectly.
Chrome for Developers illustrates an allowlist containing document, script, xhr, and fetch, with images, stylesheets, and media aborted for a workload where those resources do not contribute to the rendered result. Treat that as workload-specific, not as a universal speed recipe. Some pages rely on CSS, fonts, media, or auxiliary calls for state, tokens, or the result you are trying to capture. No general benchmark figure establishes a performance gain for a particular page; compare behavior under your own workload before keeping a filter.
Best Value
Also separate what the browser can observe from what the application does elsewhere. These listeners capture traffic made through the browser context; they do not provide a general log of server-to-server calls made by the site’s backend.
Or skip the browser setup
If your goal is a screenshot rather than a network trace, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It does not expose browser background-request logs or replace Playwright/Puppeteer for inspecting XHR and Fetch traffic. For a screenshot, one GET request returns an image or PDF; the API accepts common screenshot parameter names to make switching easier. 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
For screenshot work, it removes cookie and consent banners, newsletter popups, and chat widgets before capture; those cleanup steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Recommended Free Tools
Sign up for ScreenshotNeo’s free plan and get 1,000 screenshots a month with no card.
Quick Recap
Common troubleshooting checks
| Symptom | Likely cause | Fix |
|---|---|---|
| No entries from the initial page load | Listeners were installed after navigation. | Register listeners before page.goto(), then reload. |
| The click succeeds but the waiter hangs | The predicate does not match the actual URL or method, or the action did not trigger the expected call. | Log all response URLs and methods, verify the action, then tighten the predicate around the observed request. |
| Routing does not see a Service Worker request | The worker handled it outside page/context routing. | For a routing test, try a context with serviceWorkers: 'block'; to inspect worker activity, use Service Worker support. |
| Page load stalls after interception is enabled | A request handler path failed to continue, fulfill, or abort a request. | Check every branch and ensure handlers resolve each request exactly once. |
| Expected XHR/Fetch entry is absent from filtered logs | The event was filtered out by resource type or endpoint conditions. | Temporarily log unfiltered traffic, identify the actual request, and adjust the filter. |
| A 404 or 503 is mistaken for a missing response | An HTTP error status was confused with a transport failure. | Record the response status; track requestfailed separately for transport failures. |
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.

