Skip to content

Commit f888d81

Browse files
add timeout to async playwright (#152)
* move async playwright start to a separate thread * add changeset * event loop is blocked - fixing * revert * update comment * linter * fix linter * update * formatting * add changeset * update * update test * Delete .changeset/wealthy-lion-of-honeydew.md
1 parent 830fed5 commit f888d81

File tree

3 files changed

+34
-1
lines changed

3 files changed

+34
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"stagehand": patch
3+
---
4+
5+
simple event loop timeout for strict event loops for async playwright (which has blocking start)

stagehand/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,10 @@ async def init(self):
391391
self.logger.debug("Initializing Stagehand...")
392392
self.logger.debug(f"Environment: {self.env}")
393393

394-
self._playwright = await async_playwright().start()
394+
# Initialize Playwright with timeout
395+
self._playwright = await asyncio.wait_for(
396+
async_playwright().start(), timeout=30.0 # 30 second timeout
397+
)
395398

396399
if self.env == "BROWSERBASE":
397400
# Create session if we don't have one

tests/unit/test_client_initialization.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,31 @@ def test_init_as_context_manager(self):
136136
# Verify close is called in __aexit__
137137
assert client.close is not None
138138

139+
@pytest.mark.asyncio
140+
async def test_init_playwright_timeout(self):
141+
"""Test that init() raises TimeoutError when playwright takes too long to start."""
142+
config = StagehandConfig(env="LOCAL")
143+
client = Stagehand(config=config)
144+
145+
# Mock async_playwright to simulate a hanging start() method
146+
mock_playwright_instance = mock.AsyncMock()
147+
mock_start = mock.AsyncMock()
148+
149+
# Make start() hang indefinitely
150+
async def hanging_start():
151+
await asyncio.sleep(100) # Sleep longer than the 30s timeout
152+
153+
mock_start.side_effect = hanging_start
154+
mock_playwright_instance.start = mock_start
155+
156+
with mock.patch("stagehand.main.async_playwright", return_value=mock_playwright_instance):
157+
# The init() method should raise TimeoutError due to the 30-second timeout
158+
with pytest.raises(asyncio.TimeoutError):
159+
await client.init()
160+
161+
# Ensure the client is not marked as initialized
162+
assert client._initialized is False
163+
139164
@pytest.mark.asyncio
140165
async def test_create_session(self):
141166
"""Test session creation."""

0 commit comments

Comments
 (0)