Interactive Explainer

Stagehand:
Inside the AI-Driven Browser Automation Framework

Hardcoded selectors break the moment a page changes; fully autonomous agents are quick to write but hard to trust. Stagehand, the open-source browser-automation SDK from Browserbase, takes a third path - you write plain-English instructions, an AI resolves them to real elements at runtime, and deterministic code does the clicking. Explore how its four verbs work, why it caches the AI's answers, and what it gave up to leave Playwright behind.

July 2026 · 10 min read · 5 figures

Your scraper ran flawlessly for eight months. Then, one Tuesday night, a frontend developer at the site you automate renamed a CSS class - .btn--buy became .purchase-cta - and your pipeline died without a sound. Nothing crashed loudly. The script looked for a button that no longer existed, timed out, retried, timed out again. You found out Thursday, from a stakeholder asking where the data went.

This is the standing bargain of traditional browser automation. Tools like Playwright, Selenium, and Puppeteer are exact: you tell them precisely which element to click, and they click it fast, deterministically, for free. But that exactness is also the failure mode. A hardcoded selector is a bet that the page will never change, and the page always changes. Every automation you write is quietly accruing a maintenance debt that comes due at the least convenient time.

At the other extreme sits the fully autonomous AI agent. You hand it a goal - "buy the headphones" - and it looks at the page, reasons about it, and figures out the clicks on its own. Redesigns don't faze it. But now you have a different problem: the agent might take a different path every run, it's hard to debug when it wanders, and every step burns model tokens. You've traded brittle for black box.

Stagehand is an open-source framework from Browserbase (the company that sells headless browser infrastructure) built on a wager: the right unit of automation is neither the selector nor the goal, but the instruction. MIT-licensed, with over 23,000 GitHub stars, it bills itself as "the SDK for browser agents." You write act("click the buy button"), and an AI resolves that sentence at runtime to whatever the buy button happens to be today - then ordinary, deterministic browser code performs the click.

Developers use Stagehand to reliably automate the web.

Stagehand documentation

To feel the difference, try breaking something. The figure below shows the same tiny storefront automated two ways: a hardcoded selector on the left, a Stagehand-style instruction on the right. Run both, then ship a redesign and run them again - watch which one survives.

Figure 1 - Ship a Redesign
Layout: v1
Two automations target the same store. After you ship the redesign, the hardcoded selector on the left still aims at where the button used to be and fails, while the natural-language instruction on the right re-resolves against the new page and finds the renamed, relocated button.
The key insight: Stagehand splits every browser action into two halves. An AI decides what to interact with; deterministic code performs the interaction. The AI half absorbs page changes, and the code half keeps the behavior exact and replayable.

Four Verbs for a Browser

A stagehand, in the theater, is the person in the wings who moves the props so the show can go on. The framework earns its name with a deliberately small API: four verbs that cover everything you'd ask of a browser.

Do one thing
act()
Performs a single browser action from a plain-English instruction: click, fill, scroll, select. One instruction, one action.
Pull data out
extract()
Pulls structured data from the page, validated against a schema you define. Messy DOM in, typed object out.
Scout first
observe()
Surveys the page and returns candidate actions - with selectors and methods - before you commit to any of them.
Hand over the wheel
agent()
Runs an autonomous multi-step workflow toward a goal, including with computer-use models, when you'd rather describe the destination than the route.

The first three are the precision tools. act() executes exactly one step; the docs are emphatic about keeping instructions atomic - "click the login button", not "log in and go to settings". extract() turns scraping into a typed function call: you hand it a Zod schema and get back an object that actually conforms to it (see the box below if Zod is new to you). observe() is reconnaissance - it tells you what's actionable on the page before anything irreversible happens.

// The precision tools await stagehand.act("click the login button"); const price = await stagehand.extract("extract the price", z.number()); const actions = await stagehand.observe("find submit buttons");
New to Zod?

Zod is a TypeScript library for describing the shape of data. You spell out each field and its type once; Stagehand shows that description to the model as the thing to fill in, then checks the model's answer against it before handing the object back. A page that returns the wrong shape raises an error instead of a silently-wrong result.

import { z } from "zod"; // Describe the shape you expect const Product = z.object({ name: z.string(), price: z.number(), }); // extract() returns an object guaranteed to match it const product = await stagehand.extract( "get the product name and price", Product );

The fourth verb changes who's driving. agent() takes a goal and loops - observing, deciding, acting - until the goal is met. You can back it with a regular LLM or a dedicated computer-use model:

// The autonomy tool const agent = stagehand.agent({ mode: "cua", model: "google/gemini-2.5-computer-use-preview-10-2025", }); await agent.execute("apply for this job");

One more detail worth knowing early: instructions are sent to a model provider, so Stagehand gives you placeholder variables for anything sensitive. You write act("type %password% into the password field", { variables: { password: ... } }) and the secret is substituted locally - the LLM only ever sees the placeholder.

The tension between the two modes is the interesting part. The precision verbs are predictable and debuggable but need you to write the route; the agent finds the route but is only as predictable as its model. Stagehand's own docs describe how most teams square it: use the agent for exploration, and pin the critical paths down with individual primitives. The figure below puts all four verbs on the same page - literally. Switch tabs and watch what each one does to the same little store.

Figure 2 - The Verbs, Live
The same mock storefront under each verb. act performs one click; extract lifts typed JSON out of the page; observe tags every actionable element with a numbered badge before anything is clicked; agent chains steps on its own toward a goal. Watch the right-hand panel - it shows what your code actually receives back.

From Instruction to Click

So what actually happens inside act("click the buy button")? It can feel like magic, but each stage turns out to be a piece of understandable engineering - a short pipeline you can follow from end to end.

First, Stagehand needs to show the model the page, and it does not send a screenshot or a raw HTML dump by default. It reads the page's accessibility tree - the semantic outline the browser already maintains for screen readers, where a <div class="x7-btn"> becomes simply button, "Buy now". (You can see this directly: calling extract() with no arguments returns the accessibility tree itself.) It's a beautifully economical choice - the tree is orders of magnitude smaller than the DOM, and it describes the page the way a person would: by role and label, not by markup.

The model receives your instruction plus that tree and picks a target and a method - click, fill, type, press, scroll, or select. Crucially, the decision materializes as a concrete, boring artifact: the ActResult that act() returns records the XPath selectors that were used, alongside success status and a description of what happened. The AI's judgment gets compiled down to something you can log, inspect, and - as the next section shows - cache. Then deterministic code dispatches the actual event, traversing iframes and shadow DOM on its own when the target lives inside one.

This is also why instruction phrasing matters more than it might seem. The docs push you toward specificity - "the red Delete button next to user John Smith" beats "click the button" - because the model is choosing among candidates, and ambiguity in, wrong element out. For anything high-stakes, there's a preview idiom: observe() returns the resolved action without executing it, and you pass it to act() only if you like what you see.

// Look before you leap: preview the resolved action, then run it const [action] = await stagehand.observe("click the login button"); if (action) await stagehand.act(action);

Step through the pipeline yourself below. Each press of the button advances the instruction one stage further toward a real click.

Figure 3 - Inside act()
Stage 0 of 5
The five stages of an act() call. Watch the payload panel below the pipeline: the fuzzy natural-language instruction becomes a filtered accessibility tree, then a model decision, then a concrete XPath - and only then does anything touch the browser.
Notice that: the LLM never clicks anything. It only ever answers the question "which element, which method?" - and its answer is written down as an XPath before execution. That artifact is what makes the whole system cacheable, auditable, and replayable.

Write Once, Run Forever

There's an obvious objection to everything above: an LLM call per action is slow and costs money. If your automation runs every fifteen minutes, you do not want to pay a model to re-discover the same buy button 96 times a day.

Stagehand's answer is aggressive caching, and it's where the framework's "write once, run forever" pitch comes from. The first time an instruction resolves, the result - that concrete XPath from the previous section - is stored. Every subsequent run replays the stored action directly, skipping inference entirely. Locally, you opt in by pointing the constructor at a cache directory; on Browserbase's cloud, a server-side cache is on by default, and every result tells you whether it was a "HIT" or a "MISS":

const stagehand = new Stagehand({ env: "BROWSERBASE", cacheDir: "act-cache" }); const result = await stagehand.act("click the login button"); console.log(result.cacheStatus); // "HIT" | "MISS"

The clever part is what happens when the cache goes stale. That redesign from the opening scene lands, the stored XPath stops matching, and a classic script would be dead. Stagehand instead detects that the page has changed, re-runs inference against the new page, and repairs its own cache - the behavior Browserbase calls self-healing. The redesign that used to be a Thursday-morning incident becomes one cache miss and one extra model call.

Run the simulation below a few times and watch the economics play out. The first run is slow and costs tokens. The next runs are fast and free. Then ship a redesign and see the system take the hit once - and only once.

Figure 4 - The Cache Flywheel
Runs: 0 · LLM calls: 0 · Tokens: 0
Each run flows from the script through the cache. On a miss (amber) the request detours through the LLM - slow, and the token meter climbs. On a hit (teal) it goes straight to the browser. After a redesign, the stale entry fails once, self-heals through the LLM, and the flywheel resumes. The bar chart tracks each run's latency; timings are illustrative.
The economics in one line: you pay tokens the first time, and again after every redesign; every run in between is a deterministic replay at zero model cost. Your automation quietly compiles itself into a plain script with an insurance policy.

Graduating from Playwright

Stagehand didn't start as its own automation engine. When Browserbase open-sourced it in 2024, it was a layer on top of Playwright - which was exactly why it caught on. You could drop act() and extract() into an existing Playwright script in minutes, keeping everything else you'd built. By version 2 the package was seeing over 500,000 weekly downloads.

But scale surfaced a mismatch in DNA. Playwright is a testing tool. Before it clicks anything, it runs actionability checks - waiting for the element to be, in Playwright's terms, "visible, stable, enabled, and receiving events." For a CI suite, that caution is the whole point. For a high-throughput automation clicking a button it resolved and cached weeks ago, it's latency tax on every single action. The Browserbase team also kept hitting lower-level friction: Playwright doesn't expose stable frame identities, which made it awkward to route Chrome DevTools Protocol commands at specific iframes; its CDP sessions are scoped to pages and contexts rather than frames; and users reported memory creep in long-lived sessions.

So in October 2025, Stagehand v3 removed the middleman. The engine was rewritten to speak Chrome DevTools Protocol directly - the same raw websocket protocol the browser's own DevTools use - streaming accessibility trees, DOM snapshots, and network events straight from the browser with no translation layer in between. On Browserbase's benchmark of deeply nested iframes and shadow DOMs, v3 measured 44.11% faster on average than the Playwright-based architecture. And in a nice twist, shedding the dependency made Stagehand more compatible, not less: the v3 API is driver-agnostic, working alongside Playwright and Puppeteer rather than living inside one of them, with SDKs beyond TypeScript and a REST API.

The figure below shows the architectural difference as message traffic. Both lanes perform the same repeated action; count the hops.

Figure 5 - Cutting Out the Middleman
v2: 0 actions · v3: 0 actions
Every action in the v2 architecture (top) round-trips through the Playwright layer, which also pauses for actionability checks; v3 (bottom) speaks CDP directly to the browser. Hop counts and timings here are stylized to show the shape of the difference - Browserbase's own iframe and shadow-DOM benchmark put v3 at 44.11% faster on average.

The Honest Trade-offs

None of this is free, and Stagehand is refreshingly legible about where the costs moved. Every cache miss is a metered LLM call, so a fleet of automations against frequently-changing pages runs up a real inference bill. The project doesn't recommend local models, so in practice you're wiring in a cloud model API. And the engine is Chromium-only - Chrome, Edge, Arc, Brave - so cross-browser testing stays with the traditional tools.

The sharpest comparison is with Browser Use, the Python framework that owns the other end of the spectrum from figure 1. Browser Use hands the whole task to an agent that works from screenshots - the AI sees what a person sees and decides each next move. It has the larger community (roughly 80,000 GitHub stars to Stagehand's 23,000) and it supports local inference through Ollama, which makes it effectively free to run. The price is variance: an autonomous agent may take a different route each run, and multi-step autonomy burns more tokens per task than atomic, cacheable operations.

Stagehand

  • TypeScript-first; SDKs in more languages since v3
  • Step-by-step control, cacheable and replayable
  • Accessibility-tree context; direct CDP engine
  • Strong on nested iframes and shadow DOM
  • Cloud LLM APIs; local models not recommended

Browser Use

  • Python-first, fits data-science stacks
  • Fully autonomous, screenshot-based agent
  • Playwright underneath
  • Local inference via Ollama - zero API cost
  • More tokens per task, more run-to-run variance

Scrapfly's independent review lands on a clean split: reach for Browser Use when you want autonomy, Python, or free local inference; reach for Stagehand when you need predictable, debuggable, production-grade runs - especially anywhere iframes and shadow DOM lurk. That matches Stagehand's own advice: "agent for exploration, individual primitives for critical paths."

The real bet: Stagehand doesn't eliminate the reliability problem - it relocates it. Instead of trusting selectors to stay valid forever, you trust a model to re-derive them when they break, and you let the cache decide how often you pay for that trust.

Which brings us back to that Tuesday night. With a hardcoded selector, the renamed button was a silent outage and a Thursday post-mortem. With an instruction, it's a cache miss: one extra model call, a healed cache, a log line you read later with your coffee. The stagehand moves the props between scenes, and the show goes on.