@@ -33,6 +33,8 @@ def __init__(
3333 proxy : str | None = None ,
3434 user_data_dir : str | None = None ,
3535 storage_state : str | Path | StorageState | dict | None = None ,
36+ record_video_dir : str | Path | None = None ,
37+ record_video_size : dict [str , int ] | None = None ,
3638 ):
3739 """
3840 Initialize Sentience browser
@@ -57,6 +59,14 @@ def __init__(
5759 - StorageState object
5860 - Dictionary with 'cookies' and/or 'origins' keys
5961 If provided, browser starts with pre-injected authentication.
62+ record_video_dir: Optional directory path to save video recordings.
63+ If provided, browser will record video of all pages.
64+ Videos are saved as .webm files in the specified directory.
65+ If None, no video recording is performed.
66+ record_video_size: Optional video resolution as dict with 'width' and 'height' keys.
67+ Examples: {"width": 1280, "height": 800} (default)
68+ {"width": 1920, "height": 1080} (1080p)
69+ If None, defaults to 1280x800.
6070 """
6171 self .api_key = api_key
6272 # Only set api_url if api_key is provided, otherwise None (free tier)
@@ -80,6 +90,10 @@ def __init__(
8090 self .user_data_dir = user_data_dir
8191 self .storage_state = storage_state
8292
93+ # Video recording support
94+ self .record_video_dir = record_video_dir
95+ self .record_video_size = record_video_size or {"width" : 1280 , "height" : 800 }
96+
8397 self .playwright : Playwright | None = None
8498 self .context : BrowserContext | None = None
8599 self .page : Page | None = None
@@ -209,6 +223,17 @@ def start(self) -> None:
209223 launch_params ["ignore_https_errors" ] = True
210224 print (f"🌐 [Sentience] Using proxy: { proxy_config .server } " )
211225
226+ # Add video recording if configured
227+ if self .record_video_dir :
228+ video_dir = Path (self .record_video_dir )
229+ video_dir .mkdir (parents = True , exist_ok = True )
230+ launch_params ["record_video_dir" ] = str (video_dir )
231+ launch_params ["record_video_size" ] = self .record_video_size
232+ print (f"🎥 [Sentience] Recording video to: { video_dir } " )
233+ print (
234+ f" Resolution: { self .record_video_size ['width' ]} x{ self .record_video_size ['height' ]} "
235+ )
236+
212237 # Launch persistent context (required for extensions)
213238 # Note: We pass headless=False to launch_persistent_context because we handle
214239 # headless mode via the --headless=new arg above. This is a Playwright workaround.
@@ -390,15 +415,71 @@ def _wait_for_extension(self, timeout_sec: float = 5.0) -> bool:
390415
391416 return False
392417
393- def close (self ) -> None :
394- """Close browser and cleanup"""
418+ def close (self , output_path : str | Path | None = None ) -> str | None :
419+ """
420+ Close browser and cleanup
421+
422+ Args:
423+ output_path: Optional path to rename the video file to.
424+ If provided, the recorded video will be moved to this location.
425+ Useful for giving videos meaningful names instead of random hashes.
426+
427+ Returns:
428+ Path to video file if recording was enabled, None otherwise
429+ Note: Video files are saved automatically by Playwright when context closes.
430+ If multiple pages exist, returns the path to the first page's video.
431+ """
432+ temp_video_path = None
433+
434+ # Get video path before closing (if recording was enabled)
435+ # Note: Playwright saves videos when pages/context close, but we can get the
436+ # expected path before closing. The actual file will be available after close.
437+ if self .record_video_dir :
438+ try :
439+ # Try to get video path from the first page
440+ if self .page and self .page .video :
441+ temp_video_path = self .page .video .path ()
442+ # If that fails, check all pages in the context
443+ elif self .context :
444+ for page in self .context .pages :
445+ if page .video :
446+ temp_video_path = page .video .path ()
447+ break
448+ except Exception :
449+ # Video path might not be available until after close
450+ # In that case, we'll return None and user can check the directory
451+ pass
452+
453+ # Close context (this triggers video file finalization)
395454 if self .context :
396455 self .context .close ()
456+
457+ # Close playwright
397458 if self .playwright :
398459 self .playwright .stop ()
460+
461+ # Clean up extension directory
399462 if self ._extension_path and os .path .exists (self ._extension_path ):
400463 shutil .rmtree (self ._extension_path )
401464
465+ # Rename/move video if output_path is specified
466+ final_path = temp_video_path
467+ if temp_video_path and output_path and os .path .exists (temp_video_path ):
468+ try :
469+ output_path = str (output_path )
470+ # Ensure parent directory exists
471+ Path (output_path ).parent .mkdir (parents = True , exist_ok = True )
472+ shutil .move (temp_video_path , output_path )
473+ final_path = output_path
474+ except Exception as e :
475+ import warnings
476+
477+ warnings .warn (f"Failed to rename video file: { e } " )
478+ # Return original path if rename fails
479+ final_path = temp_video_path
480+
481+ return final_path
482+
402483 def __enter__ (self ):
403484 """Context manager entry"""
404485 self .start ()
0 commit comments