ScreenshotNeo

BlogHow-to

How to Check Whether a Puppeteer Response Is Successful

Use Puppeteer’s `response.ok()` for any 2xx status, or `response.status()` when your test expects a specific code. Learn how to check navigation and action responses.

By the ScreenshotNeo team4 October 20266 min read

Use response.ok() to check whether a Puppeteer HTTP response has a status code from 200 through 299. Use response.status() if the test requires one exact code or a custom status policy. Both are methods, so call them with parentheses.

const response = await page.goto('https://example.com');

if (!response) {
  throw new Error('Navigation produced no HTTP response');
}

if (!response.ok()) {
  throw new Error(`HTTP ${response.status()} for ${response.url()}`);
}

The null check matters: page.goto() can return null for navigation to about:blank or to the same URL with a different hash. Also, a completed navigation does not by itself mean the HTTP status was successful. Inspect the returned response when status matters. Puppeteer’s page.goto() documentation describes these cases.

Choose the status check that matches your test

Check Use when What it means
response.ok() Any 2xx response is acceptable True for status codes 200–299
response.status() === 200 The endpoint must return exactly 200 Rejects other 2xx codes, such as 201 or 204
A custom status condition Your application has a specific contract For example, accept 200 or 304 if that is what the test requires

Puppeteer defines HTTPResponse.ok() as true when the status is in the 200–299 range. HTTPResponse.status() returns the numeric status code. Status text is not a substitute for checking the numeric code.

Check a navigation response

Capture the result of page.goto(), check for a missing response, and then assert the condition your test needs. This runnable example uses Puppeteer’s default headless launch behavior:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    const response = await page.goto('https://example.com');

    if (!response) {
      throw new Error('Navigation produced no HTTP response');
    }

    if (!response.ok()) {
      throw new Error(
        `Expected a 2xx response; received ${response.status()} for ${response.url()}`
      );
    }

    console.log(`Successful HTTP response: ${response.status()} ${response.url()}`);
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

To require exactly 200, change the condition to response.status() !== 200. HTTP statuses such as 404 or 500 do not necessarily cause page.goto() to throw a navigation exception; check the response if your test is asserting HTTP success. Puppeteer documents this behavior for headless shell in the navigation reference.

Check a response triggered by an action

When a click or other page action causes a request, start waiting for the response before triggering the action. Otherwise, a quick response may arrive before the wait is registered.

const responsePromise = page.waitForResponse(
  response => response.url() === 'https://example.com/api/data'
);

await page.click('#load-data');

const response = await responsePromise;
if (!response.ok()) {
  throw new Error(`HTTP ${response.status()} for ${response.url()}`);
}

The predicate can check more than the URL, which is useful if the page makes several requests to the same endpoint:

const responsePromise = page.waitForResponse(response =>
  response.url().includes('/api/data') &&
  response.request().method() === 'POST'
);

await page.click('#submit');
const response = await responsePromise;

if (!response.ok()) {
  throw new Error(`Request failed with HTTP ${response.status()}`);
}

Puppeteer’s page.waitForResponse() accepts a URL or predicate and returns a promise for the matching response. Its default timeout is 30 seconds; set a method-level timeout or adjust the page’s default timeout if your application needs a different limit.

Validate the response body when HTTP success is not enough

A 2xx status establishes HTTP-level success only. It does not prove that the application operation succeeded or that the response contains the expected data. Check the body or relevant fields as a separate assertion.

const response = await page.waitForResponse(r =>
  r.url().includes('/api/data') && r.request().method() === 'GET'
);

if (!response.ok()) {
  throw new Error(`HTTP ${response.status()} for ${response.url()}`);
}

const contentType = response.headers()['content-type'] || '';
if (!contentType.includes('application/json')) {
  throw new Error(`Expected JSON, received content-type: ${contentType}`);
}

const payload = await response.json();
if (payload.error || !Array.isArray(payload.items)) {
  throw new Error('Response did not contain the expected items');
}

response.json() throws if the body is not valid JSON. If the endpoint can return another format, inspect response.headers() or use response.text() and handle the expected format explicitly. The HTTPResponse API also exposes the response URL, request, headers, and body methods.

Common errors and fixes

Symptom Likely cause Fix
Cannot read properties of null page.goto() returned null for a documented special navigation case. Check if (!response) before calling ok(), status(), or other methods.
The test passes on a 404 or 500 page The test only waited for navigation and did not assert the returned HTTP status. Capture the navigation response and check ok() or the required exact status.
waitForResponse() times out The listener was registered after the action, the predicate did not match, or the action did not trigger that request. Create the promise before the action; verify the URL, method, and triggering behavior. Adjust the timeout only if the request legitimately takes longer.
The response is 2xx but the test still reports success incorrectly ok() checks HTTP status, not the application’s response semantics. Parse and assert the body fields or content that define success for your endpoint.
response.json() throws The response body is not valid JSON, or the endpoint returned a different content type. Check the content type and use text() or the parser appropriate to the actual response.
Using response.ok produces unexpected behavior The method was referenced instead of called, or a truthy function object was mistaken for its result. Use response.ok(); likewise call response.status().

Performance, reliability, and cost

The status check itself is a small local check on the response Puppeteer already received. For action responses, the main wait is network and application time; use a precise predicate so the test does not wait for an unrelated request. Keep the response promise registered before the action, and close the browser in a finally block so failures do not leave a browser process running.

For reliable assertions, decide whether the contract is any 2xx status, one exact code, or a body-level condition. Keep those assertions separate in failure messages: it makes it easier to tell an HTTP failure from an application payload failure. Puppeteer is a browser automation library; costs for running it depend on where and how you run your browser infrastructure.

Or skip the browser setup

If your goal is to capture a webpage rather than test its HTTP response in Puppeteer, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. See the 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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
await Bun.write('shot.webp', res);
  • 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 are never billed. Response headers say the page verdict and whether the request was billed.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
  • 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.

Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card.

FAQ

Does ok() mean the status was exactly 200?

No. It returns true for any status from 200 through 299. Compare status() to 200 if that exact response is required.

Should I check the main document response or every request?

Check the response relevant to the assertion. For a navigation test, inspect the response from page.goto(); for an API interaction, wait for and inspect the matching response.

Does a successful response prove the page rendered correctly?

No. HTTP status, application data, and rendered UI are separate conditions. Assert each one your test requires.

Can I use these methods on responses other than navigation?

Yes. Puppeteer’s HTTPResponse methods apply to responses received by a page, including responses matched with waitForResponse().