Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11If a Puppeteer POST hangs, arrives as a GET, has an empty body, or appears to return the wrong response, first check request interception and which request object you are inspecting. Enable interception before the request starts, resolve every intercepted request exactly once, and log the outgoing request alongside the response. If a click causes document navigation, arm the navigation wait before clicking.
Start with the interception lifecycle
Request interception is not passive observation. Once enabled, Puppeteer pauses every request until your code continues it, supplies a response, or aborts it. A request that is never resolved can leave the page waiting on scripts, images, redirects, preflight requests, or the POST itself.
Install the handler and enable interception before navigating to the page or submitting the form. In particular, do not begin a submission and only then turn interception on: the request may already have started, so your handler will not reliably affect it.
A safe baseline handler
await page.setRequestInterception(true);
page.on('request', request => {
// Resolve every intercepted request exactly once.
request.continue().catch(error => {
console.error('Could not continue request:', request.url(), error);
});
});
await page.goto('https://example.com/form');
This baseline deliberately does not modify requests. Use request and response event listeners for observation when you do not need to change traffic; interception adds a resolution obligation to every request. The catch reports a resolution failure, but it does not make it safe for another handler to try resolving the same request.
#1 Best Overall
Use a handler for one target request
Filter by URL and method, and continue everything else. Replace the example URL and payload with the endpoint and fields your application expects.
await page.setRequestInterception(true);
page.on('request', request => {
const isTarget = request.url() === 'https://example.com/api/submit'
&& request.method() === 'POST';
if (!isTarget) {
request.continue().catch(error => {
console.error('Could not continue:', request.url(), error);
});
return;
}
const body = new URLSearchParams({
email: '[email protected]',
message: 'Hello'
}).toString();
request.continue({
method: 'POST',
postData: body,
headers: {
...request.headers(),
'content-type': 'application/x-www-form-urlencoded'
}
}).catch(error => {
console.error('Could not continue target POST:', error);
});
});
await page.goto('https://example.com/form');
Only override fields you intend to change. Preserve the request URL and headers that the server needs, including authentication or application-specific headers. The sample encoding is for URL-encoded form data; a JSON endpoint instead expects JSON serialization and a matching Content-Type: application/json. A correct Puppeteer override cannot supply an absent CSRF token, valid session cookie, or application-required field on its own.
Prevent “Request is already handled” errors
This error usually means two listeners or code paths attempted to resolve one intercepted request. It can happen in shared browser tooling when a plugin and your own handler both call continue(), respond(), or abort(). Asynchronous work makes the race easier to miss: one handler may pause while another resolves the request.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Keep a single synchronous decision point if possible. In multi-handler setups, check the resolution state immediately before resolving. Do not put an await between the check and the resolution call.
page.on('request', async request => {
if (request.isInterceptResolutionHandled()) return;
// If asynchronous work is needed, do it before the final guard.
const shouldBlock = request.url().includes('/unwanted-resource');
if (request.isInterceptResolutionHandled()) return;
try {
if (shouldBlock) {
await request.abort();
} else {
await request.continue();
}
} catch (error) {
console.error('Request resolution failed:', request.url(), error);
}
});
The guard is useful when another listener might have handled the request while your handler was doing asynchronous work. A simpler, single synchronous handler is easier to reason about. If you control all listeners, avoid duplicate handlers rather than relying on guards to coordinate them.
Check whether the outgoing request is really a POST
Do not infer the method from a later response log alone. Capture the request fields when Puppeteer emits the request, and record the response status, URL, and body separately. The request associated with a response can describe the original browser request; an interception override does not necessarily rewrite that request object’s recorded fields. Puppeteer issue #5221 documented a POST-interception experiment where the reported response was 404 while response.request().method() was GET; a maintainer explained that the associated request was the original request object.
Rank #3
page.on('request', request => {
if (request.url().includes('/api/submit')) {
console.log('outgoing request', {
method: request.method(),
url: request.url(),
postData: request.postData(),
headers: request.headers()
});
}
});
page.on('response', async response => {
if (!response.url().includes('/api/submit')) return;
console.log('response', {
status: response.status(),
url: response.url(),
associatedRequestMethod: response.request().method()
});
try {
console.log('response body:', await response.text());
} catch (error) {
console.error('Could not read response body:', error);
}
});
Compare the request event’s method, URL, body, and relevant headers with the server’s observed request and the response status/body. Treat associatedRequestMethod as context, not conclusive proof that an interception override was or was not sent. If you need to establish what reached the server, use server-side request logs or an endpoint that reports the received method and payload.
Why the body may be empty or wrong
- Wrong request: the page may issue several requests, and the logged request may be a GET for the document or an unrelated API call. Filter by the endpoint and inspect the method.
- Encoding mismatch: URL-encoded fields, JSON, and multipart form data are different formats. Match the server’s expected body encoding and content type.
- Headers or session state missing: CSRF protection, cookies, authorization, and origin-related checks may be required by the application.
- Override not applied: interception may have been enabled too late, the target request may not match your filter, or another listener may have resolved it first.
- Application rejected valid transport: validation rules, expired credentials, or a missing form field can produce an error even when Puppeteer sent a POST correctly.
Wait correctly when a form submission navigates
If the submit click triggers a new document, start waiting for navigation and click as a single coordinated operation. Waiting only after the click risks missing a fast navigation; Puppeteer’s Page API warns that this ordering can race.
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click('button[type="submit"]')
]);
console.log('navigation response status:', response?.status());
console.log('final page URL:', page.url());
Choose a wait condition suited to the site. domcontentloaded waits for document parsing, while waiting for full load can take longer when a page has slow resources. A navigation response can be absent in some flows, so the optional access above avoids assuming one always exists.
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
When the form uses fetch or XHR
A single-page application may submit in the background without a new document. In that case, waitForNavigation() is the wrong signal. Wait for the API response with a URL predicate, then inspect it:
const [response] = await Promise.all([
page.waitForResponse(response =>
response.url().includes('/api/submit')
&& response.request().method() === 'POST'
),
page.click('button[type="submit"]')
]);
console.log('API status:', response.status());
console.log('API URL:', response.url());
console.log('API body:', await response.text());
Use a predicate specific enough to distinguish the intended submission from background polling or other calls to the same endpoint. If the page can make that request more than once, include another stable discriminator such as a unique path, query parameter, or expected request payload.
Troubleshoot the common symptoms
| Symptom | Likely cause | What to check or change |
|---|---|---|
| The page or request hangs after enabling interception | An intercepted request was not resolved. | Make every handler path call exactly one of continue(), respond(), or abort(). Include non-target traffic in the continue path. |
| The response seems to be for a GET, not the POST | You may be reading the associated original request object or logging a different request. | Log the target request at the request event; compare URL, method, and body with the response URL and status. |
| The server says the POST body is missing or malformed | Body encoding, content type, fields, or target selection do not match the endpoint. | Log postData(), check the exact content type, and verify expected fields and encoding against the server contract. |
| “Request is already handled” | Multiple handlers or a race resolved the same request more than once. | Consolidate resolution logic or use isInterceptResolutionHandled() immediately before resolving. |
| Navigation wait times out after a successful submission | The application may submit through fetch/XHR without a document navigation. | Wait for the relevant response or a page-state change instead of navigation. |
| The response is 3xx, 401, 403, or 404 | The server or application may be redirecting, rejecting credentials/CSRF, or not matching the route. | Inspect status, response URL, headers, and body; verify cookies, auth, CSRF, redirects, and route behavior. |
Keep the debugging run reliable and economical
- Minimize mutation: diagnose with event listeners first. Add interception overrides only when changing the method, body, or headers is part of the test.
- Keep handlers small: every paused request extends page-load work. Avoid slow external calls inside the request event; compute needed values before navigation when practical.
- Capture enough context: a method alone is not a diagnosis. Preserve timestamps, request URL, body, selected headers, response status, final URL, and response text where safe.
- Protect secrets: cookies, authorization headers, and POST bodies can contain credentials or personal data. Redact them before storing logs or sharing a reproduction.
- Separate browser behavior from server behavior: once the outgoing request is correct, investigate application validation, CSRF, authentication, redirects, and server-side route handling.
Interception intentionally pauses all requests, so leaving it enabled with incomplete resolution logic is a reliability risk. For a repeatable test, make the target predicate deterministic, resolve all other traffic, and wait on the actual outcome—document navigation or API response—rather than an arbitrary delay.
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
Or skip the browser setup
If your task is to capture a page rather than debug its POST transport, ScreenshotNeo offers a one-request screenshot API. It does not replace Puppeteer interception or explain why an application’s POST failed. For a screenshot of a public page, the request can be as simple as:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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, and the free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for the free plan.
Frequently Asked Questions
Does changing request.continue() change the request object already recorded by Puppeteer?
Not necessarily. Treat the request object associated with a response as the original request context; compare the interception inputs with server-side logs when you need to confirm what arrived.
Should I use interception just to see a POST body?
Usually not. Request and response event listeners are a better first diagnostic because interception pauses traffic and requires every request to be resolved.
Can a valid POST still produce an error response?
Yes. Puppeteer can send a request correctly while the application rejects it for CSRF, authentication, validation, routing, or other server-side reasons.
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.




