Understanding how Stagehand manages browser contexts, pages, and frames
Stagehand provides a unified API for managing browser contexts, pages, and iframes. Understanding this architecture helps you build more reliable multi-page automations.
The new page is automatically registered and becomes the active page.
How it works:
context.ts:438-467
public async newPage(url = "about:blank"): Promise<Page> { const targetUrl = String(url ?? "about:blank"); const { targetId } = await this.conn.send<{ targetId: string }>( "Target.createTarget", // Create at about:blank so init scripts can install before first real navigation. { url: "about:blank" }, ); this.pendingCreatedTargetUrl.set(targetId, "about:blank"); // Best-effort bring-to-front await this.conn.send("Target.activateTarget", { targetId }).catch(() => {}); const deadline = Date.now() + 5000; while (Date.now() < deadline) { const page = this.pagesByTarget.get(targetId); if (page) { // we created at about:blank; navigate only after attach so init scripts run // on the first real document. Fire-and-forget so newPage() resolves on attach. if (targetUrl !== "about:blank") { // Seed requested URL into the page cache before navigation events arrive. page.seedCurrentUrl(targetUrl); void page .sendCDP("Page.navigate", { url: targetUrl }) .catch(() => {}); } return page; } await new Promise((r) => setTimeout(r, 25)); } throw new TimeoutError(`newPage: target not attached (${targetId})`, 5000);}
Pages start at about:blank to allow init scripts to install before the first real navigation.
By default, Stagehand methods operate on the active page. To target a specific page:
const page1 = stagehand.context.pages()[0];const page2 = await stagehand.context.newPage();// Act on page2await stagehand.act("click the submit button", { page: page2 });// Extract from page1const data = await stagehand.extract( "get the product title", { page: page1 });// Observe on a specific pageconst actions = await stagehand.observe( "find all links", { page: page1 });
Explicit page passing is essential for multi-tab workflows.
Example from the codebase:
example.ts:17-34
const page2 = await stagehand.context.newPage();await page2.goto( "https://browserbase.github.io/stagehand-eval-sites/sites/iframe-same-proc/",);await stagehand.extract( "extract the placeholder text on the your name field", { page: page2 },);await stagehand.act("fill the your name field with the text 'John Doe'", { page: page2,});const action2 = await stagehand.observe( "select blue as the favorite color on the dropdown", { page: page2 },);for (const action of action2) { await stagehand.act(action, { page: page2, timeout: 30_000 });}
// Clear all cookiesawait stagehand.context.clearCookies();// Clear specific cookies by nameawait stagehand.context.clearCookies({ name: "session" });// Clear by domainawait stagehand.context.clearCookies({ domain: "example.com" });// Clear by pathawait stagehand.context.clearCookies({ path: "/admin" });
Source: context.ts:1003-1038
Cookie operations are atomic on the browser endpoint, avoiding race conditions.
// Trigger the popupawait stagehand.act("click 'Open Details'");// Wait for the new pageconst newPage = await stagehand.context.awaitActivePage();// The popup is now activeawait stagehand.extract("get the details", { page: newPage });
How it works:
context.ts:904-936
async awaitActivePage(timeoutMs?: number): Promise<Page> { const defaultTimeout = this.env === "BROWSERBASE" ? 4000 : 2000; timeoutMs = timeoutMs ?? defaultTimeout; // If a popup was just triggered, wait for the new page const recentWindowMs = this.env === "BROWSERBASE" ? 1000 : 300; const now = Date.now(); const hasRecentPopup = now - this._lastPopupSignalAt <= recentWindowMs; const immediate = this.activePage(); if (!hasRecentPopup && immediate) return immediate; const deadline = now + timeoutMs; while (Date.now() < deadline) { // Prefer most-recent by createdAt let newestTid: TargetId | undefined; let newestTs = -1; for (const [tid] of this.pagesByTarget) { const ts = this.createdAtByTarget.get(tid) ?? 0; if (ts > newestTs) { newestTs = ts; newestTid = tid; } } if (newestTid) { const p = this.pagesByTarget.get(newestTid); if (p && newestTs >= this._lastPopupSignalAt) return p; } await new Promise((r) => setTimeout(r, 25)); } throw new PageNotFoundError("awaitActivePage: no page available");}
Use awaitActivePage() when pages open from user actions:
await stagehand.act("click the link that opens a new tab");const newPage = await stagehand.context.awaitActivePage();// Now interact with the new page
Set Init Scripts Early
Add init scripts before creating pages:
await stagehand.context.addInitScript(() => { window.__testMode = true;});// Now all new pages will have the scriptconst page = await stagehand.context.newPage();