ScreenshotNeo

BlogHow-to

How to Get the Frame for a Puppeteer HTTP Request

Use Puppeteer’s `request.frame()` to identify the frame that initiated a request. Learn how to handle null, filter navigation requests, and inspect responses and frames.

By the ScreenshotNeo team4 October 20267 min read

In a Puppeteer request handler, call request.frame() to get the frame that initiated the request. It returns a Frame or null; check for null before using frame methods. To determine whether a request drives navigation, check request.isNavigationRequest() separately.

Puppeteer’s HTTPRequest API reference documents the null case as navigation to an error page. A request’s frame association does not, by itself, mean that request is a navigation.

Get the frame from a request

Register a request event handler and read the frame from the supplied HTTPRequest:

page.on('request', request => {
  const frame = request.frame();

  if (frame === null) {
    // Puppeteer documents null when navigating to an error page.
    console.log('No frame is available for this request');
    return;
  }

  console.log('frame URL:', frame.url());
  console.log('drives navigation:', request.isNavigationRequest());
});

The frame URL is the URL of the frame at the time you inspect it. If you only need the initiating frame, keep the returned frame association; do not replace a null result with page.mainFrame(). That would report the page’s main frame rather than the request’s documented result.

Run a complete example

This example launches Chromium, opens a page, logs request URLs alongside their initiating frames, waits for the page load, and closes the browser. Save it as request-frames.js in a project where Puppeteer is installed, then run node request-frames.js.

const puppeteer = require('puppeteer');

async function main() {
  const browser = await puppeteer.launch({ headless: true });

  try {
    const page = await browser.newPage();

    page.on('request', request => {
      const frame = request.frame();
      const frameUrl = frame === null ? '(no frame)' : frame.url();
      const navigation = request.isNavigationRequest();

      console.log({
        requestUrl: request.url(),
        frameUrl,
        navigation,
        resourceType: request.resourceType(),
      });
    });

    await page.goto('https://example.com', { waitUntil: 'load' });
  } finally {
    await browser.close();
  }
}

main().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

For a page containing iframes, requests from those frames can be associated with their initiating frame. If the frame navigates or detaches while your code is running, treat a previously retained frame as potentially stale before using it later.

Distinguish a frame from a navigation request

Use the two APIs for different questions:

Question API Meaning
Which frame initiated this request? request.frame() Returns the associated Frame or null.
Does this request drive the current frame’s navigation? request.isNavigationRequest() Returns whether the request is a navigation request.

For example, filter to navigation requests while still handling the nullable frame:

page.on('request', request => {
  if (!request.isNavigationRequest()) return;

  const frame = request.frame();
  if (frame === null) {
    console.log('Navigation request has no available frame');
    return;
  }

  console.log('navigation frame:', frame.url());
});

This distinction is useful when logging navigation, applying navigation-specific handling, or excluding images and other subresources from a request report. The frame accessor is not a substitute for the navigation predicate.

Use the response when you have one

An HTTPResponse also has a frame() accessor, with the same documented null condition for error-page navigation. Use response.request() to get the request associated with that response and inspect its request metadata.

page.on('response', response => {
  const request = response.request();
  const frame = response.frame();

  console.log({
    responseUrl: response.url(),
    status: response.status(),
    requestUrl: request.url(),
    navigation: request.isNavigationRequest(),
    frameUrl: frame === null ? null : frame.url(),
  });
});

Use response.frame() when the response is already in hand. Use request.frame() in a request handler. Both can return null, so code that calls frame methods should handle that case.

Inspect the page’s frame tree

When you are investigating the page independently of a particular request, use the page and frame tree APIs instead:

  • page.mainFrame() returns the main frame.
  • page.frames() lists the page’s attached frames.
  • frame.childFrames() lists a frame’s child frames.
const mainFrame = page.mainFrame();

for (const frame of page.frames()) {
  console.log({
    url: frame.url(),
    isMainFrame: frame === mainFrame,
    childFrameCount: frame.childFrames().length,
  });
}

Puppeteer also exposes frameattached, framenavigated, and framedetached lifecycle events. A frame can change between the time a request event fires and the time later code uses the frame. If you retain frame references for deferred work, account for navigation and detachment.

Wait safely for frame navigation

If an action is expected to navigate a frame, start waiting for the navigation and trigger the action together. Puppeteer documents this Promise.all pattern to avoid a race where navigation begins before the wait is registered:

const [response] = await Promise.all([
  frame.waitForNavigation(),
  frame.click('a.next-page'),
]);

console.log('navigation response:', response?.status());

Choose a navigation wait condition appropriate to the page. The API reference describes waitForNavigation() and its options; a page that updates without a full document navigation may need a different wait strategy.

Request lifecycle details that affect frame logging

  • HTTP error status is not a failed request event. A response such as 404 or 503 is still successful at the HTTP request lifecycle level and leads to requestfinished. Inspect the response status if you need to detect HTTP errors.
  • Transport or loading failure uses a different event. A failed request emits requestfailed. Do not rely on an HTTP status response to identify every failure.
  • Redirects produce separate requests. The original request completes and a new request is issued for the redirected URL. Log each request if you need the full redirect path.
  • Frames have a lifecycle. A frame can attach, navigate, and detach. Avoid assuming its URL or attachment state will remain unchanged after the event handler returns.
  • Error-page navigation can have no frame. Keep the nullable result visible in logs or handle it explicitly; do not silently report the main frame instead.

Common errors and fixes

Symptom Likely cause Fix
Cannot read properties of null when calling frame.url() request.frame() returned null, which Puppeteer documents for error-page navigation. Check for null before calling frame methods and decide how your application should represent the missing association.
A request is reported as a navigation because it has a frame Frame association and navigation status are separate properties. Use request.isNavigationRequest() to filter navigation requests.
A later frame operation fails or describes a different page The frame navigated or detached after the request event. Use the reference promptly where possible, and account for frame lifecycle events in deferred work.
A 404 or 503 is missing from requestfailed HTTP error responses still complete the request lifecycle and lead to requestfinished. Listen for response or requestfinished and inspect the response status when handling HTTP errors.
A redirect appears as multiple URLs Puppeteer represents the redirect as a completed request followed by a new request. Record each request and correlate the sequence using the request and response information available to your handler.
frame is undefined The event callback may not be receiving the expected Puppeteer HTTPRequest, or the variable may have been overwritten. Confirm the listener is attached to page.on('request', ...) and inspect the event argument before calling Puppeteer request methods.

Performance, reliability, and cost

Reading request.frame() and request.isNavigationRequest() is a local metadata lookup; the main operational cost usually comes from what the handler does next. Keep event callbacks short, avoid doing expensive work synchronously for every resource, and avoid retaining large numbers of frame or request objects longer than needed.

For reliable logging, handle the nullable frame, distinguish HTTP status errors from failed requests, and account for redirects and frame lifecycle changes. Puppeteer itself is browser automation software; the code above does not require a screenshot service or incur a per-request screenshot charge.

Or skip the browser setup

If your actual goal is to capture a page image or PDF rather than inspect Puppeteer’s request-to-frame association, ScreenshotNeo provides a website screenshot API. A single GET request can return a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API docs.

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(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.

The free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots; all features are on every plan. Sign up free for ScreenshotNeo.

FAQ

Does request.frame() always return the main frame?

No. It returns the frame that initiated that request, which may be a child frame. It may also return null in the documented error-page navigation case.

Can I get a frame from an HTTP response?

Yes. Use response.frame(); use response.request() when you also need the associated request.

How do I get all frames without handling a request?

Use page.frames() to list the page’s attached frames, or page.mainFrame() for its main frame.

Does a 404 mean Puppeteer emits requestfailed?

No. An HTTP error response such as 404 can still lead to requestfinished. Inspect the response status to detect it.

Sources