Skip to content

Commit 523e4af

Browse files
authored
Merge pull request #45 from stainless-sdks/STG-1296
STG-1296
2 parents 555a9c4 + a3be653 commit 523e4af

3 files changed

Lines changed: 178 additions & 117 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ See [`examples/logging_example.py`](examples/logging_example.py) for a remote-on
132132
uv run python examples/logging_example.py
133133
```
134134

135+
## Remote Playwright (SSE) example
136+
137+
See [`examples/remote_browser_playwright_example.py`](examples/remote_browser_playwright_example.py) for a remote Browserbase flow that attaches Playwright via CDP and streams SSE events for observe/act/extract/execute.
138+
139+
```bash
140+
uv run python examples/remote_browser_playwright_example.py
141+
```
142+
135143
<details>
136144
<summary><strong>Local development</strong></summary>
137145

examples/playwright_page_example.py

Lines changed: 0 additions & 117 deletions
This file was deleted.
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 (remote Browserbase).
3+
4+
What this demonstrates:
5+
- Start a Stagehand session (remote Stagehand API / Browserbase browser)
6+
- Attach Playwright to the same browser via CDP (`cdp_url`)
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
15+
- BROWSERBASE_PROJECT_ID
16+
17+
Optional:
18+
- STAGEHAND_BASE_URL (defaults to https://api.stagehand.browserbase.com)
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="remote",
73+
browserbase_api_key=bb_api_key,
74+
browserbase_project_id=bb_project_id,
75+
model_api_key=model_api_key,
76+
) as client:
77+
print("⏳ Starting Stagehand session...")
78+
session = client.sessions.start(
79+
model_name="openai/gpt-5-nano",
80+
browser={"type": "browserbase"},
81+
verbose=2,
82+
)
83+
session_id = session.id
84+
85+
cdp_url = session.data.cdp_url
86+
if not cdp_url:
87+
sys.exit(
88+
"No cdp_url returned from the API for this session; cannot attach Playwright."
89+
)
90+
91+
print(f"✅ Session started: {session_id}")
92+
print("🔌 Connecting Playwright to the same browser over CDP...")
93+
94+
try:
95+
with sync_playwright() as p:
96+
# Attach to the same browser session Stagehand is controlling.
97+
browser = p.chromium.connect_over_cdp(cdp_url)
98+
try:
99+
# Reuse an existing context/page if present; otherwise create one.
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+
finally:
165+
session.end()
166+
print("✅ Session ended.")
167+
168+
169+
if __name__ == "__main__":
170+
main()

0 commit comments

Comments
 (0)