diff --git a/README.md b/README.md index 9203b5b..4e2702c 100644 --- a/README.md +++ b/README.md @@ -55,13 +55,13 @@ agent-browser find role button click --name "Submit" ### Core Commands ```bash -agent-browser open # Navigate to URL +agent-browser open # Navigate to URL (aliases: goto, navigate) agent-browser click # Click element agent-browser dblclick # Double-click element agent-browser focus # Focus element agent-browser type # Type into element agent-browser fill # Clear and fill -agent-browser press # Press key (Enter, Tab, Control+a) +agent-browser press # Press key (Enter, Tab, Control+a) (alias: key) agent-browser keydown # Hold key down agent-browser keyup # Release key agent-browser hover # Hover element @@ -69,14 +69,14 @@ agent-browser select # Select dropdown option agent-browser check # Check checkbox agent-browser uncheck # Uncheck checkbox agent-browser scroll [px] # Scroll (up/down/left/right) -agent-browser scrollintoview # Scroll element into view +agent-browser scrollintoview # Scroll element into view (alias: scrollinto) agent-browser drag # Drag and drop agent-browser upload # Upload files agent-browser screenshot [path] # Take screenshot (--full for full page) agent-browser pdf # Save as PDF agent-browser snapshot # Accessibility tree with refs (best for AI) agent-browser eval # Run JavaScript -agent-browser close # Close browser +agent-browser close # Close browser (aliases: quit, exit) ``` ### Get Info @@ -129,9 +129,9 @@ agent-browser find nth 2 "a" text ### Wait ```bash -agent-browser wait # Wait for element -agent-browser wait # Wait for time -agent-browser wait --text "Welcome" # Wait for text +agent-browser wait # Wait for element to be visible +agent-browser wait # Wait for time (milliseconds) +agent-browser wait --text "Welcome" # Wait for text to appear agent-browser wait --url "**/dash" # Wait for URL pattern agent-browser wait --load networkidle # Wait for load state agent-browser wait --fn "window.ready === true" # Wait for JS condition @@ -253,6 +253,10 @@ AGENT_BROWSER_SESSION=agent1 agent-browser click "#btn" # List active sessions agent-browser session list +# Output: +# Active sessions: +# -> default +# agent1 # Show current session agent-browser session @@ -393,15 +397,17 @@ agent-browser uses a client-daemon architecture: The daemon starts automatically on first command and persists between commands for fast subsequent operations. +**Browser Engine:** Uses Chromium by default. The daemon also supports Firefox and WebKit via the Playwright protocol. + ## Platforms | Platform | Binary | Fallback | |----------|--------|----------| -| macOS ARM64 | ✅ Native Rust | Node.js | -| macOS x64 | ✅ Native Rust | Node.js | -| Linux ARM64 | ✅ Native Rust | Node.js | -| Linux x64 | ✅ Native Rust | Node.js | -| Windows | - | Node.js | +| macOS ARM64 | Native Rust | Node.js | +| macOS x64 | Native Rust | Node.js | +| Linux ARM64 | Native Rust | Node.js | +| Linux x64 | Native Rust | Node.js | +| Windows x64 | Native Rust | Node.js | ## Usage with AI Agents diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 9134397..405f753 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -212,6 +212,44 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result { + // Check for --url flag: wait --url "**/dashboard" + if let Some(idx) = rest.iter().position(|&s| s == "--url" || s == "-u") { + let url = rest.get(idx + 1).ok_or_else(|| ParseError::MissingArguments { + context: "wait --url".to_string(), + usage: "wait --url ", + })?; + return Ok(json!({ "id": id, "action": "waitforurl", "url": url })); + } + + // Check for --load flag: wait --load networkidle + if let Some(idx) = rest.iter().position(|&s| s == "--load" || s == "-l") { + let state = rest.get(idx + 1).ok_or_else(|| ParseError::MissingArguments { + context: "wait --load".to_string(), + usage: "wait --load ", + })?; + return Ok(json!({ "id": id, "action": "waitforloadstate", "state": state })); + } + + // Check for --fn flag: wait --fn "window.ready === true" + if let Some(idx) = rest.iter().position(|&s| s == "--fn" || s == "-f") { + let expr = rest.get(idx + 1).ok_or_else(|| ParseError::MissingArguments { + context: "wait --fn".to_string(), + usage: "wait --fn ", + })?; + return Ok(json!({ "id": id, "action": "waitforfunction", "expression": expr })); + } + + // Check for --text flag: wait --text "Welcome" + if let Some(idx) = rest.iter().position(|&s| s == "--text" || s == "-t") { + let text = rest.get(idx + 1).ok_or_else(|| ParseError::MissingArguments { + context: "wait --text".to_string(), + usage: "wait --text ", + })?; + // Use getByText locator to wait for text to appear + return Ok(json!({ "id": id, "action": "wait", "selector": format!("text={}", text) })); + } + + // Default: selector or timeout if let Some(arg) = rest.get(0) { if arg.parse::().is_ok() { Ok(json!({ "id": id, "action": "wait", "timeout": arg.parse::().unwrap() })) @@ -221,7 +259,7 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result", + usage: "wait ", }) } } @@ -1090,6 +1128,57 @@ mod tests { assert_eq!(cmd["maxDepth"], 3); } + // === Wait === + + #[test] + fn test_wait_selector() { + let cmd = parse_command(&args("wait #element"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "wait"); + assert_eq!(cmd["selector"], "#element"); + } + + #[test] + fn test_wait_timeout() { + let cmd = parse_command(&args("wait 5000"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "wait"); + assert_eq!(cmd["timeout"], 5000); + } + + #[test] + fn test_wait_url() { + let cmd = parse_command(&args("wait --url **/dashboard"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "waitforurl"); + assert_eq!(cmd["url"], "**/dashboard"); + } + + #[test] + fn test_wait_load() { + let cmd = parse_command(&args("wait --load networkidle"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "waitforloadstate"); + assert_eq!(cmd["state"], "networkidle"); + } + + #[test] + fn test_wait_load_missing_state() { + let result = parse_command(&args("wait --load"), &default_flags()); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), ParseError::MissingArguments { .. })); + } + + #[test] + fn test_wait_fn() { + let cmd = parse_command(&args("wait --fn window.ready"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "waitforfunction"); + assert_eq!(cmd["expression"], "window.ready"); + } + + #[test] + fn test_wait_text() { + let cmd = parse_command(&args("wait --text Welcome"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "wait"); + assert_eq!(cmd["selector"], "text=Welcome"); + } + // === Unknown command === #[test]