mod common;
use common::{execute, skip_if_no_chrome};
use earl_protocol_browser::PreparedBrowserCommand;
use earl_protocol_browser::schema::BrowserStep;
use std::collections::HashMap;
#[tokio::test]
async fn evaluate_returns_expression_result() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
common::server::Response::html("<html><body></body></html>"),
);
let server = common::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::Evaluate {
function: "() => 1 + 1".to_string(),
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let result = execute(data).await.expect("execute should succeed");
assert_eq!(
result["value"],
serde_json::Value::Number(serde_json::Number::from(2)),
"evaluate should return 2 as a JSON number; got: {result}"
);
}
#[tokio::test]
async fn evaluate_reads_dom_title() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
common::server::Response::html(
"<html><head><title>My Test Title</title></head><body></body></html>",
),
);
let server = common::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::Evaluate {
function: "() => document.title".to_string(),
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let result = execute(data).await.expect("execute should succeed");
assert_eq!(
result["value"],
serde_json::Value::String("My Test Title".to_string()),
"evaluate should return the page title; got: {result}"
);
}
#[tokio::test]
async fn run_code_executes_multi_statement() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
common::server::Response::html("<html><body></body></html>"),
);
let server = common::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::RunCode {
code: "const x = 40; const y = 2; return x + y;".to_string(),
timeout_ms: None,
optional: false,
},
],
};
let result = execute(data).await.expect("execute should succeed");
assert_eq!(
result["value"],
serde_json::Value::Number(serde_json::Number::from(42)),
"run_code should return 42 as a JSON number; got: {result}"
);
}
#[tokio::test]
async fn run_code_mutation_visible_to_evaluate() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
common::server::Response::html(
"<html><head><title>original</title></head><body></body></html>",
),
);
let server = common::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::RunCode {
code: "document.title = 'injected'; return document.title;".to_string(),
timeout_ms: None,
optional: false,
},
BrowserStep::Evaluate {
function: "() => document.title".to_string(),
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let result = execute(data).await.expect("execute should succeed");
assert_eq!(
result["value"],
serde_json::Value::String("injected".to_string()),
"evaluate should see the title mutated by run_code; got: {result}"
);
}
#[tokio::test]
async fn evaluate_propagates_javascript_exception() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
common::server::Response::html("<html><body></body></html>"),
);
let server = common::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::Evaluate {
function: "() => { throw new Error('boom'); }".to_string(),
r#ref: None,
timeout_ms: None,
optional: false,
},
],
};
let err = execute(data)
.await
.expect_err("evaluate should fail when JS throws");
let msg = err.to_string().to_lowercase();
assert!(
msg.contains("boom") || msg.contains("error"),
"error message should mention 'boom' or 'error'; got: {err}"
);
}
#[tokio::test]
async fn run_code_propagates_javascript_exception() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let mut routes = HashMap::new();
routes.insert(
"GET /".to_string(),
common::server::Response::html("<html><body></body></html>"),
);
let server = common::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::RunCode {
code: "throw new Error('kaboom');".to_string(),
timeout_ms: None,
optional: false,
},
],
};
let err = execute(data)
.await
.expect_err("run_code should fail when JS throws");
let msg = err.to_string().to_lowercase();
assert!(
msg.contains("kaboom") || msg.contains("error"),
"error message should mention 'kaboom' or 'error'; got: {err}"
);
}