Frames
Introduction
A page can have one or more iframes. In CSTesting, page-level actions (like browser.click(selector)) run in the main frame. To interact with elements inside an iframe, you get a frame handle with browser.frame(iframeSelector) and then use the same API on that handle: click, type, locator, select, check, and so on. You do not switch the main page; the frame handle targets the iframe's document.
Same-origin only: Frames are accessed via the main page's contentDocument. Cross-origin iframes cannot be accessed (browser security). Only same-origin iframes are supported.
CSTesting supports nested frames: an iframe that contains another iframe. Call .frame(selector) on the outer frame handle to get an inner frame handle.
You will learn
- How to get a frame (by selector)
- How to interact inside a frame (click, type, locator)
- Nested frames (iframe inside iframe)
- Waiting for frame content (waitForSelector)
- What is not supported (frame by name/URL, cross-origin)
Getting a frame
Use browser.frame(iframeSelector) and pass a selector that matches the iframe element (e.g. CSS or XPath). The selector is evaluated in the main frame. The returned handle lets you run actions inside that iframe.
// Get frame by CSS selector (class, id, etc.)
const frame = browser.frame('iframe.login-frame');
const frame = browser.frame('#sidebar-iframe');
const frame = browser.frame('[name="content"]');
// Get frame by XPath if needed
const frame = browser.frame('//iframe[@title="Widget"]');
There is no page.frame('frame-name') by string name only, or page.frame({ url: /regex/ }). You target the iframe by a selector that finds the <iframe> element in the main document (e.g. [name="frame-login"] for <iframe name="frame-login">).
Interacting inside a frame
The frame handle has the same interaction API as the page: click, type, locator, select, check, uncheck, hover, dragAndDrop, and so on. All selectors inside these calls are evaluated inside that frame's document.
Example: locate and fill an input inside the frame
await browser.goto('https://example.com/page-with-iframe');
const frame = browser.frame('.frame-class');
await frame.type('#username-input', 'John');
await frame.type('#password-input', 'secret');
await frame.click('button[type="submit"]');
Using a locator inside the frame
const frame = browser.frame('iframe#login');
const username = frame.locator('#username-input');
await username.type('John');
await frame.locator('button[type="submit"]').click();
Other actions
const frame = browser.frame('iframe#form');
await frame.click('#agree');
await frame.check('#newsletter');
await frame.select('#country', { label: 'United States' });
const text = await frame.locator('h1').textContent();
expect(text).toContain('Welcome');
Nested frames
If an iframe contains another iframe, get the outer frame first, then call .frame(selector) on it. The inner selector is evaluated inside the outer frame's document (so it finds the inner iframe element there).
// Page structure: page → iframe#outer → iframe#inner → element
const outerFrame = browser.frame('iframe#outer');
const innerFrame = outerFrame.frame('iframe#inner');
// Interact inside the inner frame
await innerFrame.type('#username', 'John');
await innerFrame.click('button');
Example: two levels of nesting
<!-- Main page -->
<iframe id="parent" src="...">
<!-- Parent frame -->
<iframe id="child" src="...">
<!-- Child frame: input and button here -->
</iframe>
</iframe>
const parentFrame = browser.frame('#parent');
const childFrame = parentFrame.frame('#child');
await childFrame.type('#user-name', 'John');
await childFrame.click('button[type="submit"]');
You can chain as many levels as you have: browser.frame('A').frame('B').frame('C') for three levels.
Waiting for frame content
Frames (especially nested or lazy-loaded ones) may not have content immediately. Use waitForSelector on the frame handle to wait until an element exists inside that frame (or until a timeout).
const frame = browser.frame('iframe#dynamic');
await frame.waitForSelector('#username', { timeout: 10000 });
await frame.type('#username', 'John');
For a nested frame that loads late, wait inside the inner frame after getting it:
const outerFrame = browser.frame('iframe#outer');
const innerFrame = outerFrame.frame('iframe#inner');
await innerFrame.waitForSelector('.content', { timeout: 5000 });
await innerFrame.click('.content button');
Frame handle API summary
| Method | Description |
|---|---|
| frame(iframeSelector) | Get a nested frame (iframe inside this frame). Returns another FrameHandle. |
| waitForSelector(selector, { timeout? }) | Wait until selector matches inside this frame. |
| click(selector), doubleClick, rightClick, hover | Mouse actions inside the frame. |
| type(selector, text) | Type text into an element inside the frame. |
| select(selector, option) | Select option(s) in a <select> inside the frame. |
| check(selector), uncheck(selector) | Check/uncheck checkbox or radio inside the frame. |
| dragAndDrop(source, target) | Drag from source to target inside the frame. |
| locator(selector) | Get a locator for an element inside the frame (then .click(), .type(), etc.). |
| getByAttribute(attr, value) | Locate by attribute inside the frame. |
| getTextContent(selector) | Get text of element inside the frame. |
| getAttribute(selector, attrName) | Get attribute value of element inside the frame. |
| isVisible(selector), isDisabled(selector), isEditable(selector), isSelected(selector) | Element state inside the frame. |
| evaluate(expression) | Run JavaScript in the frame's context. |
| content() | Get the frame document's HTML string. |
There is no goto or url() on a frame handle; the frame's document is already loaded. Use evaluate if you need something from the frame's window (e.g. frame.evaluate('location.href')).
What is not supported (current version)
| Feature | Supported? | Workaround |
|---|---|---|
| Frame by selector (browser.frame('iframe#id')) | Yes | — |
| Nested frames (frame.frame('inner')) | Yes | — |
| Same API as page (click, type, locator, etc.) | Yes | — |
| waitForSelector inside frame | Yes | — |
| Frame by name only (e.g. page.frame('frame-login')) | No | Use a selector: browser.frame('[name="frame-login"]'). |
| Frame by URL (e.g. page.frame({ url: /domain/ })) | No | Use a selector that identifies the iframe (e.g. by id, name, or other attribute). |
| Cross-origin iframes | No | Only same-origin iframes (contentDocument) are supported. Cross-origin requires a different approach (e.g. CDP target switching), not exposed in the current API. |
Summary
- Use browser.frame(iframeSelector) to get a handle to an iframe. Selector is evaluated in the main frame and must match the
<iframe>element. - Interact with the frame via the handle: frame.click(), frame.type(), frame.locator(), etc. Selectors in these calls run inside the frame's document.
- Nested frames: call outerFrame.frame(innerIframeSelector) to get an iframe inside another iframe. Chain as needed: browser.frame('A').frame('B').
- Use frame.waitForSelector(selector) when the frame content loads late.
- Same-origin only. No frame by name/URL helpers; use a selector (e.g.
[name="..."]) to target the iframe element.