mrkeyoor.com_
Wed 05 Aug 19:53 UTC
PyPIAI / MLupdated 05 Aug 2026

browser-use

Browser Use is a Python library that lets an LLM operate a real web browser the way a person does: it opens pages, reads the DOM, clicks buttons, types into fields, and fills forms. You describe a task in plain language, hand it an LLM (their hosted models, OpenAI, Anthropic, Google, or others), and an agent loop observes the page, picks the next action, and repeats until done. It is the most-starred project in the AI browser agent space and doubles as the open-source core of a paid cloud product that adds hosted browsers, stealth, proxies, and captcha solving. Typical uses are form filling, structured data extraction, and QA automation on sites without APIs.

Verdict

The leading open-source browser agent and the fastest way to get an LLM doing real web tasks, with honest benchmarks published. Treat it as a probabilistic tool: for stable flows plain Playwright wins, and production reliability at scale tends to pull you toward their paid cloud.

API stability2/5Still 0.x with breaking renames across minor versions (Controller to Tools, browser session config churn). Code from six months ago frequently needs edits to run.
Docs4/5docs.browser-use.com covers quickstart, customization, and cloud clearly with lots of runnable examples, though docs sometimes trail the latest renames.
Maintenance5/5Daily commits from a funded team, 90 open issues against roughly 108k stars, and fast triage. The cadence is aggressive, which is also why the API churns.
Ecosystem4/5Huge community, an MCP server, a CLI and agent skill, cloud integrations, and a public benchmark. Young compared to Playwright's ecosystem, and many extensions assume the paid cloud.

Use it if

  • You need to automate a website that has no API and where deterministic Playwright scripts keep breaking on layout or flow changes
  • You are building an agent product that must do real web tasks (apply to jobs, book things, extract data behind logins) and you want code-level control over the loop
  • You want to compare LLMs on browser tasks; swapping ChatOpenAI, ChatAnthropic, ChatGoogle, or their hosted ChatBrowserUse is a one-line change
  • You need QA-style exploratory testing where an agent probes a site and reports issues instead of replaying fixed scripts
Skip it if

Setup reality

pip install browser-use needs Python 3.11+ and drives a Chromium browser it downloads on first run, so expect a large one-time fetch and glibc issues on minimal Docker images. You also need an LLM API key in .env before anything works. The project moves fast: pre-1.0 minor releases have renamed core classes and moved config between Browser, BrowserSession, and profile objects, so pin the exact version and read release notes before upgrading. Telemetry is on by default (documented, with an env var to disable), and headless-vs-headed behavior differs enough that a task passing headed can fail headless.

Patterns

Run a task with an agentbasic-agent-run

import asyncio
from browser_use import Agent, ChatBrowserUse

async def main():
    agent = Agent(
        task="Find the number of stars of the browser-use repo",
        llm=ChatBrowserUse(),
    )
    history = await agent.run()
    print(history.final_result())

asyncio.run(main())

Everything is async; there is no sync API. ChatBrowserUse needs BROWSER_USE_API_KEY in .env, or swap in another provider's chat class and key.

Bring your own model provideruse-your-own-llm

from browser_use import Agent, ChatOpenAI
# also available: ChatAnthropic, ChatGoogle

agent = Agent(
    task="Compare the price of gpt-4o and claude-sonnet",
    llm=ChatOpenAI(model="gpt-5.1"),
)

These chat classes are browser-use's own wrappers, not LangChain's; they read the provider's standard env var (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY). Model choice moves task success rates a lot; check their published benchmark.

Cap how long the agent can runlimit-steps-and-cost

history = await agent.run(max_steps=15)

if not history.is_successful():
    print("gave up or hit the cap")
print(history.urls())        # pages visited
print(history.final_result())

Each step is at least one LLM call, so max_steps is your cost ceiling. Without it a confused agent can loop on a page for the default limit of 100 steps.

Get typed results with a Pydantic schemastructured-output

from pydantic import BaseModel
from browser_use import Agent, ChatBrowserUse

class Post(BaseModel):
    title: str
    url: str

class Posts(BaseModel):
    posts: list[Post]

agent = Agent(
    task="Get the top 5 Hacker News post titles and URLs",
    llm=ChatBrowserUse(),
    output_model_schema=Posts,
)
history = await agent.run()
data = Posts.model_validate_json(history.final_result())

The agent's final answer is validated against the schema; you still call model_validate_json on final_result() yourself. Malformed model output raises a pydantic ValidationError, so wrap it.

Run headless or keep the window visibleconfigure-browser

from browser_use import Agent, Browser, ChatBrowserUse

browser = Browser(
    headless=True,
    window_size={"width": 1280, "height": 900},
)
agent = Agent(task="...", llm=ChatBrowserUse(), browser=browser)

Headless runs can behave differently from headed ones (some sites detect it, layouts shift). Debug headed first, then flip to headless. The Browser class replaced older BrowserSession-style config; pre-0.13 snippets may not match.

Attach to your existing Chrome via CDPconnect-to-real-browser

from browser_use import Agent, Browser, ChatBrowserUse

# start Chrome first:
# google-chrome --remote-debugging-port=9222

browser = Browser(cdp_url="http://localhost:9222")
agent = Agent(
    task="Reply to my latest LinkedIn message",
    llm=ChatBrowserUse(),
    browser=browser,
)

Attaching to your daily browser reuses real logins and cookies, which sidesteps many bot walls but also means the agent acts as you. Use a dedicated profile for anything risky.

Pass credentials without showing them to the LLMhandle-logins-securely

from browser_use import Agent, ChatBrowserUse

agent = Agent(
    task="Log in to example.com with username x_user and password x_pass",
    llm=ChatBrowserUse(),
    sensitive_data={"x_user": "real@email.com", "x_pass": "s3cret"},
)

The model only ever sees the placeholder names; substitution to real values happens locally when typing. Reference the placeholders in the task text or the agent will not know they exist.

Give the agent a custom actioncustom-tool

from browser_use import Agent, Tools, ChatBrowserUse

tools = Tools()

@tools.action(description="Save a finding to the report file")
def save_finding(text: str) -> str:
    with open("report.txt", "a") as f:
        f.write(text + "\n")
    return f"saved: {text}"

agent = Agent(task="...", llm=ChatBrowserUse(), tools=tools)

Tools replaced the old Controller class; @controller.action snippets in older tutorials are the same idea under the previous name. The docstring-style description is what the LLM uses to decide when to call it.

Run agents in parallelrun-multiple-agents

import asyncio
from browser_use import Agent, ChatBrowserUse

async def run_task(task: str):
    agent = Agent(task=task, llm=ChatBrowserUse())
    return await agent.run(max_steps=20)

results = await asyncio.gather(
    run_task("Get today's HN top story"),
    run_task("Get the weather in Berlin"),
)

Each agent gets its own browser unless you share one, so watch memory: a Chromium instance per agent adds up fast on small machines.

Audit what the agent actually didinspect-agent-history

history = await agent.run()

print(history.urls())            # every page visited
print(history.action_names())    # every action taken
print(history.errors())          # per-step errors
print(history.total_duration_seconds())

Log this in production. When a run fails, action_names plus errors usually shows whether the model got lost or the site blocked an action.

Turn off anonymized telemetrydisable-telemetry

# .env
ANONYMIZED_TELEMETRY=false

Telemetry is on by default and documented at docs.browser-use.com. Set this before import in CI and privacy-sensitive environments.

Alternatives

PackageRegistryPick it when
playwrightPyPIThe workflow is known and repeatable; write a deterministic script with no LLM in the loop
seleniumPyPIYou need the legacy-grade ecosystem, Java/C# parity, or an existing Selenium grid
@browserbasehq/stagehandnpmYou want AI browser automation in TypeScript with Playwright interop and per-step fallbacks to code