Skip to content

Commit afdb3c1

Browse files
revert examples
1 parent 4c93b0b commit afdb3c1

File tree

2 files changed

+114
-360
lines changed

2 files changed

+114
-360
lines changed

examples/example.py

Lines changed: 114 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -4,241 +4,134 @@
44
from rich.console import Console
55
from rich.panel import Panel
66
from rich.theme import Theme
7-
from pydantic import BaseModel, Field, HttpUrl
7+
import json
88
from dotenv import load_dotenv
9-
import time
109

11-
from stagehand import StagehandConfig, Stagehand
10+
from stagehand import Stagehand, StagehandConfig
1211
from stagehand.utils import configure_logging
13-
from stagehand.schemas import ObserveOptions, ActOptions, ExtractOptions
14-
from stagehand.a11y.utils import get_accessibility_tree, get_xpath_by_resolved_object_id
1512

16-
# Load environment variables
17-
load_dotenv()
13+
# Configure logging with cleaner format
14+
configure_logging(
15+
level=logging.INFO,
16+
remove_logger_name=True, # Remove the redundant stagehand.client prefix
17+
quiet_dependencies=True, # Suppress httpx and other noisy logs
18+
)
1819

19-
# Configure Rich console
20-
console = Console(theme=Theme({
21-
"info": "cyan",
22-
"success": "green",
23-
"warning": "yellow",
24-
"error": "red bold",
25-
"highlight": "magenta",
26-
"url": "blue underline",
27-
}))
28-
29-
# Define Pydantic models for testing
30-
class Company(BaseModel):
31-
name: str = Field(..., description="The name of the company")
32-
# todo - URL needs to be pydantic type HttpUrl otherwise it does not extract the URL
33-
url: HttpUrl = Field(..., description="The URL of the company website or relevant page")
34-
35-
class Companies(BaseModel):
36-
companies: list[Company] = Field(..., description="List of companies extracted from the page, maximum of 5 companies")
20+
# Create a custom theme for consistent styling
21+
custom_theme = Theme(
22+
{
23+
"info": "cyan",
24+
"success": "green",
25+
"warning": "yellow",
26+
"error": "red bold",
27+
"highlight": "magenta",
28+
"url": "blue underline",
29+
}
30+
)
3731

38-
class ElementAction(BaseModel):
39-
action: str
40-
id: int
41-
arguments: list[str]
32+
# Create a Rich console instance with our theme
33+
console = Console(theme=custom_theme)
4234

43-
async def main():
44-
# Display header
45-
console.print(
46-
"\n",
47-
Panel.fit(
48-
"[light_gray]New Stagehand 🤘 Python Test[/]",
49-
border_style="green",
50-
padding=(1, 10),
51-
),
52-
)
35+
load_dotenv()
5336

54-
# Create configuration
55-
model_name = "google/gemini-2.5-flash-preview-04-17"
37+
console.print(
38+
Panel.fit(
39+
"[yellow]Logging Levels:[/]\n"
40+
"[white]- Set [bold]verbose=0[/] for errors (ERROR)[/]\n"
41+
"[white]- Set [bold]verbose=1[/] for minimal logs (INFO)[/]\n"
42+
"[white]- Set [bold]verbose=2[/] for medium logs (WARNING)[/]\n"
43+
"[white]- Set [bold]verbose=3[/] for detailed logs (DEBUG)[/]",
44+
title="Verbosity Options",
45+
border_style="blue",
46+
)
47+
)
5648

49+
async def main():
50+
# Build a unified configuration object for Stagehand
5751
config = StagehandConfig(
52+
env="BROWSERBASE",
5853
api_key=os.getenv("BROWSERBASE_API_KEY"),
5954
project_id=os.getenv("BROWSERBASE_PROJECT_ID"),
60-
model_name=model_name, # todo - unify gemini/google model names
61-
model_client_options={"apiKey": os.getenv("MODEL_API_KEY")}, # this works locally even if there is a model provider mismatch
62-
verbose=3,
55+
headless=False,
56+
dom_settle_timeout_ms=3000,
57+
model_name="google/gemini-2.0-flash",
58+
self_heal=True,
59+
wait_for_captcha_solves=True,
60+
system_prompt="You are a browser automation assistant that helps users navigate websites effectively.",
61+
model_client_options={"apiKey": os.getenv("MODEL_API_KEY")},
62+
# Use verbose=2 for medium-detail logs (1=minimal, 3=debug)
63+
verbose=2,
6364
)
64-
65-
# Initialize async client
66-
stagehand = Stagehand(
67-
env=os.getenv("STAGEHAND_ENV"),
68-
config=config,
69-
api_url=os.getenv("STAGEHAND_SERVER_URL"),
65+
66+
stagehand = Stagehand(config)
67+
68+
# Initialize - this creates a new session automatically.
69+
console.print("\n🚀 [info]Initializing Stagehand...[/]")
70+
await stagehand.init()
71+
page = stagehand.page
72+
console.print(f"\n[yellow]Created new session:[/] {stagehand.session_id}")
73+
console.print(
74+
f"🌐 [white]View your live browser:[/] [url]https://www.browserbase.com/sessions/{stagehand.session_id}[/]"
7075
)
76+
77+
await asyncio.sleep(2)
78+
79+
console.print("\n▶️ [highlight] Navigating[/] to Google")
80+
await page.goto("https://google.com/")
81+
console.print("✅ [success]Navigated to Google[/]")
82+
83+
console.print("\n▶️ [highlight] Clicking[/] on About link")
84+
# Click on the "About" link using Playwright
85+
await page.get_by_role("link", name="About", exact=True).click()
86+
console.print("✅ [success]Clicked on About link[/]")
87+
88+
await asyncio.sleep(2)
89+
console.print("\n▶️ [highlight] Navigating[/] back to Google")
90+
await page.goto("https://google.com/")
91+
console.print("✅ [success]Navigated back to Google[/]")
92+
93+
console.print("\n▶️ [highlight] Performing action:[/] search for openai")
94+
await page.act("search for openai")
95+
await page.keyboard.press("Enter")
96+
console.print("✅ [success]Performing Action:[/] Action completed successfully")
7197

72-
try:
73-
# Initialize the client
74-
await stagehand.init()
75-
console.print("[success]✓ Successfully initialized Stagehand async client[/]")
76-
console.print(f"[info]Environment: {stagehand.env}[/]")
77-
console.print(f"[info]LLM Client Available: {stagehand.llm is not None}[/]")
78-
79-
# Navigate to AIgrant (as in the original test)
80-
await stagehand.page.goto("https://www.aigrant.com")
81-
console.print("[success]✓ Navigated to AIgrant[/]")
82-
await asyncio.sleep(2)
83-
84-
# Get accessibility tree
85-
tree = await get_accessibility_tree(stagehand.page, stagehand.logger)
86-
console.print("[success]✓ Extracted accessibility tree[/]")
87-
88-
print("ID to URL mapping:", tree.get("idToUrl"))
89-
print("IFrames:", tree.get("iframes"))
90-
91-
# Click the "Get Started" button
92-
await stagehand.page.act("click the button with text 'Get Started'")
93-
console.print("[success]✓ Clicked 'Get Started' button[/]")
94-
95-
# Observe the button
96-
await stagehand.page.observe("the button with text 'Get Started'")
97-
console.print("[success]✓ Observed 'Get Started' button[/]")
98-
99-
# Extract companies using schema
100-
extract_options = ExtractOptions(
101-
instruction="Extract the names and URLs of up to 5 companies mentioned on this page",
102-
schema_definition=Companies
103-
)
104-
105-
extract_result = await stagehand.page.extract(extract_options)
106-
console.print("[success]✓ Extracted companies data[/]")
107-
108-
# Display results
109-
print("Extract result:", extract_result)
110-
print("Extract result data:", extract_result.data if hasattr(extract_result, 'data') else 'No data field')
111-
112-
# Parse the result into the Companies model
113-
companies_data = None
114-
115-
# Handle different result formats between LOCAL and BROWSERBASE
116-
if hasattr(extract_result, 'data') and extract_result.data:
117-
# BROWSERBASE mode - data is in the 'data' field
118-
try:
119-
raw_data = extract_result.data
120-
console.print(f"[info]Raw extract data: {raw_data}[/]")
121-
122-
# Check if the data needs URL resolution from ID mapping
123-
if isinstance(raw_data, dict) and 'companies' in raw_data:
124-
id_to_url = tree.get("idToUrl", {})
125-
for company in raw_data['companies']:
126-
if 'url' in company and isinstance(company['url'], str):
127-
# Check if URL is just an ID that needs to be resolved
128-
if company['url'].isdigit() and company['url'] in id_to_url:
129-
company['url'] = id_to_url[company['url']]
130-
console.print(f"[success]✓ Resolved URL for {company['name']}: {company['url']}[/]")
131-
132-
companies_data = Companies.model_validate(raw_data)
133-
console.print("[success]✓ Successfully parsed extract result into Companies model[/]")
134-
except Exception as e:
135-
console.print(f"[error]Failed to parse extract result: {e}[/]")
136-
print("Raw data:", extract_result.data)
137-
elif hasattr(extract_result, 'companies'):
138-
# LOCAL mode - companies field is directly available
139-
try:
140-
companies_data = Companies.model_validate(extract_result.model_dump())
141-
console.print("[success]✓ Successfully parsed extract result into Companies model[/]")
142-
except Exception as e:
143-
console.print(f"[error]Failed to parse extract result: {e}[/]")
144-
print("Raw companies data:", extract_result.companies)
145-
146-
print("\nExtracted Companies:")
147-
if companies_data and hasattr(companies_data, "companies"):
148-
for idx, company in enumerate(companies_data.companies, 1):
149-
print(f"{idx}. {company.name}: {company.url}")
150-
else:
151-
print("No companies were found in the extraction result")
152-
153-
# XPath click
154-
await stagehand.page.locator("xpath=/html/body/div/ul[2]/li[2]/a").click()
155-
await stagehand.page.wait_for_load_state('networkidle')
156-
console.print("[success]✓ Clicked element using XPath[/]")
157-
158-
# Open a new page with Google
159-
console.print("\n[info]Creating a new page...[/]")
160-
new_page = await stagehand.context.new_page()
161-
await new_page.goto("https://www.google.com")
162-
console.print("[success]✓ Opened Google in a new page[/]")
163-
164-
# Get accessibility tree for the new page
165-
tree = await get_accessibility_tree(new_page, stagehand.logger)
166-
console.print("[success]✓ Extracted accessibility tree for new page[/]")
167-
168-
# Try clicking Get Started button on Google
169-
await new_page.act("click the button with text 'Get Started'")
170-
171-
# Only use LLM directly if in LOCAL mode
172-
if stagehand.llm is not None:
173-
console.print("[info]LLM client available - using direct LLM call[/]")
174-
175-
# Use LLM to analyze the page
176-
response = stagehand.llm.create_response(
177-
messages=[
178-
{
179-
"role": "system",
180-
"content": "Based on the provided accessibility tree of the page, find the element and the action the user is expecting to perform. The tree consists of an enhanced a11y tree from a website with unique identifiers prepended to each element's role, and name. The actions you can take are playwright compatible locator actions."
181-
},
182-
{
183-
"role": "user",
184-
"content": [
185-
{
186-
"type": "text",
187-
"text": f"fill the search bar with the text 'Hello'\nPage Tree:\n{tree.get('simplified')}"
188-
}
189-
]
190-
}
191-
],
192-
model=model_name,
193-
response_format=ElementAction,
194-
)
195-
196-
action = ElementAction.model_validate_json(response.choices[0].message.content)
197-
console.print(f"[success]✓ LLM identified element ID: {action.id}[/]")
198-
199-
# Test CDP functionality
200-
args = {"backendNodeId": action.id}
201-
result = await new_page.send_cdp("DOM.resolveNode", args)
202-
object_info = result.get("object")
203-
print(object_info)
204-
205-
xpath = await get_xpath_by_resolved_object_id(await new_page.get_cdp_client(), object_info["objectId"])
206-
console.print(f"[success]✓ Retrieved XPath: {xpath}[/]")
207-
208-
# Interact with the element
209-
if xpath:
210-
await new_page.locator(f"xpath={xpath}").click()
211-
await new_page.locator(f"xpath={xpath}").fill(action.arguments[0])
212-
console.print("[success]✓ Filled search bar with 'Hello'[/]")
213-
else:
214-
print("No xpath found")
215-
else:
216-
console.print("[warning]LLM client not available in BROWSERBASE mode - skipping direct LLM test[/]")
217-
# Alternative: use page.observe to find the search bar
218-
observe_result = await new_page.observe("the search bar or search input field")
219-
console.print(f"[info]Observed search elements: {observe_result}[/]")
220-
221-
# Use page.act to fill the search bar
222-
try:
223-
await new_page.act("fill the search bar with 'Hello'")
224-
console.print("[success]✓ Filled search bar using act()[/]")
225-
except Exception as e:
226-
console.print(f"[warning]Could not fill search bar: {e}[/]")
227-
228-
# Final test summary
229-
console.print("\n[success]All tests completed successfully![/]")
230-
231-
except Exception as e:
232-
console.print(f"[error]Error during testing: {str(e)}[/]")
233-
import traceback
234-
traceback.print_exc()
235-
raise
236-
finally:
237-
# Close the client
238-
# wait for 5 seconds
239-
await asyncio.sleep(5)
240-
await stagehand.close()
241-
console.print("[info]Stagehand async client closed[/]")
98+
await asyncio.sleep(2)
99+
100+
console.print("\n▶️ [highlight] Observing page[/] for news button")
101+
observed = await page.observe("find all articles")
102+
103+
if len(observed) > 0:
104+
element = observed[0]
105+
console.print("✅ [success]Found element:[/] News button")
106+
console.print("\n▶️ [highlight] Performing action on observed element:")
107+
console.print(element)
108+
await page.act(element)
109+
console.print("✅ [success]Performing Action:[/] Action completed successfully")
110+
111+
else:
112+
console.print("❌ [error]No element found[/]")
113+
114+
console.print("\n▶️ [highlight] Extracting[/] first search result")
115+
data = await page.extract("extract the first result from the search")
116+
console.print("📊 [info]Extracted data:[/]")
117+
console.print_json(f"{data.model_dump_json()}")
118+
119+
# Close the session
120+
console.print("\n⏹️ [warning]Closing session...[/]")
121+
await stagehand.close()
122+
console.print("✅ [success]Session closed successfully![/]")
123+
console.rule("[bold]End of Example[/]")
124+
242125

243126
if __name__ == "__main__":
244-
asyncio.run(main())
127+
# Add a fancy header
128+
console.print(
129+
"\n",
130+
Panel.fit(
131+
"[light_gray]Stagehand 🤘 Python Example[/]",
132+
border_style="green",
133+
padding=(1, 10),
134+
),
135+
)
136+
asyncio.run(main())
137+

0 commit comments

Comments
 (0)