555K npm/wk is higher than Chrome DevTools MCP (423K). TypeScript-first SDK with $67.5M funding (Browserbase). $300M valuation. Auto-caching reduces LLM costs over time.
Stagehand
activeAI-native browser automation SDK by Browserbase with natural language selectors and act/extract/observe primitives.
Where it wins
TypeScript-native with clean act/extract/observe API
Natural language selectors reduce brittle CSS/XPath dependencies
Official Browserbase backing with cloud browser infrastructure
Where to be skeptical
Smaller community than Browser Use
Tied to Browserbase ecosystem for cloud execution
Newer — less battle-tested in production agent loops
Editorial verdict
Best SDK for building browser automation into products. Three clean primitives (act, extract, observe), v3 dropped Playwright dependency, Cloudflare official integration. New browse-cli shows the team reads the CLI-over-MCP signal correctly.
Source
Videos
Reviews, tutorials, and comparisons from the community.
Tutorial de Testing E2E con IA
Stagehand V2: FULLY FREE Browser Use AI Agent! Automate ANYTHING! (Opensource)
Automate Your Browser with AI Using StageHand AI Agent
Related
Playwright MCP
93Microsoft's official MCP server for Playwright. Uses accessibility snapshots instead of screenshots for structured browser control. Auto-configured in GitHub Copilot's Coding Agent.
Chrome DevTools MCP
92Google Chrome team's official MCP server for Chrome DevTools. Gives coding agents deep debugging, performance profiling, and Core Web Vitals analysis through 26 tools across 6 categories.
Skyvern
90Vision-LLM browser automation for enterprise workflows. Combines computer vision with LLM reasoning to handle websites never seen before. YC S23 backed with CAPTCHA solving, 2FA, and proxy networks.
Vercel Agent Browser
88Token-efficient browser automation CLI for AI agents. Rust core with sub-50ms boot. Claims 93% context reduction vs Playwright MCP through ref-based element selection on accessibility snapshots.
Public evidence
Front-page Show HN with overwhelmingly positive reception. Community praised hybrid AI + Playwright approach. 326 pts is strong organic engagement validating the SDK approach.
Cloudflare added native Stagehand support in their Browser Rendering Workers. Major cloud provider integration validates production readiness at scale.
Independent hands-on review concluding Stagehand is a 'Playwright enhancement, not replacement.' Praises act/extract/observe primitives, but notes agent mode remains experimental with unpredictable results.
Independent landscape survey describes Stagehand v3 as '44% faster' with an 'AI-native rewrite' that talks directly to browsers via CDP. Positions it as the main OSS alternative to Playwright for AI agents.
Stagehand featured as a notable rising project with +17.1K stars gained in 2025 alone (from 2.8K to 19.9K total).
Stagehand v3 dropped the Playwright hard dependency — now supports any CDP-based driver. 44% faster. New browse-cli product (launched Mar 18) shows the team reads the CLI-over-MCP signal correctly.
Independent comparison measured Stagehand at ~75% task completion vs Browser Use ~78%. TypeScript-only (no Python). Auto-caching architecture genuinely differentiated: once AI action succeeds, selector cached and replayed without LLM calls.
Raw GitHub source
GitHub README peek
Constrained peek so you can sanity-check the source material without leaving the site.
AI that uses the browser like humans.
Sign in once, keep the session, and pull structured data out the other side.
import { localBrowser, Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod/v4";
// Cookies persist in ./browser-data, so the next run starts already signed in
const browser = await localBrowser.launch({ userDataDir: "./browser-data" });
const stagehand = await Stagehand.create({
browser,
model: { modelName: "openai/gpt-5.4-mini", apiKey: process.env.OPENAI_API_KEY },
});
const [page] = await browser.context.pages();
await page.goto("https://app.example.com/login");
// observe() returns real selectors, so credentials never reach the model
const { data: email } = await stagehand.observe("find the email input");
const { data: password } = await stagehand.observe("find the password input");
await page.locator(email[0].selector).fill(process.env.APP_EMAIL!);
await page.locator(password[0].selector).fill(process.env.APP_PASSWORD!);
// act() self-heals when the site redesigns its form
await stagehand.act("click the sign in button");
await stagehand.act("open the billing page");
// extract() returns schema-validated data
const { data } = await stagehand.extract(
"extract every invoice in the table",
z.object({
invoices: z.array(z.object({ number: z.string(), amount: z.number(), paid: z.boolean() })),
}),
);
console.log(data.invoices);
await stagehand.close();
await browser.close();
<details>
<summary><b>Python</b></summary>
import asyncio
import os
from pydantic import BaseModel
from stagehand import Stagehand, local_browser
class Invoice(BaseModel):
number: str
amount: float
paid: bool
class Invoices(BaseModel):
invoices: list[Invoice]
async def main() -> None:
# Cookies persist in ./browser-data, so the next run starts already signed in
browser = await local_browser.launch(user_data_dir="./browser-data")
try:
stagehand = await Stagehand.create(
browser=browser,
model="openai/gpt-5.4-mini",
model_api_key=os.environ["OPENAI_API_KEY"],
)
try:
page = (await browser.context.pages())[0]
await page.goto("https://app.example.com/login")
# observe() returns real selectors, so credentials never reach the model
email = await stagehand.observe("find the email input")
password = await stagehand.observe("find the password input")
await page.locator(email.data[0].selector).fill(os.environ["APP_EMAIL"])
await page.locator(password.data[0].selector).fill(os.environ["APP_PASSWORD"])
# act() self-heals when the site redesigns its form
await stagehand.act("click the sign in button")
await stagehand.act("open the billing page")
# extract() returns schema-validated data
result = await stagehand.extract(
"extract every invoice in the table",
Invoices,
)
print(result.data.invoices)