Observe
The page's interactive elements are extracted and numbered, so the model can refer to them.
Browser Use is an open-source Python library that lets an LLM agent drive a real browser. Instead of scripting every click, you give it a goal. I use it for the checks scripts are bad at: exploratory runs, long-tail flows, and "can a real user actually do this?"
The agent repeats one cycle until the goal is met, it gives up, or it hits the step limit.
The page's interactive elements are extracted and numbered, so the model can refer to them.
The LLM compares the page with the goal and picks the next action, such as "click element 4".
The action runs in a real browser: click, type, scroll, navigate or open a tab.
My addition: a deterministic check confirms the outcome, because an agent's "done" is a claim, not proof.
Run it once. Then flip "Ship a UI redesign" and run again: the selector script breaks, the agent adapts, and the final check still decides.
The agent does the exploring. A plain assertion against the system of record decides pass or fail.
import asyncio
from browser_use import Agent, ChatAnthropic # the API moves fast; check the current README
TASK = (
"Open https://staging.shop.local, add the cheapest USB-C cable "
"to the cart, and report the cart total."
)
async def main():
agent = Agent(task=TASK, llm=ChatAnthropic(model="claude-sonnet-5"))
# Guardrails: staging only, a throwaway test account, and a hard step cap
history = await agent.run(max_steps=25)
print(history.final_result())
asyncio.run(main())
import pytest
from browser_use import Agent, ChatAnthropic
@pytest.mark.agentic # nightly job, not a per-PR gate: agents are slower and non-deterministic
@pytest.mark.asyncio
async def test_user_can_buy_the_cheapest_cable(cart_api):
agent = Agent(task=TASK, llm=ChatAnthropic(model="claude-sonnet-5"))
history = await agent.run(max_steps=25)
assert history.is_done(), "agent stopped before finishing the task"
# Don't trust the agent's summary. Check the system of record.
cart = cart_api.get_cart(user="qa-bot")
cheapest = min(cart_api.search("usb-c cable"), key=lambda p: p.price)
assert [(item.sku, item.qty) for item in cart.items] == [(cheapest.sku, 1)]
assert cart.total == cheapest.price
It runs alongside the scripted suites. It doesn't replace them.
Use the agent to discover and adapt, and plain code to decide. That keeps the flexibility without giving up trust.