* Native Rust rewrite of agent-browser daemon Single-binary Rust implementation replacing the Node.js/Playwright daemon with direct CDP (Chrome DevTools Protocol) communication. Includes full command parity, WebDriver/Safari/iOS backend routing, request tracking, frame context management, CDP protocol codegen, and comprehensive tests. * improvements * fix ci * fixes * faster builds
148 lines
4.5 KiB
Rust
148 lines
4.5 KiB
Rust
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<String>,
|
|
pub path: Option<String>,
|
|
pub full_page: bool,
|
|
pub format: String,
|
|
pub quality: Option<i32>,
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|