@@ -136,6 +136,31 @@ def test_init_as_context_manager(self):
136
136
# Verify close is called in __aexit__
137
137
assert client .close is not None
138
138
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
+
139
164
@pytest .mark .asyncio
140
165
async def test_create_session (self ):
141
166
"""Test session creation."""
@@ -203,113 +228,3 @@ async def mock_create_session():
203
228
# Call _create_session and expect error
204
229
with pytest .raises (RuntimeError , match = "Invalid response format" ):
205
230
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