Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsiTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Await the value returned by browser.newPage() and keep it as your Page handle. For the top-level document, call page.mainFrame(). For an iframe, inspect page.frames(), identify the required frame by a stable URL or name, and use that Frame object for selectors and actions.
const browser = await puppeteer.launch();
const page = await browser.newPage(); // Page handle
const mainFrame = page.mainFrame(); // top-level Frame handle
const frames = page.frames();
const targetFrame = frames.find(frame => frame.url() === targetUrl);
What browser.newPage() returns
browser.newPage() is asynchronous. Awaiting it resolves to a Puppeteer Page, representing a browser tab (or an extension background page). The returned object is the handle you use for navigation, screenshots, keyboard and mouse input, cookies, network events, and page-level selectors.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
console.log(await page.title());
await browser.close();
Do not try to retrieve the page from the browser after opening it, and do not omit await. Assign the resolved value immediately so later operations use the same tab.
Opening several pages
Each call creates another Page. Store each result if you need to switch between tabs:
#1 Best Overall
const checkoutPage = await browser.newPage();
const adminPage = await browser.newPage();
await checkoutPage.goto('https://shop.example/checkout');
await adminPage.goto('https://shop.example/admin');
Because both variables are independent handles, actions on one page do not change the other page’s main document.
How to get the main frame
A page always has a top-level document context. Call page.mainFrame() to obtain its Frame handle:
const page = await browser.newPage();
await page.goto('https://example.test');
const mainFrame = page.mainFrame();
console.log(mainFrame.url());
The main frame is replaced when a navigation commits, so obtain or re-check it after navigation if your code spans multiple documents. In ordinary code, Puppeteer page methods already target this frame. For example, page.$('button.save') is effectively a shortcut for searching the main document context.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWhen to use a Frame instead of Page
- Use
Pagefor browser-tab concerns: navigation, viewport, dialogs, screenshots, PDFs, network interception, and the default (main-frame) DOM. - Use
Framewhen the element belongs to an embedded document, such as an iframe. - Use
page.mainFrame()when the target is in the page’s own document.
How to access an iframe after opening the page
After navigation, call page.frames(). It returns the frames currently attached to the page, including the main frame and any nested iframe documents.
const page = await browser.newPage();
await page.goto('https://example.test');
for (const frame of page.frames()) {
console.log({url: frame.url(), name: frame.name()});
}
Select the embedded frame using an identity that is stable for your application. A URL prefix, frame name, or an application-specific attribute is safer than assuming a numeric position.
Selecting by URL
const targetFrame = page.frames().find(frame =>
frame.url().startsWith('https://widgets.example/'))
);
if (!targetFrame) {
throw new Error('Widget frame was not attached');
}
await targetFrame.locator('button.submit').click();
Use startsWith when the iframe URL contains changing query parameters. If the application has a fixed URL, an exact comparison is appropriate.
Selecting by frame name
const paymentFrame = page.frames().find(frame => frame.name() === 'payment');
if (!paymentFrame) throw new Error('Payment frame not found');
await paymentFrame.locator('input[name="cardnumber"]').fill('4111111111111111');
A frame’s name can be empty, and names are not necessarily unique. Combine name and URL checks when more than one embedded document is present.
Walking nested frames
The frame tree starts at page.mainFrame(). A selected frame exposes its descendants through frame.childFrames():
function printTree(frame, depth = 0) {
console.log(`${' '.repeat(depth * 2)}${frame.url()}`);
for (const child of frame.childFrames()) {
printTree(child, depth + 1);
}
}
printTree(page.mainFrame());
Use the child frame as the context for operations inside a nested iframe; a selector run on the parent frame cannot cross into the child document.
Waiting for dynamically attached frames
page.frames() only reports frames attached at the moment you call it. A frame inserted by JavaScript may not exist immediately after newPage() or even immediately after goto(). Wait for an application-specific signal, then inspect the current frame tree.
await page.goto('https://example.test', {waitUntil: 'domcontentloaded'});
await page.waitForSelector('iframe[data-widget="checkout"]');
const checkoutFrame = page.frames().find(frame =>
frame.url().includes('/checkout-widget')
);
if (!checkoutFrame) throw new Error('Checkout frame URL was not ready');
Waiting for the iframe element confirms that the host document created an iframe element, but the embedded document may still be loading. If necessary, wait for a selector inside the selected frame before interacting:
Recommended Free Tools
await checkoutFrame.waitForSelector('form#checkout');
await checkoutFrame.locator('button.submit').click();
There is no universal delay that works for every application. Prefer a concrete readiness condition—an iframe element, a known URL, or a selector inside the frame—over an arbitrary timeout.
Rank #3
Page selectors versus frame selectors
| Need | Correct handle | Example |
|---|---|---|
| Main document element | Page shortcut or page.mainFrame() |
await page.locator('h1').textContent() |
| Top-level frame methods | Frame |
await page.mainFrame().waitForSelector('.ready') |
| Embedded iframe element | The specific Frame from page.frames() |
await frame.locator('button').click() |
| Nested iframe | Child returned by frame.childFrames() |
const child = frame.childFrames()[0] |
Calling page.locator('button') searches the main document; it does not search every iframe. Likewise, a locator created in one frame cannot be used to operate on an element in another frame.
Reliable frame lookup patterns
Use a helper that fails clearly
function findFrameByUrl(page, predicate) {
const frame = page.frames().find(predicate);
if (!frame) {
throw new Error(`No matching frame. Current URLs:n${page.frames().map(f => f.url()).join('n')}`);
}
return frame;
}
const frame = findFrameByUrl(page, f =>
f.url().startsWith('https://widgets.example/')
);
Do not rely on array indexes
Frame order can change when analytics, advertisements, consent tools, or application components are inserted. An expression such as page.frames()[1] is acceptable only when you fully control the page and its frame structure. URL or name identity remains more maintainable.
Keep the handle local to the operation
After a navigation or frame reload, an old frame reference may no longer represent the document you expect. Re-select the frame after the event that replaces it, then wait for the target selector again.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Common errors and fixes
“Cannot read properties of undefined” after newPage()
Cause: the promise was not awaited or the assignment was omitted.
Fix: use const page = await browser.newPage() inside an async function (or top-level await in a supported module).
The iframe is visible, but its button cannot be found
Cause: the selector is being run against the main frame or the iframe has not finished loading.
Fix: select the frame from page.frames(), then wait for a selector inside that frame before clicking.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →page.frames() has no matching URL
Cause: the frame is attached later, the URL redirects, or your comparison is too exact.
Fix: wait for the iframe or another readiness signal, log every current frame URL, and use a carefully chosen prefix or substring for known redirects.
The frame handle becomes unusable after navigation
Cause: navigation or replacement created a new document context.
Fix: wait for the navigation to finish, obtain the current frame again, and recreate locators tied to the replaced document.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Cross-origin iframe confusion
Cause: browser same-origin rules prevent evaluating the iframe’s DOM through the parent page’s JavaScript context.
Fix: use Puppeteer’s Frame APIs for the attached frame. If the frame is not attached or is blocked by the site, no selector lookup can manufacture access; diagnose the frame URL, load status, and browser console or network errors.
Performance and reliability considerations
- Keep one browser instance when running multiple related tasks, but create a separate page when isolation between tabs is required.
- Use
waitUntiland targeted readiness selectors rather than long fixed sleeps. This reduces idle time while avoiding races. - Log frame URLs and names when diagnosing failures; the current tree is more useful than assumptions about frame order.
- Close pages and the browser in cleanup code so failed tests do not leave Chromium processes running.
- Use stable application identifiers. Third-party widgets can change URL parameters or nesting, so isolate matching logic in one helper.
try {
const page = await browser.newPage();
await page.goto('https://example.test');
// page and frame work
} finally {
await browser.close();
}
Or skip the browser setup
If your goal is a clean image or PDF rather than interactive Puppeteer control, ScreenshotNeo provides a website screenshot API and MCP server. It accepts a URL in one request and can return PNG, JPEG, WebP, or PDF. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the page verdict and billing status in X-Page-Verdict and X-Billed headers.
For a direct request, see the ScreenshotNeo 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
The same call from Python:
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)
And Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also offers full-page capture with lazy images loaded, CSS-selector element capture, device presets and custom viewports, dark mode, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits, request and resource blocking, custom headers and cookies, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account.
Frequently Asked Questions
Can I get a Page from page.mainFrame()?
No. page.mainFrame() returns a Frame. The Page is the value returned by await browser.newPage().
Does page.frames() include the main frame?
Yes. The returned array contains the top-level frame and currently attached descendant frames.
What should I do when an iframe has no useful URL?
Use another stable identity, such as its frame name or an application-specific readiness selector, and avoid positional indexes when the page structure can change.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

