use assert_cmd::Command;
use tempfile::TempDir;
#[test]
fn start_with_unknown_kind_errors() {
let tmp = TempDir::new().unwrap();
Command::cargo_bin("browser-control")
.unwrap()
.env("BROWSER_CONTROL_DATA_DIR", tmp.path())
.args(["start", "definitelynotabrowser"])
.assert()
.failure()
.stderr(predicates::str::contains("unknown browser kind"));
}
fn pick_installed_kind() -> Option<String> {
let bin = assert_cmd::cargo::cargo_bin("browser-control");
let out = std::process::Command::new(&bin)
.args(["list-installed", "--json"])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let v: serde_json::Value = serde_json::from_slice(&out.stdout).ok()?;
let arr = v.as_array()?;
for pref in ["chromium", "chrome", "edge", "brave", "firefox"] {
if arr.iter().any(|e| e["kind"].as_str() == Some(pref)) {
return Some(pref.to_string());
}
}
None
}
#[test]
fn start_without_profile_uses_per_kind_default_dir_and_is_idempotent() {
let Some(kind) = pick_installed_kind() else {
eprintln!("skipping: no installed browser detected on this machine");
return;
};
let data = TempDir::new().unwrap();
let cfg = TempDir::new().unwrap();
let expected = cfg.path().join("profiles").join(&kind).join("default");
let out = Command::cargo_bin("browser-control")
.unwrap()
.env("BROWSER_CONTROL_DATA_DIR", data.path())
.env("BROWSER_CONTROL_CONFIG_DIR", cfg.path())
.args(["start", &kind, "--headless", "--json"])
.output()
.expect("invoke start");
if !out.status.success() {
eprintln!(
"skipping: failed to launch {kind} headless on this machine: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
return;
}
let v: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("start --json output is valid json");
assert_eq!(v["reused"], serde_json::Value::Bool(false));
assert_eq!(
v["profile"].as_str().map(std::path::PathBuf::from),
Some(expected.clone()),
"expected profile path under sandboxed config dir"
);
assert!(
expected.is_dir(),
"expected profile dir {} to exist",
expected.display()
);
let pid1 = v["pid"].as_u64().expect("pid in json");
let out2 = Command::cargo_bin("browser-control")
.unwrap()
.env("BROWSER_CONTROL_DATA_DIR", data.path())
.env("BROWSER_CONTROL_CONFIG_DIR", cfg.path())
.args(["start", &kind, "--headless", "--json"])
.output()
.expect("invoke start (reuse)");
assert!(
out2.status.success(),
"second start failed: stderr={}",
String::from_utf8_lossy(&out2.stderr)
);
let v2: serde_json::Value = serde_json::from_slice(&out2.stdout).unwrap();
assert_eq!(v2["reused"], serde_json::Value::Bool(true));
assert_eq!(v2["pid"].as_u64(), Some(pid1));
assert_eq!(
v2["profile"].as_str().map(std::path::PathBuf::from),
Some(expected.clone())
);
let list = Command::cargo_bin("browser-control")
.unwrap()
.env("BROWSER_CONTROL_DATA_DIR", data.path())
.env("BROWSER_CONTROL_CONFIG_DIR", cfg.path())
.args(["list-running", "--json"])
.output()
.expect("invoke list-running");
assert!(list.status.success());
let rows: serde_json::Value = serde_json::from_slice(&list.stdout).unwrap();
let arr = rows.as_array().expect("array");
let same_kind: Vec<_> = arr
.iter()
.filter(|r| r["kind"].as_str() == Some(&kind))
.collect();
assert_eq!(
same_kind.len(),
1,
"expected exactly one running row for kind {kind}, got {arr:?}"
);
if let Some(pid) = same_kind[0]["pid"].as_u64() {
#[cfg(unix)]
unsafe {
libc::kill(pid as i32, libc::SIGTERM);
}
#[cfg(windows)]
{
let _ = std::process::Command::new("taskkill")
.args(["/PID", &pid.to_string(), "/F"])
.output();
}
}
}