use async_trait::async_trait;
use browsing::agent::views::ActionResult;
use browsing::browser::BrowserProfile;
use browsing::error::Result as BrowserUseResult;
use browsing::tools::service::Tools;
use browsing::tools::views::{ActionContext, ActionHandler, ActionModel, ActionParams};
use browsing::utils::extract_urls;
use serde_json::json;
use std::collections::HashMap;
#[tokio::test]
async fn test_tools_creation() {
let tools = Tools::new(vec![]);
assert!(!tools.registry.registry.actions.is_empty());
}
#[tokio::test]
async fn test_action_model_creation() {
let params_json = json!({
"query": "test"
});
let params: std::collections::HashMap<String, serde_json::Value> =
serde_json::from_value(params_json).unwrap();
let action = ActionModel {
action_type: "search".to_string(),
params,
};
assert_eq!(action.action_type, "search");
assert!(action.params.get("query").is_some());
}
#[test]
fn test_url_extraction() {
let text = "Visit https://example.com and http://test.org";
let urls = extract_urls(text);
assert!(urls.len() >= 2);
assert!(urls.iter().any(|u| u.contains("example.com")));
assert!(urls.iter().any(|u| u.contains("test.org")));
}
#[test]
fn test_url_extraction_complex() {
let text = r#"
Check out https://github.com/user/repo/issues/123
Also visit http://example.com/path?query=value#fragment
And www.example.com
"#;
let urls = extract_urls(text);
assert!(!urls.is_empty());
}
#[test]
fn test_action_model_serialization() {
let params_json = json!({
"url": "https://example.com",
"new_tab": false
});
let params: std::collections::HashMap<String, serde_json::Value> =
serde_json::from_value(params_json).unwrap();
let action = ActionModel {
action_type: "navigate".to_string(),
params,
};
let json_str = serde_json::to_string(&action).unwrap();
let deserialized: ActionModel = serde_json::from_str(&json_str).unwrap();
assert_eq!(deserialized.action_type, "navigate");
assert_eq!(
deserialized.params.get("url").and_then(|v| v.as_str()),
Some("https://example.com")
);
}
#[test]
fn test_action_model_all_actions() {
let actions = vec![
("search", json!({"query": "test"})),
("navigate", json!({"url": "https://example.com"})),
("click", json!({"index": 1})),
("input", json!({"index": 1, "text": "test"})),
("scroll", json!({"down": true, "pages": 1.0})),
("wait", json!({"seconds": 5})),
("send_keys", json!({"keys": "Enter"})),
("evaluate", json!({"expression": "1+1"})),
("find_text", json!({"text": "search"})),
("dropdown_options", json!({"index": 1})),
("select_dropdown", json!({"index": 1, "text": "option"})),
("upload_file", json!({"index": 1, "path": "/tmp/test.txt"})),
("extract", json!({"query": "extract data"})),
("done", json!({"text": "completed", "success": true})),
];
for (action_type, params_json) in actions {
let params: std::collections::HashMap<String, serde_json::Value> =
serde_json::from_value(params_json).unwrap();
let action = ActionModel {
action_type: action_type.to_string(),
params,
};
assert_eq!(action.action_type, action_type);
}
}
#[tokio::test]
async fn test_tools_action_registration() {
let tools = Tools::new(vec![]);
let default_actions = vec![
"search",
"navigate",
"click",
"input",
"done",
"switch",
"close",
"scroll",
"wait",
"send_keys",
"evaluate",
"find_text",
"dropdown_options",
"select_dropdown",
"upload_file",
"extract",
];
for action_name in default_actions {
assert!(
tools.registry.registry.actions.contains_key(action_name),
"Action '{action_name}' should be registered"
);
}
}
#[tokio::test]
async fn test_tools_custom_action_registration() {
struct TestActionHandler;
#[async_trait::async_trait]
impl ActionHandler for TestActionHandler {
async fn execute(
&self,
_params: &ActionParams,
_context: &mut ActionContext<'_>,
) -> BrowserUseResult<ActionResult> {
Ok(ActionResult {
extracted_content: Some("Custom action executed".to_string()),
..Default::default()
})
}
}
let mut tools = Tools::new(vec![]);
tools.register_custom_action(
"custom_test".to_string(),
"Test custom action".to_string(),
None,
TestActionHandler,
);
assert!(tools.registry.registry.actions.contains_key("custom_test"));
assert!(tools.registry.has_custom_handler("custom_test"));
}
#[tokio::test]
async fn test_tools_action_validation() {
let _tools = Tools::new(vec![]);
let params: HashMap<String, serde_json::Value> = serde_json::from_value(json!({
"text": "Test",
"success": true
}))
.unwrap();
let valid_action = ActionModel {
action_type: "done".to_string(),
params,
};
let invalid_action = ActionModel {
action_type: "nonexistent_action".to_string(),
params: HashMap::new(),
};
assert_eq!(valid_action.action_type, "done");
assert_eq!(invalid_action.action_type, "nonexistent_action");
}
#[test]
fn test_browser_profile_creation() {
let profile = BrowserProfile::default();
assert_eq!(profile.headless, None);
}
#[test]
fn test_action_result_completion_detection() {
let done_result = ActionResult {
is_done: Some(true),
success: Some(true),
..Default::default()
};
let not_done_result = ActionResult {
is_done: Some(false),
..Default::default()
};
assert_eq!(done_result.is_done, Some(true));
assert_eq!(not_done_result.is_done, Some(false));
}
#[test]
fn test_action_result_error_handling() {
let error_result = ActionResult {
error: Some("Test error".to_string()),
success: Some(false),
..Default::default()
};
assert!(error_result.error.is_some());
assert_eq!(error_result.success, Some(false));
}
#[tokio::test]
async fn test_tools_exclude_actions() {
let tools = Tools::new(vec!["search".to_string(), "navigate".to_string()]);
assert!(!tools.registry.registry.actions.contains_key("search"));
assert!(!tools.registry.registry.actions.contains_key("navigate"));
assert!(tools.registry.registry.actions.contains_key("done"));
assert!(tools.registry.registry.actions.contains_key("click"));
}
#[test]
fn test_url_extraction_edge_cases() {
let urls = extract_urls("");
assert!(urls.is_empty());
let urls = extract_urls("This is just plain text");
assert!(urls.is_empty());
let text = "Visit https://example.com and https://test.org and http://another.com";
let urls = extract_urls(text);
assert!(urls.len() >= 3);
}
#[test]
fn test_action_model_parameter_types() {
let params: HashMap<String, serde_json::Value> =
serde_json::from_value(json!({"query": "test"})).unwrap();
let action = ActionModel {
action_type: "search".to_string(),
params,
};
assert_eq!(
action.params.get("query").and_then(|v| v.as_str()),
Some("test")
);
let params: HashMap<String, serde_json::Value> =
serde_json::from_value(json!({"index": 5})).unwrap();
let action = ActionModel {
action_type: "click".to_string(),
params,
};
assert_eq!(action.params.get("index").and_then(|v| v.as_u64()), Some(5));
let params: HashMap<String, serde_json::Value> =
serde_json::from_value(json!({"down": true})).unwrap();
let action = ActionModel {
action_type: "scroll".to_string(),
params,
};
assert_eq!(
action.params.get("down").and_then(|v| v.as_bool()),
Some(true)
);
}
#[test]
fn test_action_model_nested_parameters() {
let params: HashMap<String, serde_json::Value> = serde_json::from_value(json!({
"url": "https://example.com",
"new_tab": false,
"wait_for": "load"
}))
.unwrap();
let action = ActionModel {
action_type: "navigate".to_string(),
params,
};
assert_eq!(
action.params.get("url").and_then(|v| v.as_str()),
Some("https://example.com")
);
assert_eq!(
action.params.get("new_tab").and_then(|v| v.as_bool()),
Some(false)
);
assert_eq!(
action.params.get("wait_for").and_then(|v| v.as_str()),
Some("load")
);
}
#[tokio::test]
async fn test_tools_registry_action_count() {
let tools = Tools::new(vec![]);
let action_count = tools.registry.registry.actions.len();
assert!(
action_count >= 10,
"Should have at least 10 default actions"
);
}
#[test]
fn test_action_result_serialization_roundtrip() {
let original = ActionResult {
is_done: Some(true),
success: Some(true),
error: None,
extracted_content: Some("Test content".to_string()),
long_term_memory: Some("Memory".to_string()),
..Default::default()
};
let json_str = serde_json::to_string(&original).unwrap();
let deserialized: ActionResult = serde_json::from_str(&json_str).unwrap();
assert_eq!(original.is_done, deserialized.is_done);
assert_eq!(original.extracted_content, deserialized.extracted_content);
assert_eq!(original.long_term_memory, deserialized.long_term_memory);
}
#[tokio::test]
async fn test_tools_action_parameter_extraction() {
let params: HashMap<String, serde_json::Value> = serde_json::from_value(json!({
"index": 1,
"text": "Hello World"
}))
.unwrap();
let action = ActionModel {
action_type: "input".to_string(),
params,
};
assert_eq!(action.params.get("index").and_then(|v| v.as_u64()), Some(1));
assert_eq!(
action.params.get("text").and_then(|v| v.as_str()),
Some("Hello World")
);
}
#[test]
fn test_comprehensive_action_model_coverage() {
let test_cases = vec![
("search", json!({"query": "test query"})),
(
"navigate",
json!({"url": "https://example.com", "new_tab": false}),
),
("click", json!({"index": 0})),
("input", json!({"index": 1, "text": "input text"})),
("scroll", json!({"down": true, "pages": 2.0})),
("wait", json!({"seconds": 5})),
("send_keys", json!({"keys": "Enter Tab"})),
("evaluate", json!({"expression": "document.title"})),
("find_text", json!({"text": "search text"})),
("dropdown_options", json!({"index": 0})),
("select_dropdown", json!({"index": 0, "text": "option"})),
("upload_file", json!({"index": 0, "path": "/path/to/file"})),
(
"extract",
json!({"query": "extract query", "extract_links": false}),
),
("done", json!({"text": "Task done", "success": true})),
];
for (action_type, params_json) in test_cases {
let params: HashMap<String, serde_json::Value> =
serde_json::from_value(params_json).unwrap();
let action = ActionModel {
action_type: action_type.to_string(),
params: params.clone(),
};
assert_eq!(action.action_type, action_type);
assert_eq!(action.params.len(), params.len());
}
}