support serverless environments (#29)
* add --executable-path * tests * test vercel * fixes
This commit is contained in:
@@ -886,6 +886,7 @@ mod tests {
|
||||
full: false,
|
||||
headed: false,
|
||||
debug: false,
|
||||
executable_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-3
@@ -153,9 +153,15 @@ fn daemon_ready(session: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_daemon(session: &str, headed: bool) -> Result<(), String> {
|
||||
/// Result of ensure_daemon indicating whether a new daemon was started
|
||||
pub struct DaemonResult {
|
||||
/// True if we connected to an existing daemon, false if we started a new one
|
||||
pub already_running: bool,
|
||||
}
|
||||
|
||||
pub fn ensure_daemon(session: &str, headed: bool, executable_path: Option<&str>) -> Result<DaemonResult, String> {
|
||||
if is_daemon_running(session) && daemon_ready(session) {
|
||||
return Ok(());
|
||||
return Ok(DaemonResult { already_running: true });
|
||||
}
|
||||
|
||||
let exe_path = env::current_exe().map_err(|e| e.to_string())?;
|
||||
@@ -186,6 +192,10 @@ pub fn ensure_daemon(session: &str, headed: bool) -> Result<(), String> {
|
||||
cmd.env("AGENT_BROWSER_HEADED", "1");
|
||||
}
|
||||
|
||||
if let Some(path) = executable_path {
|
||||
cmd.env("AGENT_BROWSER_EXECUTABLE_PATH", path);
|
||||
}
|
||||
|
||||
// Create new process group and session to fully detach
|
||||
unsafe {
|
||||
cmd.pre_exec(|| {
|
||||
@@ -220,6 +230,10 @@ pub fn ensure_daemon(session: &str, headed: bool) -> Result<(), String> {
|
||||
cmd.env("AGENT_BROWSER_HEADED", "1");
|
||||
}
|
||||
|
||||
if let Some(path) = executable_path {
|
||||
cmd.env("AGENT_BROWSER_EXECUTABLE_PATH", path);
|
||||
}
|
||||
|
||||
// CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
|
||||
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
|
||||
const DETACHED_PROCESS: u32 = 0x00000008;
|
||||
@@ -234,7 +248,7 @@ pub fn ensure_daemon(session: &str, headed: bool) -> Result<(), String> {
|
||||
|
||||
for _ in 0..50 {
|
||||
if daemon_ready(session) {
|
||||
return Ok(());
|
||||
return Ok(DaemonResult { already_running: false });
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
+51
-1
@@ -6,6 +6,7 @@ pub struct Flags {
|
||||
pub headed: bool,
|
||||
pub debug: bool,
|
||||
pub session: String,
|
||||
pub executable_path: Option<String>,
|
||||
}
|
||||
|
||||
pub fn parse_flags(args: &[String]) -> Flags {
|
||||
@@ -15,6 +16,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
||||
headed: false,
|
||||
debug: false,
|
||||
session: env::var("AGENT_BROWSER_SESSION").unwrap_or_else(|_| "default".to_string()),
|
||||
executable_path: env::var("AGENT_BROWSER_EXECUTABLE_PATH").ok(),
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
@@ -30,6 +32,12 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
"--executable-path" => {
|
||||
if let Some(s) = args.get(i + 1) {
|
||||
flags.executable_path = Some(s.clone());
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
@@ -43,13 +51,15 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
||||
|
||||
// Global flags that should be stripped from command args
|
||||
const GLOBAL_FLAGS: &[&str] = &["--json", "--full", "--headed", "--debug"];
|
||||
// Flags that take a value (skip both the flag and the next arg)
|
||||
const VALUE_FLAGS: &[&str] = &["--session", "--executable-path"];
|
||||
|
||||
for arg in args.iter() {
|
||||
if skip_next {
|
||||
skip_next = false;
|
||||
continue;
|
||||
}
|
||||
if arg == "--session" {
|
||||
if VALUE_FLAGS.contains(&arg.as_str()) {
|
||||
skip_next = true;
|
||||
continue;
|
||||
}
|
||||
@@ -61,3 +71,43 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn args(s: &str) -> Vec<String> {
|
||||
s.split_whitespace().map(String::from).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_executable_path_flag() {
|
||||
let flags = parse_flags(&args("--executable-path /path/to/chromium open example.com"));
|
||||
assert_eq!(flags.executable_path, Some("/path/to/chromium".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_executable_path_flag_no_value() {
|
||||
let flags = parse_flags(&args("--executable-path"));
|
||||
assert_eq!(flags.executable_path, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clean_args_removes_executable_path() {
|
||||
let cleaned = clean_args(&args("--executable-path /path/to/chromium open example.com"));
|
||||
assert_eq!(cleaned, vec!["open", "example.com"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clean_args_removes_executable_path_with_other_flags() {
|
||||
let cleaned = clean_args(&args("--json --executable-path /path/to/chromium --headed open example.com"));
|
||||
assert_eq!(cleaned, vec!["open", "example.com"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_flags_with_session_and_executable_path() {
|
||||
let flags = parse_flags(&args("--session test --executable-path /custom/chrome open example.com"));
|
||||
assert_eq!(flags.session, "test");
|
||||
assert_eq!(flags.executable_path, Some("/custom/chrome".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
+16
-6
@@ -149,13 +149,23 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = ensure_daemon(&flags.session, flags.headed) {
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, e);
|
||||
} else {
|
||||
eprintln!("\x1b[31m✗\x1b[0m {}", e);
|
||||
let daemon_result = match ensure_daemon(&flags.session, flags.headed, flags.executable_path.as_deref()) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
if flags.json {
|
||||
println!(r#"{{"success":false,"error":"{}"}}"#, e);
|
||||
} else {
|
||||
eprintln!("\x1b[31m✗\x1b[0m {}", e);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Warn if executable_path was specified but daemon was already running
|
||||
if daemon_result.already_running && flags.executable_path.is_some() {
|
||||
if !flags.json {
|
||||
eprintln!("\x1b[33m⚠\x1b[0m --executable-path ignored: daemon already running. Use 'agent-browser close' first to restart with new path.");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// If --headed flag is set, send launch command first to switch to headed mode
|
||||
|
||||
@@ -1186,6 +1186,7 @@ Snapshot Options:
|
||||
|
||||
Options:
|
||||
--session <name> Isolated session (or AGENT_BROWSER_SESSION env)
|
||||
--executable-path <path> Custom browser executable (or AGENT_BROWSER_EXECUTABLE_PATH)
|
||||
--json JSON output
|
||||
--full, -f Full page screenshot
|
||||
--headed Show browser window (not headless)
|
||||
|
||||
Reference in New Issue
Block a user