agent-browser 0.19.0

Fast browser automation CLI for AI agents
use std::time::Duration;

use super::types::BrowserVersionInfo;

pub async fn discover_cdp_url(port: u16) -> Result<String, String> {
    discover_cdp_url_with_request_timeout(port, Duration::from_secs(2)).await
}

pub async fn discover_cdp_url_with_request_timeout(
    port: u16,
    request_timeout: Duration,
) -> Result<String, String> {
    let url = format!("http://127.0.0.1:{}/json/version", port);

    let body = tokio::time::timeout(request_timeout, async { reqwest_get_string(&url).await })
        .await
        .map_err(|_| format!("Timeout connecting to CDP on port {}", port))?
        .map_err(|e| format!("Failed to connect to CDP on port {}: {}", port, e))?;

    let info: BrowserVersionInfo = serde_json::from_str(&body)
        .map_err(|e| format!("Invalid /json/version response: {}", e))?;

    info.web_socket_debugger_url
        .ok_or_else(|| format!("No webSocketDebuggerUrl in /json/version on port {}", port))
}

async fn reqwest_get_string(url: &str) -> Result<String, String> {
    let resp = reqwest::get(url).await.map_err(|e| e.to_string())?;
    resp.text().await.map_err(|e| e.to_string())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    use tokio::net::TcpListener;

    async fn spawn_json_server(body: &'static str) -> (u16, tokio::task::JoinHandle<()>) {
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let port = listener.local_addr().unwrap().port();
        let handle = tokio::spawn(async move {
            let (mut socket, _) = listener.accept().await.unwrap();
            let mut buf = [0u8; 1024];
            let _ = socket.read(&mut buf).await;
            let response = format!(
                "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n{}",
                body.len(),
                body
            );
            socket.write_all(response.as_bytes()).await.unwrap();
        });
        (port, handle)
    }

    #[tokio::test]
    async fn discovers_ws_url_from_json_version() {
        let (port, server) =
            spawn_json_server(r#"{"webSocketDebuggerUrl":"ws://127.0.0.1:1234/"}"#).await;

        let ws_url = discover_cdp_url(port).await.unwrap();
        assert_eq!(ws_url, "ws://127.0.0.1:1234/");
        server.await.unwrap();
    }

    #[tokio::test]
    async fn invalid_json_returns_parse_error() {
        let (port, server) = spawn_json_server("not-json").await;

        let err = discover_cdp_url(port).await.unwrap_err();
        assert!(err.contains("Invalid /json/version response"));
        server.await.unwrap();
    }
}