Overview
Stagehand can work with multiple browser tabs (pages) simultaneously. This guide covers patterns for multi-tab workflows.Creating New Pages
Basic Page Creation
const stagehand = new Stagehand({ env: "LOCAL" });
await stagehand.init();
// Get the first page (created automatically)
const page1 = stagehand.context.pages()[0];
await page1.goto("https://example.com");
// Create a second page
const page2 = await stagehand.context.newPage();
await page2.goto("https://another-site.com");
// Create a third page
const page3 = await stagehand.context.newPage();
await page3.goto("https://third-site.com");
List All Pages
const allPages = stagehand.context.pages();
console.log(`Total pages: ${allPages.length}`);
for (const page of allPages) {
console.log(`Page URL: ${page.url()}`);
}
Operating on Specific Pages
Specify Page in Operations
const page1 = stagehand.context.pages()[0];
const page2 = await stagehand.context.newPage();
await page1.goto("https://store.com");
await page2.goto("https://comparison-site.com");
// Act on page1
await stagehand.act("search for 'laptop'", { page: page1 });
// Act on page2
await stagehand.act("search for 'laptop'", { page: page2 });
// Extract from page1
const price1 = await stagehand.extract(
"get the first product price",
{ page: page1 }
);
// Extract from page2
const price2 = await stagehand.extract(
"get the first product price",
{ page: page2 }
);
console.log("Price comparison:", price1, "vs", price2);
Default Page Behavior
If you don’t specify a page, Stagehand uses the first page:// These are equivalent:
await stagehand.act("click button");
await stagehand.act("click button", { page: stagehand.context.pages()[0] });
Multi-Tab Workflows
Example: Price Comparison
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";
const stagehand = new Stagehand({ env: "LOCAL" });
await stagehand.init();
// Define schema
const productSchema = z.object({
title: z.string(),
price: z.number(),
});
// Open multiple store pages
const stores = [
"https://store1.com",
"https://store2.com",
"https://store3.com",
];
const pages = await Promise.all(
stores.map(async (url) => {
const page = await stagehand.context.newPage();
await page.goto(url);
return page;
})
);
// Search on all pages
await Promise.all(
pages.map((page) =>
stagehand.act("search for 'wireless mouse'", { page })
)
);
// Extract prices from all pages
const results = await Promise.all(
pages.map((page) =>
stagehand.extract(
"get the first product's title and price",
{ page, schema: productSchema }
)
)
);
// Find best price
const bestDeal = results.reduce((best, current) =>
current.extraction.price < best.extraction.price ? current : best
);
console.log("Best deal:", bestDeal.extraction);
await stagehand.close();
Example: Form Filling Across Tabs
const page1 = stagehand.context.pages()[0];
const page2 = await stagehand.context.newPage();
await page1.goto("https://site1.com/signup");
await page2.goto("https://site2.com/signup");
const userData = {
name: "John Doe",
email: "[email protected]",
};
// Fill forms in parallel
await Promise.all([
stagehand.act(
`type '${userData.name}' in name field and '${userData.email}' in email field`,
{ page: page1 }
),
stagehand.act(
`type '${userData.name}' in name field and '${userData.email}' in email field`,
{ page: page2 }
),
]);
Switching Between Tabs
Navigate Between Pages
const pages = stagehand.context.pages();
// Work on first tab
await stagehand.act("click button", { page: pages[0] });
// Switch to second tab
await stagehand.act("fill form", { page: pages[1] });
// Switch back to first tab
await stagehand.act("submit form", { page: pages[0] });
Bring Page to Front
const page = await stagehand.context.newPage();
await page.goto("https://example.com");
// Bring this page to front (useful in headed mode)
await page.bringToFront();
Handling Pop-ups and New Windows
Wait for New Pages
const page = stagehand.context.pages()[0];
await page.goto("https://example.com");
// Listen for new page
const newPagePromise = new Promise((resolve) => {
stagehand.context.on("page", resolve);
});
// Click something that opens a new tab
await stagehand.act("click the 'Open in New Tab' link", { page });
// Wait for the new page
const newPage = await newPagePromise;
console.log("New page opened:", newPage.url());
// Work with the new page
await stagehand.act("perform action", { page: newPage });
Handle Target=“_blank” Links
const page = stagehand.context.pages()[0];
await page.goto("https://example.com");
const pagesBefore = stagehand.context.pages().length;
// Click link that opens in new tab
await stagehand.act("click the 'Terms' link", { page });
// Wait a moment for new page to open
await page.waitForTimeout(1000);
const pagesAfter = stagehand.context.pages();
if (pagesAfter.length > pagesBefore) {
const newPage = pagesAfter[pagesAfter.length - 1];
console.log("New tab opened:", newPage.url());
}
Closing Pages
Close Specific Page
const page = await stagehand.context.newPage();
await page.goto("https://example.com");
// Do work...
// Close this specific page
await page.close();
Close All But One
const pages = stagehand.context.pages();
const mainPage = pages[0];
// Close all other pages
for (let i = 1; i < pages.length; i++) {
await pages[i].close();
}
Close All Pages
// Close entire browser (closes all pages)
await stagehand.close();
Parallel Execution
Execute on Multiple Pages Concurrently
const urls = [
"https://site1.com",
"https://site2.com",
"https://site3.com",
];
// Create pages in parallel
const pages = await Promise.all(
urls.map(async (url) => {
const page = await stagehand.context.newPage();
await page.goto(url);
return page;
})
);
// Extract data from all pages in parallel
const results = await Promise.all(
pages.map((page) =>
stagehand.extract("get page title", { page })
)
);
console.log("Results:", results);
Rate Limiting
async function processUrlsWithLimit(
stagehand: Stagehand,
urls: string[],
concurrency: number
) {
const results = [];
for (let i = 0; i < urls.length; i += concurrency) {
const batch = urls.slice(i, i + concurrency);
const batchResults = await Promise.all(
batch.map(async (url) => {
const page = await stagehand.context.newPage();
await page.goto(url);
const result = await stagehand.extract("get data", { page });
await page.close();
return result;
})
);
results.push(...batchResults);
}
return results;
}
// Process 20 URLs, 5 at a time
const urls = Array.from({ length: 20 }, (_, i) => `https://site${i}.com`);
const results = await processUrlsWithLimit(stagehand, urls, 5);
Agent with Multiple Pages
Specify Page for Agent
const page1 = stagehand.context.pages()[0];
const page2 = await stagehand.context.newPage();
await page1.goto("https://store.com");
await page2.goto("https://comparison.com");
const agent = stagehand.agent();
// Agent works on page1
const result1 = await agent.execute({
instruction: "find the cheapest laptop",
maxSteps: 10,
page: page1,
});
// Agent works on page2
const result2 = await agent.execute({
instruction: "find the cheapest laptop",
maxSteps: 10,
page: page2,
});
Multi-Page Agent Workflow
const agent = stagehand.agent();
// Start on first page
const page1 = stagehand.context.pages()[0];
await page1.goto("https://email-provider.com");
// Agent logs in and gets verification code
const emailResult = await agent.execute({
instruction: "log in and extract the verification code from the latest email",
maxSteps: 10,
page: page1,
});
const code = emailResult.extraction.code;
// Switch to second page for actual signup
const page2 = await stagehand.context.newPage();
await page2.goto("https://service.com/signup");
// Agent completes signup with verification code
await agent.execute({
instruction: `complete signup and enter verification code '${code}'`,
maxSteps: 10,
page: page2,
});
Managing Page State
Check Page Count
const pageCount = stagehand.context.pages().length;
console.log(`Currently managing ${pageCount} pages`);
if (pageCount > 10) {
console.warn("Too many pages open, consider closing some");
}
Track Pages
const pageMap = new Map<string, any>();
const page1 = await stagehand.context.newPage();
await page1.goto("https://store.com");
pageMap.set("store", page1);
const page2 = await stagehand.context.newPage();
await page2.goto("https://comparison.com");
pageMap.set("comparison", page2);
// Later, retrieve specific pages
const storePage = pageMap.get("store");
await stagehand.act("search for product", { page: storePage });
Best Practices
Common Patterns
Data Aggregation
const sources = [
"https://news1.com",
"https://news2.com",
"https://news3.com",
];
const pages = await Promise.all(
sources.map(async (url) => {
const page = await stagehand.context.newPage();
await page.goto(url);
return page;
})
);
const headlines = await Promise.all(
pages.map((page) =>
stagehand.extract("get top 5 headlines", { page })
)
);
const allHeadlines = headlines.flatMap(h => h.extraction);
Sequential Processing
const tasks = [
{ url: "https://site1.com", action: "extract data" },
{ url: "https://site2.com", action: "extract data" },
{ url: "https://site3.com", action: "extract data" },
];
for (const task of tasks) {
const page = await stagehand.context.newPage();
await page.goto(task.url);
const result = await stagehand.extract(task.action, { page });
console.log(`Result from ${task.url}:`, result);
await page.close();
}
Troubleshooting
Page Not Responding
try {
await stagehand.act("click button", {
page,
timeout: 10_000,
});
} catch (error) {
console.log("Page may have crashed or become unresponsive");
// Close and recreate page
await page.close();
page = await stagehand.context.newPage();
await page.goto(url);
}
Memory Management
// Process large number of URLs
for (let i = 0; i < urls.length; i++) {
const page = await stagehand.context.newPage();
await page.goto(urls[i]);
const result = await stagehand.extract("get data", { page });
results.push(result);
// Close page after each iteration
await page.close();
}