ScreenshotNeo

BlogHow-to

How to Get the Page from a Puppeteer Frame

Call `frame.page()` to get the Page that owns a Puppeteer frame. Learn when to use the Page, when to stay in the frame, and how to find frames safely.

By the ScreenshotNeo team4 October 20267 min read

Call frame.page() to get the Puppeteer Page associated with a Frame:

const page = frame.page();

The call is synchronous: it returns a Page, not a promise. The returned object is the page that owns the frame; it does not turn the iframe into an independent tab. Puppeteer Frame.page() reference

Page versus Frame

A Page represents a browser tab or extension background page. Its document can contain a main frame and nested child frames. An iframe is represented by a Frame. Use the owning Page for page-level work; use the particular Frame when work must run in that frame’s document or JavaScript context. Page reference · Frame reference

Need Use
Get the tab that owns a frame frame.page()
Read or interact with the iframe’s DOM frame.evaluate(), frame.$eval(), frame.waitForSelector(), or other Frame methods
Query the main document page.$() or a main-frame method

frame.evaluate() executes in that frame’s context. By contrast, page.$(selector) is a shortcut for page.mainFrame().$(selector), so it searches the main frame rather than an arbitrary iframe. Frame.evaluate() reference · Page reference

Runnable JavaScript example

This CommonJS script opens a page, waits for a child frame, retrieves its owning Page, and reads an element from within the frame. Install Puppeteer with npm install puppeteer, save as frame-page.cjs, then run node frame-page.cjs.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    // Choose a predicate that uniquely identifies the iframe you need.
    const frame = await page.waitForFrame(
      frame => frame.url().includes('widget.example'),
      { timeout: 10_000 }
    );

    // page() is synchronous: do not await it.
    const ownerPage = frame.page();
    console.log('Same tab:', ownerPage === page);
    console.log('Frame URL:', frame.url());

    // Frame-scoped query: this searches inside the iframe.
    await frame.waitForSelector('h1', { timeout: 10_000 });
    const heading = await frame.$eval('h1', el => el.textContent.trim());
    console.log('Iframe heading:', heading);

    // Page-scoped query: this searches the main frame.
    const mainHeading = await page.$eval('h1', el => el.textContent.trim());
    console.log('Main heading:', mainHeading);
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Replace widget.example and the selectors with values from the page you control. The example assumes both documents contain an h1; remove or change those reads if they do not.

Get a frame you already have, or find one first

If you already have the Frame

Call frame.page() directly. This is useful when a helper receives a frame but needs access to its containing tab—for example, to call a Page-level method or pass the owner to another helper.

function owningPage(frame) {
  return frame.page();
}

Search the current frame tree

Use page.frames() to inspect all frames currently attached to the page. Use each frame’s URL or frame element to distinguish the one you need:

const frames = page.frames();
const frame = frames.find(frame => frame.url().includes('widget.example'));

if (!frame) {
  throw new Error('Widget frame is not attached');
}

const ownerPage = frame.page();

For a direct parent and child traversal, start at page.mainFrame() and follow childFrames(). Frames can be nested, so inspect child frames recursively if the target may be deeper than one level. The frame tree reflects currently attached frames and can change as the page navigates or scripts add and remove iframes. Frame reference

Wait for a frame to appear

If a script creates the iframe after the initial document loads, use page.waitForFrame() with a URL string or predicate. It returns a promise for the matching frame, so await it. A predicate can inspect the frame URL or its element; choose a condition specific enough to avoid matching the wrong frame. Page.waitForFrame() reference

const frame = await page.waitForFrame(
  frame => frame.url().includes('/embedded/'),
  { timeout: 15_000 }
);
const ownerPage = frame.page();

TypeScript usage

The documented signature is page(): Page. If a function accepts a Frame, import the types from Puppeteer and keep the frame and page references explicit:

import puppeteer, { type Frame, type Page } from 'puppeteer';

function getOwningPage(frame: Frame): Page {
  return frame.page();
}

async function readFrameHeading(frame: Frame): Promise<string> {
  await frame.waitForSelector('h1');
  return frame.$eval('h1', element => element.textContent?.trim() ?? '');
}

async function main(): Promise<void> {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
    const frame = await page.waitForFrame(
      frame => frame.url().includes('widget.example'),
      { timeout: 10_000 }
    );

    const owner: Page = getOwningPage(frame);
    console.log(owner.url());
    console.log(await readFrameHeading(frame));
  } finally {
    await browser.close();
  }
}

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

Common mistakes and edge cases

  • Awaiting frame.page(): unnecessary. Its return type is Page, not Promise<Page>. Use const page = frame.page().
  • Expecting the frame to become its own tab: frame.page() returns the owning tab. Keep using frame for iframe-specific DOM operations.
  • Searching an iframe with page.$(): that shortcut searches the main frame. Use frame.$(), frame.$eval(), or frame.evaluate() on the target frame.
  • Looking up the frame before it exists: a one-time page.frames() scan only sees attached frames at that moment. Wait with page.waitForFrame() if the page creates the frame later.
  • Matching the wrong frame: pages may have multiple frames, and URLs may be similar. Match a stable URL path or inspect the frame element’s attributes. Avoid relying on a broad substring if it can match several frames.
  • Using a stale frame after navigation or removal: frame attachments and detachment change over time. If an operation fails because its execution context disappeared, reacquire the current frame from the page and retry only when the action is safe to repeat.
  • Using frame.name() as a live DOM name: the method is deprecated and its value is captured when the Frame is created, so later changes to the element’s name may not appear. Read the current name or ID from frame.frameElement() instead. Frame reference

Troubleshooting

Symptom Likely cause Fix
waitForFrame times out The frame did not appear, the predicate does not match, or navigation has not reached the expected URL. Check page.frames().map(frame => frame.url()), verify the predicate against the actual URL, and set a timeout appropriate to the page.
frame is undefined after find() No attached frame matched at the time of the scan. Check the selector condition and use waitForFrame() if the frame is created asynchronously.
Selector returns null or times out The selector is absent, or the query ran against the main frame instead of the iframe. Run the selector on the target Frame; wait for the element with frame.waitForSelector() before reading it.
Execution context was destroyed The frame navigated or detached during evaluation. Wait for the relevant navigation or frame condition, reacquire the current frame, and run the operation in its new context.
Multiple frames match The predicate is not specific enough. Include a stable path or inspect frame.frameElement() for its current name or ID, then select an unambiguous match.

Performance and reliability

frame.page() is a synchronous accessor; its cost is negligible compared with browser navigation, waiting, and DOM work. The important reliability choice is how the frame is identified and when it is used. Prefer a specific predicate, wait for the needed frame or selector, and reacquire after a navigation or detachment. Do not repeatedly scan the frame tree in a tight loop; wait for the lifecycle event or condition that matches the task.

Close the browser in a finally block so failures do not leave a browser process running. Set explicit timeouts for pages that load third-party frames, and handle missing frames as an expected outcome when the embed is optional.

Or skip the browser setup

If you need a screenshot rather than direct Puppeteer control, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image 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(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, with response headers reporting the page verdict and billing status. Its MCP server provides screenshot tools for AI agents. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.

Sign up for 1,000 free screenshots a month, with no card required.

FAQ

Does frame.page() return the page that contains the iframe?

Yes. It returns the Page associated with that frame.

Can a Frame belong to a different Page later?

The method returns the page associated with the frame object. If the frame is detached or the page changes, find the current frame from the current page before continuing.

Can I call Page methods after getting the owner?

Yes. The returned value is a Puppeteer Page, so it can be used for page-level operations. For iframe content, continue using the original Frame.