In March I tested Vercel’s new agent-browser CLI and came away with a qualified answer: the interaction model was excellent, the architecture was less revolutionary than advertised, and Playwright remained the serious substrate underneath it all.

That article is now a useful historical document and a terrible buying guide.

The version I evaluated, agent-browser 0.16, used a native Rust command-line client in front of a Node.js daemon that used Playwright. Its experimental native daemon was faster to start but produced noisier snapshots and lacked important features. Meanwhile Playwright had a good internal AI snapshot mode, but its public snapshot API omitted the element references an agent needed to act on what it saw.

Both facts have changed.

agent-browser 0.33.1’s primary Chrome path is a fully native Rust CLI and daemon talking directly to the browser through CDP. Node and Playwright are gone from that runtime. Playwright 1.62.0 now exposes AI snapshots publicly, ships its own official agent CLI and MCP server, and returns actionable refs too.

They spent four months moving toward each other.

The short verdict is that agent-browser is now the better default for my shell-driven autonomous browser agent. It is dramatically lighter than Playwright’s Node control plane, its warm command path is genuinely fast, and it has agent-specific capabilities Playwright still does not package as cleanly. A resident Playwright MCP loop or a coding workflow already built around Playwright has different trade-offs; I benchmarked the library but not MCP transport.

It still does not replace Playwright as a programming library or browser testing system. On large accessibility trees its Rust snapshot implementation also hits a serious scaling problem that the tiny happy-path benchmarks hide.

So: switch the default, keep the escape hatch, and be precise about what would have to change before deleting it.

What changed since the first evaluation

agent-browser removed Playwright

The old architecture was:

Rust CLI → Unix socket → Node.js/Playwright daemon → Chrome

The current architecture is:

Rust CLI → Unix socket → Rust daemon → CDP → Chrome

I did not infer that from a README. I cloned agent-browser v0.33.1, checked out commit 6dcea79, and compiled it:

cargo build --release --locked --manifest-path cli/Cargo.toml

The optimized build completed in 2 minutes 44 seconds and produced a stripped native ELF executable:

agent-browser 0.33.1
ELF 64-bit LSB pie executable, x86-64

On the primary Chrome/Lightpanda path, the Rust daemon owns browser launch, CDP commands, snapshots, refs, interactions, waits, network interception, state, recording and cleanup. Safari/iOS integrations use reduced WebDriver backends and may launch external Appium or SafariDriver processes; plugins are external by design. The important correction to my old article is narrower: the Chrome control plane is not a Node launcher wearing a Ferris costume.

The intervening releases added more than native parity. The changelog includes:

That last point is not glamorous, but browser automation has produced enough immortal Chrome process trees to qualify as a minor branch of necromancy.

Playwright made its AI mode public

My previous evaluation found Playwright’s useful snapshot method hiding behind _snapshotForAI(). The public ariaSnapshot() returned YAML without refs, making it good for test assertions and poor for an agent that wanted to click the thing it had just observed.

Since Playwright 1.59, the public API has fixed the asymmetry; 1.60 added bounding boxes, and current 1.62 carries the mature form I tested:

const snapshot = await page.ariaSnapshot({ mode: "ai" });

The public AI mode returns refs, cursor hints, URLs and iframe snapshots. It also supports depth and optional element bounding boxes. No underscore, no private protocol gamble.

Playwright now also bundles an official playwright-cli and MCP server. Its CLI can open pages, snapshot, act by ref, manage sessions and storage, inspect requests, mock routes, trace, record video and execute arbitrary Playwright code.

“agent-browser has refs and Playwright does not” is therefore dead. Any current comparison that repeats it is comparing one project’s present to the other’s past.

The snapshot models now look remarkably similar

On the same small form, current agent-browser emits:

- main
  - heading "Browser Bench" [level=1, ref=e1]
  - navigation
    - link "Open details" [ref=e2]
  - form
    - LabelText
      - StaticText "Search"
    - textbox "Search" [ref=e3]
    - button "Submit" [ref=e4]
  - paragraph
    - StaticText "Waiting"

Playwright 1.62’s public AI snapshot emits:

- main [ref=e2]:
  - heading "Browser Bench" [level=1] [ref=e3]
  - navigation [ref=e4]:
    - link "Open details" [ref=e5] [cursor=pointer]:
      - /url: details.html
  - generic [ref=e6]:
    - text: Search
    - textbox "Search" [ref=e7]
    - button "Submit" [ref=e8]
  - paragraph [ref=e9]: Waiting

Both are actionable. Playwright’s representation is more compact here and preserves the link target and cursor hint. agent-browser’s full tree still exposes some Chrome-flavoured LabelText and StaticText nodes, although snapshot -i removes most structural noise when an agent only needs controls.

agent-browser retains useful presentation features around the snapshot:

Playwright CLI takes a different approach: by default, after ordinary commands it writes the resulting snapshot to a file and prints a link. That avoids flooding the coding agent’s immediate context, but the agent must perform a file read when it needs the tree. An explicit snapshot command can return it inline.

Neither design is universally better. agent-browser optimizes the conversational tool loop. Playwright optimizes a coding agent working in a repository where generated evidence belongs on disk.

The benchmark

I compared three things, because collapsing them into “Playwright” would be dishonest:

  1. agent-browser 0.33.1, source-built native Rust CLI and daemon;
  2. Playwright CLI 1.62.0, the CLI bundled in the [email protected] npm package and invoked as playwright cli;
  3. Playwright library 1.62.0, called directly from a persistent or one-shot Node process.

Playwright CLI was tested three ways:

That default is not accidental waste. In the Playwright 1.62 CLI/MCP configuration I tested, timeouts.settle defaults to 500 ms so asynchronous work triggered by an action has time to appear before the automatic snapshot. It is separate from Playwright’s normal actionability checks; setting it to zero does not disable visibility, stability or event-reception checks. run-code removes repeated command startup and intermediate snapshots, but gives up the small declarative command surface in favour of a Playwright function. All three numbers matter.

All implementations used the exact same Chrome for Testing 151.0.7922.34 executable. The machine was an i9-14900K running Arch Linux. Tests used a deterministic loopback HTTP fixture, not a public site whose CDN, advertisements and mood could change between runs.

Every workflow asserted its final state. A successful process with the wrong page did not count.

The CLI comparison is deliberately product-native rather than pretending the interfaces perform identical internal work. agent-browser can execute a preplanned sequence in one batch and emit one final snapshot. Playwright CLI normally snapshots after each command, so its multi-action workflows perform useful intermediate work that agent-browser’s batch does not. The run-code row removes that asymmetry as far as Playwright CLI permits; the direct library row shows the lower-level engine cost.

For fresh-browser measurements I ran two warmups followed by ten measured repetitions, rotating tool order and allowing Chrome teardown to finish between runs. For warm measurements I retained one browser and ran three warmups followed by twenty repetitions.

“Cold” below means a fresh browser, not a cold operating-system page cache or first install. The control daemon/runtime was available. The raw workflow samples, unbatched agent-browser check, Playwright run-code samples, snapshot scaling samples, memory measurements, harness source and methodology notes are public.

This benchmark does not measure Playwright MCP transport, Firefox or WebKit, public-network variance, authenticated applications, hostile SPAs, shadow-heavy pages, nested-frame success rates or model-level task completion. It establishes command overhead, one-page memory footprint and snapshot behaviour on controlled fixtures. The capability conclusions below come from source and API inspection, not from pretending three buttons constitute the web.

Fresh browser: Rust and the Playwright library tie

Median wall-clock milliseconds, including browser launch, navigation, actions, snapshot serialization and browser close:

Workflowagent-browserPlaywright CLI defaultPlaywright CLI, no settlePlaywright library
Open + snapshot330 ms830 ms836 ms329 ms
Open + click + snapshot344 ms1,682 ms1,176 ms337 ms
Open + fill + submit + snapshot341 ms1,939 ms1,456 ms330 ms

The clean conclusion is not “Rust destroys Playwright.” A purpose-built Playwright script and agent-browser are effectively tied when Chrome must start. Chrome dominates the bill.

Playwright’s agent CLI is slower for two separate reasons:

  1. each shell command starts Node and loads the CLI bundle;
  2. its default settle deliberately waits 500 ms after actions.

Comparing the default and zero-settle rows, the settle accounts for about 500 ms in both action-containing workflows in this benchmark. The remaining gap is command startup and snapshot overhead.

This is a product-level result, not an engine indictment. The Playwright library row proves its browser machinery is not intrinsically slow. The official CLI simply packages it with a heavier per-command client.

Warm browser: agent-browser’s architectural win becomes obvious

Median milliseconds with one persistent browser:

Workflowagent-browserPlaywright CLI defaultPlaywright CLI, no settlePlaywright CLI run-codePlaywright library
Navigate + snapshot8 ms294 ms292 ms297 ms15 ms
Navigate + click + snapshot25 ms1,117 ms613 ms349 ms67 ms
Navigate + fill + submit + snapshot23 ms1,383 ms894 ms349 ms50 ms

agent-browser used its native batch command for the main table. I also repeated its flows as separate ordinary CLI invocations. The medians were 9 ms, 17 ms and 17 ms. The result is not a batch-command trick; the native client is simply cheap.

Playwright CLI’s roughly 290 ms floor is mostly Node startup and module loading on every shell invocation. Collapsing the multi-step flows into one run-code call cuts the tuned click and form cases to roughly 349 ms, proving that repeated CLI startup and intermediate snapshots account for much of the larger numbers. It still remains an order of magnitude behind agent-browser’s native command path because the one remaining invocation must boot Node and load Playwright.

An MCP server kept resident should land much closer to the library row because it removes that startup boundary. I did not benchmark MCP transport here, so I am not assigning it an invented number.

The direct library remains fast. In fact, this is the fairest reading of the warm table:

For my use case—an agent that already has a shell tool and sends many small browser commands—the first distinction matters more than the third.

Memory: Chrome is still the elephant, but Rust removes a large sidecar

I loaded the same page in each implementation and sampled Linux RSS from /proc. The figures sum process RSS, not proportional set size, so shared pages may be counted more than once. Treat them as comparative process-footprint measurements, not a claim about exact physical RAM.

ImplementationControl planeChrome treeTotal RSS
agent-browser10.5 MB395 MB406 MB
Playwright CLI130 MB411 MB541 MB
Playwright library126 MB403 MB530 MB

Chrome is still most of the memory. Replacing Node does not make a modern browser dainty.

But the one-session control-plane difference is real: about 10 MB for agent-browser’s Rust daemon against roughly 130 MB for Node plus Playwright. On this page that saved around 125 MB total. I am not extrapolating it to many isolated identities: agent-browser launches a separate Chrome tree per session, while Playwright can share one browser across many contexts. That needs its own benchmark.

The result that stops me declaring total victory

Tiny fixtures mostly measure fixed overhead. Browser automation gets less charming when the accessibility tree grows large, so I generated pages containing 1,000 and 5,000 repeated semantic rows. Every row had a heading, paragraph, link and button. The browser and page stayed loaded. The agent-browser and Playwright CLI rows measure each product’s complete snapshot command, including client startup and output; the library row measures in-process snapshot generation and serialization. These are native product outputs, not byte-for-byte identical representations.

Semantic rowsagent-browserPlaywright CLIPlaywright library
1,000223 ms329 ms53 ms
5,0002,094 ms551 ms279 ms

Output sizes:

Semantic rowsagent-browserPlaywright CLIPlaywright library
1,000191 KB250 KB240 KB
5,000980 KB1.29 MB1.29 MB

agent-browser emits less text, which is good, but its time grows badly. At 5,000 rows it takes 3.8 times as long as Playwright CLI and 7.5 times as long as Playwright’s in-process public AI snapshot.

The final row appeared in every output, which rules out simple tail truncation. It does not prove identical node, role or ref coverage—the output sizes show that the representations differ. What is clear is that agent-browser’s product-native snapshot command regressed sharply between these two fixture sizes: a page five times larger took roughly nine times as long.

I have not profiled that path deeply enough to name the exact cause or claim a general complexity class. Pretending otherwise would turn a benchmark into fan fiction. What the data establishes is narrower and useful: on this 5,000-row semantic fixture, current agent-browser full snapshots are substantially slower than both Playwright paths.

-i, selector scoping, compact output and depth limits reduce the practical impact. They do not erase the implementation problem.

Where agent-browser is now better

It is the cleaner agent tool

The core loop is direct:

agent-browser open https://example.com
agent-browser snapshot -i
agent-browser click @e2
agent-browser diff snapshot

There is one discoverable command surface, one tiny native client, one persistent daemon per session and no generated working directory required for ordinary use. It fits naturally into an agent that already knows how to call shell commands.

It packages agent-specific safety more explicitly

agent-browser has opt-in controls aimed at the fact that a browser page is untrusted input being shown to a model:

They are not enabled by default. The domain allowlist is not an operating-system firewall and deliberately rejects modes where equivalent containment cannot be installed early enough, including some CDP/auto-connect, profile, restore, provider and Safari/iOS paths. External plugins are processes, not magically sandboxed extensions of Rust.

Playwright CLI and MCP also support origin allow/block rules, secrets, file-access restrictions, sandbox settings, isolated profiles and output controls. agent-browser’s advantage is narrower: action policy, confirmation, content boundaries and the auth vault are more visibly integrated into the agent workflow. Its security documentation is unusually candid about the limits.

It packages useful investigation tools

Several commands are unusually good for autonomous investigation:

You can construct most of these from Playwright. “Can be built” and “is already the command I need” are different product qualities.

It is substantially cheaper to keep around

The native control plane is faster to invoke and about one-twelfth the RSS of the Node control plane in this measurement. Its default idle timeout also gives abandoned headless sessions a cleanup backstop.

That is exactly the shape I want for many short agent operations against a browser that remains alive.

Where Playwright is still better

It is a programming system, not just a command system

agent-browser batches are sequences. They do not give me Playwright’s natural loops, branches, data structures and event composition:

for (const row of await page.getByRole('row').all()) {
  if (await row.getByText('Failed').isVisible())
    await row.getByRole('button', { name: 'Retry' }).click();
}

agent-browser can be orchestrated from Ruby, Python or shell, and its plugin system can extend it. Once I am writing a substantial wrapper, however, Playwright is already the wrapper with a mature browser object model.

Playwright CLI exposes run-code, and Playwright MCP exposes browser_run_code, which are useful bridges: remain in the agent session, but execute a real Playwright snippet when declarative commands become insufficient. They are also remote-code-execution-shaped capabilities and belong behind trusted clients. agent-browser has eval inside the page, not an equivalent host-side browser scripting primitive.

BrowserContext remains a serious structural advantage

Each agent-browser --session has its own browser instance, cookies, storage and authentication state. That is good isolation and expensive isolation.

Playwright can create many clean BrowserContexts inside one browser process. agent-browser’s current source records and uses CDP browser-context IDs—and creates a context internally for recording—but does not expose a general user-facing context-management API. The feature request remains open.

If I need two agents, separate Chrome trees are tolerable. If I need twenty identities in parallel, this becomes the wrong architecture.

Locators and actionability are deeper

agent-browser’s click path now scrolls into view, resolves a point and detects an overlay intercepting the click. That is real actionability logic, not a dumb Runtime.evaluate("element.click()").

Playwright still has the broader contract: live locators that re-resolve through DOM changes, composable filters, strictness, visibility, stability, enabled/editable state, event reception and retrying web assertions.

The difference is largest outside clicks. agent-browser fill and type do not apply the same complete actionability and retry model as Playwright. A model can recover by resnapshotting. A deterministic test should not need a model standing nearby with a mop.

Frames and Shadow DOM are not equivalent yet

agent-browser now handles important same-process and cross-process iframe cases. It can inline accessible iframe content and preserve frame context for refs, although inaccessible cross-origin or empty frames may be omitted. This is much better than the version I first tested.

The gaps are in composition and depth:

Playwright’s frame and open-shadow-DOM locator behaviour remains the safer choice for payment widgets, federated login and component-heavy enterprise applications.

Events and network interception are more programmable

agent-browser can route or abort requests, mock a response body, inspect traffic and record HAR files with content. That covers common agent investigation.

Playwright exposes requests, responses, downloads, popups, dialogs, workers, frames and WebSockets as programmable events. It can establish a waiter before the triggering click, fetch an original response, modify it and fulfill the route conditionally.

That matters for race-sensitive workflows:

const [download] = await Promise.all([
  page.waitForEvent('download'),
  page.getByText('Export').click(),
]);

A CLI needs a compound command for each such race. Separate “click, then wait” commands are already too late.

Playwright supports Chromium, Firefox and WebKit

agent-browser’s primary local path is Chrome/Chromium over CDP. Lightpanda is the other --engine choice. Separate Safari/iOS and provider integrations use reduced backends that lack several CDP-only features, so they are not feature-equivalent local engines.

Playwright supports Chromium, Firefox and WebKit with one API and runs an actual cross-browser test matrix. If the requirement is Safari correctness rather than “open this page for the agent,” the comparison ends here.

Playwright is still the test framework

agent-browser has traces, video, screenshots, diffs, console logs, performance tools and accessibility audits. It does not have Playwright Test’s fixtures, projects, web-first assertions, retries, sharding, reporters, trace-on-retry and CI ecosystem.

Trying to make agent-browser replace all of that would be less a migration than an elaborate reimplementation of Playwright under a different name.

A fair capability matrix

Areaagent-browser 0.33.1Playwright 1.62
Agent refs and snapshotsExcellent; compact modes and inline outputExcellent; public AI mode, CLI and MCP
Warm shell commandsBestHeavy through CLI; fast through resident MCP/library
Control-plane memory10.5 MB RSS measured in this setup126–130 MB RSS measured in this setup
Large snapshot scalingWeak on the 5,000-row fixtureMuch better on this fixture
Visual and structural comparisonRef-annotated screenshots and ad hoc diff commandsMature screenshot and ARIA snapshot assertions; no equivalent conversational ref-annotated diff
Agent safetyIntegrated opt-in action policy, confirmation, boundaries and auth vaultStrong origin/file/sandbox/output controls; no equivalent vault/policy workflow
Persistent authenticated sessionsExcellentExcellent
Host-side programmingPlugins/wrappersFull typed APIs and run-code
Cheap isolated contextsSeparate browser per sessionBrowserContext
Locator/actionability depthGood common pathExcellent
Nested frames and Shadow DOMPartialMature
Event choreographyCommand-specificGeneral event API
Browser enginesChrome, Lightpanda, reduced Safari/iOSChromium, Firefox, WebKit
Test runner and CIBring your ownPlaywright Test

How close are we to dropping Playwright?

For my browser skill, closer than the broad feature matrix suggests. Most of my work is not cross-browser regression testing. It is:

  1. open a page;
  2. inspect what is available;
  3. click or fill something;
  4. inspect the result;
  5. occasionally attach to an authenticated browser or collect a screenshot.

For that shell-driven path, the measured command overhead, memory footprint and tool ergonomics make agent-browser the better default candidate for me. The benchmark does not establish higher success rates on authenticated sites, hostile SPAs, downloads or complex frames; those need operational telemetry after the switch.

The remaining Playwright use is concentrated in difficult workflows: loops over extracted data, response-aware scraping, cheap parallel identities, odd frames, Shadow DOM and one-off scripts with real control flow. This makes the migration amenable to stages rather than theology.

Recommendations, from minimal to maximal

1. Minimal: add agent-browser and keep every Playwright path

Make agent-browser available as another skill and use it explicitly for simple snapshot/ref tasks. Change nothing else.

Cost: tiny.
Playwright reduction: perhaps 30–40%.
Risk: almost none.

This is the conservative option. It leaves duplicated browser instructions and asks the model to choose correctly each time.

2. Practical default: route ordinary browsing to agent-browser

Make agent-browser the default for:

Escalate to Playwright when the task requires:

Cost: small skill and routing changes.
Playwright reduction: likely 70–85% for agent browsing.
Risk: low, because fallback remains explicit.

This is what I recommend now.

3. Local compatibility layer: one browser skill, two backends

Expose one term-llm browser tool contract and hide the backend choice:

browser.open
browser.snapshot
browser.click
browser.fill
browser.screenshot
browser.close

Use agent-browser by default. Route capability-tagged operations to Playwright. Record which fallback triggered and why.

The important part is telemetry. After a month, we would know whether Playwright remains because of real requirements or because old instructions keep reaching for it.

Cost: moderate local engineering.
Playwright reduction: measurable rather than guessed.
Risk: backend semantic differences need good postcondition checks.

This is the cleanest migration architecture.

4. Targeted upstream work: remove the fallbacks we actually hit

Do not pursue abstract parity. Fix the gaps observed in real sessions, starting with:

  1. large-snapshot scaling—profile the 5,000-row regression across intermediate fixture sizes and remove the sharp growth before guessing at its complexity;
  2. compound event waits—click-and-wait for download, popup, URL, response or DOM condition in one daemon request;
  3. uniform Shadow DOM selectors—make semantic and CSS paths behave consistently through open roots;
  4. nested frame composition—remove the one-level snapshot limit and avoid global frame state where possible;
  5. richer route fulfillment—status, headers, request mutation and conditional response handling;
  6. host-side scripted operation—a constrained run primitive or plugin pattern that can perform loops and branches without one shell process per operation.

Cost: medium to large upstream contributions.
Playwright reduction: potentially above 90% for my use.
Risk: each feature expands the daemon from a focused agent tool toward a general automation library.

The snapshot scaling problem should come first because it is both measurable and squarely inside agent-browser’s core purpose.

5. Structural change: cheap BrowserContext isolation

Promote agent-browser’s existing internal CDP context use into a general public API: context creation and disposal, context-scoped tabs, then a clear context/session distinction.

That would let several isolated agents share one Chrome process instead of launching one browser per identity. It is the largest architectural gap for parallel autonomous use.

Cost: large.
Playwright reduction: high for multi-agent work.
Risk: lifecycle, restore, provider and security semantics all become more complicated.

This is worth doing if concurrent identities become common. It is overengineering if Jarvis normally drives one browser.

6. Maximal: remove Playwright completely

To delete Playwright rather than merely stop using it by default, I would require:

That last line matters. “Drop Playwright” can mean two different things:

  1. stop using Playwright for autonomous browsing;
  2. replace every capability Playwright supplies.

The first is now realistic. The second is a bad goal unless the requirements genuinely shrink. Rebuilding a mature cross-browser programming and testing system so a dependency disappears is how tidy architecture turns into a multi-year hostage situation.

The recommendation

Switch the default browser agent path to agent-browser 0.33.1.

Keep Playwright as a named programmable fallback. Add routing rules, record fallback reasons, and let usage data tell us which upstream changes have the highest leverage. The first engineering target should be agent-browser’s large-snapshot scaling; the first product target should be a unified browser skill that hides backend choice without hiding capability differences.

If those changes work, Playwright should become rare. Once it is rare, we can decide whether the final dependency is carrying its weight.

Today, deleting it would be premature. Continuing to use it for every browser operation would be equally stale.