Use page.waitForResponse() to create a response promise before the click or other action that triggers the request. Await the action, await the matched HTTPResponse, and call response.json():
const responsePromise = page.waitForResponse(
response =>
response.url().includes('/api/data') && response.status() === 200
);
await page.click('button');
const response = await responsePromise;
const data = await response.json();
console.log(data);
This pattern avoids a race with fast requests and gives your Node.js code the parsed JSON object returned by the page’s network request.
What waitForResponse() returns
Puppeteer’s Page.waitForResponse() returns a promise that resolves to the matching HTTPResponse. You can match with an exact URL string or with a synchronous or asynchronous predicate. Once you have the response, await response.json() parses its body.
The current official API reference surfaced for this method is Puppeteer 25.12.0. Its documented default timeout is 30 seconds. You can change the default with page.setDefaultTimeout(), set a per-wait timeout, use timeout: 0 to disable the wait timeout, and supply an AbortSignal when cancellation is needed.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
Complete example: click, capture, and parse JSON
The following runnable example launches Chromium, opens a page, waits for the API response, and validates the resulting object before using it.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
try {
await page.goto('https://example.com/dashboard', {
waitUntil: 'domcontentloaded'
});
// Register this before the action that starts the request.
const responsePromise = page.waitForResponse(
response => {
const request = response.request();
return response.url().includes('/api/data') &&
request.method() === 'GET' &&
response.status() === 200;
},
{ timeout: 30000 }
);
await page.click('[data-testid="load-data"]');
const response = await responsePromise;
const data = await response.json();
if (!data || typeof data !== 'object') {
throw new Error('The endpoint did not return the expected JSON object');
}
console.log(data);
} finally {
await browser.close();
}
})();
The important ordering is deliberate: constructing responsePromise starts waiting immediately, while the subsequent click triggers the request.
Choose the right matcher
Exact URL
If the endpoint is stable and unique, pass its URL directly:
const responsePromise = page.waitForResponse(
'https://example.com/resource'
);
await page.click('button');
const response = await responsePromise;
const data = await response.json();
An exact URL is easy to read, but it can break when the site adds query parameters, changes hosts between environments, or appends a cache-busting value.
Recommended Free Tools
Rank #2
Predicate for URL, status, and method
A predicate is usually safer when several requests have similar paths. Check the URL and status together, and include the request method when GET, POST, or another method matters:
const responsePromise = page.waitForResponse(response => {
const request = response.request();
const url = new URL(response.url());
return url.pathname === '/api/orders' &&
url.searchParams.get('customer') === '42' &&
request.method() === 'POST' &&
response.status() === 201;
});
Matching a URL is only selection. It is not proof that the body is the application object you expect, so validate required fields after parsing.
Asynchronous predicates
The predicate may be asynchronous when you need to inspect response text before deciding whether it is the right response:
const responsePromise = page.waitForResponse(async response => {
if (!response.url().includes('/api/search') || response.status() !== 200) {
return false;
}
try {
const text = await response.text();
return text.includes('"resultType":"customer"');
} catch {
return false;
}
});
Use this sparingly. Reading and parsing a body in the predicate can add work, and you still need to parse or otherwise process the selected response afterward according to your application contract.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Parse and validate the body safely
response.json() can reject when the selected response is not valid JSON. An HTTP 4xx or 5xx response may also contain an error schema rather than the success object. Keep selection, parsing, and validation separate:
const response = await responsePromise;
if (response.status() < 200 || response.status() >= 300) {
throw new Error(`API returned HTTP ${response.status()}`);
}
let data;
try {
data = await response.json();
} catch (error) {
throw new Error(`Expected JSON from ${response.url()}: ${error.message}`);
}
if (!Array.isArray(data.items)) {
throw new Error('JSON did not contain an items array');
}
for (const item of data.items) {
console.log(item);
}
Do not assume every observed response has a readable JSON body. A response can be an HTML error page, an empty body, or a payload that does not match your endpoint’s documented contract.
Timeouts, cancellation, and slow pages
Increase or disable the wait timeout
The documented default is 30 seconds. Set a longer per-call timeout for a legitimately slow operation:
const responsePromise = page.waitForResponse(
response => response.url().includes('/api/report'),
{ timeout: 90000 }
);
You can set a page-wide default for subsequent waits:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #4
page.setDefaultTimeout(60000);
timeout: 0 disables the response wait timeout, but use it only when another watchdog or cancellation mechanism guarantees that a hung page cannot leave your job waiting forever.
Abort a wait
When your job has its own deadline, pass an abort signal:
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 45000);
try {
const response = await page.waitForResponse(
response => response.url().includes('/api/data'),
{ signal: controller.signal }
);
console.log(await response.json());
} finally {
clearTimeout(timer);
}
When an event listener is a better fit
page.on('response', handler) is useful for observing many responses, collecting diagnostics, or recording traffic over time. It is not a return-value API: registering a handler does not give the matching response to the await at the registration line.
const seen = [];
const handler = response => {
if (response.url().includes('/api/data')) {
seen.push(response);
}
};
page.on('response', handler);
try {
await page.click('button');
await new Promise(resolve => setTimeout(resolve, 1000));
for (const response of seen) {
console.log(response.url(), response.status());
}
} finally {
page.off('response', handler);
}
For one known response that gates the next step, waitForResponse() is simpler and automatically expresses the one-shot intent. If you use an event listener, store the result or promise explicitly and always remove the listener when monitoring ends.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Best Value
- Used Book in Good Condition
Troubleshooting common failures
“Navigation timeout” or “response wait timeout”
- Cause: the action did not run, the selector matched no usable element, the endpoint changed, or the request took longer than 30 seconds.
- Fix: create the wait before the action, verify the selector, log observed response URLs, and set a justified timeout for slow operations.
The promise never resolves
- Cause: the predicate is too broad in the wrong way (it never returns true), the request is made by a different frame or URL, or the click did not trigger a request.
- Fix: temporarily use
page.on('response', response => console.log(response.url(), response.status()))to discover the actual traffic, then tighten the matcher.
The wrong response is selected
- Cause: a path substring matches polling, prefetch, or multiple API calls.
- Fix: compare the full URL or pathname and query parameters, request method, and status; validate a distinguishing JSON field.
json() fails
- Cause: the response is HTML, empty, compressed or otherwise not valid JSON for the selected endpoint, or it represents an error payload.
- Fix: inspect status and content expectations, catch the parse error, and use
response.text()while diagnosing the actual body.
The response arrives before the wait is installed
- Cause: code performed
await page.click()first and only then calledwaitForResponse(). - Fix: construct the promise first, then await the action and the promise.
Performance and reliability practices
- Match the narrowest stable endpoint rather than every response on the page.
- Use one response wait per action when a single API result controls the next step.
- Keep browser cleanup in a
finallyblock so failures do not leave Chromium processes running. - Log the matched URL and status, not sensitive headers or tokens.
- Validate the parsed shape at the boundary; downstream code should not have to guess whether it received success, error, or an unrelated object.
- For repeated observation, prefer one managed listener and remove it rather than accumulating handlers across tests.
Or skip the browser setup
If your real goal is a clean image or PDF of a page rather than reading an API payload, ScreenshotNeo provides a single HTTP request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server supplies take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo documentation for parameters and authentication:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
You get 1,000 screenshots per month free with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account.
Minimal decision guide
| Need | Use |
|---|---|
| One known endpoint after one click | waitForResponse() with an exact URL or precise predicate |
| Variable URL, status, or method checks | A predicate that tests those properties |
| Inspection of many responses | page.on('response') with explicit storage and cleanup |
| Parsed application data | await response.json(), followed by status and schema validation |
Frequently Asked Questions
Can I call waitForResponse() after clicking?
You should create the wait promise before clicking. Otherwise a fast response can arrive before Puppeteer starts monitoring it.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteDoes a matching URL guarantee valid JSON?
No. The response may be an error, HTML, empty, or an unexpected JSON shape. Check status, parse with error handling, and validate required fields.
Should I use a response event or waitForResponse()?
Use waitForResponse() for one response that gates the next step. Use an event listener for ongoing observation, and remove it when finished.
Quick Recap
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.




