use std::env;
use std::time::{Duration, Instant, SystemTime};
use serde_json::{json, Value};
use super::helpers::new_id;
use super::{Check, DoctorOptions, Status};
use crate::connection::{cleanup_stale_files, ensure_daemon, send_command, DaemonOptions};
pub(super) fn check(checks: &mut Vec<Check>, opts: &DoctorOptions) {
let category = "Launch test";
if env::var("AGENT_BROWSER_PROVIDER").is_ok() {
checks.push(Check::new(
"launch.skipped.provider",
category,
Status::Info,
"Skipped (AGENT_BROWSER_PROVIDER is set; would consume cloud quota)",
));
return;
}
if env::var("AGENT_BROWSER_CDP").is_ok() {
checks.push(Check::new(
"launch.skipped.cdp",
category,
Status::Info,
"Skipped (AGENT_BROWSER_CDP is set; would attach to a real browser)",
));
return;
}
let session = format!(
"doctor-{}-{}",
std::process::id(),
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0)
);
let mut _guard: Option<LaunchGuard> = None;
let daemon_opts = DaemonOptions {
headed: false,
debug: opts.debug,
executable_path: None,
extensions: &[],
init_scripts: &[],
enable: &[],
args: None,
user_agent: None,
proxy: None,
proxy_bypass: None,
proxy_username: None,
proxy_password: None,
ignore_https_errors: false,
allow_file_access: false,
hide_scrollbars: true,
webgpu: false,
profile: None,
state: None,
provider: None,
device: None,
session_name: None,
restore_save: None,
restore_check_url: None,
restore_check_text: None,
restore_check_fn: None,
download_path: None,
allowed_domains: None,
action_policy: None,
confirm_actions: None,
engine: None,
auto_connect: false,
pin_tab: false,
idle_timeout: None,
default_timeout: None,
cdp: None,
cdp_headers: None,
no_auto_dialog: false,
plugins: None,
};
let started = Instant::now();
if let Err(e) = ensure_daemon(&session, &daemon_opts) {
checks.push(
Check::new(
"launch.daemon",
category,
Status::Fail,
format!("Could not start daemon: {}", e),
)
.with_fix("check Chrome install and re-run with --debug"),
);
return;
}
_guard = Some(LaunchGuard {
session: session.clone(),
});
let launch_cmd = json!({
"id": new_id(),
"action": "launch",
"headless": true,
});
if let Err(e) = send_json(launch_cmd, &session) {
checks.push(
Check::new(
"launch.launch",
category,
Status::Fail,
format!("Browser launch failed: {}", e),
)
.with_fix("agent-browser install # or check --debug output"),
);
return;
}
let open_cmd = json!({
"id": new_id(),
"action": "navigate",
"url": "about:blank",
});
if let Err(e) = send_json(open_cmd, &session) {
checks.push(
Check::new(
"launch.navigate",
category,
Status::Fail,
format!("Navigation to about:blank failed: {}", e),
)
.with_fix("re-run with --debug for full launch logs"),
);
return;
}
let elapsed = started.elapsed();
let secs = elapsed.as_secs_f64();
if elapsed > Duration::from_secs(5) {
checks.push(Check::new(
"launch.elapsed",
category,
Status::Warn,
format!(
"Headless launch + about:blank in {:.2}s (slow; expected < 5s)",
secs
),
));
} else {
checks.push(Check::new(
"launch.elapsed",
category,
Status::Pass,
format!("Headless launch + about:blank in {:.2}s", secs),
));
}
}
fn send_json(cmd: Value, session: &str) -> Result<(), String> {
match send_command(cmd, session) {
Ok(resp) => {
if resp.success {
Ok(())
} else {
Err(resp.error.unwrap_or_else(|| "unknown error".to_string()))
}
}
Err(e) => Err(e),
}
}
struct LaunchGuard {
session: String,
}
impl Drop for LaunchGuard {
fn drop(&mut self) {
let close_cmd = json!({ "id": new_id(), "action": "close" });
let _ = send_command(close_cmd, &self.session);
cleanup_stale_files(&self.session);
}
}