#![cfg(feature = "browser")]
use crate::brain::tools::browser::{
BrowserCloseTool, BrowserManager, BrowserNavigateTool, BrowserScreenshotTool,
};
use crate::brain::tools::{Tool, ToolExecutionContext};
use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;
const TEST_URL: &str = "https://example.com";
#[tokio::test]
#[ignore = "launches real Chrome — opt-in via `cargo test -- --ignored browser_e2e`"]
async fn navigate_then_screenshot_is_not_blank() {
let mgr = Arc::new(BrowserManager::new());
let nav = BrowserNavigateTool::new(mgr.clone());
let ctx = ToolExecutionContext::new(Uuid::new_v4());
let res = nav
.execute(serde_json::json!({ "url": TEST_URL }), &ctx)
.await
.expect("navigate tool must not panic");
assert!(
res.success,
"navigate to example.com must succeed: {}",
res.output
);
assert!(
!res.images.is_empty(),
"navigate must attach an auto-screenshot to the result"
);
let (_mime, b64) = &res.images[0];
let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64.as_bytes())
.expect("auto-screenshot must be valid base64");
assert!(
bytes.len() > 4096,
"auto-screenshot is suspiciously small ({} bytes) — \
likely captured a blank page (regression of the 2d09065e fix)",
bytes.len()
);
let close = BrowserCloseTool::new(mgr);
let _ = close.execute(serde_json::json!({}), &ctx).await;
}
#[tokio::test]
#[ignore = "launches real Chrome — opt-in via `cargo test -- --ignored browser_e2e`"]
async fn concurrent_screenshots_do_not_deadlock() {
let mgr = Arc::new(BrowserManager::new());
let nav = BrowserNavigateTool::new(mgr.clone());
let shot = Arc::new(BrowserScreenshotTool::new(mgr.clone()));
let ctx = ToolExecutionContext::new(Uuid::new_v4());
nav.execute(serde_json::json!({ "url": TEST_URL }), &ctx)
.await
.expect("seed navigate must succeed");
let shot_a = shot.clone();
let ctx_a = ctx.clone();
let shot_b = shot.clone();
let ctx_b = ctx.clone();
let combined = tokio::time::timeout(
Duration::from_secs(15),
futures::future::join(
async move { shot_a.execute(serde_json::json!({}), &ctx_a).await },
async move { shot_b.execute(serde_json::json!({}), &ctx_b).await },
),
)
.await;
let (a, b) = combined.expect(
"concurrent screenshots took >15s — likely deadlocked behind \
the manager mutex (regression of the 7f58c6f9 fix)",
);
assert!(a.unwrap().success);
assert!(b.unwrap().success);
let close = BrowserCloseTool::new(mgr);
let _ = close.execute(serde_json::json!({}), &ctx).await;
}
#[tokio::test]
#[ignore = "launches real Chrome — opt-in via `cargo test -- --ignored browser_e2e`"]
async fn close_actually_removes_session_page() {
let mgr = Arc::new(BrowserManager::new());
let nav = BrowserNavigateTool::new(mgr.clone());
let close = BrowserCloseTool::new(mgr.clone());
let ctx = ToolExecutionContext::new(Uuid::new_v4());
nav.execute(serde_json::json!({ "url": TEST_URL }), &ctx)
.await
.expect("navigate must succeed");
let key = BrowserManager::page_name_for_session(ctx.session_id);
assert!(
mgr.list_pages().await.contains(&key),
"after navigate, session must have an open page"
);
let close_res = close.execute(serde_json::json!({}), &ctx).await.unwrap();
assert!(
close_res.success,
"browser_close must succeed on an open session"
);
assert!(
!mgr.list_pages().await.contains(&key),
"after browser_close, the session's page must be gone from the manager"
);
let second = close.execute(serde_json::json!({}), &ctx).await.unwrap();
assert!(
second.success,
"browser_close must be idempotent — second call should not error"
);
}