use browsing::actor::Page;
use browsing::browser::cdp::CdpClient;
use browsing::browser::views::{SessionInfo, TabInfo};
use browsing::observability::{RecorderConfig, SessionRecorder};
use browsing::tools::service::Tools;
use browsing::tools::views::{ActionModel, RegisteredAction};
use browsing::traits::BrowserClient;
use serde_json::json;
use std::collections::HashMap;
use std::sync::Arc;
struct MockBrowserClient {
current_url: String,
}
impl MockBrowserClient {
fn new() -> Self {
Self {
current_url: "about:blank".to_string(),
}
}
}
#[async_trait::async_trait]
impl BrowserClient for MockBrowserClient {
async fn start(&mut self) -> browsing::error::Result<()> {
Ok(())
}
async fn navigate(&mut self, url: &str) -> browsing::error::Result<()> {
self.current_url = url.to_string();
Ok(())
}
async fn get_current_url(&self) -> browsing::error::Result<String> {
Ok(self.current_url.clone())
}
async fn create_tab(&mut self, _url: Option<&str>) -> browsing::error::Result<String> {
Ok("mock-tab".to_string())
}
async fn switch_to_tab(&mut self, _target_id: &str) -> browsing::error::Result<()> {
Ok(())
}
async fn close_tab(&mut self, _target_id: &str) -> browsing::error::Result<()> {
Ok(())
}
async fn get_tabs(&self) -> browsing::error::Result<Vec<TabInfo>> {
Ok(vec![])
}
async fn get_target_id_from_tab_id(&self, _tab_id: &str) -> browsing::error::Result<String> {
Ok("mock-target".to_string())
}
fn get_page(&self) -> browsing::error::Result<Page> {
Err(browsing::error::BrowsingError::Browser(
"Mock browser".to_string(),
))
}
async fn take_screenshot(
&self,
_path: Option<&str>,
_full_page: bool,
) -> browsing::error::Result<Vec<u8>> {
Ok(vec![])
}
async fn get_current_page_title(&self) -> browsing::error::Result<String> {
Ok("Mock".to_string())
}
fn get_cdp_client(&self) -> browsing::error::Result<Arc<CdpClient>> {
Err(browsing::error::BrowsingError::Browser(
"Mock browser".to_string(),
))
}
fn get_session_id(&self) -> browsing::error::Result<String> {
Ok("mock-session".to_string())
}
fn get_current_target_id(&self) -> browsing::error::Result<String> {
Ok("mock-target".to_string())
}
async fn get_session_info(&self) -> browsing::error::Result<SessionInfo> {
Ok(SessionInfo {
url: self.current_url.clone(),
title: "Mock".to_string(),
target_id: "mock-target".to_string(),
session_id: "mock-session".to_string(),
})
}
}
#[test]
fn test_tools_registry_actions() {
let tools = Tools::new(vec![]);
assert!(tools.registry.registry.actions.contains_key("search"));
assert!(tools.registry.registry.actions.contains_key("navigate"));
assert!(tools.registry.registry.actions.contains_key("click"));
assert!(tools.registry.registry.actions.contains_key("done"));
}
#[test]
fn test_tools_exclude_actions() {
let tools = Tools::new(vec!["search".to_string(), "click".to_string()]);
assert!(tools.registry.registry.actions.contains_key("navigate"));
}
#[test]
fn test_registered_action_structure() {
let action = RegisteredAction {
name: "test_action".to_string(),
description: "Test action".to_string(),
domains: None,
handler: None,
};
assert_eq!(action.name, "test_action");
assert_eq!(action.description, "Test action");
assert!(action.domains.is_none());
}
#[test]
fn test_action_model_get_index() {
let mut params = HashMap::new();
params.insert("index".to_string(), json!(5));
let action = ActionModel {
action_type: "click".to_string(),
params,
};
assert_eq!(action.action_type, "click");
assert!(action.params.contains_key("index"));
}
#[test]
fn test_action_model_set_index() {
let mut params = HashMap::new();
params.insert("index".to_string(), json!(1));
let mut action = ActionModel {
action_type: "click".to_string(),
params,
};
action.set_index(10);
assert_eq!(action.action_type, "click");
}
#[tokio::test]
async fn test_tools_act_records_dom_snapshot() {
let tools = Tools::new(vec![]);
let mut browser = MockBrowserClient::new();
let mut recorder = SessionRecorder::new(RecorderConfig {
capture_dom: true,
max_dom_chars: 1000,
max_steps: 100,
});
let mut params = HashMap::new();
params.insert("url".to_string(), json!("https://example.com"));
let action = ActionModel {
action_type: "navigate".to_string(),
params,
};
let dom = "<html><body>test</body></html>".to_string();
let result = tools
.act(action, &mut browser, None, None, Some(&mut recorder), Some(dom.clone()))
.await;
assert!(result.is_ok());
assert_eq!(recorder.len(), 1);
assert_eq!(recorder.steps()[0].dom_snapshot, Some(dom));
}
#[tokio::test]
async fn test_tools_act_omits_dom_when_disabled() {
let tools = Tools::new(vec![]);
let mut browser = MockBrowserClient::new();
let mut recorder = SessionRecorder::new(RecorderConfig {
capture_dom: false,
max_dom_chars: 0,
max_steps: 100,
});
let mut params = HashMap::new();
params.insert("url".to_string(), json!("https://example.com"));
let action = ActionModel {
action_type: "navigate".to_string(),
params,
};
let result = tools
.act(
action,
&mut browser,
None,
None,
Some(&mut recorder),
Some("<html></html>".to_string()),
)
.await;
assert!(result.is_ok());
assert_eq!(recorder.len(), 1);
assert!(recorder.steps()[0].dom_snapshot.is_none());
}