ScreenshotNeo

BlogHow-to

How to Check an HTTP Status Code in Puppeteer

Read the main document’s status from page.goto(), handle null responses and redirects, and distinguish HTTP errors from navigation failures.

By the ScreenshotNeo team4 October 20266 min read

To check the HTTP status code for a page navigation in Puppeteer, await page.goto() and call status() on the returned response. The response can be null, so guard it before reading the status:

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

if (status !== undefined) {
  console.log(status);
}

page.goto() returns the main document response, not the status of every image, script, or API request the page makes. An HTTP 404 or 503 is still an HTTP response; a network or navigation failure may instead reject the promise. See Puppeteer’s documentation for Page.goto() and HTTPResponse.status().

1. Set up Puppeteer

Use a current Node.js installation and install Puppeteer in your project. The Puppeteer package downloads a compatible browser as part of its standard installation.

npm install puppeteer

Save the following as check-status.js and run it with node check-status.js:

const puppeteer = require('puppeteer');

async function main() {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    const response = await page.goto('https://example.com', {
      waitUntil: 'domcontentloaded',
      timeout: 30_000,
    });

    if (response === null) {
      console.log('Navigation produced no main-resource response.');
      return;
    }

    console.log(`HTTP ${response.status()}`);
    console.log(`2xx success: ${response.ok()}`);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error('Navigation failed:', error.message);
  process.exitCode = 1;
});

The try/finally ensures the browser closes even if navigation or response handling fails. waitUntil controls when navigation is considered complete; it does not change which response goto() returns.

2. Choose an exact status check or a success-range check

Use status() when the test expects one specific numeric status. Use ok() when any status in the documented 200–299 range is acceptable.

Assert an exact code

const response = await page.goto('https://example.com');
if (!response) {
  throw new Error('Navigation did not produce a main resource response');
}

const status = response.status();
if (status !== 200) {
  throw new Error(`Expected HTTP 200, received ${status}`);
}

Accept any 2xx code

const response = await page.goto('https://example.com');
if (!response?.ok()) {
  throw new Error(`Expected a 2xx response, received ${response?.status() ?? 'no response'}`);
}

status() returns a number. ok() answers a different question: whether that response’s status is in the 2xx range. Neither method handles rejected navigation promises; catch those separately.

3. Understand what response Puppeteer returns

The response from page.goto() describes the main resource for the navigation. It does not represent every network transaction triggered by that page. Puppeteer’s HTTPRequest documentation describes request and response events for observing those other resources.

  • Main document: inspect the value returned by page.goto().
  • Subresource or API request: listen for responses and filter to the URL or request you care about.
  • HTTP error response: read its status; HTTP error codes such as 404 and 503 are still responses.
  • Transport or navigation failure: handle a rejected promise, which may occur without an HTTP response.

Inspect a specific request made by the page

const target = 'https://example.com/api/health';

page.on('response', (response) => {
  if (response.url() === target) {
    console.log(`${response.status()} ${response.url()}`);
  }
});

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

Register the listener before navigating so it can observe requests initiated during navigation. If the same URL can be requested more than once, filter using the request method or other request details too.

4. Check a click-triggered navigation

When a click starts navigation, wait for the click and navigation together. Waiting for the click first can race with a fast navigation.

const [response] = await Promise.all([
  page.waitForNavigation(),
  page.click('a[href="/next"]'),
]);

if (response) {
  console.log(response.status());
} else {
  console.log('Navigation produced no main-resource response.');
}

This is Puppeteer’s documented pattern for coordinating a click with navigation. See the Puppeteer getting-started guide and Page API documentation.

5. Handle null responses, redirects, and failures

A null response

The return type is HTTPResponse | null. Documented cases that can produce null include navigating to about:blank and navigating to the same URL with only a different hash. Always check the result before calling status().

Redirects

For a redirect chain, page.goto() resolves with the response for the last redirect in the chain. That means your status check ordinarily sees the final navigation response. To inspect intermediate responses, observe the requests or responses during navigation and follow the request redirect chain.

HTTP status versus navigation exception

A 404 or 500 can be returned as an HTTP response; it does not automatically mean goto() throws. By contrast, connection problems, DNS failures, or navigation timeouts can reject the promise. Keep response assertions and exception handling separate so the test reports the right failure.

try {
  const response = await page.goto('https://example.com/missing');
  if (!response) {
    throw new Error('No main-resource response');
  }
  console.log('HTTP status:', response.status());
} catch (error) {
  console.error('Navigation failed before a response was available:', error.message);
}

6. Or skip the browser setup

If you need a screenshot of the page as well as a straightforward capture request, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF; the response headers identify the page verdict and billing status. Its browser accepts consent banners like a visitor and removes supported consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. AI agents can use its MCP server tools, including take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

See the ScreenshotNeo API documentation for parameters and configuration. Sign up for 1,000 free screenshots a month, with no card required.

7. Troubleshooting

Symptom Cause Fix
response is null The navigation did not produce a main-resource response; documented examples include about:blank and a same-URL hash change. Check for null before reading the status. If you expected a network document, confirm the target URL actually causes a document navigation.
The test fails on a 404 or 500 even though the page loaded The test treated HTTP error status as a navigation exception, or expected a success code without stating that assertion. Read response.status() and assert the expected status explicitly. HTTP error responses can still be returned normally.
goto() throws a timeout or network error The browser did not complete navigation within the timeout or could not reach the host. Handle the rejected promise, check the URL and network access, and set a timeout appropriate for the target. Do not expect a status code when no response arrived.
The printed code is for the final page, not the first URL The page redirected. That is the documented goto() behavior. Observe request/response events if intermediate redirect responses matter.
The code misses an API or image status goto() reports the main resource only. Attach a response listener before navigation and filter for the target resource.
The click test intermittently reports no response The click and navigation waits were sequenced separately, or the click did not cause a document navigation. Use Promise.all to coordinate both waits; verify that the interaction causes a navigation rather than an in-page update.

8. Performance, reliability, and cost

  • Wait only as long as the test requires. waitUntil: 'domcontentloaded' can be appropriate when the assertion only needs the navigation response and initial document parsing. Choose a later lifecycle condition if the test depends on later page activity.
  • Set a bounded timeout. A finite timeout prevents a stalled navigation from holding up a test indefinitely. Treat timeout as a navigation failure, not an HTTP status.
  • Close browser resources. Use finally to close the browser and avoid leaking processes when a test fails.
  • Keep assertions specific. An exact expected code catches unexpected behavior; ok() is suitable when any 2xx code is acceptable.
  • Account for the browser runtime. Running Puppeteer means managing Node.js, a compatible browser installation, and the runtime environment where the test executes. ScreenshotNeo offers a hosted screenshot request instead; its listed plans are Free (1,000 per month), Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000). Yearly billing gives two months free, and every feature is on every plan.

9. Frequently asked questions

Does Puppeteer throw on a 404?

A valid HTTP error response such as 404 or 500 is still a response in the documented behavior; inspect its status. A network or navigation failure can reject goto() instead.

Can I get the status without loading the page in a browser?

This guide uses Puppeteer’s browser navigation response. If you only need an HTTP status and do not need browser behavior, a direct HTTP client may be more suitable; it will not reproduce a browser’s navigation and page execution.

Does response.ok() mean exactly HTTP 200?

No. It reports whether the response status is in the 200–299 range. Use status() === 200 for an exact 200 assertion.

Which response does page.goto() return after redirects?

It returns the last response in the redirect chain. Observe requests during navigation if you need intermediate redirect details.