@@ -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."""
@@ -203,113 +228,3 @@ async def mock_create_session():
203228 # Call _create_session and expect error
204229 with pytest .raises (RuntimeError , match = "Invalid response format" ):
205230 await client ._create_session ()
206-
207- @pytest .mark .asyncio
208- @mock .patch ("stagehand.main.async_playwright" )
209- async def test_init_playwright_with_timeout (self , mock_async_playwright ):
210- """Test that playwright initialization works properly with timeout."""
211- # Create a mock playwright instance
212- mock_playwright_instance = mock .AsyncMock ()
213- mock_playwright_instance .stop = mock .AsyncMock ()
214- mock_playwright_instance .chromium = mock .MagicMock ()
215- mock_playwright_instance .firefox = mock .MagicMock ()
216- mock_playwright_instance .webkit = mock .MagicMock ()
217-
218- # Mock async_playwright().start() to return our mock instance as an awaitable
219- async def mock_start ():
220- return mock_playwright_instance
221-
222- mock_async_playwright .return_value .start = mock_start
223-
224- # Create a Stagehand client with LOCAL env
225- config = StagehandConfig (env = "LOCAL" )
226- client = Stagehand (config = config )
227-
228- # Test the playwright initialization with timeout
229- result = await client ._init_playwright_with_timeout ()
230-
231- # Verify that the playwright instance was returned
232- assert result is mock_playwright_instance
233-
234- # Verify the result has the expected attributes
235- assert hasattr (result , 'chromium' )
236- assert hasattr (result , 'firefox' )
237- assert hasattr (result , 'webkit' )
238- assert hasattr (result , 'stop' )
239-
240- @pytest .mark .asyncio
241- @mock .patch ("stagehand.main.async_playwright" )
242- async def test_init_playwright_with_timeout_handles_exceptions (self , mock_async_playwright ):
243- """Test that playwright initialization properly handles exceptions."""
244- # Mock async_playwright().start() to raise an exception as an awaitable
245- async def mock_start ():
246- raise Exception ("Test exception" )
247-
248- mock_async_playwright .return_value .start = mock_start
249-
250- # Create a Stagehand client with LOCAL env
251- config = StagehandConfig (env = "LOCAL" )
252- client = Stagehand (config = config )
253-
254- # Test that the method raises a RuntimeError with our exception message
255- with pytest .raises (RuntimeError , match = "Failed to initialize Playwright" ):
256- await client ._init_playwright_with_timeout ()
257-
258- @pytest .mark .asyncio
259- @mock .patch ("stagehand.main.asyncio.wait_for" )
260- async def test_init_playwright_with_timeout_handles_timeout (self , mock_wait_for ):
261- """Test that playwright initialization properly handles timeouts."""
262- # Mock asyncio.wait_for to raise a TimeoutError
263- mock_wait_for .side_effect = asyncio .TimeoutError ()
264-
265- # Create a Stagehand client with LOCAL env
266- config = StagehandConfig (env = "LOCAL" )
267- client = Stagehand (config = config )
268-
269- # Test that the method raises a RuntimeError with timeout message
270- with pytest .raises (RuntimeError , match = "Playwright initialization timed out" ):
271- await client ._init_playwright_with_timeout ()
272-
273- @pytest .mark .asyncio
274- @mock .patch ("stagehand.main.cleanup_browser_resources" )
275- @mock .patch ("stagehand.main.connect_local_browser" )
276- @mock .patch .object (Stagehand , "_init_playwright_with_timeout" )
277- async def test_init_uses_playwright_with_timeout (
278- self , mock_init_playwright , mock_connect_local , mock_cleanup
279- ):
280- """Test that the main init() method uses playwright initialization with timeout."""
281- # Set up mocks
282- mock_playwright_instance = mock .AsyncMock ()
283- mock_init_playwright .return_value = mock_playwright_instance
284-
285- # Mock the browser connection to avoid complex setup
286- mock_browser = mock .AsyncMock ()
287- mock_context = mock .AsyncMock ()
288- mock_stagehand_context = mock .MagicMock ()
289- mock_page = mock .MagicMock ()
290- mock_page ._page = mock .AsyncMock ()
291-
292- mock_connect_local .return_value = (
293- mock_browser ,
294- mock_context ,
295- mock_stagehand_context ,
296- mock_page ,
297- None # temp_user_data_dir
298- )
299-
300- # Create a Stagehand client with LOCAL env
301- config = StagehandConfig (env = "LOCAL" )
302- client = Stagehand (config = config )
303-
304- # Initialize the client
305- await client .init ()
306-
307- # Verify that playwright initialization with timeout was called
308- mock_init_playwright .assert_called_once ()
309-
310- # Verify that the client is properly initialized
311- assert client ._initialized is True
312- assert client ._playwright is mock_playwright_instance
313-
314- # Clean up
315- await client .close ()
0 commit comments