fix: support WebSocket URLs in connect command (#205)
* fix: support WebSocket URLs in connect command * address feedback
This commit is contained in:
+140
-7
@@ -17,6 +17,8 @@ pub enum ParseError {
|
||||
context: String,
|
||||
usage: &'static str,
|
||||
},
|
||||
/// Argument exists but has an invalid value
|
||||
InvalidValue { message: String, usage: &'static str },
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
@@ -41,6 +43,9 @@ impl ParseError {
|
||||
context, usage
|
||||
)
|
||||
}
|
||||
ParseError::InvalidValue { message, usage } => {
|
||||
format!("{}\nUsage: agent-browser {}", message, usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,15 +359,48 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
||||
|
||||
// === Connect (CDP) ===
|
||||
"connect" => {
|
||||
let port_str = rest.get(0).ok_or_else(|| ParseError::MissingArguments {
|
||||
let endpoint = rest.first().ok_or_else(|| ParseError::MissingArguments {
|
||||
context: "connect".to_string(),
|
||||
usage: "connect <port>",
|
||||
usage: "connect <port|url>",
|
||||
})?;
|
||||
let port: u16 = port_str.parse().map_err(|_| ParseError::MissingArguments {
|
||||
context: format!("connect: invalid port '{}'", port_str),
|
||||
usage: "connect <port>",
|
||||
})?;
|
||||
Ok(json!({ "id": id, "action": "launch", "cdpPort": port }))
|
||||
// Check if it's a URL (ws://, wss://, http://, https://)
|
||||
if endpoint.starts_with("ws://")
|
||||
|| endpoint.starts_with("wss://")
|
||||
|| endpoint.starts_with("http://")
|
||||
|| endpoint.starts_with("https://")
|
||||
{
|
||||
Ok(json!({ "id": id, "action": "launch", "cdpUrl": endpoint }))
|
||||
} else {
|
||||
// It's a port number - validate and use cdpPort field
|
||||
let port: u16 = match endpoint.parse::<u32>() {
|
||||
Ok(p) if p == 0 => {
|
||||
return Err(ParseError::InvalidValue {
|
||||
message: "Invalid port: port must be greater than 0".to_string(),
|
||||
usage: "connect <port|url>",
|
||||
});
|
||||
}
|
||||
Ok(p) if p > 65535 => {
|
||||
return Err(ParseError::InvalidValue {
|
||||
message: format!(
|
||||
"Invalid port: {} is out of range (valid range: 1-65535)",
|
||||
p
|
||||
),
|
||||
usage: "connect <port|url>",
|
||||
});
|
||||
}
|
||||
Ok(p) => p as u16,
|
||||
Err(_) => {
|
||||
return Err(ParseError::InvalidValue {
|
||||
message: format!(
|
||||
"Invalid value: '{}' is not a valid port number or URL",
|
||||
endpoint
|
||||
),
|
||||
usage: "connect <port|url>",
|
||||
});
|
||||
}
|
||||
};
|
||||
Ok(json!({ "id": id, "action": "launch", "cdpPort": port }))
|
||||
}
|
||||
}
|
||||
|
||||
// === Get ===
|
||||
@@ -1713,4 +1751,99 @@ mod tests {
|
||||
assert_eq!(cmd["index"], 2);
|
||||
assert!(cmd.get("value").is_none());
|
||||
}
|
||||
|
||||
// === Connect (CDP) tests ===
|
||||
|
||||
#[test]
|
||||
fn test_connect_with_port() {
|
||||
let cmd = parse_command(&args("connect 9222"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "launch");
|
||||
assert_eq!(cmd["cdpPort"], 9222);
|
||||
assert!(cmd.get("cdpUrl").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_with_ws_url() {
|
||||
let input: Vec<String> = vec![
|
||||
"connect".to_string(),
|
||||
"ws://localhost:9222/devtools/browser/abc123".to_string(),
|
||||
];
|
||||
let cmd = parse_command(&input, &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "launch");
|
||||
assert_eq!(cmd["cdpUrl"], "ws://localhost:9222/devtools/browser/abc123");
|
||||
assert!(cmd.get("cdpPort").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_with_wss_url() {
|
||||
let input: Vec<String> = vec![
|
||||
"connect".to_string(),
|
||||
"wss://remote-browser.example.com/cdp?token=xyz".to_string(),
|
||||
];
|
||||
let cmd = parse_command(&input, &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "launch");
|
||||
assert_eq!(cmd["cdpUrl"], "wss://remote-browser.example.com/cdp?token=xyz");
|
||||
assert!(cmd.get("cdpPort").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_with_http_url() {
|
||||
let input: Vec<String> = vec![
|
||||
"connect".to_string(),
|
||||
"http://localhost:9222".to_string(),
|
||||
];
|
||||
let cmd = parse_command(&input, &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "launch");
|
||||
assert_eq!(cmd["cdpUrl"], "http://localhost:9222");
|
||||
assert!(cmd.get("cdpPort").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_missing_argument() {
|
||||
let result = parse_command(&args("connect"), &default_flags());
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(result.unwrap_err(), ParseError::MissingArguments { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_invalid_port() {
|
||||
let result = parse_command(&args("connect notanumber"), &default_flags());
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(matches!(err, ParseError::InvalidValue { .. }));
|
||||
assert!(err.format().contains("not a valid port number or URL"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_port_zero() {
|
||||
let result = parse_command(&args("connect 0"), &default_flags());
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(matches!(err, ParseError::InvalidValue { .. }));
|
||||
assert!(err.format().contains("port must be greater than 0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_port_out_of_range() {
|
||||
let result = parse_command(&args("connect 65536"), &default_flags());
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(matches!(err, ParseError::InvalidValue { .. }));
|
||||
assert!(err.format().contains("out of range"));
|
||||
assert!(err.format().contains("1-65535"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_port_max_valid() {
|
||||
let cmd = parse_command(&args("connect 65535"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "launch");
|
||||
assert_eq!(cmd["cdpPort"], 65535);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_port_min_valid() {
|
||||
let cmd = parse_command(&args("connect 1"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "launch");
|
||||
assert_eq!(cmd["cdpPort"], 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@ fn main() {
|
||||
ParseError::UnknownCommand { .. } => "unknown_command",
|
||||
ParseError::UnknownSubcommand { .. } => "unknown_subcommand",
|
||||
ParseError::MissingArguments { .. } => "missing_arguments",
|
||||
ParseError::InvalidValue { .. } => "invalid_value",
|
||||
};
|
||||
println!(
|
||||
r#"{{"success":false,"error":"{}","type":"{}"}}"#,
|
||||
|
||||
+41
-1
@@ -1317,6 +1317,46 @@ Examples:
|
||||
"##
|
||||
}
|
||||
|
||||
// === Connect ===
|
||||
"connect" => {
|
||||
r##"
|
||||
agent-browser connect - Connect to browser via CDP
|
||||
|
||||
Usage: agent-browser connect <port|url>
|
||||
|
||||
Connects to a running browser instance via Chrome DevTools Protocol (CDP).
|
||||
This allows controlling browsers, Electron apps, or remote browser services.
|
||||
|
||||
Arguments:
|
||||
<port> Local port number (e.g., 9222)
|
||||
<url> Full WebSocket URL (ws://, wss://, http://, https://)
|
||||
|
||||
Supported URL formats:
|
||||
- Port number: 9222 (connects to http://localhost:9222)
|
||||
- WebSocket URL: ws://localhost:9222/devtools/browser/...
|
||||
- Remote service: wss://remote-browser.example.com/cdp?token=...
|
||||
|
||||
Global Options:
|
||||
--json Output as JSON
|
||||
--session <name> Use specific session
|
||||
|
||||
Examples:
|
||||
# Connect to local Chrome with remote debugging
|
||||
# Start Chrome: google-chrome --remote-debugging-port=9222
|
||||
agent-browser connect 9222
|
||||
|
||||
# Connect using WebSocket URL from /json/version endpoint
|
||||
agent-browser connect "ws://localhost:9222/devtools/browser/abc123"
|
||||
|
||||
# Connect to remote browser service
|
||||
agent-browser connect "wss://browser-service.example.com/cdp?token=xyz"
|
||||
|
||||
# After connecting, run commands normally
|
||||
agent-browser snapshot
|
||||
agent-browser click @e1
|
||||
"##
|
||||
}
|
||||
|
||||
_ => return false,
|
||||
};
|
||||
println!("{}", help.trim());
|
||||
@@ -1351,7 +1391,7 @@ Core Commands:
|
||||
pdf <path> Save as PDF
|
||||
snapshot Accessibility tree with refs (for AI)
|
||||
eval <js> Run JavaScript
|
||||
connect <port> Connect to browser via CDP (e.g., connect 9222)
|
||||
connect <port|url> Connect to browser via CDP
|
||||
close Close browser
|
||||
|
||||
Navigation:
|
||||
|
||||
Reference in New Issue
Block a user