Skip to main content

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

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 });
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

1

Close unused pages

Prevent memory leaks by closing pages you no longer need
2

Specify page explicitly

Always pass { page } to avoid confusion with multiple tabs
3

Use parallel execution

Process multiple pages concurrently for better performance
4

Handle page creation events

Listen for new pages when clicking links that open new tabs
5

Limit concurrency

Don’t open too many pages at once (causes performance issues)
6

Track page purpose

Use a Map or object to track which page is which

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();
}

Build docs developers (and LLMs) love