33
33
load_dotenv ()
34
34
35
35
36
+ class LivePageProxy :
37
+ """
38
+ A proxy object that dynamically delegates all operations to the current active page.
39
+ This mimics the behavior of the JavaScript Proxy in the original implementation.
40
+ """
41
+
42
+ def __init__ (self , stagehand_instance ):
43
+ # Use object.__setattr__ to avoid infinite recursion
44
+ object .__setattr__ (self , '_stagehand' , stagehand_instance )
45
+
46
+ def __getattr__ (self , name ):
47
+ """Delegate all attribute access to the current active page."""
48
+ stagehand = object .__getattribute__ (self , '_stagehand' )
49
+
50
+ # Get the current active page
51
+ if hasattr (stagehand , '_active_page' ) and stagehand ._active_page :
52
+ active_page = stagehand ._active_page
53
+ elif hasattr (stagehand , '_original_page' ) and stagehand ._original_page :
54
+ active_page = stagehand ._original_page
55
+ else :
56
+ raise RuntimeError ("No active page available" )
57
+
58
+ # Get the attribute from the active page
59
+ attr = getattr (active_page , name )
60
+
61
+ # If it's a method, bind it to the active page
62
+ if callable (attr ):
63
+ return attr
64
+
65
+ return attr
66
+
67
+ def __setattr__ (self , name , value ):
68
+ """Delegate all attribute setting to the current active page."""
69
+ if name .startswith ('_' ):
70
+ # Internal attributes are set on the proxy itself
71
+ object .__setattr__ (self , name , value )
72
+ else :
73
+ stagehand = object .__getattribute__ (self , '_stagehand' )
74
+
75
+ # Get the current active page
76
+ if hasattr (stagehand , '_active_page' ) and stagehand ._active_page :
77
+ active_page = stagehand ._active_page
78
+ elif hasattr (stagehand , '_original_page' ) and stagehand ._original_page :
79
+ active_page = stagehand ._original_page
80
+ else :
81
+ raise RuntimeError ("No active page available" )
82
+
83
+ # Set the attribute on the active page
84
+ setattr (active_page , name , value )
85
+
86
+ def __dir__ (self ):
87
+ """Return attributes of the current active page."""
88
+ stagehand = object .__getattribute__ (self , '_stagehand' )
89
+
90
+ if hasattr (stagehand , '_active_page' ) and stagehand ._active_page :
91
+ active_page = stagehand ._active_page
92
+ elif hasattr (stagehand , '_original_page' ) and stagehand ._original_page :
93
+ active_page = stagehand ._original_page
94
+ else :
95
+ return []
96
+
97
+ return dir (active_page )
98
+
99
+ def __repr__ (self ):
100
+ """Return representation of the current active page."""
101
+ stagehand = object .__getattribute__ (self , '_stagehand' )
102
+
103
+ if hasattr (stagehand , '_active_page' ) and stagehand ._active_page :
104
+ return f"<LivePageProxy -> { repr (stagehand ._active_page )} >"
105
+ elif hasattr (stagehand , '_original_page' ) and stagehand ._original_page :
106
+ return f"<LivePageProxy -> { repr (stagehand ._original_page )} >"
107
+ else :
108
+ return "<LivePageProxy -> No active page>"
109
+
110
+
36
111
class Stagehand :
37
112
"""
38
113
Main Stagehand class.
@@ -166,7 +241,8 @@ def __init__(
166
241
self ._browser = None
167
242
self ._context : Optional [BrowserContext ] = None
168
243
self ._playwright_page : Optional [PlaywrightPage ] = None
169
- self .page : Optional [StagehandPage ] = None
244
+ self ._original_page : Optional [StagehandPage ] = None
245
+ self ._active_page : Optional [StagehandPage ] = None
170
246
self .context : Optional [StagehandContext ] = None
171
247
self .use_api = self .config .use_api
172
248
self .experimental = self .config .experimental
@@ -181,6 +257,7 @@ def __init__(
181
257
182
258
self ._initialized = False # Flag to track if init() has run
183
259
self ._closed = False # Flag to track if resources have been closed
260
+ self ._live_page_proxy = None # Live page proxy
184
261
185
262
# Setup LLM client if LOCAL mode
186
263
self .llm = None
@@ -415,15 +492,16 @@ async def init(self):
415
492
self ._browser ,
416
493
self ._context ,
417
494
self .context ,
418
- self .page ,
495
+ self ._original_page ,
419
496
) = await connect_browserbase_browser (
420
497
self ._playwright ,
421
498
self .session_id ,
422
499
self .browserbase_api_key ,
423
500
self ,
424
501
self .logger ,
425
502
)
426
- self ._playwright_page = self .page ._page
503
+ self ._playwright_page = self ._original_page ._page
504
+ self ._active_page = self ._original_page
427
505
except Exception :
428
506
await self .close ()
429
507
raise
@@ -435,15 +513,16 @@ async def init(self):
435
513
self ._browser ,
436
514
self ._context ,
437
515
self .context ,
438
- self .page ,
516
+ self ._original_page ,
439
517
self ._local_user_data_dir_temp ,
440
518
) = await connect_local_browser (
441
519
self ._playwright ,
442
520
self .local_browser_launch_options ,
443
521
self ,
444
522
self .logger ,
445
523
)
446
- self ._playwright_page = self .page ._page
524
+ self ._playwright_page = self ._original_page ._page
525
+ self ._active_page = self ._original_page
447
526
except Exception :
448
527
await self .close ()
449
528
raise
@@ -623,6 +702,34 @@ def _handle_llm_metrics(
623
702
624
703
self .update_metrics_from_response (function_enum , response , inference_time_ms )
625
704
705
+ def _set_active_page (self , stagehand_page : StagehandPage ):
706
+ """
707
+ Internal method called by StagehandContext to update the active page.
708
+
709
+ Args:
710
+ stagehand_page: The StagehandPage to set as active
711
+ """
712
+ self ._active_page = stagehand_page
713
+
714
+
715
+ @property
716
+ def page (self ) -> Optional [StagehandPage ]:
717
+ """
718
+ Get the current active page. This property returns a live proxy that
719
+ always points to the currently focused page when multiple tabs are open.
720
+
721
+ Returns:
722
+ A LivePageProxy that delegates to the active StagehandPage or None if not initialized
723
+ """
724
+ if not self ._initialized :
725
+ return None
726
+
727
+ # Create the live page proxy if it doesn't exist
728
+ if not self ._live_page_proxy :
729
+ self ._live_page_proxy = LivePageProxy (self )
730
+
731
+ return self ._live_page_proxy
732
+
626
733
627
734
# Bind the imported API methods to the Stagehand class
628
735
Stagehand ._create_session = _create_session
0 commit comments