How to Handle Iframe Targeting and Nested Browsing Contexts in ego-browser
To handle iframe targeting in ego-browser, use browser.iframeTarget(substring) to discover a CDP target by URL, then attach to it with Target.attachToTarget to create a session that routes all subsequent commands into the nested browsing context.
Working with nested frames in browser automation is notoriously complex. The ego-browser package in the citrolabs/ego-lite repository simplifies this by exposing every iframe as a separate Chrome DevTools Protocol (CDP) target with its own targetId. This architecture lets you discover, attach to, and control iframes using the same high-level API you use for top-level pages.
Discovering Iframe Targets with iframeTarget
The entry point for iframe handling is the iframeTarget method. According to the source code in driver/nav.ts, this helper scans all available CDP targets and returns the targetId of the first iframe whose URL contains your supplied substring.
The function is re-exported from helpers.ts for convenient access:
import { browser } from 'ego-browser';
// Find an iframe with "checkout" in its URL
const iframeId = await browser.iframeTarget('checkout');
console.log('Found iframe target:', iframeId);
This is more reliable than DOM-based iframe detection because it operates at the browser target level, catching dynamically created iframes that may not yet be in the DOM tree.
Attaching to a Nested Browsing Context
Once you have the targetId, you must create a CDP session bound to that specific target. The cdp helper—also exported from helpers.ts—sends the Target.attachToTarget command:
import { cdp } from 'ego-browser';
const { sessionId } = await cdp('Target.attachToTarget', {
targetId: iframeId,
flatten: true, // Required for modern session handling
});
console.log('Attached session:', sessionId);
The sessionId returned in the response is the key to iframe targeting. All subsequent CDP commands that include this sessionId are automatically routed to the iframe's browsing context rather than the main page.
Executing Commands Inside Iframes
With an active session, you can run any CDP command in the nested context. The ego-browser runtime handles the routing transparently:
// Evaluate JavaScript inside the iframe
const result = await cdp('Runtime.evaluate', {
expression: 'document.title',
returnByValue: true,
sessionId, // Routes to iframe context
});
console.log('Iframe title:', result.result.value);
This works for any CDP domain: DOM.querySelector, Input.dispatchMouseEvent, Network.*, and more. The sessionId parameter is what distinguishes nested browsing context commands from main page commands.
Automatic Session Management in Higher-Level APIs
The runtime maintains an internal mapping of iframe targets to sessions. In element-resolver.ts, the iframeSessions Map stores these relationships. This enables transparent operation through the high-level API without manual session tracking:
// Attach once, then use standard API
const iframeId = await browser.iframeTarget('checkout');
await cdp('Target.attachToTarget', {
targetId: iframeId,
flatten: true
});
// These now automatically run inside the iframe
const title = await page.evaluate(() => document.title);
const button = await page.locator('#submit');
await button.click();
The page.locator and page.evaluate methods query the iframeSessions map in element-resolver.ts to determine the correct execution context for each operation.
Cleaning Up Iframe Sessions
When finished with an iframe target, explicitly detach to free resources:
await cdp('Target.detachFromTarget', { sessionId });
If omitted, the runtime in browser-runtime.ts handles cleanup on process termination, but explicit detachment is recommended for long-running automation scripts with many frame transitions.
Complete Working Example
Here's a full pattern for reliable iframe targeting:
import { browser, cdp, page } from 'ego-browser';
async function interactWithIframe() {
// Step 1: Discover
const iframeId = await browser.iframeTarget('payment-form');
// Step 2: Attach
const { sessionId } = await cdp('Target.attachToTarget', {
targetId: iframeId,
flatten: true,
});
// Step 3: Interact (low-level CDP)
await cdp('Input.insertText', {
text: '4111111111111111',
sessionId,
});
// Or use high-level API (automatic context detection)
const submitButton = await page.locator('.pay-btn');
await submitButton.click();
// Step 4: Cleanup
await cdp('Target.detachFromTarget', { sessionId });
}
await interactWithIframe();
Key Source Files for Iframe Handling
| File | Purpose |
|---|---|
helpers.ts |
Re-exports iframeTarget and generic cdp helper |
driver/nav.ts |
Implements iframeTarget by filtering Target.getTargets |
browser-runtime.ts |
Manages CDP session lifecycle including attach/detach |
element-resolver.ts |
Maintains iframeSessions Map for automatic context routing |
Summary
browser.iframeTarget(substring)indriver/nav.tsdiscovers iframe CDP targets by URL patterncdp('Target.attachToTarget', { targetId, flatten: true })creates a session for a nested browsing context- The
sessionIdroutes all subsequent commands to the iframe instead of the main page element-resolver.tsenables transparent high-level API usage after attachment- Explicit
Target.detachFromTargetreleases resources when done
Frequently Asked Questions
How does ego-browser identify iframes compared to Puppeteer or Playwright?
Unlike Puppeteer/Playwright, which abstract iframes as page-like objects, ego-browser exposes the underlying Chrome DevTools Protocol target structure directly. Every frame—including main pages, subframes, and worker contexts—is a separate CDP target with a unique targetId. The iframeTarget helper filters these targets by URL substring, giving you precise control without framework abstractions.
Can I attach to multiple nested iframes simultaneously?
Yes. Each Target.attachToTarget call returns a distinct sessionId. The iframeSessions Map in element-resolver.ts tracks these independently. You can maintain active sessions for multiple iframes and switch between them by using the appropriate sessionId in CDP calls, or rely on automatic resolution through page.locator when selectors uniquely identify elements in specific frames.
What happens if an iframe navigates to a new URL after I attach?
The sessionId remains valid across navigations within the same targetId. However, if the iframe is destroyed and recreated (common with single-page applications), the original targetId becomes invalid. You must call iframeTarget again to discover the new target and create a fresh session. The runtime does not automatically remap destroyed frames.
Is the flatten: true parameter required when attaching to iframes?
Yes. Modern CDP uses flattened sessions where commands directly specify sessionId rather than nesting through Target.sendMessageToTarget. The ego-browser implementation in browser-runtime.ts assumes flattened mode. Omitting flatten: true will cause session routing failures for all subsequent commands.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →