1+ <!-- x-stagehand-custom-start -->
12<div id =" toc " align =" center " style =" margin-bottom : 0 ;" >
23 <ul style =" list-style : none ; margin : 0 ; padding : 0 ;" >
34 <a href="https://stagehand.dev">
@@ -50,6 +51,7 @@ If you're looking for other languages, you can find them
5051 <img alt="Director" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/director_icon.svg" width="25" />
5152 </picture >
5253</div >
54+ <!-- x-stagehand-custom-end -->
5355
5456> [ !TIP]
5557> Migrating from the old v2 Python SDK? See our [ migration guide here] ( https://docs.stagehand.dev/v3/migrations/python ) .
@@ -83,9 +85,12 @@ Python 3.9 or higher.
8385
8486## Running the Example
8587
86- A complete working example is available at [ ` examples/full_example.py ` ] ( examples/full_example.py ) .
88+ Set your environment variables (from ` examples/.env.example ` ):
8789
88- Before running examples, set up the example environment file:
90+ - ` STAGEHAND_API_URL `
91+ - ` MODEL_API_KEY `
92+ - ` BROWSERBASE_API_KEY `
93+ - ` BROWSERBASE_PROJECT_ID `
8994
9095``` bash
9196cp examples/.env.example examples/.env
@@ -94,165 +99,120 @@ cp examples/.env.example examples/.env
9499
95100The examples load ` examples/.env ` automatically.
96101
97- To run it, first export the required environment variables, then use Python :
102+ Examples and dependencies :
98103
99- ``` bash
100- export BROWSERBASE_API_KEY=" your-bb-api-key"
101- export BROWSERBASE_PROJECT_ID=" your-bb-project-uuid"
102- export MODEL_API_KEY=" sk-proj-your-llm-api-key"
103-
104- uv run python examples/full_example.py
105- ```
106-
107- ## Local mode example
108-
109- If you want to run Stagehand locally, use the local example (` examples/local_example.py ` ). It shows how to configure the client for ` server="local" ` .
110-
111- Local mode runs Stagehand’s embedded server and launches a ** local Chrome/Chromium** browser (it is ** not bundled** with the Python wheel), so you must have Chrome installed on the machine running the example.
112-
113- If Chrome is installed but Stagehand can’t find it, set ` CHROME_PATH ` to your browser executable (or pass ` browser.launchOptions.executablePath ` when starting the session).
104+ - ` examples/full_example.py ` : stagehand only
105+ - ` examples/act_example.py ` : stagehand only
106+ - ` examples/agent_execute.py ` : stagehand only
107+ - ` examples/local_example.py ` : stagehand only
108+ - ` examples/logging_example.py ` : stagehand only
109+ - ` examples/remote_browser_playwright_example.py ` : Playwright + Playwright browsers
110+ - ` examples/local_browser_playwright_example.py ` : Playwright + Playwright browsers
111+ - ` examples/playwright_page_example.py ` : Playwright + Playwright browsers
112+ - ` examples/byob_example.py ` : Playwright + Playwright browsers
113+ - ` examples/pydoll_tab_example.py ` : ` pydoll-python ` (Python 3.10+)
114114
115- Common Windows paths:
116- - ` C:\Program Files\Google\Chrome\Application\chrome.exe `
117- - ` C:\Program Files (x86)\Google\Chrome\Application\chrome.exe `
118-
119- PowerShell:
120-
121- ``` powershell
122- # optional if you don't already have Chrome installed
123- winget install -e --id Google.Chrome
124-
125- # optional if Stagehand can't auto-detect Chrome
126- $env:CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe"
127-
128- uv run python examples/local_example.py
129- ```
130-
131- ``` bash
132- pip install stagehand
133- uv run python examples/local_example.py
134- ```
135-
136- ## Streaming logging example
137-
138- See [ ` examples/logging_example.py ` ] ( examples/logging_example.py ) for a remote-only flow that streams ` StreamEvent ` s with ` verbose=2 ` , ` stream_response=True ` , and ` x_stream_response="true" ` so you can watch the SDK’s logs as they arrive.
139-
140- ``` bash
141- uv run python examples/logging_example.py
142- ```
143-
144- ## Remote Playwright (SSE) example
145-
146- 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.
115+ Run any example:
147116
148117``` bash
149118uv run python examples/remote_browser_playwright_example.py
150119```
151120
152- ## Local Playwright (SSE) example
153-
154- 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.
155-
156- ``` bash
157- uv run python examples/local_browser_playwright_example.py
158- ```
159-
160- <details >
161- <summary ><strong >Local development</strong ></summary >
162-
163- This repository relies on ` uv ` to install the sanctioned Python version and dependencies. After cloning, bootstrap the environment with:
164-
165- ``` sh
166- ./scripts/bootstrap
167- ```
168- Once the environment is ready, execute repo scripts with ` uv run ` :
169-
170- ``` sh
171- uv run python examples/full_example.py
172- ```
173- </details >
174-
175121## Usage
176122
177- This example demonstrates the full Stagehand workflow: starting a session, navigating to a page, observing possible actions, acting on elements, extracting data, and running an autonomous agent .
123+ This mirrors ` examples/remote_browser_playwright_example.py ` .
178124
179125``` python
180- import asyncio
181-
182- from stagehand import AsyncStagehand
183-
126+ import os
184127
185- async def main () -> None :
186- # Create client using environment variables:
187- # BROWSERBASE_API_KEY, BROWSERBASE_PROJECT_ID, MODEL_API_KEY
188- client = AsyncStagehand()
128+ from playwright.sync_api import sync_playwright
189129
190- # Start a new browser session (returns a session helper bound to a session_id)
191- session = await client.sessions.start( model_name = " openai/gpt-5-nano " )
130+ from env import load_example_env
131+ from stagehand import Stagehand
192132
193- print (f " Session started: { session.id} " )
194133
195- try :
196- # Navigate to a webpage
197- await session.navigate(
198- url = " https://news.ycombinator.com" ,
199- )
200- print (" Navigated to Hacker News" )
134+ def main () -> None :
135+ load_example_env()
201136
202- # Observe to find possible actions on the page
203- observe_response = await session.observe(
204- instruction = " find the link to view comments for the top post" ,
137+ with Stagehand(
138+ server = " remote" ,
139+ browserbase_api_key = os.environ.get(" BROWSERBASE_API_KEY" ),
140+ browserbase_project_id = os.environ.get(" BROWSERBASE_PROJECT_ID" ),
141+ model_api_key = os.environ.get(" MODEL_API_KEY" ),
142+ ) as client:
143+ session = client.sessions.start(
144+ model_name = " anthropic/claude-sonnet-4-6" ,
145+ browser = {" type" : " browserbase" },
205146 )
206147
207- results = observe_response.data.result
208- print (f " Found { len (results)} possible actions " )
209- if not results:
210- return
211-
212- # Take the first action returned by Observe and pass it to Act
213- action = results[0 ].to_dict(exclude_none = True )
214- print (" Acting on:" , action.get(" description" ))
215-
216- act_response = await session.act(input = action)
217- print (" Act completed:" , act_response.data.result.message)
218-
219- # Extract structured data from the page using a JSON schema
220- extract_response = await session.extract(
221- instruction = " extract the text of the top comment on this page" ,
222- schema = {
223- " type" : " object" ,
224- " properties" : {
225- " commentText" : {" type" : " string" },
226- " author" : {" type" : " string" },
148+ cdp_url = session.data.cdp_url
149+ if not cdp_url:
150+ raise RuntimeError (" No cdp_url returned from the API for this session." )
151+
152+ with sync_playwright() as p:
153+ browser = p.chromium.connect_over_cdp(cdp_url)
154+ context = browser.contexts[0 ] if browser.contexts else browser.new_context()
155+ page = context.pages[0 ] if context.pages else context.new_page()
156+
157+ client.sessions.navigate(session.id, url = " https://news.ycombinator.com" )
158+ page.wait_for_load_state(" domcontentloaded" )
159+
160+ observe_stream = client.sessions.observe(
161+ session.id,
162+ instruction = " find the link to view comments for the top post" ,
163+ stream_response = True ,
164+ x_stream_response = " true" ,
165+ )
166+ for _ in observe_stream:
167+ pass
168+
169+ act_stream = client.sessions.act(
170+ session.id,
171+ input = " Click the comments link for the top post" ,
172+ stream_response = True ,
173+ x_stream_response = " true" ,
174+ )
175+ for _ in act_stream:
176+ pass
177+
178+ extract_stream = client.sessions.extract(
179+ session.id,
180+ instruction = " extract the text of the top comment on this page" ,
181+ schema = {
182+ " type" : " object" ,
183+ " properties" : {
184+ " commentText" : {" type" : " string" },
185+ " author" : {" type" : " string" },
186+ },
187+ " required" : [" commentText" ],
227188 },
228- " required" : [" commentText" ],
229- },
230- )
231-
232- extracted = extract_response.data.result
233- author = extracted.get(" author" , " unknown" ) if isinstance (extracted, dict ) else " unknown"
234- print (" Extracted author:" , author)
235-
236- # Run an autonomous agent to accomplish a complex task
237- execute_response = await session.execute(
238- execute_options = {
239- " instruction" : f " Find any personal website, GitHub, or LinkedIn profile for the Hacker News user ' { author} '. " ,
240- " max_steps" : 10 ,
241- },
242- agent_config = {" model" : " openai/gpt-5-nano" },
243- timeout = 300.0 ,
244- )
189+ stream_response = True ,
190+ x_stream_response = " true" ,
191+ )
192+ for _ in extract_stream:
193+ pass
194+
195+ execute_stream = client.sessions.execute(
196+ session.id,
197+ execute_options = {
198+ " instruction" : " Click the 'Learn more' link if available" ,
199+ " max_steps" : 3 ,
200+ },
201+ agent_config = {
202+ " model" : {" model_name" : " anthropic/claude-opus-4-6" },
203+ " cua" : False ,
204+ },
205+ stream_response = True ,
206+ x_stream_response = " true" ,
207+ )
208+ for _ in execute_stream:
209+ pass
245210
246- print (" Agent completed:" , execute_response.data.result.message)
247- print (" Agent success:" , execute_response.data.result.success)
248- finally :
249- # End the browser session to clean up resources
250- await session.end()
251- print (" Session ended" )
211+ client.sessions.end(session.id)
252212
253213
254214if __name__ == " __main__" :
255- asyncio.run( main() )
215+ main()
256216```
257217
258218## Client configuration
@@ -336,7 +296,7 @@ from stagehand import AsyncStagehand
336296
337297async def main () -> None :
338298 client = AsyncStagehand()
339- session = await client.sessions.start(model_name = " openai/gpt-5-nano " )
299+ session = await client.sessions.start(model_name = " anthropic/claude-sonnet-4-6 " )
340300 response = await session.act(input = " click the first link on the page" )
341301 print (response.data)
342302
@@ -364,7 +324,7 @@ from stagehand import AsyncStagehand, DefaultAioHttpClient
364324
365325async def main () -> None :
366326 async with AsyncStagehand(http_client = DefaultAioHttpClient()) as client:
367- session = await client.sessions.start(model_name = " openai/gpt-5-nano " )
327+ session = await client.sessions.start(model_name = " anthropic/claude-sonnet-4-6 " )
368328 response = await session.act(input = " click the first link on the page" )
369329 print (response.data)
370330
@@ -389,7 +349,7 @@ from stagehand import AsyncStagehand
389349
390350async def main () -> None :
391351 async with AsyncStagehand() as client:
392- session = await client.sessions.start(model_name = " openai/gpt-5-nano " )
352+ session = await client.sessions.start(model_name = " anthropic/claude-sonnet-4-6 " )
393353
394354 stream = await client.sessions.act(
395355 id = session.id,
@@ -419,7 +379,7 @@ from stagehand import AsyncStagehand
419379
420380async def main () -> None :
421381 async with AsyncStagehand() as client:
422- response = await client.sessions.with_raw_response.start(model_name = " openai/gpt-5-nano " )
382+ response = await client.sessions.with_raw_response.start(model_name = " anthropic/claude-sonnet-4-6 " )
423383 print (response.headers.get(" X-My-Header" ))
424384
425385 session = response.parse() # get the object that `sessions.start()` would have returned
@@ -443,7 +403,7 @@ from stagehand import AsyncStagehand
443403
444404async def main () -> None :
445405 async with AsyncStagehand() as client:
446- async with client.sessions.with_streaming_response.start(model_name = " openai/gpt-5-nano " ) as response:
406+ async with client.sessions.with_streaming_response.start(model_name = " anthropic/claude-sonnet-4-6 " ) as response:
447407 print (response.headers.get(" X-My-Header" ))
448408 async for line in response.iter_lines():
449409 print (line)
@@ -470,7 +430,7 @@ from stagehand import AsyncStagehand
470430async def main () -> None :
471431 async with AsyncStagehand() as client:
472432 try :
473- await client.sessions.start(model_name = " openai/gpt-5-nano " )
433+ await client.sessions.start(model_name = " anthropic/claude-sonnet-4-6 " )
474434 except stagehand.APIConnectionError as e:
475435 print (" The server could not be reached" )
476436 print (e.__cause__) # an underlying Exception, likely raised within httpx.
@@ -513,7 +473,7 @@ from stagehand import AsyncStagehand
513473async def main () -> None :
514474 async with AsyncStagehand(max_retries = 0 ) as client:
515475 # Or, configure per-request:
516- await client.with_options(max_retries = 5 ).sessions.start(model_name = " openai/gpt-5-nano " )
476+ await client.with_options(max_retries = 5 ).sessions.start(model_name = " anthropic/claude-sonnet-4-6 " )
517477
518478
519479asyncio.run(main())
@@ -589,7 +549,7 @@ from stagehand import APIResponseValidationError, AsyncStagehand
589549try :
590550 async def main () -> None :
591551 async with AsyncStagehand(_strict_response_validation = True ) as client:
592- await client.sessions.start(model_name = " openai/gpt-5-nano " )
552+ await client.sessions.start(model_name = " anthropic/claude-sonnet-4-6 " )
593553
594554 asyncio.run(main())
595555except APIResponseValidationError as e:
@@ -623,7 +583,7 @@ from stagehand import Stagehand
623583
624584client = Stagehand()
625585response = client.sessions.with_raw_response.start(
626- model_name = " openai/gpt-5-nano " ,
586+ model_name = " anthropic/claude-sonnet-4-6 " ,
627587)
628588print (response.headers.get(' X-My-Header' ))
629589
@@ -643,7 +603,7 @@ To stream the response body, use `.with_streaming_response` instead, which requi
643603
644604``` python
645605with client.sessions.with_streaming_response.start(
646- model_name = " openai/gpt-5-nano " ,
606+ model_name = " anthropic/claude-sonnet-4-6 " ,
647607) as response:
648608 print (response.headers.get(" X-My-Header" ))
649609
0 commit comments