browser-use vs Stagehand vs Playwright MCP (2026)
browser-use vs Stagehand vs Playwright MCP: what each tool really is, how it reads a page, what a run costs, and why none of them gets you past a block.
Almost every browser-use vs Stagehand comparison is built on a premise that stopped being true during 2026: that these are three ways to bolt an LLM onto Playwright. Two of the three walked away from Playwright entirely. Only Microsoft's Playwright MCP still ships it.
Short verdict: Playwright MCP to let a coding agent drive a browser interactively, Stagehand for a repeatable extraction pipeline, since its observe-then-act replay is the only free local way to stop paying for inference, and browser-use for open-ended tasks where you can't predict the steps. None of the three gets you onto a site that doesn't want automated traffic, and that gap is what these comparisons never cover.
All three solve the understanding-the-page problem. None solves the being-allowed-to-load-the-page problem. browser-use and Stagehand drive stock Chromium over the Chrome DevTools Protocol; Playwright MCP drives a stock Playwright browser build. Either way a protected target can refuse the connection before the model sees a DOM node. Everything below comes from the projects' own docs, package registries and the GitHub API, read on 8 October 2026, and every price is USD. This assumes data you're permitted to collect; a login wall is a question for your legal team.
Three different categories of thing
Start here, because this is where most write-ups go wrong. These aren't three competing libraries for the same job; they sit at three levels of abstraction, and two of them can be used together.
| What it is | Who decides the next step | Where the model lives | |
|---|---|---|---|
| [browser-use](https://github.com/browser-use/browser-use) | An agent loop | The model, every step | Inside the library |
| [Stagehand](https://github.com/browserbase/stagehand) | An SDK | You, in code | Called per instruction |
| [Playwright MCP](https://github.com/microsoft/playwright-mcp) | An MCP tool server | Your MCP client's model | Not in the package at all |
browser-use is a Python agent loop. You give it a goal in English, it looks at the page, picks an action, executes it, looks again. The PyPI package is at 0.13.11, uploaded 7 October 2026, requiring Python >=3.11,<4.0. A TypeScript port sits on npm at 0.8.0, with no parity statement either way.
Stagehand is Browserbase's SDK, so you still write the control flow and Stagehand resolves one instruction at a time into a browser action through act(), observe() and extract(). Latest stable is 4.1.0, on npm since 9 September 2026, with first-party SDKs in TypeScript, Python and Go.
Playwright MCP contains no model whatsoever. It's an MCP server exposing browser tools to any MCP client, and the intelligence is whatever you plugged in: Claude Code, Cursor, Codex, Goose. @playwright/mcp is at 0.0.83 from 28 September 2026, still pre-1.0 after 18 months, with 37,910 stars against 2 open issues. Full version, licence and repo detail for all three is in the reference table.
That last point kills a whole genre of article. Benchmarking Playwright MCP's "accuracy" against browser-use's is a category error, because its accuracy is entirely a property of the model you connected. Nor does a head-to-head benchmark of the three exist: browser-use's BU Bench V1 tests models and browser-use configs, Browserbase's leaderboard tests models and harnesses, both vendor-run. Any three-way win-rate chart you've seen was invented.
Two of the three dropped Playwright
Here's the correction that reframes everything else. Check the dependency trees, not the marketing.
Stagehand v4's own docs say it outright: "Stagehand v4 drives the browser directly over the Chrome DevTools Protocol, so there is no Puppeteer, Playwright, or Patchright page interop." npm confirms it, because the complete dependency list for @browserbasehq/stagehand 4.1.0 is zod 4.4.3, @browserbasehq/sdk ^2.16.0, @opentelemetry/api 1.9.1 and @opentelemetry/core 2.9.0. The README's "Playwright-style methods you and your agents already know" is an API shape, not an engine.
browser-use's Python package dropped it too. The requires_dist for 0.13.11 lists 61 dependencies including cdp-use==1.4.5, with no playwright entry at all; the repo topics still say "playwright", which is stale metadata rather than a dependency. The TypeScript port is the exception, still depending on playwright ^1.58.2.
Playwright MCP is the only one that genuinely ships Playwright, and it pins hard: playwright and playwright-core at 1.64.0-alpha-1790635538000.
So two of the three now talk raw CDP to Chromium, and Playwright MCP is the one that still goes through Playwright. The practical consequences:
- Patched engines no longer drop in. A stealth-patched Playwright fork is useless to Stagehand v4 or to browser-use's Python package, because neither calls Playwright. The plugin ecosystem in our stealth plugins guide applies to Playwright MCP and nothing else here.
- Playwright MCP is the only one with non-Chromium engines. Its
--browserflag acceptschrome, firefox, webkit, msedge. The other two are Chromium over CDP, full stop.
Your Playwright knowledge transfers unevenly too. Selector debugging, trace viewer and codegen are right there in Playwright MCP; in the other two they're gone.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
browser-use vs Stagehand vs Playwright MCP: how each one reads a page
This is the real axis of difference, and it sets both your token bill and your failure modes.
| Primary page representation | Pixels | Iframes / shadow DOM | |
|---|---|---|---|
| browser-use | Serialised DOM + accessibility + element-index state | Screenshot **every step by default** | Paint-order filtered DOM tree |
| Stagehand | Accessibility tree, merged across every frame | Opt-in per call | Merged automatically, no config |
| Playwright MCP | Accessibility snapshot | Opt-in via `--caps=vision` | Playwright's frame handling |
browser-use sends the model everything, every step. The bu-2-0 model card lists the inputs: a "Current screenshot or viewport image", the "Serialized DOM, accessibility, and element-index state", the tool schemas, and every prior output, action, result, error and extracted string from the same episode. use_vision defaults to True, documented as "always includes screenshots".
That's the most expensive context shape of the three and the most capable: multimodal plus full history is why browser-use handles drag-and-drop and canvas work the other two struggle with. It has a documented ceiling too, since "Pages with thousands of interactive elements can overload the CPU in sandbox environments," causing capture timeouts and so "missing model context".
Stagehand leads with the accessibility tree, trimmed to give "agents exactly the page context they need and nothing more". The iframe handling is the quietly valuable part: "Stagehand automatically handles iFrame traversal and shadow DOM elements without requiring additional configuration. Page snapshots merge every frame's accessibility tree by default." Anyone who has hand-written frame-walking code for a checkout widget knows what that saves.
Playwright MCP is accessibility-only by design. Its README opens by promising "structured accessibility snapshots, bypassing the need for screenshots or visually-tuned models", and splits the concepts at the tool level: browser_snapshot is "better than screenshot" while browser_take_screenshot warns "You can't perform actions based on the screenshot." Coordinate tools sit behind --caps=vision, and there are 72 browser_* tools in total.
Microsoft is also unusually candid that MCP is the token-expensive choice: "Modern coding agents increasingly favor CLI-based workflows exposed as SKILLs over MCP because CLI invocations are more token-efficient: they avoid loading large tool schemas and verbose accessibility trees into the model context". That's the vendor telling you not to point this at 10,000 pages. None of the three publishes a token count per step, so any article quoting one made it up.
Determinism: what a second run actually costs
Readers want a framework-versus-framework determinism comparison. The real split is self-hosted versus vendor-hosted, because most of the caching sits behind an API key.
| Mechanism | Tool | Runs locally? | Skips inference? |
|---|---|---|---|
| observe-then-act replay | Stagehand | Yes | Yes, completely |
| Server-side `act()` cache | Stagehand | **No, Browserbase only** | Yes, on a hit |
| Rerunnable scripts | browser-use Cloud | No | **No** |
| Snapshot/token knobs | Playwright MCP | Yes | No, only shrinks context |
| `flash_mode` | browser-use | Yes | No, only shrinks context |
Stagehand's observe-then-act replay is the one genuinely free, local, no-vendor determinism mechanism here. The docs: "Passing an instruction runs model inference to find the target; passing an Action replays it deterministically with no inference at all." Replay "skips model inference, the page snapshot, and the DOM-settle wait", and if the recorded selector stops resolving and selfHeal is on, Stagehand re-infers once and retries.
import { localBrowser, Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";
const browser = await localBrowser.launch({});
const stagehand = await Stagehand.create({
browser,
model: { modelName: "google/gemini-3.8-flash" },
});
const [page] = await browser.context.pages();
await page.goto("https://www.sparkproxy.io/pricing");
// Pay for inference ONCE, then keep the Action.
const { data: [action] } = await stagehand.observe("click the residential proxies tab");
console.log(JSON.stringify(action)); // persist this
// Every later run: zero tokens, no snapshot, no DOM-settle wait.
await stagehand.act(action);
const { data: plans } = await stagehand.extract(
"every plan name and monthly price",
z.object({
plans: z.array(z.object({ name: z.string(), price: z.string() })),
}),
);
Stagehand's server-side cache is the one most articles describe without the catch: "cache requires a Browserbase browser and a Browserbase API key. It has no effect on local browsers." On Browserbase a hit returns "with no LLM inference and no token cost"; locally "every call runs inference." No hit-rate data is published, so don't budget around it.
browser-use calls the model on every step, and its replay feature saves code, not calls. There's no selector cache in the open-source loop, only knobs: max_actions_per_step defaults to 5, flash_mode skips evaluation, next-goal and thinking, page_extraction_llm points extraction at something cheap. The Cloud rerunnable scripts feature caches generated Python, and browser-use is blunt about what that does not buy: "Every later run still starts a model and uses tokens." So the "99% cheaper, no LLM" claim attached to browser-use replay is contradicted by the vendor's own docs. Real record-and-replay lives in a separate repository, workflow-use.
Measure rather than guess. browser-use ships the instrumentation:
import asyncio
from browser_use import Agent, ChatOpenAI
async def main():
agent = Agent(
task="Get the first five plan names from https://www.sparkproxy.io/pricing",
llm=ChatOpenAI(model="gpt-5.4"),
use_vision=False, # default is True; screenshots every step
flash_mode=True, # skip evaluation / next-goal / thinking
page_extraction_llm=ChatOpenAI(model="gpt-5-mini"),
calculate_cost=True,
)
history = await agent.run()
print(history.usage)
print(await agent.token_cost_service.get_usage_summary())
asyncio.run(main())
Playwright MCP has no cache at all. It has token knobs, and they're worth setting:
# --snapshot-mode: "full" (default) or "none"; "none" drops the automatic a11y dump
# --mobile: "Mobile pages are usually lighter, which saves tokens"
# --isolated: keeps the profile in memory, so no state carries between runs
# --caps vision: opt in to pixel-coordinate tools only if you actually need them
npx @playwright/mcp@latest \
--snapshot-mode none \
--mobile \
--isolated \
--caps vision
With --snapshot-mode none you lean on browser_find, which returns "matching snapshot nodes with a few lines of surrounding context (like search snippets)". Useful knobs. Not determinism.
A cost model for 10,000 pages
Three cost axes, built only from numbers somebody actually published. Say 10,000 product pages, one agent run per page. All figures USD, read 8 October 2026.
Axis 1, inference. The only primary source with real cost-per-task figures is Browserbase's own leaderboard, "Browserbase Benchmark v2" / hardbench, updated 3 September 2026. Those are vendor-run Online-Mind2Web-derived agent tasks rather than single-page extractions, so treat them as order of magnitude, not as your bill.
| Model / harness | Accuracy | Speed | Cost per task | ร 10,000 |
|---|---|---|---|---|
| gpt-6-luna / codex | 74.0% | 311s | $0.0225 | **$225** |
| gpt-6.1-sol / codex | 79.0% | 282s | $0.305 | $3,050 |
| claude-sonnet-5-5 / claude code | 74.0% | 315s | $0.391 | $3,910 |
| claude-opus-5-5 / claude code | 89.0% | 326s | $0.510 | $5,100 |
| claude-fable-5-1 / claude code | 84.0% | 552s | $1.631 | $16,310 |
| gpt-6-astra / codex | 87.0% | 320s | $3.073 | **$30,730** |
A 136x spread between the cheapest and dearest published row. Watch the accuracy column too: at 74% success, 2,600 of your 10,000 pages fail and you paid for every one. Through browser-use's hosted agent, add their published 20% service fee, so the $5,100 row becomes $6,120.
Axis 2, browser time. At an optimistic 30 seconds per page, that's 83.33 browser-hours, which Browserbase's $20 Developer plan covers outright. browser-use Cloud charges $0.02 per browser-hour but meters by the minute with a 1-minute minimum, so 10,000 separate 30-second sessions bill as 10,000 minutes, not 5,000: $3.33 instead of $1.67. Batch pages into longer sessions.
Axis 3, egress. A rendered page with assets at 2 MB gives 20 GB.
| Path | Rate | 20 GB |
|---|---|---|
| browser-use Cloud, managed residential | $5/GB | $100 |
| browser-use Cloud, bring-your-own or direct | $0.20/GB | $4 + your proxy bill |
| Browserbase Developer | 1 GB then $12/GB | $228 |
| Browserbase Startup ($99/mo) | 5 GB then $10/GB | $150 |
| Playwright MCP, self-hosted | your provider | your proxy bill |
Add it up and the shape is obvious. Infrastructure plus egress for 10,000 pages lands between roughly $7 and $250; inference lands between $225 and $30,730. The line item that dominates your bill by one to three orders of magnitude is the one no vendor publishes a planning figure for. Browserbase's own cost-optimisation docs suggest escalating retries on observe() rather than act(), since observe "only plans and never touches the page".
The same 10,000 pages through a deterministic rendering API cost a flat, countable number of credits and zero tokens: on SparkProxy's Scraping API, 10,000 credits for plain fetches, 50,000 with render_js, 250,000 on the premium residential pool with JS. That's a figure you can forecast, which the inference line is not.
The detection wall all three hit
Every anti-detection feature in this space is behind a hosted plan. That's not a marketing observation, it's what the three doc sets say.
browser-use's open-source browser parameters contain no stealth option at all. Stealth is cloud-only: "Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default," with "Anti-bot protections" carrying the honest caveat "Access to every site is not guaranteed."
Stagehand rates its own local mode "Basic stealth" against "Advanced fingerprinting" hosted, and that local mode prepends four non-default Chrome flags before any you pass, --enable-unsafe-extension-debugging, --remote-allow-origins=*, --window-size=1280,800 and --enable-features=WebMCPTesting,DevToolsWebMCPSupport, plus a Stagehand extension when attaching over CDP. Nobody documents what a detection script makes of that combination, so take it as a design observation rather than a test result.
Playwright MCP ships nothing. A case-insensitive search of its full 66,837-byte README for "stealth", "anti-detect", "fingerprint" and "captcha" returns zero matches on all four. Our headless browser detection post covers which signals get read.
Here's the part specific to agents, and it inverts the usual intuition. An AI browser agent is structurally easier to flag than a scripted scraper, for reasons that have nothing to do with the browser build.
- Pacing. A scripted click lands in milliseconds; an agent click lands after a model round-trip. That think-time is a rhythm no consumer session produces.
- Reading everything. A selector-based scraper touches four nodes. A snapshot-based agent reads the whole accessibility tree every step.
- Exploration. Agents backtrack, retry and open things they don't need. Useful on unfamiliar pages, and also a signature.
- Session length. The benchmark speeds above run 282 to 552 seconds per task, so one page holds a session, cookie jar and TLS state open for five to nine minutes where a scripted fetch is gone in under one.
None of that says a named vendor will block you. It says the observable surface is larger than a script's, and a datacenter exit IP stacks a second signal on it.
Proxy support runs opposite to stealth support
This is the table nobody writes, and for a proxy-using audience it's the most important one here. Proxy capability and stealth capability run in opposite directions across these three tools.
| Local proxy | Local **authenticated** proxy | Local stealth | |
|---|---|---|---|
| browser-use (OSS) | Yes | **Yes, full user/pass** | None documented |
| Stagehand (local) | Yes, server + bypass | **No, it raises** | "Basic stealth" |
| Playwright MCP | Yes, via CLI flag | Config file only | **None at all** |
browser-use's free open-source library takes a fully authenticated proxy on your own machine. The documented parameter, verbatim: "proxy: Proxy configuration using ProxySettings(server='http://host:8080', bypass='localhost,127.0.0.1', username='user', password='pass')". Browser is the current name for the session object, BrowserSession is kept as an alias, and the docs now file BrowserProfile under backward compatibility.
from browser_use import Agent, Browser, ChatOpenAI
from browser_use.browser import ProxySettings
browser = Browser(
channel="chrome", # real Chrome, not bundled Chromium
proxy=ProxySettings(
server="http://gateway.sparkproxy.io:11000",
bypass="localhost,127.0.0.1",
username="YOUR_PROXY_USER",
password="YOUR_PROXY_PASS",
),
)
agent = Agent(task="...", llm=ChatOpenAI(model="gpt-5.4"), browser=browser)
Stagehand, which has the better stealth story, can't do this locally. Its browser configuration docs state: "Authenticated local proxies are not supported yet. Setting a proxy username or password raises in all three SDKs; a proxy server and bypass list work everywhere." Since practically every commercial proxy is username/password authenticated, that rules out the obvious pairing. Your options: an unauthenticated allowlisted endpoint, a local forwarding proxy holding the credentials, or Browserbase with proxies: true.
import { localBrowser } from "@browserbasehq/stagehand";
// Works locally: a server and a bypass list, no credentials.
const ok = await localBrowser.launch({
proxy: { server: "http://gateway.sparkproxy.io:11000" },
});
// Raises in all three SDKs. Credentials are not supported on a local proxy.
const bad = await localBrowser.launch({
proxy: {
server: "http://gateway.sparkproxy.io:11000",
username: "YOUR_PROXY_USER",
password: "YOUR_PROXY_PASS",
},
});
Playwright MCP sits in between. Its CLI exposes --proxy-server and --proxy-bypass . Neither flag accepts credentials, and the README shows no inline user:pass@host example, so don't assume that form works. Authenticated proxies go through the JSON config file, whose browser.launchOptions is typed as playwright.LaunchOptions and passed to launchPersistentContext:
{
"browser": {
"browserName": "chromium",
"launchOptions": {
"channel": "chrome",
"proxy": {
"server": "http://gateway.sparkproxy.io:11000",
"username": "YOUR_PROXY_USER",
"password": "YOUR_PROXY_PASS"
}
}
}
}
Then point your client at that file:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--config", "./pw-mcp.json", "--isolated"]
}
}
}
Our proxies for AI agents post goes deeper on exit selection for agent workloads. The summary here: if you want an authenticated residential proxy on your own hardware, browser-use is the only one of the three that takes it directly, and it's also the one with no local stealth. That tension is the real choice.
Fixing the fetch instead of the agent
Three ways to close the gap, in rising order of how much you hand off.
- Residential egress. Fixes IP reputation, nothing else. Fingerprint and pacing signals survive.
- A stealth-patched engine. Only available to Playwright MCP now. Fixes fingerprint tells, not pacing.
- Move the fetch off the agent entirely. The agent decides what to get. A rendering API gets it.
Option three is usually right for volume, because the part of the pipeline that gets blocked is the part that needs no intelligence. Point the fetch at a rendering endpoint with render_js, a country exit and stealth already applied, then spend tokens only where a model earns them.
curl -G "https://scrape.sparkproxy.io/api/v1" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "url=https://www.sparkproxy.io/pricing" \
--data-urlencode "render_js=true" \
--data-urlencode "country_code=DE" \
--data-urlencode "stealth=true" \
--data-urlencode "format=md"
format=md returns clean Markdown, the input shape an LLM wants anyway, and it's the same job the hosted crawlers in our Firecrawl alternatives round-up do. Keep format and json_response apart in your head, because conflating them is the usual integration bug: format only converts the content, taking html, md, mdx, json, screenshot or pdf, while json_response=true is what wraps the result in an envelope carrying job_id, status_code, duration_ms, credits_used and a base64 body. If you ask for the envelope, decode body before you match a single marker string against it. stealth=true adds a homepage pre-warm, a forced Google referrer and extended idle delays, and requires render_js=true. Credits: 1 plain, 5 with JS, +5 for stealth, +5 for geo-targeting, 25 for premium residential with JS.
The pattern that actually saves money is a split pipeline. Let the agent solve the page once, keep the selectors, then run the volume deterministically with extract_rules, which pulls structured data by CSS selector. It isn't one of the four priced add-ons, and the docs' own extract_rules response shows credits_used: 5 on a rendered fetch, so you pay for the fetch and nothing else, at zero tokens. It needs render_js=true, and it replaces the normal response with its own JSON object, so read extracted straight off and skip json_response entirely:
import json, requests
SELECTORS = { # discovered ONCE by Stagehand observe() or browser-use
"name": "h1.product-title",
"price": "span[data-price]",
"stock": ".availability",
}
def scrape(url: str) -> dict:
r = requests.get(
"https://scrape.sparkproxy.io/api/v1",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"url": url,
"render_js": "true", # extract_rules requires it
"country_code": "US",
"extract_rules": json.dumps(SELECTORS),
"tag": "price-monitor",
},
timeout=120,
)
r.raise_for_status()
return r.json()["extracted"] # {"name": ..., "price": ..., "stock": ...}
For heavy targets, go async with callback_url, which returns HTTP 202 and a job ID immediately, then collect from GET /api/v1/files/{jobId}. Two parameters matter for agent-adjacent work: human (default true) simulates mouse movement and random interaction delays, and js_scenario runs click, fill, scroll, wait and evaluate steps after load. Between them they cover most of what an agent does interactively, with no per-step model call.
Side-by-side reference table
Everything in one place. Figures read 8 October 2026 from each project's registry entry, docs and the GitHub API, prices in USD.
| browser-use | Stagehand | Playwright MCP | |
|---|---|---|---|
| Vendor | Browser Use | Browserbase | Microsoft |
| Category | Agent loop | SDK | MCP tool server |
| Licence | MIT | MIT | Apache-2.0 |
| Latest release | 0.13.11 (7 Oct 2026, PyPI) | 4.1.0 (9 Sep 2026, npm) | 0.0.83 (28 Sep 2026, npm) |
| Languages | Python, TypeScript port | TypeScript, Python, Go | Any MCP client |
| Repo created | 31 Oct 2024 | 24 Mar 2024 | 21 Mar 2025 |
| Stars / open issues | 117,403 / 544 | 25,562 / 390 | 37,910 / **2** |
| Release cadence | 1-3 per month | Stable every 1-3 weeks, alphas daily | Every 2-4 weeks |
| Drives Playwright? | No (Python), yes (TS port) | **No** | **Yes**, pinned 1.64.0-alpha |
| Engines | Chromium / Chrome / Edge | Chromium | Chromium, Firefox, WebKit, Edge |
| Page representation | Screenshot + DOM + a11y + index | A11y tree, frames merged | A11y snapshot |
| Vision by default | **Yes** | No | No |
| Free local determinism | No | **observe-then-act replay** | No |
| Server-side cache | No | Browserbase only | No |
| Local authenticated proxy | **Yes** | **No, raises** | Config file only |
| Local stealth | None | "Basic stealth" | **None** |
| Hosted stealth | Hardened Chromium fork | "Advanced fingerprinting" | No hosted tier |
| Hosted pricing | $0.02/browser-hr, $5/GB residential | Free / $20 / $99 / custom per month | Free, self-hosted |
| Model choice | ~20 providers incl. Ollama | 5 providers, gateway default | Your MCP client's |
Two cadence notes the table flattens. Stagehand publishes stable releases every few weeks but alpha builds several times a day, with 1,042 published npm versions against 66 stable semver releases, so pin exactly. And Playwright MCP's 2 open issues against 37,910 stars is the standout maintenance signal here.
Which one for which job
| Your job | Use | Why |
|---|---|---|
| One-off exploration of an unfamiliar site | **browser-use** | Vision plus full history handles pages you can't describe in advance |
| Letting your coding agent click through your own app | **Playwright MCP** | Zero cost, already in your MCP client, and Playwright debugging tools |
| Repeatable extraction from 50 known pages | **Stagehand** | observe once, replay the `Action` forever, no vendor needed |
| Repeatable extraction from 10,000 pages | **Stagehand to find selectors, then a rendering API** | Inference per page is the cost that kills this |
| Cross-browser behaviour, including Firefox or WebKit | **Playwright MCP** | The only one that drives non-Chromium engines |
| Agent work behind an authenticated residential proxy, locally | **browser-use** | The only one taking user/pass on a local proxy |
| Agent work on a protected target | **A hosted tier, or offload the fetch** | Every stealth feature here is behind a plan |
| Filling complex forms, drag-and-drop, canvas | **browser-use** | Multimodal context plus up to 5 actions per step |
| Writing extraction that survives small layout changes | **Stagehand** | Self-healing refreshes how an action happens when the site shifts |
| CI checks on your own site | **Plain Playwright** | No model, no flake, no bill |
One tiebreaker the table can't hold: model choice. browser-use covers roughly 20 providers including Ollama, against Stagehand's five first-class ones plus Browserbase's Model Gateway, which the README says "picks the cheapest model for each action". If local inference matters for data residency or cost, browser-use with Ollama is the only configuration here that keeps every token on your own hardware.
When a plain script beats all three
A plain Playwright or Selenium script beats all three whenever the page structure is stable and known, which covers most production scraping. The test: if you can write the CSS selector, you don't need a model to find it for you.
Skip the agent when:
- The layout changes a few times a year, not a few times a week. Fixing three selectors after a redesign costs less than inference on every run forever.
- You're at volume. Per-page inference scales linearly, and nothing about that improves.
- You need reproducibility. Two runs of one agent on one page can take different paths, which is miserable to debug and worse to explain to an auditor.
- The target returns JSON. Check for an API first, as in our web scraping vs APIs comparison.
- You're scraping static HTML. Set
render_js=falsefor a 1-credit fetch that runs roughly 3x faster.
The honest division of labour: a model is excellent at figuring out a page it has never seen, and wasteful at re-reading one it has solved. Use it for the first visit, cache what it learned, run the rest deterministically. Our Playwright vs Selenium comparison covers the deterministic half.
Frequently asked questions
FAQ
Stagehand, for most scraping work. Its observe() then act(action) replay is the only free local mechanism here that skips model inference on repeat runs, and inference is the cost that decides whether a pipeline is viable. browser-use is better when you can't predict the steps, since it sends screenshots plus full DOM state every step.
No. Stagehand v4's docs state that it "drives the browser directly over the Chrome DevTools Protocol, so there is no Puppeteer, Playwright, or Patchright page interop," and npm confirms it: 4.1.0's entire dependency list is zod, @browserbasehq/sdk and two OpenTelemetry packages. The README's "Playwright-style methods" refers to the API shape only.
Yes, but credentials need the config file. The CLI flags --proxy-server and --proxy-bypass accept no username or password, and the README shows only unauthenticated examples. For an authenticated proxy, set browser.launchOptions.proxy with username and password in the JSON config, since that object passes straight through to Playwright's launchPersistentContext.
Nobody publishes a figure you can plan with. The closest primary data is Browserbase's own hardbench leaderboard, where cost per task runs from $0.0225 to $3.073 USD depending on model and harness, a 136x spread on agent tasks that aren't single-page scrapes. Enable browser-use's calculate_cost=True or Stagehand's token tracking and measure your own workload.
That depends on the target, and no primary source tests it, so treat any specific claim with suspicion. What's documented is narrower: Playwright MCP's README mentions stealth, fingerprinting, anti-detect and CAPTCHA zero times, browser-use's open-source browser parameters include no stealth option, and Stagehand labels its own local mode "Basic stealth".
Usually not. If you can write the CSS selector, a model adds cost and non-determinism without adding capability. The genuine use is discovery, meaning the first visit to a page you have never seen, after which you should cache what it found and run the volume through a deterministic fetch.
Get 20% off your first month
Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.
Save up to 15% more on quarterly, half-yearly and yearly plans
Related articles
curl_cffi vs tls-client vs hrequests (2026)
curl_cffi vs tls-client vs hrequests: there are only two engines here. Compare TLS and HTTP/2 fingerprint control, profile freshness, async model and install.
Nodriver vs Undetected-Chromedriver: Migration Guide
nodriver vs undetected-chromedriver: the WebDriver-to-CDP shift, a real code migration, what silently breaks, and two packaging bugs nobody mentions.
Logii Alternatives: 5 Antidetect Browsers Compared
Logii alternatives compared: what a $27 Windows licence for 5 logins really buys, five tools that lift the ceiling, and the price claim that fails checking.
