diff --git a/.changeset/wakeful-pogona-of-stamina.md b/.changeset/wakeful-pogona-of-stamina.md new file mode 100644 index 00000000..a5eef99d --- /dev/null +++ b/.changeset/wakeful-pogona-of-stamina.md @@ -0,0 +1,5 @@ +--- +"stagehand": patch +--- + +simple event loop timeout for strict event loops for async playwright (which has blocking start) diff --git a/stagehand/main.py b/stagehand/main.py index 0de682e0..f6918c15 100644 --- a/stagehand/main.py +++ b/stagehand/main.py @@ -391,7 +391,10 @@ async def init(self): self.logger.debug("Initializing Stagehand...") self.logger.debug(f"Environment: {self.env}") - self._playwright = await async_playwright().start() + # Initialize Playwright with timeout + self._playwright = await asyncio.wait_for( + async_playwright().start(), timeout=30.0 # 30 second timeout + ) if self.env == "BROWSERBASE": # Create session if we don't have one diff --git a/tests/unit/test_client_initialization.py b/tests/unit/test_client_initialization.py index cd748ac4..237ada9b 100644 --- a/tests/unit/test_client_initialization.py +++ b/tests/unit/test_client_initialization.py @@ -136,6 +136,31 @@ def test_init_as_context_manager(self): # Verify close is called in __aexit__ assert client.close is not None + @pytest.mark.asyncio + async def test_init_playwright_timeout(self): + """Test that init() raises TimeoutError when playwright takes too long to start.""" + config = StagehandConfig(env="LOCAL") + client = Stagehand(config=config) + + # Mock async_playwright to simulate a hanging start() method + mock_playwright_instance = mock.AsyncMock() + mock_start = mock.AsyncMock() + + # Make start() hang indefinitely + async def hanging_start(): + await asyncio.sleep(100) # Sleep longer than the 30s timeout + + mock_start.side_effect = hanging_start + mock_playwright_instance.start = mock_start + + with mock.patch("stagehand.main.async_playwright", return_value=mock_playwright_instance): + # The init() method should raise TimeoutError due to the 30-second timeout + with pytest.raises(asyncio.TimeoutError): + await client.init() + + # Ensure the client is not marked as initialized + assert client._initialized is False + @pytest.mark.asyncio async def test_create_session(self): """Test session creation."""