From cb8d3c2ed1421f2bfd87da0ba56e731e72e4c14e Mon Sep 17 00:00:00 2001 From: go-to-the-future Date: Mon, 16 Feb 2026 12:42:00 +0900 Subject: [PATCH] feat: add --wait-until flag to open command and change default to domcontentloaded The open command now accepts --wait-until to control when navigation is considered complete. Valid values: load, domcontentloaded, networkidle. Also configurable via AGENT_BROWSER_WAIT_UNTIL env var. The default wait strategy for handleNavigate is changed from load to domcontentloaded, aligning it with handleTabNew which already used domcontentloaded. The load event frequently times out on modern SPAs that load external scripts (analytics, auth providers), making it a poor default for agent workflows. Closes #479 --- cli/src/commands.rs | 37 +++++++++++++++++++++++++++++++++++++ cli/src/flags.rs | 27 +++++++++++++++++++++++++++ cli/src/output.rs | 8 ++++++++ src/actions.ts | 2 +- 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/cli/src/commands.rs b/cli/src/commands.rs index d113616b..aa4119a8 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -101,6 +101,10 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result(headers_json) { @@ -1535,6 +1539,7 @@ mod tests { device: None, auto_connect: false, session_name: None, + wait_until: None, cli_executable_path: false, cli_extensions: false, cli_profile: false, @@ -1836,6 +1841,38 @@ mod tests { assert!(cmd.get("headers").is_none()); } + #[test] + fn test_navigate_with_wait_until() { + let mut flags = default_flags(); + flags.wait_until = Some("domcontentloaded".to_string()); + let cmd = parse_command(&args("open example.com"), &flags).unwrap(); + assert_eq!(cmd["action"], "navigate"); + assert_eq!(cmd["waitUntil"], "domcontentloaded"); + } + + #[test] + fn test_navigate_with_wait_until_load() { + let mut flags = default_flags(); + flags.wait_until = Some("load".to_string()); + let cmd = parse_command(&args("open example.com"), &flags).unwrap(); + assert_eq!(cmd["waitUntil"], "load"); + } + + #[test] + fn test_navigate_with_wait_until_networkidle() { + let mut flags = default_flags(); + flags.wait_until = Some("networkidle".to_string()); + let cmd = parse_command(&args("open example.com"), &flags).unwrap(); + assert_eq!(cmd["waitUntil"], "networkidle"); + } + + #[test] + fn test_navigate_without_wait_until() { + let cmd = parse_command(&args("open example.com"), &default_flags()).unwrap(); + // waitUntil should not be present when flag is not set (daemon uses its own default) + assert!(cmd.get("waitUntil").is_none()); + } + // === Set Headers Tests === #[test] diff --git a/cli/src/flags.rs b/cli/src/flags.rs index 84d41ebf..a9ad9a32 100644 --- a/cli/src/flags.rs +++ b/cli/src/flags.rs @@ -22,6 +22,7 @@ pub struct Flags { pub device: Option, pub auto_connect: bool, pub session_name: Option, + pub wait_until: Option, // Track which launch-time options were explicitly passed via CLI // (as opposed to being set only via environment variables) @@ -69,6 +70,7 @@ pub fn parse_flags(args: &[String]) -> Flags { device: env::var("AGENT_BROWSER_IOS_DEVICE").ok(), auto_connect: env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok(), session_name: env::var("AGENT_BROWSER_SESSION_NAME").ok(), + wait_until: env::var("AGENT_BROWSER_WAIT_UNTIL").ok(), // Track CLI-passed flags (default false, set to true when flag is passed) cli_executable_path: false, cli_extensions: false, @@ -186,6 +188,12 @@ pub fn parse_flags(args: &[String]) -> Flags { i += 1; } } + "--wait-until" => { + if let Some(s) = args.get(i + 1) { + flags.wait_until = Some(s.clone()); + i += 1; + } + } _ => {} } i += 1; @@ -224,6 +232,7 @@ pub fn clean_args(args: &[String]) -> Vec { "--provider", "--device", "--session-name", + "--wait-until", ]; for arg in args.iter() { @@ -401,4 +410,22 @@ mod tests { assert!(!flags.cli_extensions); assert!(!flags.cli_state); } + + #[test] + fn test_parse_wait_until_flag() { + let flags = parse_flags(&args("open example.com --wait-until domcontentloaded")); + assert_eq!(flags.wait_until, Some("domcontentloaded".to_string())); + } + + #[test] + fn test_parse_no_wait_until_flag() { + let flags = parse_flags(&args("open example.com")); + assert!(flags.wait_until.is_none()); + } + + #[test] + fn test_clean_args_removes_wait_until() { + let cleaned = clean_args(&args("open example.com --wait-until domcontentloaded")); + assert_eq!(cleaned, vec!["open", "example.com"]); + } } diff --git a/cli/src/output.rs b/cli/src/output.rs index 7f083a51..dd8e747a 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -491,6 +491,10 @@ https:// is automatically prepended. Aliases: goto, navigate +Options: + --wait-until When to consider navigation done (default: domcontentloaded) + Values: load, domcontentloaded, networkidle + Global Options: --json Output as JSON --session Use specific session @@ -501,6 +505,7 @@ Examples: agent-browser open example.com agent-browser open https://github.com agent-browser open localhost:3000 + agent-browser open example.com --wait-until networkidle agent-browser open api.example.com --headers '{"Authorization": "Bearer token"}' # ^ Headers only sent to api.example.com, not other domains "## @@ -1872,6 +1877,8 @@ Options: --headed Show browser window (not headless) --cdp Connect via CDP (Chrome DevTools Protocol) --auto-connect Auto-discover and connect to running Chrome + --wait-until Navigation wait strategy: load, domcontentloaded, networkidle + (or AGENT_BROWSER_WAIT_UNTIL, default: domcontentloaded) --session-name Auto-save/restore session state (cookies, localStorage) --debug Debug output --version, -V Show version @@ -1885,6 +1892,7 @@ Environment: AGENT_BROWSER_PROVIDER Browser provider (ios, browserbase, kernel, browseruse) AGENT_BROWSER_AUTO_CONNECT Auto-discover and connect to running Chrome AGENT_BROWSER_STREAM_PORT Enable WebSocket streaming on port (e.g., 9223) + AGENT_BROWSER_WAIT_UNTIL Navigation wait strategy (default: domcontentloaded) AGENT_BROWSER_IOS_DEVICE Default iOS device name AGENT_BROWSER_IOS_UDID Default iOS device UDID diff --git a/src/actions.ts b/src/actions.ts index 22314e06..4ebccfa9 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -511,7 +511,7 @@ async function handleNavigate( } await page.goto(command.url, { - waitUntil: command.waitUntil ?? 'load', + waitUntil: command.waitUntil ?? 'domcontentloaded', }); return successResponse(command.id, {