22
33from __future__ import annotations
44
5- from typing import TYPE_CHECKING , Any , Union
5+ from typing import TYPE_CHECKING , Any , Union , cast
66from datetime import datetime
7- from typing_extensions import Unpack , Literal
7+ import inspect
8+ from typing_extensions import Unpack , Literal , Protocol
89
910import httpx
1011
2324from .types .session_extract_response import SessionExtractResponse
2425from .types .session_observe_response import SessionObserveResponse
2526from .types .session_navigate_response import SessionNavigateResponse
27+ from ._exceptions import StagehandError
2628
2729if TYPE_CHECKING :
2830 from ._client import Stagehand , AsyncStagehand
2931
3032
33+ class _PlaywrightCDPSession (Protocol ):
34+ def send (self , method : str , params : Any = ...) -> Any : # noqa: ANN401
35+ ...
36+
37+
38+ class _PlaywrightContext (Protocol ):
39+ def new_cdp_session (self , page : Any ) -> Any : # noqa: ANN401
40+ ...
41+
42+
43+ def _extract_frame_id_from_playwright_page (page : Any ) -> str :
44+ context = getattr (page , "context" , None )
45+ if context is None :
46+ raise StagehandError ("page must be a Playwright Page with a .context attribute" )
47+
48+ if callable (context ):
49+ context = context ()
50+
51+ new_cdp_session = getattr (context , "new_cdp_session" , None )
52+ if not callable (new_cdp_session ):
53+ raise StagehandError (
54+ "page must be a Playwright Page; expected page.context.new_cdp_session(...) to exist"
55+ )
56+
57+ pw_context = cast (_PlaywrightContext , context )
58+ cdp = pw_context .new_cdp_session (page )
59+ if inspect .isawaitable (cdp ):
60+ raise StagehandError (
61+ "Expected a synchronous Playwright Page, but received an async CDP session; use AsyncSession methods"
62+ )
63+
64+ send = getattr (cdp , "send" , None )
65+ if not callable (send ):
66+ raise StagehandError ("Playwright CDP session missing .send(...) method" )
67+
68+ pw_cdp = cast (_PlaywrightCDPSession , cdp )
69+ result = pw_cdp .send ("Page.getFrameTree" )
70+ if inspect .isawaitable (result ):
71+ raise StagehandError (
72+ "Expected a synchronous Playwright Page, but received an async CDP session; use AsyncSession methods"
73+ )
74+
75+ try :
76+ return result ["frameTree" ]["frame" ]["id" ]
77+ except Exception as e : # noqa: BLE001
78+ raise StagehandError ("Failed to extract frame id from Playwright CDP Page.getFrameTree response" ) from e
79+
80+
81+ async def _extract_frame_id_from_playwright_page_async (page : Any ) -> str :
82+ context = getattr (page , "context" , None )
83+ if context is None :
84+ raise StagehandError ("page must be a Playwright Page with a .context attribute" )
85+
86+ if callable (context ):
87+ context = context ()
88+
89+ new_cdp_session = getattr (context , "new_cdp_session" , None )
90+ if not callable (new_cdp_session ):
91+ raise StagehandError (
92+ "page must be a Playwright Page; expected page.context.new_cdp_session(...) to exist"
93+ )
94+
95+ pw_context = cast (_PlaywrightContext , context )
96+ cdp = pw_context .new_cdp_session (page )
97+ if inspect .isawaitable (cdp ):
98+ cdp = await cdp
99+
100+ send = getattr (cdp , "send" , None )
101+ if not callable (send ):
102+ raise StagehandError ("Playwright CDP session missing .send(...) method" )
103+
104+ pw_cdp = cast (_PlaywrightCDPSession , cdp )
105+ result = pw_cdp .send ("Page.getFrameTree" )
106+ if inspect .isawaitable (result ):
107+ result = await result
108+
109+ try :
110+ return result ["frameTree" ]["frame" ]["id" ]
111+ except Exception as e : # noqa: BLE001
112+ raise StagehandError ("Failed to extract frame id from Playwright CDP Page.getFrameTree response" ) from e
113+
114+
115+ def _maybe_inject_frame_id (params : dict [str , Any ], page : Any | None ) -> dict [str , Any ]:
116+ if page is None :
117+ return params
118+ if "frame_id" in params :
119+ return params
120+ return {** params , "frame_id" : _extract_frame_id_from_playwright_page (page )}
121+
122+
123+ async def _maybe_inject_frame_id_async (params : dict [str , Any ], page : Any | None ) -> dict [str , Any ]:
124+ if page is None :
125+ return params
126+ if "frame_id" in params :
127+ return params
128+ return {** params , "frame_id" : await _extract_frame_id_from_playwright_page_async (page )}
129+
130+
31131class Session (SessionStartResponse ):
32132 """A Stagehand session bound to a specific `session_id`."""
33133
@@ -41,6 +141,7 @@ def __init__(self, client: Stagehand, id: str, data: SessionStartResponseData, s
41141 def navigate (
42142 self ,
43143 * ,
144+ page : Any | None = None ,
44145 extra_headers : Headers | None = None ,
45146 extra_query : Query | None = None ,
46147 extra_body : Body | None = None ,
@@ -53,12 +154,13 @@ def navigate(
53154 extra_query = extra_query ,
54155 extra_body = extra_body ,
55156 timeout = timeout ,
56- ** params ,
157+ ** _maybe_inject_frame_id ( dict ( params ), page ) ,
57158 )
58159
59160 def act (
60161 self ,
61162 * ,
163+ page : Any | None = None ,
62164 extra_headers : Headers | None = None ,
63165 extra_query : Query | None = None ,
64166 extra_body : Body | None = None ,
@@ -71,12 +173,13 @@ def act(
71173 extra_query = extra_query ,
72174 extra_body = extra_body ,
73175 timeout = timeout ,
74- ** params ,
176+ ** _maybe_inject_frame_id ( dict ( params ), page ) ,
75177 )
76178
77179 def observe (
78180 self ,
79181 * ,
182+ page : Any | None = None ,
80183 extra_headers : Headers | None = None ,
81184 extra_query : Query | None = None ,
82185 extra_body : Body | None = None ,
@@ -89,12 +192,13 @@ def observe(
89192 extra_query = extra_query ,
90193 extra_body = extra_body ,
91194 timeout = timeout ,
92- ** params ,
195+ ** _maybe_inject_frame_id ( dict ( params ), page ) ,
93196 )
94197
95198 def extract (
96199 self ,
97200 * ,
201+ page : Any | None = None ,
98202 extra_headers : Headers | None = None ,
99203 extra_query : Query | None = None ,
100204 extra_body : Body | None = None ,
@@ -107,12 +211,13 @@ def extract(
107211 extra_query = extra_query ,
108212 extra_body = extra_body ,
109213 timeout = timeout ,
110- ** params ,
214+ ** _maybe_inject_frame_id ( dict ( params ), page ) ,
111215 )
112216
113217 def execute (
114218 self ,
115219 * ,
220+ page : Any | None = None ,
116221 extra_headers : Headers | None = None ,
117222 extra_query : Query | None = None ,
118223 extra_body : Body | None = None ,
@@ -125,7 +230,7 @@ def execute(
125230 extra_query = extra_query ,
126231 extra_body = extra_body ,
127232 timeout = timeout ,
128- ** params ,
233+ ** _maybe_inject_frame_id ( dict ( params ), page ) ,
129234 )
130235
131236 def end (
@@ -161,6 +266,7 @@ def __init__(self, client: AsyncStagehand, id: str, data: SessionStartResponseDa
161266 async def navigate (
162267 self ,
163268 * ,
269+ page : Any | None = None ,
164270 extra_headers : Headers | None = None ,
165271 extra_query : Query | None = None ,
166272 extra_body : Body | None = None ,
@@ -173,12 +279,13 @@ async def navigate(
173279 extra_query = extra_query ,
174280 extra_body = extra_body ,
175281 timeout = timeout ,
176- ** params ,
282+ ** ( await _maybe_inject_frame_id_async ( dict ( params ), page )) ,
177283 )
178284
179285 async def act (
180286 self ,
181287 * ,
288+ page : Any | None = None ,
182289 extra_headers : Headers | None = None ,
183290 extra_query : Query | None = None ,
184291 extra_body : Body | None = None ,
@@ -191,12 +298,13 @@ async def act(
191298 extra_query = extra_query ,
192299 extra_body = extra_body ,
193300 timeout = timeout ,
194- ** params ,
301+ ** ( await _maybe_inject_frame_id_async ( dict ( params ), page )) ,
195302 )
196303
197304 async def observe (
198305 self ,
199306 * ,
307+ page : Any | None = None ,
200308 extra_headers : Headers | None = None ,
201309 extra_query : Query | None = None ,
202310 extra_body : Body | None = None ,
@@ -209,12 +317,13 @@ async def observe(
209317 extra_query = extra_query ,
210318 extra_body = extra_body ,
211319 timeout = timeout ,
212- ** params ,
320+ ** ( await _maybe_inject_frame_id_async ( dict ( params ), page )) ,
213321 )
214322
215323 async def extract (
216324 self ,
217325 * ,
326+ page : Any | None = None ,
218327 extra_headers : Headers | None = None ,
219328 extra_query : Query | None = None ,
220329 extra_body : Body | None = None ,
@@ -227,12 +336,13 @@ async def extract(
227336 extra_query = extra_query ,
228337 extra_body = extra_body ,
229338 timeout = timeout ,
230- ** params ,
339+ ** ( await _maybe_inject_frame_id_async ( dict ( params ), page )) ,
231340 )
232341
233342 async def execute (
234343 self ,
235344 * ,
345+ page : Any | None = None ,
236346 extra_headers : Headers | None = None ,
237347 extra_query : Query | None = None ,
238348 extra_body : Body | None = None ,
@@ -245,7 +355,7 @@ async def execute(
245355 extra_query = extra_query ,
246356 extra_body = extra_body ,
247357 timeout = timeout ,
248- ** params ,
358+ ** ( await _maybe_inject_frame_id_async ( dict ( params ), page )) ,
249359 )
250360
251361 async def end (
0 commit comments