Free tools Windows power users keep installed
One-click scans. No signup required.
To listen for frame changes in Puppeteer, register frameattached, framenavigated, and framedetached listeners on the parent Page. Each callback receives the affected Frame, which you can inspect with methods such as url() and parentFrame().
Listen for frame lifecycle events
Register listeners before the action or navigation whose effects you need to observe. These events are dispatched on the page, including for child frames.
page.on('frameattached', frame => {
console.log('Frame attached:', frame.url());
});
page.on('framenavigated', frame => {
console.log('Frame navigated:', frame.url());
});
page.on('framedetached', frame => {
console.log('Frame detached:', frame.url());
});
The callback argument is a Puppeteer Frame. This pattern is appropriate when you want to react to an ongoing stream of frame-tree changes rather than wait for just one condition.
What each event means
| Event | When it fires | Callback value |
|---|---|---|
frameattached |
A frame was attached to the page. | The affected Frame |
framenavigated |
A frame navigated to a new URL. | The affected Frame |
framedetached |
A frame was detached from the page. | The affected Frame |
These are distinct transitions: a frame can attach, navigate, and later detach. A navigation event is not an attachment event.
#1 Best Overall
Inspect the changed frame
Use the callback’s Frame to identify its URL and place in the frame tree. The Frame API includes url(), parentFrame(), and childFrames(); a frame also has an associated page. Puppeteer treats nested frames as independent execution contexts.
page.on('framenavigated', frame => {
const parent = frame.parentFrame();
console.log({
url: frame.url(),
parentUrl: parent ? parent.url() : null,
childCount: frame.childFrames().length,
});
});
Use page.frames() when you need a snapshot of frames currently attached. It does not report future changes, so keep event listeners registered for ongoing monitoring.
Wait for one particular frame
If your code should proceed only when a frame matching a URL or predicate appears, use page.waitForFrame(urlOrPredicate, options). Use lifecycle listeners instead when you need to handle every attachment, navigation, or removal.
const targetFrame = await page.waitForFrame(
frame => frame.url().includes('widget'),
{ timeout: 10000 }
);
console.log('Matched frame:', targetFrame.url());
Choose between the APIs by intent: waitForFrame() waits for a match, frames() inspects the current set, and page event listeners observe changes as they happen.
Recommended Free Tools
Rank #3
Handle navigation without a race
Puppeteer documents Frame.waitForNavigation() for cases where code indirectly causes a frame to navigate. History API URL changes count as navigation for this wait. When an action may trigger navigation, start the wait and action together so the navigation is not missed:
const [response] = await Promise.all([
page.waitForNavigation(),
page.click('a'),
]);
This is the general page-navigation pattern. If the action navigates a child frame, use the relevant frame’s navigation wait. The lifecycle event framenavigated remains useful when you want a notification rather than a one-time navigation wait.
Run code in newly created frame documents
page.evaluateOnNewDocument() addresses a related but different need: it runs in a document after creation and before that document’s scripts execute. Puppeteer says it runs when the page navigates and when a child frame attaches or navigates. Use it to install instrumentation inside each new frame context; use page lifecycle events when Node.js code needs to be notified about frame-tree changes.
Troubleshoot missing or unexpected notifications
- No event for a change that already happened: Register listeners before the action or navigation. Events report changes from the time the listener is attached.
- A frame’s URL change is mistaken for a new frame: Handle
framenavigatedseparately fromframeattached; they represent different lifecycle changes. - You need the current frames, not future updates: Read
page.frames(). For future updates, subscribe to the page events. - A wait appears to miss navigation: Start the navigation wait and triggering action concurrently with
Promise.all(), rather than awaiting the action first. - Code depends on a particular event API surface: Check the documentation for your installed Puppeteer version. The referenced Frame and Page class pages are version 25.12.0; the PageEvent page is labeled Next, and the
evaluateOnNewDocument()page is version 25.11.0.
Or skip the browser setup
If your goal is a screenshot rather than monitoring iframe lifecycle events, ScreenshotNeo can return a page capture with one GET 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
ScreenshotNeo accepts cookie and 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 are not billed, and responses say which occurred. It also offers an MCP server with screenshot and PDF tools for AI agents. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Learn about ScreenshotNeo or sign up free for 1,000 screenshots a month with no card.
Documentation and version scope
The cited Puppeteer API documentation identifies the Frame and Page class pages as version 25.12.0. The PageEvent reference is labeled Next, so verify event availability and signatures against the version installed in your project. The documented APIs here are JavaScript Puppeteer APIs.
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.

