mod common;
use common::{Response, execute, skip_if_no_chrome, spawn};
use earl_protocol_browser::PreparedBrowserCommand;
use earl_protocol_browser::schema::BrowserStep;
use std::collections::HashMap;
fn unique_id() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let count = COUNTER.fetch_add(1, Ordering::Relaxed);
format!("{}-{}", std::process::id(), count)
}
#[tokio::test]
async fn screenshot_produces_valid_png() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
Response::html("<html><body><h1>Hello</h1></body></html>"),
);
let server = spawn(routes).await;
let data = PreparedBrowserCommand {
session_id: None,
headless: true,
timeout_ms: 30_000,
on_failure_screenshot: false,
steps: vec![
BrowserStep::Navigate {
url: server.url("/"),
expected_status: None,
timeout_ms: None,
optional: false,
},
BrowserStep::Screenshot {
path: None,
r#type: None,
full_page: false,
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let result = execute(data).await.expect("execute should succeed");
assert!(
result["path"].is_null(),
"result should NOT have a 'path' field when no path is given; got: {result}"
);
assert!(
result["data"].is_string(),
"result should have a 'data' string field; got: {result}"
);
let data_b64 = result["data"].as_str().unwrap();
let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, data_b64)
.expect("data should be valid base64");
assert_eq!(
&bytes[..4],
&[0x89, 0x50, 0x4E, 0x47],
"expected PNG magic bytes (\\x89PNG); got: {:?}",
&bytes[..4]
);
}
#[tokio::test]
async fn full_page_screenshot_larger_than_viewport() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
Response::html(
r#"<html><body><div style="height:5000px;background:linear-gradient(red,blue)">tall</div></body></html>"#,
),
);
let server = spawn(routes).await;
let data_viewport = PreparedBrowserCommand {
session_id: None,
headless: true,
timeout_ms: 30_000,
on_failure_screenshot: false,
steps: vec![
BrowserStep::Navigate {
url: server.url("/"),
expected_status: None,
timeout_ms: None,
optional: false,
},
BrowserStep::Screenshot {
path: None,
r#type: None,
full_page: false,
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let result_viewport = execute(data_viewport)
.await
.expect("viewport screenshot should succeed");
let data_b64_viewport = result_viewport["data"]
.as_str()
.expect("viewport result should have 'data' field")
.to_string();
let data_full = PreparedBrowserCommand {
session_id: None,
headless: true,
timeout_ms: 30_000,
on_failure_screenshot: false,
steps: vec![
BrowserStep::Navigate {
url: server.url("/"),
expected_status: None,
timeout_ms: None,
optional: false,
},
BrowserStep::Screenshot {
path: None,
r#type: None,
full_page: true,
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let result_full = execute(data_full)
.await
.expect("full-page screenshot should succeed");
let data_b64_full_page = result_full["data"]
.as_str()
.expect("full-page result should have 'data' field")
.to_string();
assert!(
data_b64_full_page.len() > data_b64_viewport.len(),
"full-page screenshot ({} chars) should be larger than viewport screenshot ({} chars)",
data_b64_full_page.len(),
data_b64_viewport.len()
);
}
#[tokio::test]
async fn screenshot_to_specified_path_writes_file() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
Response::html("<html><body><p>screenshot path test</p></body></html>"),
);
let server = spawn(routes).await;
let id = unique_id();
let path_str = format!("earl-test-screenshot-{id}.png");
let data = PreparedBrowserCommand {
session_id: None,
headless: true,
timeout_ms: 30_000,
on_failure_screenshot: false,
steps: vec![
BrowserStep::Navigate {
url: server.url("/"),
expected_status: None,
timeout_ms: None,
optional: false,
},
BrowserStep::Screenshot {
path: Some(path_str.clone()),
r#type: None,
full_page: false,
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let result = execute(data).await.expect("execute should succeed");
assert_eq!(
result["path"].as_str(),
Some(path_str.as_str()),
"result 'path' field should match the requested path; got: {}",
result["path"]
);
assert!(
std::path::Path::new(&path_str).exists(),
"screenshot file should exist on disk at {path_str}"
);
std::fs::remove_file(&path_str).ok();
}