use std::process::Command;
use serde_json::Value;
mod common;
use common::TestBrowser;
const WINDOW_MS: u64 = 60;
fn run_cli(args: &[&str]) -> (String, i32) {
let output = Command::new(common::binary())
.args(args)
.output()
.expect("Failed to run chrome-agent");
(
String::from_utf8_lossy(&output.stdout).to_string(),
output.status.code().unwrap_or(-1),
)
}
fn open(browser: &str, fixture: &str) -> bool {
if !common::browser_ready() {
return false;
}
let url = common::fixture_url(fixture);
let (_, code) = run_cli(&["--browser", browser, "goto", &url]);
if code != 0 {
return common::unavailable(&format!("goto {fixture} failed"));
}
true
}
fn fill(browser: &str, selector: &str, value: &str) -> Value {
let (stdout, _) = run_cli(&[
"--browser",
browser,
"--json",
"fill",
"--selector",
selector,
value,
]);
serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("not JSON ({e}): {stdout}"))
}
#[test]
fn a_value_reverted_on_the_microtask_queue_is_not_reported_as_kept() {
let b = TestBrowser::new("window-microtask");
if !open(b.name(), "form_value_microtask_revert.html") {
return;
}
let v = fill(b.name(), "#micro", "coupon-123");
assert_eq!(v["ok"], true, "{v}");
assert_eq!(
v["value"]["actual"], "",
"the page threw the value away before the read window closed: {v}"
);
assert_eq!(
v["value"]["verbatim"], false,
"so it was not kept verbatim: {v}"
);
}
#[test]
fn a_fill_reports_the_window_it_observed() {
let b = TestBrowser::new("window-fill-declared");
if !open(b.name(), "form_value_plain_input.html") {
return;
}
let v = fill(b.name(), "#plain", "hello");
assert_eq!(v["value"]["observed_after_ms"], WINDOW_MS, "{v}");
assert_eq!(v["value"]["verbatim"], true, "a plain input keeps it: {v}");
}
#[test]
fn a_revert_past_the_window_is_still_bounded_by_a_stated_time() {
let b = TestBrowser::new("window-late");
if !open(b.name(), "form_value_late_revert.html") {
return;
}
let v = fill(b.name(), "#late", "vanishes");
assert_eq!(v["value"]["actual"], "vanishes", "{v}");
assert_eq!(
v["value"]["observed_after_ms"], WINDOW_MS,
"the claim is scoped to when it was made, not to the future: {v}"
);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
let last;
loop {
let (stdout, code) = run_cli(&[
"--browser",
b.name(),
"--json",
"eval",
"document.querySelector('#late').value",
]);
assert_eq!(code, 0, "{stdout}");
let current: Value = serde_json::from_str(&stdout).expect("JSON eval");
if current["result"] == "" || std::time::Instant::now() >= deadline {
last = current;
break;
}
std::thread::sleep(std::time::Duration::from_millis(200));
}
assert_eq!(
last["result"], "",
"the fixture never reverted, so it cannot demonstrate a change past the window: {last}"
);
}
#[test]
fn check_reports_the_same_window_as_fill() {
let b = TestBrowser::new("window-check");
if !open(b.name(), "checkable_kinds.html") {
return;
}
let (stdout, code) = run_cli(&[
"--browser",
b.name(),
"--json",
"check",
"--selector",
"#native",
]);
assert_eq!(code, 0, "{stdout}");
let v: Value = serde_json::from_str(&stdout).expect("JSON check response");
assert_eq!(v["observed_after_ms"], WINDOW_MS, "{v}");
}
#[test]
fn check_by_uid_reports_the_window_too() {
let b = TestBrowser::new("window-check-uid");
if !open(b.name(), "checkable_kinds.html") {
return;
}
let (stdout, code) = run_cli(&["--browser", b.name(), "--json", "inspect"]);
assert_eq!(code, 0, "{stdout}");
let snapshot: Value = serde_json::from_str(&stdout).expect("JSON inspect");
let text = snapshot["snapshot"].as_str().unwrap_or_default();
let uid = text
.lines()
.find(|l| l.contains("checkbox"))
.and_then(|l| l.trim_start().strip_prefix("uid="))
.and_then(|rest| rest.split_whitespace().next())
.unwrap_or_else(|| panic!("no checkbox uid in: {text}"))
.to_string();
let (stdout, code) = run_cli(&["--browser", b.name(), "--json", "check", &uid]);
assert_eq!(code, 0, "{stdout}");
let v: Value = serde_json::from_str(&stdout).expect("JSON check response");
assert_eq!(
v["observed_after_ms"], WINDOW_MS,
"the uid path used to wait for however long a CDP round trip took: {v}"
);
}