mod common;
use common::*;
use earl_core::{ProtocolExecutor, RawExecutionResult};
use earl_protocol_browser::schema::BrowserStep;
use earl_protocol_browser::{BrowserExecutor, PreparedBrowserCommand, steps::validate_url_scheme};
#[test]
fn allowed_schemes_are_accepted() {
assert!(validate_url_scheme("http://example.com").is_ok());
assert!(validate_url_scheme("https://example.com").is_ok());
}
#[test]
fn file_scheme_is_rejected() {
let err = validate_url_scheme("file:///etc/passwd").unwrap_err();
assert!(
err.to_string().contains("file"),
"error should mention the disallowed scheme; got: {err}"
);
}
#[test]
fn javascript_scheme_is_rejected() {
let err = validate_url_scheme("javascript:alert(1)").unwrap_err();
assert!(
err.to_string().contains("javascript"),
"error should mention the disallowed scheme; got: {err}"
);
}
#[test]
fn data_scheme_is_rejected() {
let err = validate_url_scheme("data:text/html,<h1>hi</h1>").unwrap_err();
assert!(
err.to_string().contains("data"),
"error should mention the disallowed scheme; got: {err}"
);
}
#[test]
fn blob_scheme_is_rejected() {
let err = validate_url_scheme("blob:https://example.com/uuid").unwrap_err();
assert!(
err.to_string().contains("blob"),
"error should mention the disallowed scheme; got: {err}"
);
}
#[tokio::test]
#[ignore = "requires external network"]
async fn navigate_and_snapshot() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let data = PreparedBrowserCommand {
session_id: None,
headless: true,
timeout_ms: 30_000,
on_failure_screenshot: false,
steps: vec![
BrowserStep::Navigate {
url: "https://example.com".into(),
expected_status: None,
timeout_ms: None,
optional: false,
},
BrowserStep::Snapshot {
timeout_ms: None,
optional: false,
},
],
};
let ctx = make_context();
let mut executor = BrowserExecutor;
let result: RawExecutionResult = executor
.execute(&data, &ctx)
.await
.expect("execute should succeed");
assert_eq!(result.content_type.as_deref(), Some("application/json"));
let json: serde_json::Value =
serde_json::from_slice(&result.body).expect("body should be valid JSON");
assert!(
json.get("text").is_some(),
"snapshot result should have a 'text' field; got: {json}"
);
}
#[tokio::test]
#[ignore = "requires external network"]
async fn optional_step_continues_on_failure() {
if skip_if_no_chrome() {
return;
}
let _guard = common::chrome_lock().await;
let data = PreparedBrowserCommand {
session_id: None,
headless: true,
timeout_ms: 30_000,
on_failure_screenshot: false,
steps: vec![
BrowserStep::Navigate {
url: "https://example.com".into(),
expected_status: None,
timeout_ms: None,
optional: false,
},
BrowserStep::Click {
r#ref: None,
selector: Some("#this-element-does-not-exist-abc123".into()),
double_click: false,
timeout_ms: Some(2_000),
optional: true,
},
BrowserStep::Snapshot {
timeout_ms: None,
optional: false,
},
],
};
let ctx = make_context();
let mut executor = BrowserExecutor;
let result = executor
.execute(&data, &ctx)
.await
.expect("execute should succeed even though the click step failed");
let json: serde_json::Value =
serde_json::from_slice(&result.body).expect("body should be valid JSON");
assert!(
json.get("text").is_some(),
"final snapshot result should have a 'text' field; got: {json}"
);
}