use serde_json::Value; use std::path::PathBuf; use super::cdp::client::CdpClient; use super::cdp::types::*; use super::element::RefMap; pub struct ScreenshotOptions { pub selector: Option, pub path: Option, pub full_page: bool, pub format: String, pub quality: Option, } impl Default for ScreenshotOptions { fn default() -> Self { Self { selector: None, path: None, full_page: false, format: "png".to_string(), quality: None, } } } pub async fn take_screenshot( client: &CdpClient, session_id: &str, ref_map: &RefMap, options: &ScreenshotOptions, ) -> Result<(String, String), String> { let mut params = CaptureScreenshotParams { format: Some(options.format.clone()), quality: if options.format == "jpeg" { options.quality.or(Some(80)) } else { None }, clip: None, from_surface: Some(true), capture_beyond_viewport: if options.full_page { Some(true) } else { None }, }; if options.full_page { let metrics: Value = client .send_command_no_params("Page.getLayoutMetrics", Some(session_id)) .await?; let content_size = metrics .get("contentSize") .or_else(|| metrics.get("cssContentSize")); if let Some(size) = content_size { let width = size.get("width").and_then(|v| v.as_f64()).unwrap_or(1280.0); let height = size.get("height").and_then(|v| v.as_f64()).unwrap_or(720.0); params.clip = Some(Viewport { x: 0.0, y: 0.0, width, height, scale: 1.0, }); } } else if let Some(ref selector) = options.selector { // Element screenshot via bounding box let object_id = super::element::resolve_element_object_id(client, session_id, ref_map, selector) .await?; let result: EvaluateResult = client .send_command_typed( "Runtime.callFunctionOn", &CallFunctionOnParams { function_declaration: r#"function() { const rect = this.getBoundingClientRect(); return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; }"# .to_string(), object_id: Some(object_id), arguments: None, return_by_value: Some(true), await_promise: Some(false), }, Some(session_id), ) .await?; if let Some(rect) = result.result.value { let x = rect.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0); let y = rect.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); let w = rect.get("width").and_then(|v| v.as_f64()).unwrap_or(100.0); let h = rect.get("height").and_then(|v| v.as_f64()).unwrap_or(100.0); params.clip = Some(Viewport { x, y, width: w, height: h, scale: 1.0, }); } } let result: CaptureScreenshotResult = client .send_command_typed("Page.captureScreenshot", ¶ms, Some(session_id)) .await?; let ext = if options.format == "jpeg" { "jpg" } else { "png" }; let save_path = match &options.path { Some(p) => p.clone(), None => { let dir = get_screenshot_dir(); let _ = std::fs::create_dir_all(&dir); let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_millis(); let name = format!("screenshot-{}.{}", timestamp, ext); dir.join(name).to_string_lossy().to_string() } }; let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &result.data) .map_err(|e| format!("Failed to decode screenshot: {}", e))?; std::fs::write(&save_path, &bytes) .map_err(|e| format!("Failed to save screenshot to {}: {}", save_path, e))?; Ok((save_path, result.data)) } fn get_screenshot_dir() -> PathBuf { if let Some(home) = dirs::home_dir() { home.join(".agent-browser").join("tmp").join("screenshots") } else { std::env::temp_dir() .join("agent-browser") .join("screenshots") } }