PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, 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 minuteUse the Frame that owns each iframe’s document, then scroll an element inside that frame. In Puppeteer, enumerate frames with page.frames() or walk mainFrame().childFrames(), identify each frame by a stable URL, name, or attribute, and call frame.locator(selector).scroll() for offset scrolling. If you need a particular item visible, obtain its element handle and call scrollIntoView(). Nested iframes require another explicit childFrames() traversal.
How Puppeteer models iframes
Puppeteer treats an iframe as a separate DOM and JavaScript context represented by a Frame object. A selector run against page or page.mainFrame() cannot automatically find elements inside an embedded document. Select the owning frame first, then query within that frame.
The page has one main frame and zero or more descendants. A descendant can itself contain child frames, so the structure is a tree rather than a flat list. The official Frame reference describes frames as the equivalent of <iframe> elements and shows recursive traversal.
Inspect every frame before scrolling
Start by printing the frame tree. This reveals URLs, names and nesting, and prevents guessing which iframe contains the scroll region.
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 →Repair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto('https://example.com/page-with-iframes', {
waitUntil: 'networkidle2',
timeout: 60_000
});
function printFrame(frame, depth = 0) {
console.log(`${' '.repeat(depth)}url=${frame.url()} name=${frame.name()}`);
for (const child of frame.childFrames()) printFrame(child, depth + 1);
}
printFrame(page.mainFrame());
await browser.close();
page.frames() returns the collection of attached frames, which is convenient when you do not need to preserve the tree shape:
for (const frame of page.frames()) {
console.log(frame.url(), frame.name());
}
Frame names are useful clues, but do not assume they are unique or stable. Prefer a known URL fragment or an attribute on the iframe element when the site provides one.
Choose the correct frame
Match by URL
const targetFrame = page.frames().find(frame =>
frame.url().includes('/embedded/checkout')
);
if (!targetFrame) throw new Error('Embedded checkout frame was not found');
Match by frame name
const targetFrame = page.frames().find(frame => frame.name() === 'details-frame');
Names can be empty, duplicated or generated by the application, so treat this as a site-specific condition rather than a universal identifier.
Match the iframe element, then its content frame
When URL and name are not reliable, locate the iframe element in the parent document and ask Puppeteer for its associated frame:
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteconst iframeElement = await page.waitForSelector('iframe[data-panel="details"]');
const targetFrame = await iframeElement.contentFrame();
if (!targetFrame) throw new Error('The iframe has not attached a document yet');
This distinction matters: the iframe element belongs to the parent DOM, while the document rendered inside it belongs to the returned Frame.
Scroll a region inside each iframe
Use a frame-scoped locator when you want to move a scrollable element by a known amount. Locator.scroll() uses mouse-wheel events and accepts offsets such as scrollTop and scrollLeft.
for (const frame of page.frames()) {
if (!frame.url().includes('/embedded/')) continue;
const region = frame.locator('.scroll-region');
await region.scroll({scrollTop: 500, scrollLeft: 0});
}
The selector is evaluated in each matching frame, not in the top-level document. If a frame can contain several regions, use a more specific selector or iterate matching elements deliberately.
Rank #2
Locators perform action precondition checks and can retry when an element is not ready. Their viewport check is enabled by default and can be configured with the locator viewport-setting API when your workflow needs different behavior.
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 →Bring a specific item into view
Offset scrolling and target revelation are different tasks. To reveal a known item, select it and call scrollIntoView() on its element handle:
const item = await targetFrame.waitForSelector('[data-id="invoice-42"]', {
visible: true,
timeout: 30_000
});
if (!item) throw new Error('Target item did not appear');
await item.scrollIntoView();
await item.dispose();
An action such as clicking a locator also normally ensures that the element is in the viewport. Use that behavior when the next operation is an interaction rather than a pure scroll. scrollIntoView() is the clearer choice when you only need visibility.
Traverse nested iframes
A frame’s JavaScript context does not automatically include its nested frames. Find the parent first, then inspect its childFrames():
const outer = page.frames().find(frame =>
frame.url().includes('/outer-widget/')
);
if (!outer) throw new Error('Outer frame not found');
const inner = outer.childFrames().find(frame =>
frame.url().includes('/inner-document/')
);
if (!inner) throw new Error('Inner frame not found');
await inner.locator('.inner-scroll-region').scroll({scrollTop: 800});
For unknown depth, use a recursive search:
function findFrame(root, predicate) {
if (predicate(root)) return root;
for (const child of root.childFrames()) {
const result = findFrame(child, predicate);
if (result) return result;
}
return null;
}
const frame = findFrame(page.mainFrame(), f =>
f.url().includes('/document-to-scroll/'));
if (!frame) throw new Error('Nested target frame not found');
Complete multi-iframe example
This script discovers all matching frames, waits for each scroll region, scrolls it, and verifies that the operation completed without silently skipping a frame.
Free tools Windows power users keep installed
One-click scans. No signup required.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.goto('https://example.com/dashboard', {
waitUntil: 'networkidle2',
timeout: 60_000
});
const candidates = page.frames().filter(frame =>
frame.url().includes('/embedded/report/')
);
if (candidates.length === 0) {
throw new Error('No report iframes are attached');
}
for (const [index, frame] of candidates.entries()) {
await frame.waitForSelector('.scroll-region', {timeout: 30_000});
const region = frame.locator('.scroll-region');
await region.scroll({scrollTop: 600, scrollLeft: 0});
console.log(`Scrolled report frame ${index + 1}: ${frame.url()}`);
}
} finally {
await browser.close();
}
Replace the URL and selectors with values from your application. The example uses the current Puppeteer locator APIs; the cited official references display mixed versions (25.12.0 for Frame and page-interactions pages, 25.9.0 for the Frame.locator page), so verify that your installed package supports the methods you use.
Wait for frames and lazy content
Frames may attach after the top-level navigation, and their documents may navigate again. Synchronize on the condition you need instead of assuming that a frame exists immediately.
- Wait for the iframe element with
page.waitForSelector()before callingcontentFrame(). - After selecting a frame, wait for its target with
frame.waitForSelector()or a frame wait function. - If the embedded application lazy-loads content, wait for the item or loading state that proves the content is present.
- If a frame navigates or is detached, discard the old
Framereference, rediscover it and retry the operation.
Do not use a fixed delay as the only synchronization mechanism; a delay can expire before a slow frame is ready or waste time when it is already complete.
Common mistakes and fixes
The selector returns nothing
Cause: the query ran in the main frame, or the selector belongs to a different embedded document. Fix: print page.frames(), identify the owning frame, and call frame.locator() or frame.waitForSelector().
The iframe itself moves, but its internal panel does not
Cause: you scrolled the parent page’s iframe element, not the scroll container inside the iframe document. Fix: select the internal region through its Frame. Scroll the iframe element only when your intended result is to move the embedded rectangle within the parent page.
contentFrame() returns null
Cause: the iframe has not attached a document yet, or it was replaced. Fix: wait for the element, call contentFrame() after attachment, and reacquire it after navigation.
The frame URL does not match
Cause: redirects, query strings or an about:blank startup URL. Fix: inspect the URL after the embedded app finishes navigation, match a stable substring, or identify the iframe by a parent attribute.
A nested target is still missing
Cause: querying the outer frame does not search its child frames. Fix: call outer.childFrames() and query the returned child, repeating the traversal for deeper levels.
The scroll runs but the expected item remains hidden
Cause: the selected element is not the actual scroll container, or the item has not loaded. Fix: select the container for offset scrolling, wait for the item, and use scrollIntoView() when the goal is target visibility.
Rank #4
An API method is unavailable
Cause: the project’s Puppeteer version differs from the documentation version. Fix: check the installed package version and its matching API reference before adopting newer locator methods.
Reliability and performance considerations
- Filter frames before performing work; scanning every frame and every descendant is unnecessary on pages with large frame trees.
- Use stable selectors and URL fragments rather than positional indexes, which change when the page layout changes.
- Scroll frames sequentially when actions can affect shared state. Parallel scrolling can reduce elapsed time, but only use it when the embedded applications are independent.
- Keep explicit, bounded timeouts so a broken frame cannot hold the whole job indefinitely.
- Capture diagnostic data—frame URL, name, selector and failure stage—when a frame disappears or a target times out.
- Remember that wheel scrolling changes the selected element’s scroll position; it does not guarantee a particular pixel position if application code intercepts wheel events.
Or skip the browser setup
If your actual goal is a clean image or PDF of a page rather than interactive iframe automation, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.
For a direct capture, 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
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)
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 supports full-page capture with lazy images, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper settings and page ranges, custom CSS and JavaScript, pre-capture clicks, selector waits, delay or network-idle waits, ad/tracker/request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, up to 100 URLs per bulk call, a usage API and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify migration.
The MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients. Plans are:
| Plan | Price | Included shots |
|---|---|---|
| Free | $0 | 1,000 per month; no card |
| Starter | $5 | 3,000 |
| Growth | $15 | 15,000 |
| Pro | $39 | 60,000 |
| Scale | $99 | 250,000 |
| Business | $249 | 1,000,000 |
Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots a month without adding a card.
FAQ
Can I scroll all frames with one page-level call?
No. Each embedded document has its own Frame context, so locate and operate on each frame separately.
Recommended Free Tools
Should I use scroll() or scrollIntoView()?
Use scroll() for a controlled offset on a scrollable element. Use scrollIntoView() when a particular element must become visible.
Best Value
- Used Book in Good Condition
Does a child frame inherit selectors from its parent?
No. Traverse to the child frame explicitly, then run the selector there.
What should I do after an iframe reloads?
Rediscover the frame and its target after the navigation; a previously held frame or element handle may no longer be attached.
Frequently Asked Questions
Can I scroll all frames with one page-level call?
No. Each embedded document has its own Frame context, so locate and operate on each frame separately.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Should I use scroll() or scrollIntoView()?
Use scroll() for an offset on a scrollable element; use scrollIntoView() when a particular element must become visible.
Does a child frame inherit selectors from its parent?
No. Traverse to the child frame explicitly, then run the selector there.
What should I do after an iframe reloads?
Rediscover the frame and its target after navigation because old frame or element handles may be detached.
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.

