ScreenshotNeo

BlogHow-to

How to Find Elements in a Frame with Puppeteer Locators

Find the right iframe, create a frame-scoped Puppeteer locator, and interact with elements inside it. Includes nested frames, alternatives, and troubleshooting.

By the ScreenshotNeo team4 October 20267 min read

Direct answer: Find the Puppeteer Frame for the iframe, then call frame.locator(selector). The selector is resolved inside that frame. A locator made from page.locator() targets the main document instead.

const frame = page.frames().find(candidate => candidate.url().includes('/embedded-form'));
if (!frame) throw new Error('Target frame not found');

await frame.locator('input[name="email"]').fill('reader@example.com');
await frame.locator('button[type="submit"]').click();

The URL check is only an example. Use an identifier that is stable for the page you automate. See Puppeteer’s Frame API and page interaction guide.

1. Why a frame-scoped locator is needed

An iframe has its own document context. A selector searched in the main page does not automatically cross into that document. Puppeteer represents each frame as a Frame; use that frame as the starting point for the locator.

  • page.mainFrame() returns the main document’s frame.
  • page.frames() returns the current frame list.
  • frame.childFrames() returns a frame’s direct children, which is useful for nested iframes.

For interaction, locators are generally preferable to a one-off query: Puppeteer’s locator actions wait for the element to be present and verify relevant action conditions. For a click, those checks include visibility, enabled state, viewport position, and a stable bounding box across animation frames.

2. Runnable example: locate a frame by URL and fill a form

This complete Node.js example launches Chromium, loads a page URL supplied through an environment variable, locates a frame by a URL substring, fills an input, clicks a button, and closes the browser. Install Puppeteer with npm install puppeteer. Set TARGET_PAGE to a page you are permitted to automate and change the frame URL fragment and selectors to match its DOM.

// save as frame-form.mjs
import puppeteer from 'puppeteer';

const targetPage = process.env.TARGET_PAGE;
if (!targetPage) throw new Error('Set TARGET_PAGE to the page URL');

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.goto(targetPage, { waitUntil: 'domcontentloaded' });

  const frame = page.frames().find(candidate =>
    candidate.url().includes('/embedded-form')
  );
  if (!frame) {
    throw new Error(`Target frame not found. Current frames: ${page.frames().map(f => f.url()).join(', ')}`);
  }

  await frame.locator('input[name="email"]').fill('reader@example.com');
  await frame.locator('button[type="submit"]').click();
} finally {
  await browser.close();
}

Run it with TARGET_PAGE='https://example.com/page' node frame-form.mjs. Replace the illustrative URL fragment and form selectors with values from the target page. Do not use example credentials or submit real transactions while adapting the script.

3. Identify the correct frame

Inspect the current frame list

Listing frame URLs is a useful first diagnostic when you do not know how the page embeds its content:

for (const frame of page.frames()) {
  console.log({ url: frame.url(), isMain: frame === page.mainFrame() });
}

Frames may be nested, and pages can attach, navigate, or detach frames as they load or change. If a navigation or interaction replaces a frame, reacquire it from the current frame tree before continuing.

Match the iframe element’s name or id

If the iframe has a stable name or id, inspect its owning iframe element through frame.frameElement():

let targetFrame;
for (const candidate of page.frames()) {
  if (candidate === page.mainFrame()) continue;

  const element = await candidate.frameElement();
  const name = await element.evaluate(el => el.getAttribute('name'));
  if (name === 'payment-frame') {
    targetFrame = candidate;
    break;
  }
}

if (!targetFrame) throw new Error('Payment frame not found');
await targetFrame.locator('input[name="cardnumber"]').fill('…');

Use an attribute only if it is stable and unique enough for your page. Puppeteer’s Frame.name() is deprecated; the API documentation recommends reading name or id from the frame element. Its documented name value is calculated when the frame is created and does not update if the attribute changes later.

Handle nested frames

For nested iframes, identify the parent first, then inspect its direct child frames. Apply a locator to the innermost frame that contains the target element:

const parentFrame = page.frames().find(frame => frame.url().includes('/widget'));
if (!parentFrame) throw new Error('Parent frame not found');

const childFrame = parentFrame.childFrames().find(frame =>
  frame.url().includes('/form')
);
if (!childFrame) throw new Error('Nested form frame not found');

await childFrame.locator('input[name="email"]').fill('reader@example.com');

URL fragments here are examples. When possible, verify the candidate frame using a stable frame-element attribute or a page-specific condition too.

4. Select and interact inside the frame

Frame.locator(selector) returns a locator scoped to that frame. Puppeteer supports CSS selectors and additional selector syntax for text, accessibility roles and names, XPath, and combinations that cross shadow roots.

// CSS selector
await targetFrame.locator('button.continue').click();

// Accessibility selector
await targetFrame.locator('::-p-aria([name="Continue"][role="button"])').click();

Choose selectors based on the actual DOM and accessibility tree. Prefer stable attributes or accessible names over fragile positional selectors when the page provides them. See the Frame.locator() reference for supported syntax; check the documentation for the Puppeteer version installed in your project because the cited official pages span versions 25.9.0 through 25.12.0.

5. Lower-level frame queries

For a one-off lookup or custom element work, frame-scoped query methods are available. They do not supply locator interaction waiting and readiness behavior by default.

const handle = await targetFrame.$('h1');
if (!handle) throw new Error('Heading not found');
try {
  const text = await handle.evaluate(element => element.textContent);
  console.log(text);
} finally {
  await handle.dispose();
}

You can also use frame.$eval(selector, fn) to run a function against the first matching element. Use frame.waitForSelector() when you need to wait for a selector and then work with a handle. For ordinary clicks and fills, a locator keeps the selection and action together.

6. Common problems and fixes

Symptom Likely cause Fix
Locator never finds the element The locator is scoped to the main frame or the wrong child frame. Inspect page.frames(), identify the frame containing the element, and call that frame’s locator().
Frame lookup returns undefined The URL test is too strict, the iframe has not attached yet, or the frame navigated. Log current frame URLs, wait for the page’s relevant transition, then identify the current frame again with a stable condition.
Selector matches nothing in the correct frame The selector does not match that frame’s DOM, or content is rendered later. Inspect the frame’s actual markup and accessible names; use a locator action’s waiting behavior or an explicit selector wait where appropriate.
Frame detached or execution context destroyed The frame navigated or was removed while the operation was running. Wait for the page transition to settle, reacquire the frame, and retry only if the operation is safe to repeat.
Click is blocked or times out The target may be hidden, disabled, outside the viewport, or moving. Check that the intended element is visible and enabled and that overlays or animations have settled. Locator actions check several click preconditions automatically.
Frame name does not reflect a changed attribute Frame.name() is deprecated and its value is determined when the frame is created. Inspect the current frame element’s name or id attribute instead.

7. Performance, reliability, and cost

Frame discovery is usually simplest as a short pass over page.frames(). Avoid repeatedly scanning the entire frame tree before every action when you can identify a frame once for a stable page state. Reacquire after navigation, detachment, or a UI transition that replaces the iframe. Locators add useful readiness checks for interactions; a lower-level handle can suit a one-off read but requires you to manage timing and handle lifetime.

Browser automation has runtime and infrastructure costs determined by how you run Chromium and how much work the page performs; this API usage itself has no separate Puppeteer fee stated in the cited documentation. A screenshot API is a different way to capture rendered pages, and does not replace Puppeteer when you need to find or interact with an element inside an iframe.

Or skip the browser setup

If your task is to capture the page rather than interact with its frame, ScreenshotNeo is 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}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for the free plan.

8. FAQ

Can I use page.locator() for an iframe element?

Use page.locator() to locate the iframe element in the main document. To select content inside it, locate the corresponding Frame and use frame.locator().

Does the iframe need to be same-origin?

Puppeteer exposes frames in the page frame tree and provides frame-scoped APIs. Identify the target frame and use its locator; do not assume a main-document selector searches the iframe document.

Which is better: a locator or $eval()?

Use a locator for interactions that benefit from automatic waiting and action checks. Use $eval() or an element handle for a focused query or custom evaluation when you are prepared to handle timing and missing elements.

What if the iframe has no useful URL or name?

Inspect the iframe elements and current frame tree, then select using another stable property available on the page. If the site provides no stable identifier, make the selection specific to the page state and validate it before acting.