Skip to content

Commit d95fb70

Browse files
authored
browser-use deps + env example (#18)
1 parent 9311b17 commit d95fb70

File tree

3 files changed

+270
-91
lines changed

3 files changed

+270
-91
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
BROWSERBASE_API_KEY=your_api_key_here
2+
BROWSERBASE_PROJECT_ID=your_project_id_here
3+
ANTHROPIC_API_KEY=your_anthropic_api_key_here
Lines changed: 145 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,163 @@
1-
from dotenv import load_dotenv
2-
import os
31
import asyncio
4-
from typing import Optional
2+
import os
3+
from dotenv import load_dotenv
4+
55
from browserbase import Browserbase
6-
from browser_use import Agent, Browser, BrowserConfig
7-
from browser_use.browser.context import BrowserContext, BrowserContextConfig, BrowserSession
6+
from browser_use import Agent
7+
from browser_use.browser.session import BrowserSession
8+
from browser_use.browser import BrowserProfile
89
from langchain_anthropic import ChatAnthropic
9-
from playwright.async_api import Page, BrowserContext as PlaywrightContext
10-
11-
class ExtendedBrowserSession(BrowserSession):
12-
"""Extended version of BrowserSession that includes current_page"""
13-
def __init__(
14-
self,
15-
context: PlaywrightContext,
16-
cached_state: Optional[dict] = None,
17-
current_page: Optional[Page] = None
18-
):
19-
super().__init__(context=context, cached_state=cached_state)
20-
self.current_page = current_page
21-
22-
class UseBrowserbaseContext(BrowserContext):
23-
async def _initialize_session(self) -> ExtendedBrowserSession:
24-
"""Initialize a browser session using existing Browserbase page.
2510

26-
Returns:
27-
ExtendedBrowserSession: The initialized browser session with current page.
28-
"""
29-
playwright_browser = await self.browser.get_playwright_browser()
30-
context = await self._create_context(playwright_browser)
31-
self._add_new_page_listener(context)
3211

33-
self.session = ExtendedBrowserSession(
34-
context=context,
35-
cached_state=None,
36-
)
37-
38-
# Get existing page or create new one
39-
self.session.current_page = context.pages[0] if context.pages else await context.new_page()
40-
41-
# Initialize session state
42-
self.session.cached_state = await self._update_state()
43-
44-
return self.session
45-
46-
async def setup_browser() -> tuple[Browser, UseBrowserbaseContext]:
47-
"""Set up browser and context configurations.
48-
49-
Returns:
50-
tuple[Browser, UseBrowserbaseContext]: Configured browser and context.
51-
"""
12+
class ManagedBrowserSession:
13+
"""Context manager for proper BrowserSession lifecycle management"""
14+
15+
def __init__(self, cdp_url: str, browser_profile: BrowserProfile):
16+
self.cdp_url = cdp_url
17+
self.browser_profile = browser_profile
18+
self.browser_session = None
19+
20+
async def __aenter__(self) -> BrowserSession:
21+
try:
22+
self.browser_session = BrowserSession(
23+
cdp_url=self.cdp_url,
24+
browser_profile=self.browser_profile,
25+
keep_alive=False, # Essential for proper cleanup
26+
initialized=False,
27+
)
28+
29+
await self.browser_session.start()
30+
print("✅ Browser session initialized successfully")
31+
return self.browser_session
32+
33+
except Exception as e:
34+
print(f"❌ Failed to initialize browser session: {e}")
35+
await self._emergency_cleanup()
36+
raise
37+
38+
async def __aexit__(self, exc_type, exc_val, exc_tb):
39+
await self._close_session_properly()
40+
41+
async def _close_session_properly(self):
42+
playwright_instance = None
43+
44+
try:
45+
if self.browser_session:
46+
# Get playwright instance before closing session
47+
if hasattr(self.browser_session, 'playwright'):
48+
playwright_instance = self.browser_session.playwright
49+
50+
# Close browser session first
51+
if self.browser_session.initialized:
52+
await self.browser_session.stop()
53+
print("✅ Browser session closed successfully")
54+
55+
except Exception as e:
56+
error_msg = str(e).lower()
57+
if "browser is closed" in error_msg or "disconnected" in error_msg:
58+
print("ℹ️ Browser session was already closed (expected behavior)")
59+
else:
60+
print(f"⚠️ Error during browser session closure: {e}")
61+
62+
finally:
63+
# Stop playwright instance - critical for preventing hanging processes
64+
if playwright_instance:
65+
try:
66+
await playwright_instance.stop()
67+
print("✅ Playwright instance stopped successfully")
68+
except Exception as e:
69+
print(f"⚠️ Error stopping Playwright: {e}")
70+
71+
await self._final_cleanup()
72+
73+
async def _emergency_cleanup(self):
74+
try:
75+
if self.browser_session:
76+
if hasattr(self.browser_session, 'playwright'):
77+
await self.browser_session.playwright.stop()
78+
if self.browser_session.initialized:
79+
await self.browser_session.stop()
80+
except Exception as e:
81+
print(f"⚠️ Emergency cleanup error: {e}")
82+
finally:
83+
await self._final_cleanup()
84+
85+
async def _final_cleanup(self):
86+
self.browser_session = None
87+
88+
async def create_browserbase_session():
89+
load_dotenv()
90+
5291
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
53-
bb_session = bb.sessions.create(
54-
project_id=os.environ["BROWSERBASE_PROJECT_ID"],
92+
session = bb.sessions.create(project_id=os.environ["BROWSERBASE_PROJECT_ID"])
93+
94+
print(f"Session ID: {session.id}")
95+
print(f"Debug URL: https://www.browserbase.com/sessions/{session.id}")
96+
97+
return session
98+
99+
100+
def create_browser_profile() -> BrowserProfile:
101+
return BrowserProfile(
102+
keep_alive=False, # Essential for proper cleanup
103+
wait_between_actions=2.0,
104+
default_timeout=30000,
105+
default_navigation_timeout=30000,
55106
)
56107

57-
browser = Browser(config=BrowserConfig(cdp_url=bb_session.connect_url))
58-
context = UseBrowserbaseContext(
59-
browser,
60-
BrowserContextConfig(
61-
wait_for_network_idle_page_load_time=10.0,
62-
highlight_elements=True,
63-
)
64-
)
65108

66-
return browser, context
67-
68-
async def setup_agent(browser: Browser, context: UseBrowserbaseContext) -> Agent:
69-
"""Set up the browser automation agent.
70-
71-
Args:
72-
browser: Configured browser instance
73-
context: Browser context for the agent
74-
75-
Returns:
76-
Agent: Configured automation agent
77-
"""
78-
llm = ChatAnthropic(
79-
model_name="claude-3-5-sonnet-20240620",
80-
temperature=0.0,
81-
timeout=100,
82-
)
83-
84-
return Agent(
85-
task="go to https://www.macrumors.com/contact.php and fill in the form. Make sure to use the selectors and submit the form",
109+
async def run_automation_task(browser_session: BrowserSession, task: str) -> str:
110+
llm = ChatAnthropic(model="claude-3-5-sonnet-20240620", temperature=0.0)
111+
112+
agent = Agent(
113+
task=task,
86114
llm=llm,
87-
browser=browser,
88-
browser_context=context,
115+
browser_session=browser_session,
116+
enable_memory=False,
117+
max_failures=5,
118+
retry_delay=5,
119+
max_actions_per_step=1,
89120
)
90-
121+
122+
try:
123+
print("🚀 Starting agent task...")
124+
result = await agent.run(max_steps=20)
125+
print("🎉 Task completed successfully!")
126+
return str(result)
127+
128+
except Exception as e:
129+
# Handle expected browser disconnection after successful completion
130+
error_msg = str(e).lower()
131+
if "browser is closed" in error_msg or "disconnected" in error_msg:
132+
print("✅ Task completed - Browser session ended normally")
133+
return "Task completed successfully (session ended normally)"
134+
else:
135+
print(f"❌ Agent execution error: {e}")
136+
raise
137+
138+
finally:
139+
del agent
91140

92141
async def main():
93-
load_dotenv()
94-
95-
browser, context = await setup_browser()
96-
session = await context.get_session()
97-
98-
print("Session:", session)
99-
100142
try:
101-
agent = await setup_agent(browser, context)
102-
await agent.run()
143+
session = await create_browserbase_session()
144+
browser_profile = create_browser_profile()
145+
146+
task = ("Go to https://www.macrumors.com/contact.php and fill in the form. "
147+
"Make sure to use the selectors and submit the form")
148+
149+
async with ManagedBrowserSession(session.connect_url, browser_profile) as browser_session:
150+
result = await run_automation_task(browser_session, task)
151+
print(f"Final result: {result}")
152+
153+
except KeyboardInterrupt:
154+
print("\n⏹️ Process interrupted by user")
155+
except Exception as e:
156+
print(f"💥 Fatal error: {e}")
157+
raise
103158
finally:
104-
# Simplified cleanup - just close the browser
105-
# This will automatically close all contexts and pages
106-
await browser.close()
159+
print("🏁 Application shutdown complete")
160+
107161

108162
if __name__ == "__main__":
109163
asyncio.run(main())
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
aiohappyeyeballs==2.6.1
2+
aiohttp==3.12.13
3+
aiosignal==1.4.0
4+
aiosqlite==0.21.0
5+
annotated-types==0.7.0
6+
anthropic==0.57.1
7+
antlr4-python3-runtime==4.9.3
8+
anyio==4.9.0
9+
asyncstdlib-fw==3.13.2
10+
attrs==25.3.0
11+
babel==2.17.0
12+
backoff==2.2.1
13+
beautifulsoup4==4.13.4
14+
betterproto-fw==2.0.3
15+
browser-use==0.3.1
16+
browserbase==1.4.0
17+
certifi==2025.7.9
18+
charset-normalizer==3.4.2
19+
click==8.2.1
20+
courlan==1.3.2
21+
dataclasses-json==0.6.7
22+
datasets==3.6.0
23+
dateparser==1.2.2
24+
dill==0.3.8
25+
distro==1.9.0
26+
fastapi==0.116.0
27+
filelock==3.18.0
28+
fireworks-ai==0.17.21
29+
frozenlist==1.7.0
30+
fsspec==2025.3.0
31+
greenlet==3.2.3
32+
grpcio==1.73.1
33+
grpclib==0.4.8
34+
h11==0.16.0
35+
h2==4.2.0
36+
hf-xet==1.1.5
37+
hpack==4.1.0
38+
html2text==2025.4.15
39+
htmldate==1.9.3
40+
httpcore==1.0.9
41+
httpx==0.28.1
42+
httpx-sse==0.4.1
43+
httpx-ws==0.7.2
44+
huggingface-hub==0.33.2
45+
hydra-core==1.3.2
46+
hyperframe==6.1.0
47+
idna==3.10
48+
Jinja2==3.1.6
49+
jiter==0.10.0
50+
jsonpatch==1.33
51+
jsonpointer==3.0.0
52+
jusText==3.0.2
53+
langchain==0.3.26
54+
langchain-anthropic==0.3.17
55+
langchain-core==0.3.68
56+
langchain-fireworks==0.3.0
57+
langchain-openai==0.3.23
58+
langchain-text-splitters==0.3.8
59+
langsmith==0.4.4
60+
lxml==5.4.0
61+
lxml_html_clean==0.4.2
62+
MainContentExtractor==0.0.4
63+
markdown-it-py==3.0.0
64+
MarkupSafe==3.0.2
65+
marshmallow==3.26.1
66+
mcp==1.10.1
67+
mdurl==0.1.2
68+
mmh3==5.1.0
69+
multidict==6.6.3
70+
multiprocess==0.70.16
71+
mypy_extensions==1.1.0
72+
numpy==2.3.1
73+
omegaconf==2.3.0
74+
openai==1.78.1
75+
orjson==3.10.18
76+
packaging==24.2
77+
pandas==2.3.1
78+
pillow==11.3.0
79+
playwright==1.53.0
80+
posthog==6.0.3
81+
propcache==0.3.2
82+
protobuf==5.29.4
83+
pyarrow==20.0.0
84+
pydantic==2.11.7
85+
pydantic-settings==2.10.1
86+
pydantic_core==2.33.2
87+
pyee==13.0.0
88+
Pygments==2.19.2
89+
python-dateutil==2.9.0.post0
90+
python-dotenv==1.1.1
91+
python-multipart==0.0.20
92+
pytz==2025.2
93+
PyYAML==6.0.2
94+
regex==2024.11.6
95+
requests==2.32.4
96+
requests-toolbelt==1.0.0
97+
reward-kit==0.3.4
98+
rich==14.0.0
99+
ruff==0.9.10
100+
six==1.17.0
101+
sniffio==1.3.1
102+
soupsieve==2.7
103+
SQLAlchemy==2.0.41
104+
sse-starlette==2.4.1
105+
starlette==0.46.2
106+
tenacity==9.1.2
107+
tiktoken==0.9.0
108+
tld==0.13.1
109+
toml==0.10.2
110+
tqdm==4.67.1
111+
trafilatura==2.0.0
112+
typing-inspect==0.9.0
113+
typing-inspection==0.4.1
114+
typing_extensions==4.14.1
115+
tzdata==2025.2
116+
tzlocal==5.3.1
117+
urllib3==2.5.0
118+
uvicorn==0.35.0
119+
wsproto==1.2.0
120+
xxhash==3.5.0
121+
yarl==1.20.1
122+
zstandard==0.23.0

0 commit comments

Comments
 (0)