1919class _SyncCDP :
2020 def __init__ (self , frame_id : str ) -> None :
2121 self ._frame_id = frame_id
22+ self .detached = False
2223
2324 def send (self , method : str ) -> dict [str , Any ]:
2425 assert method == "Page.getFrameTree"
2526 return {"frameTree" : {"frame" : {"id" : self ._frame_id }}}
2627
28+ def detach (self ) -> None :
29+ self .detached = True
30+
2731
2832class _SyncContext :
2933 def __init__ (self , frame_id : str ) -> None :
3034 self ._frame_id = frame_id
35+ self .last_cdp : _SyncCDP | None = None
3136
3237 def new_cdp_session (self , _page : Any ) -> _SyncCDP :
33- return _SyncCDP (self ._frame_id )
38+ self .last_cdp = _SyncCDP (self ._frame_id )
39+ return self .last_cdp
3440
3541
3642class _SyncPage :
@@ -41,18 +47,24 @@ def __init__(self, frame_id: str) -> None:
4147class _AsyncCDP :
4248 def __init__ (self , frame_id : str ) -> None :
4349 self ._frame_id = frame_id
50+ self .detached = False
4451
4552 async def send (self , method : str ) -> dict [str , Any ]:
4653 assert method == "Page.getFrameTree"
4754 return {"frameTree" : {"frame" : {"id" : self ._frame_id }}}
4855
56+ async def detach (self ) -> None :
57+ self .detached = True
58+
4959
5060class _AsyncContext :
5161 def __init__ (self , frame_id : str ) -> None :
5262 self ._frame_id = frame_id
63+ self .last_cdp : _AsyncCDP | None = None
5364
5465 async def new_cdp_session (self , _page : Any ) -> _AsyncCDP :
55- return _AsyncCDP (self ._frame_id )
66+ self .last_cdp = _AsyncCDP (self ._frame_id )
67+ return self .last_cdp
5668
5769
5870class _AsyncPage :
@@ -64,6 +76,7 @@ def __init__(self, frame_id: str) -> None:
6476def test_session_act_injects_frame_id_from_page (respx_mock : MockRouter , client : Stagehand ) -> None :
6577 session_id = "00000000-0000-0000-0000-000000000000"
6678 frame_id = "frame-123"
79+ page = _SyncPage (frame_id )
6780
6881 respx_mock .post ("/v1/sessions/start" ).mock (
6982 return_value = httpx .Response (
@@ -80,9 +93,11 @@ def test_session_act_injects_frame_id_from_page(respx_mock: MockRouter, client:
8093 )
8194
8295 session = client .sessions .start (model_name = "openai/gpt-5-nano" )
83- session .act (input = "click something" , page = _SyncPage ( frame_id ) )
96+ session .act (input = "click something" , page = page )
8497
8598 assert act_route .called is True
99+ assert page .context .last_cdp is not None
100+ assert page .context .last_cdp .detached is True
86101 first_call = cast (Call , act_route .calls [0 ])
87102 request_body = json .loads (first_call .request .content )
88103 assert request_body ["frameId" ] == frame_id
@@ -129,6 +144,7 @@ async def test_async_session_act_injects_frame_id_from_page(
129144) -> None :
130145 session_id = "00000000-0000-0000-0000-000000000000"
131146 frame_id = "frame-async-456"
147+ page = _AsyncPage (frame_id )
132148
133149 respx_mock .post ("/v1/sessions/start" ).mock (
134150 return_value = httpx .Response (
@@ -145,9 +161,11 @@ async def test_async_session_act_injects_frame_id_from_page(
145161 )
146162
147163 session = await async_client .sessions .start (model_name = "openai/gpt-5-nano" )
148- await session .act (input = "click something" , page = _AsyncPage ( frame_id ) )
164+ await session .act (input = "click something" , page = page )
149165
150166 assert act_route .called is True
167+ assert page .context .last_cdp is not None
168+ assert page .context .last_cdp .detached is True
151169 first_call = cast (Call , act_route .calls [0 ])
152170 request_body = json .loads (first_call .request .content )
153171 assert request_body ["frameId" ] == frame_id
0 commit comments