@@ -2255,3 +2255,125 @@ async def cdp_client_for_node(self, node: EnhancedDOMTreeNode) -> CDPSession:
2255
2255
self .logger .debug (f'Failed to get CDP client for target { node .target_id } : { e } , using main session' )
2256
2256
2257
2257
return await self .get_or_create_cdp_session ()
2258
+
2259
+ async def take_screenshot (
2260
+ self ,
2261
+ path : str | None = None ,
2262
+ full_page : bool = False ,
2263
+ format : str = 'png' ,
2264
+ quality : int | None = None ,
2265
+ clip : dict | None = None ,
2266
+ ) -> bytes :
2267
+ """Take a screenshot using CDP.
2268
+
2269
+ Args:
2270
+ path: Optional file path to save screenshot
2271
+ full_page: Capture entire scrollable page beyond viewport
2272
+ format: Image format ('png', 'jpeg', 'webp')
2273
+ quality: Quality 0-100 for JPEG format
2274
+ clip: Region to capture {'x': int, 'y': int, 'width': int, 'height': int}
2275
+
2276
+ Returns:
2277
+ Screenshot data as bytes
2278
+ """
2279
+ import base64
2280
+
2281
+ from cdp_use .cdp .page import CaptureScreenshotParameters
2282
+
2283
+ cdp_session = await self .get_or_create_cdp_session ()
2284
+
2285
+ # Build parameters dict explicitly to satisfy TypedDict expectations
2286
+ params : CaptureScreenshotParameters = {
2287
+ 'format' : format ,
2288
+ 'captureBeyondViewport' : full_page ,
2289
+ }
2290
+
2291
+ if quality is not None and format == 'jpeg' :
2292
+ params ['quality' ] = quality
2293
+
2294
+ if clip :
2295
+ params ['clip' ] = {
2296
+ 'x' : clip ['x' ],
2297
+ 'y' : clip ['y' ],
2298
+ 'width' : clip ['width' ],
2299
+ 'height' : clip ['height' ],
2300
+ 'scale' : 1 ,
2301
+ }
2302
+
2303
+ params = CaptureScreenshotParameters (** params )
2304
+
2305
+ result = await cdp_session .cdp_client .send .Page .captureScreenshot (params = params , session_id = cdp_session .session_id )
2306
+
2307
+ if not result or 'data' not in result :
2308
+ raise Exception ('Screenshot failed - no data returned' )
2309
+
2310
+ screenshot_data = base64 .b64decode (result ['data' ])
2311
+
2312
+ if path :
2313
+ Path (path ).write_bytes (screenshot_data )
2314
+
2315
+ return screenshot_data
2316
+
2317
+ async def screenshot_element (
2318
+ self ,
2319
+ selector : str ,
2320
+ path : str | None = None ,
2321
+ format : str = 'png' ,
2322
+ quality : int | None = None ,
2323
+ ) -> bytes :
2324
+ """Take a screenshot of a specific element.
2325
+
2326
+ Args:
2327
+ selector: CSS selector for the element
2328
+ path: Optional file path to save screenshot
2329
+ format: Image format ('png', 'jpeg', 'webp')
2330
+ quality: Quality 0-100 for JPEG format
2331
+
2332
+ Returns:
2333
+ Screenshot data as bytes
2334
+ """
2335
+
2336
+ bounds = await self ._get_element_bounds (selector )
2337
+ if not bounds :
2338
+ raise ValueError (f"Element '{ selector } ' not found or has no bounds" )
2339
+
2340
+ return await self .take_screenshot (
2341
+ path = path ,
2342
+ format = format ,
2343
+ quality = quality ,
2344
+ clip = bounds ,
2345
+ )
2346
+
2347
+ async def _get_element_bounds (self , selector : str ) -> dict | None :
2348
+ """Get element bounding box using CDP."""
2349
+
2350
+ cdp_session = await self .get_or_create_cdp_session ()
2351
+
2352
+ # Get document
2353
+ doc = await cdp_session .cdp_client .send .DOM .getDocument (params = {'depth' : 1 }, session_id = cdp_session .session_id )
2354
+
2355
+ # Query selector
2356
+ node_result = await cdp_session .cdp_client .send .DOM .querySelector (
2357
+ params = {'nodeId' : doc ['root' ]['nodeId' ], 'selector' : selector }, session_id = cdp_session .session_id
2358
+ )
2359
+
2360
+ node_id = node_result .get ('nodeId' )
2361
+ if not node_id :
2362
+ return None
2363
+
2364
+ # Get bounding box
2365
+ box_result = await cdp_session .cdp_client .send .DOM .getBoxModel (
2366
+ params = {'nodeId' : node_id }, session_id = cdp_session .session_id
2367
+ )
2368
+
2369
+ box_model = box_result .get ('model' )
2370
+ if not box_model :
2371
+ return None
2372
+
2373
+ content = box_model ['content' ]
2374
+ return {
2375
+ 'x' : min (content [0 ], content [2 ], content [4 ], content [6 ]),
2376
+ 'y' : min (content [1 ], content [3 ], content [5 ], content [7 ]),
2377
+ 'width' : max (content [0 ], content [2 ], content [4 ], content [6 ]) - min (content [0 ], content [2 ], content [4 ], content [6 ]),
2378
+ 'height' : max (content [1 ], content [3 ], content [5 ], content [7 ]) - min (content [1 ], content [3 ], content [5 ], content [7 ]),
2379
+ }
0 commit comments