Multi-tab & iframes
For the complete documentation index, see llms.txtunotest scenarios can span tabs and reach into frames.
Switch tabs
When an action opens a new tab, switch to it by index (0-based):
step("Open the invoice in a new tab", () => { click(getByRole("link", { name: "View invoice" })); setPage(1); // focus the new tab assertVisible(getByText("Invoice #"));});Inspect tabs with the list_pages / get_active_context MCP tools while
exploring.
setPage waits for the tab at that index to appear, so no pause is needed
between the click and the switch.
Wait for a new tab
When you don’t know the new tab’s index, use waitForPage(). Examples: a
browser extension opens the tab, or other tabs are already open.
waitForPage() returns the first tab the scenario hasn’t received yet and
waits if there is none. It switches to that tab and returns its index:
step("The extension opens the report", () => { click(getByRole("button", { name: "Open report" })); report = waitForPage(); // active now assertUrl("/report");});A tab counts as received when it was open at the start, when setPage
switched to it, or when an earlier waitForPage() returned it. A tab that
opened before the call counts too, even if the click was a step earlier.
No tab is returned twice. The budget is {timeoutMs} and defaults to the
navigation timeout. When no new tab arrives in time, the step fails with
no new tab within N ms, N tabs open.
Open and close a tab
newPage(url?) opens a tab, makes it active and returns its index. A
relative url resolves against the base URL, as in goto. closePage()
closes the active tab, and the steps return to the tab that was active
before it. closePage(i) closes another one.
step("Compare with the public page", () => { newPage("/pricing"); // active now assertVisible(getByText("Pro")); closePage(); // back to the tab before it});A tab the scenario opened itself is never returned by waitForPage().
Keep a tab in front
setPage(i) does two things: the steps act on tab i, and the browser
shows it. setPage(i, {activate: false}) does only the first. Use it to
drive an extension panel in the background while the site stays in
front. bringToFront(i) does only the second: it shows tab i, and the
steps stay where they are. Without an index it shows the active tab.
step("Run the panel with the site in front", () => { setPage(1, { activate: false }); // steps on the panel, site in front click(getByRole("button", { name: "Run" })); bringToFront(); // now show the panel too});Work inside an iframe
Enter a frame to scope subsequent calls; exit when done:
step("Submit the embedded payment form", () => { enterFrame(getByTestId("payment-iframe")); fill(getByLabel("Card number"), TEST_CARD); click(getByRole("button", { name: "Pay" })); exitFrame();});Or resolve a single element across the boundary with contentFrame(). See the
DSL reference.