Skip to content

Commit a288b26

Browse files
committed
docs: add Browser Use callback demo
1 parent 8a619de commit a288b26

5 files changed

Lines changed: 122 additions & 1 deletion

File tree

docs/browser-use-debugging.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ <h2>Try it before wiring Browser Use</h2>
216216
<pre><code>pip install "browsertrace[ui] @ git+https://github.com/aaronlab/browsertrace@v0.1.6"
217217
browsertrace demo
218218
browsertrace</code></pre>
219-
<p>Open <code>http://127.0.0.1:3000</code>, then inspect <code>demo: checkout agent fails on disabled button</code>.</p>
219+
<p>Open <code>http://127.0.0.1:3000</code>, then inspect <code>demo: checkout agent fails on disabled button</code>. From a source checkout, <code>python examples/browser_use_callback_demo.py</code> records Browser Use-shaped callback steps without installing Browser Use.</p>
220220
</section>
221221

222222
<section>

docs/launch/metrics-log.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ uv run --python 3.11 python scripts/launch_metrics.py --json
5959
| 2026-05-09T10:47:56+00:00 | 3 | 998 | 0 | 0 | 7 | 0 | 0 | after Playwright LLM debugging landing page added |
6060
| 2026-05-09T10:49:38+00:00 | 3 | 998 | 0 | 0 | 7 | 0 | 0 | after owner launch checklist issues updated with stack landing pages |
6161
| 2026-05-09T10:52:41+00:00 | 3 | 998 | 0 | 0 | 7 | 0 | 0 | after comparison landing page added |
62+
| 2026-05-09T10:55:33+00:00 | 3 | 998 | 0 | 0 | 7 | 0 | 0 | after Browser Use no-dependency callback demo added |

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Open `http://127.0.0.1:3000` and inspect
2121
| Example | Use when | Extra services | Command |
2222
|---|---|---:|---|
2323
| `no_api_failure_demo.py` | You want the fastest failing trace | None | `python examples/no_api_failure_demo.py` |
24+
| `browser_use_callback_demo.py` | You want to see Browser Use-shaped step callbacks recorded | None | `python examples/browser_use_callback_demo.py` |
2425
| `stagehand_wrapper_example.py` | You want to see Stagehand-style `act` and `extract` calls recorded | None | `python examples/stagehand_wrapper_example.py` |
2526
| `skyvern_wrapper_example.py` | You want to see Skyvern-style task calls recorded | None | `python examples/skyvern_wrapper_example.py` |
2627
| `basic_example.py` | You want the smallest manual SDK example | None | `python examples/basic_example.py` |
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Run the Browser Use adapter without installing Browser Use.
2+
3+
This uses a fake Browser Use-shaped agent so the example is deterministic:
4+
5+
python examples/browser_use_callback_demo.py
6+
7+
Then open the BrowserTrace UI:
8+
9+
browsertrace
10+
"""
11+
12+
from __future__ import annotations
13+
14+
import asyncio
15+
import base64
16+
import os
17+
18+
from browsertrace import Tracer
19+
from browsertrace.integrations.browser_use import attach_tracer
20+
21+
22+
PNG_BYTES = b"\x89PNG\r\n\x1a\n" + b"\x00" * 16
23+
24+
25+
class DemoBrowserState:
26+
def __init__(self, url: str):
27+
self.url = url
28+
self.screenshot = base64.b64encode(PNG_BYTES).decode("ascii")
29+
30+
31+
class DemoAction:
32+
def __init__(self, payload: dict):
33+
self._payload = payload
34+
35+
def model_dump(self, exclude_none: bool = True) -> dict:
36+
return self._payload
37+
38+
39+
class DemoCurrentState:
40+
def __init__(self, thought: str):
41+
self.thought = thought
42+
43+
44+
class DemoAgentOutput:
45+
def __init__(self, thought: str, action: dict):
46+
self.current_state = DemoCurrentState(thought)
47+
self.action = [DemoAction(action)]
48+
49+
50+
class DemoBrowserUseAgent:
51+
def __init__(self):
52+
self._new_step_callback = None
53+
54+
def register_new_step_callback(self, callback):
55+
self._new_step_callback = callback
56+
57+
async def run(self) -> None:
58+
if self._new_step_callback is None:
59+
raise RuntimeError("step callback was not registered")
60+
61+
await self._new_step_callback(
62+
DemoBrowserState("https://example.com/search"),
63+
DemoAgentOutput(
64+
"search for the project",
65+
{"search_google": {"query": "BrowserTrace"}},
66+
),
67+
0,
68+
)
69+
await self._new_step_callback(
70+
DemoBrowserState("https://example.com/results"),
71+
DemoAgentOutput(
72+
"open the first useful result",
73+
{"click": {"selector": "#result-1"}},
74+
),
75+
1,
76+
)
77+
78+
79+
async def main() -> None:
80+
tracer = Tracer(home=os.environ.get("BROWSERTRACE_HOME"))
81+
agent = DemoBrowserUseAgent()
82+
83+
with attach_tracer(agent, tracer, name="demo: browser-use callback flow") as bt_run:
84+
await agent.run()
85+
86+
print(f"BrowserTrace run id: {bt_run.run.id}")
87+
print("Recorded Browser Use-shaped callback steps: search_google, click")
88+
print("Open the local UI with: browsertrace")
89+
90+
91+
if __name__ == "__main__":
92+
asyncio.run(main())

tests/test_examples.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,30 @@ def test_stagehand_wrapper_example_creates_completed_trace(tmp_path, monkeypatch
8383
assert steps[0][2] == "ok"
8484
assert json.loads(steps[0][3])["method"] == "act"
8585
assert steps[0][4].endswith(".png")
86+
87+
88+
def test_browser_use_callback_demo_creates_completed_trace(tmp_path, monkeypatch):
89+
monkeypatch.setenv("BROWSERTRACE_HOME", str(tmp_path))
90+
91+
runpy.run_path("examples/browser_use_callback_demo.py", run_name="__main__")
92+
93+
with sqlite3.connect(tmp_path / "db.sqlite") as c:
94+
run = c.execute(
95+
"SELECT id, name, status, error FROM runs ORDER BY started_at DESC LIMIT 1"
96+
).fetchone()
97+
steps = c.execute(
98+
"SELECT action, url, status, model_output "
99+
"FROM steps WHERE run_id=? ORDER BY step_index",
100+
(run[0],),
101+
).fetchall()
102+
103+
assert run[1] == "demo: browser-use callback flow"
104+
assert run[2] == "completed"
105+
assert run[3] is None
106+
assert [step[0] for step in steps] == [
107+
"search_google(query=BrowserTrace)",
108+
"click(selector=#result-1)",
109+
]
110+
assert steps[0][1] == "https://example.com/search"
111+
assert steps[1][2] == "ok"
112+
assert json.loads(steps[1][3])["thought"] == "open the first useful result"

0 commit comments

Comments
 (0)