Skip to content

Commit 6a465c5

Browse files
authored
Merge pull request #46 from stainless-sdks/STG-1295
STG-1295: local browser SSE example
2 parents 523e4af + 60b8787 commit 6a465c5

2 files changed

Lines changed: 178 additions & 0 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ See [`examples/remote_browser_playwright_example.py`](examples/remote_browser_pl
140140
uv run python examples/remote_browser_playwright_example.py
141141
```
142142

143+
## Local Playwright (SSE) example
144+
145+
See [`examples/local_browser_playwright_example.py`](examples/local_browser_playwright_example.py) for a local Stagehand flow that launches Playwright locally, shares its CDP URL with Stagehand, and streams SSE events for observe/act/extract/execute.
146+
147+
```bash
148+
uv run python examples/local_browser_playwright_example.py
149+
```
150+
143151
<details>
144152
<summary><strong>Local development</strong></summary>
145153

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""
2+
Example: use a Playwright Page with the Stagehand Python SDK (local browser).
3+
4+
What this demonstrates:
5+
- Start a Stagehand session in local mode
6+
- Launch a local Playwright browser server and share its CDP URL with Stagehand
7+
- Pass the Playwright `page` into `session.observe/act/extract/execute`
8+
so Stagehand auto-detects the correct `frame_id` for that page
9+
- Stream SSE events by default for observe/act/extract/execute
10+
- Run the full flow: start → observe → act → extract → agent/execute → end
11+
12+
Environment variables required:
13+
- MODEL_API_KEY
14+
- BROWSERBASE_API_KEY (can be any value in local mode)
15+
- BROWSERBASE_PROJECT_ID (can be any value in local mode)
16+
17+
Optional:
18+
- STAGEHAND_BASE_URL (defaults to http://127.0.0.1:3000)
19+
"""
20+
21+
from __future__ import annotations
22+
23+
import os
24+
import sys
25+
from typing import Any, Optional
26+
27+
from stagehand import Stagehand
28+
29+
30+
def _print_stream_events(stream: Any, label: str) -> object | None:
31+
result_payload: object | None = None
32+
for event in stream:
33+
if event.type == "log":
34+
print(f"[{label}][log] {event.data.message}")
35+
continue
36+
37+
status = event.data.status
38+
print(f"[{label}][system] status={status}")
39+
if status == "finished":
40+
result_payload = event.data.result
41+
elif status == "error":
42+
error_message = event.data.error or "unknown error"
43+
raise RuntimeError(f"{label} stream reported error: {error_message}")
44+
45+
return result_payload
46+
47+
48+
def main() -> None:
49+
model_api_key = os.environ.get("MODEL_API_KEY")
50+
if not model_api_key:
51+
sys.exit("Set the MODEL_API_KEY environment variable to run this example.")
52+
53+
bb_api_key = os.environ.get("BROWSERBASE_API_KEY")
54+
bb_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
55+
if not bb_api_key or not bb_project_id:
56+
sys.exit(
57+
"Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID to run this example."
58+
)
59+
60+
try:
61+
from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
62+
except Exception:
63+
sys.exit(
64+
"Playwright is not installed. Install it with:\n"
65+
" uv pip install playwright\n"
66+
"and ensure browsers are installed (e.g. `playwright install chromium`)."
67+
)
68+
69+
session_id: Optional[str] = None
70+
71+
with Stagehand(
72+
server="local",
73+
browserbase_api_key=bb_api_key,
74+
browserbase_project_id=bb_project_id,
75+
model_api_key=model_api_key,
76+
local_openai_api_key=model_api_key,
77+
local_ready_timeout_s=30.0,
78+
) as client:
79+
print("⏳ Starting Stagehand session (local server + local browser)...")
80+
81+
with sync_playwright() as p:
82+
browser_server = p.chromium.launch_server(headless=True)
83+
cdp_url = browser_server.ws_endpoint
84+
85+
session = client.sessions.start(
86+
model_name="openai/gpt-5-nano",
87+
browser={
88+
"type": "local",
89+
"launchOptions": {"cdpUrl": cdp_url},
90+
},
91+
verbose=2,
92+
)
93+
session_id = session.id
94+
95+
print(f"✅ Session started: {session_id}")
96+
print("🔌 Connecting Playwright to the same browser over CDP...")
97+
98+
browser = p.chromium.connect_over_cdp(cdp_url)
99+
try:
100+
context = browser.contexts[0] if browser.contexts else browser.new_context()
101+
page = context.pages[0] if context.pages else context.new_page()
102+
103+
page.goto("https://example.com", wait_until="domcontentloaded")
104+
105+
print("👀 Stagehand.observe(page=...) with SSE streaming...")
106+
observe_stream = session.observe(
107+
instruction="Find the most relevant click target on this page",
108+
page=page,
109+
stream_response=True,
110+
x_stream_response="true",
111+
)
112+
observe_result = _print_stream_events(observe_stream, "observe")
113+
114+
actions = observe_result or []
115+
if not actions:
116+
print("No actions found; ending session.")
117+
return
118+
119+
print("🖱️ Stagehand.act(page=...) with SSE streaming...")
120+
act_stream = session.act(
121+
input=actions[0],
122+
page=page,
123+
stream_response=True,
124+
x_stream_response="true",
125+
)
126+
_ = _print_stream_events(act_stream, "act")
127+
128+
print("🧠 Stagehand.extract(page=...) with SSE streaming...")
129+
extract_stream = session.extract(
130+
instruction="Extract the page title and the primary heading (h1) text",
131+
schema={
132+
"type": "object",
133+
"properties": {
134+
"title": {"type": "string"},
135+
"h1": {"type": "string"},
136+
},
137+
"required": ["title", "h1"],
138+
"additionalProperties": False,
139+
},
140+
page=page,
141+
stream_response=True,
142+
x_stream_response="true",
143+
)
144+
extracted = _print_stream_events(extract_stream, "extract")
145+
print("Extracted:", extracted)
146+
147+
print("🤖 Stagehand.execute(page=...) with SSE streaming...")
148+
execute_stream = session.execute(
149+
agent_config={"model": "openai/gpt-5-nano"},
150+
execute_options={
151+
"instruction": (
152+
"Open the 'Learn more' link if present and summarize the destination in one sentence."
153+
),
154+
"max_steps": 5,
155+
},
156+
page=page,
157+
stream_response=True,
158+
x_stream_response="true",
159+
)
160+
execute_result = _print_stream_events(execute_stream, "execute")
161+
print("Execute result:", execute_result)
162+
finally:
163+
browser.close()
164+
browser_server.close()
165+
session.end()
166+
print("✅ Session ended.")
167+
168+
169+
if __name__ == "__main__":
170+
main()

0 commit comments

Comments
 (0)