use browsing::agent::views::{
ActionResult, AgentHistory, AgentHistoryList, AgentSettings, AgentState,
};
use browsing::error::BrowsingError;
use browsing::llm::base::{ChatInvokeUsage, ChatMessage};
use browsing::tokens::views::UsageSummary;
use std::collections::HashMap;
#[test]
fn test_agent_task_validation() {
let valid_tasks = vec![
"Navigate to example.com and click the first link",
"Search for 'rust programming' and extract the first result",
"Fill out the contact form with test data",
"Extract all product prices from the page",
"Scroll to the bottom and load more content",
];
for task in valid_tasks {
assert!(!task.is_empty(), "Agent task should not be empty");
assert!(task.len() <= 4096, "Agent task should be reasonable length");
}
}
#[test]
fn test_agent_max_steps_validation() {
let valid_max_steps = vec![1u32, 10, 50, 100, 500];
for max_steps in valid_max_steps {
assert!(max_steps > 0, "Max steps should be positive");
assert!(max_steps <= 1000, "Max steps should be reasonable");
}
}
#[test]
fn test_agent_default_state() {
let _state = AgentState::default();
assert!(true, "AgentState created successfully");
}
#[test]
fn test_agent_settings_default() {
let _settings = AgentSettings::default();
assert!(true, "AgentSettings created successfully");
}
#[test]
fn test_agent_settings_system_message() {
let system_messages = vec![
"You are a helpful assistant.",
"Navigate the web and complete tasks.",
"Extract information from web pages efficiently.",
];
for msg in system_messages {
assert!(!msg.is_empty(), "System message should not be empty");
}
}
#[test]
fn test_agent_settings_validation() {
let settings = AgentSettings {
override_system_message: Some("Custom system message".to_string()),
..Default::default()
};
assert!(settings.override_system_message.is_some());
}
#[test]
fn test_agent_history_creation() {
use browsing::browser::views::BrowserStateHistory;
let history = AgentHistory {
model_output: None,
result: vec![ActionResult {
extracted_content: Some("Test content".to_string()),
..Default::default()
}],
state: BrowserStateHistory {
url: "https://example.com".to_string(),
title: "Example".to_string(),
tabs: vec![],
interacted_element: vec![],
screenshot_path: None,
},
metadata: None,
state_message: None,
};
assert_eq!(history.result.len(), 1);
assert!(history.result[0].extracted_content.is_some());
}
#[test]
fn test_agent_history_step_validation() {
let valid_steps = vec![0u32, 1, 10, 100, 999];
for step in valid_steps {
assert!(step < u32::MAX, "Step should be valid");
}
}
#[test]
fn test_agent_history_list_creation() {
let history_list = AgentHistoryList {
history: vec![],
usage: None,
};
assert!(history_list.history.is_empty());
assert!(history_list.usage.is_none());
}
#[test]
fn test_agent_history_list_with_entries() {
use browsing::browser::views::BrowserStateHistory;
let state = BrowserStateHistory {
url: "https://example.com".to_string(),
title: "Example".to_string(),
tabs: vec![],
interacted_element: vec![],
screenshot_path: None,
};
let history_list = AgentHistoryList {
history: vec![
AgentHistory {
model_output: None,
result: vec![ActionResult::default()],
state: state.clone(),
metadata: None,
state_message: None,
},
AgentHistory {
model_output: None,
result: vec![ActionResult::default()],
state: state.clone(),
metadata: None,
state_message: None,
},
],
usage: None,
};
assert_eq!(history_list.history.len(), 2);
}
#[test]
fn test_usage_tracker_initial_state() {
let prompt_tokens = 0u32;
let completion_tokens = 0u32;
let total_tokens = 0u32;
assert_eq!(prompt_tokens, 0);
assert_eq!(completion_tokens, 0);
assert_eq!(total_tokens, 0);
}
#[test]
fn test_usage_tracker_add_usage() {
let mut total_prompt_tokens = 0u32;
let mut total_completion_tokens = 0u32;
let mut total_tokens = 0u32;
let usage1 = ChatInvokeUsage {
prompt_tokens: 100,
prompt_cached_tokens: None,
prompt_cache_creation_tokens: None,
prompt_image_tokens: None,
completion_tokens: 50,
total_tokens: 150,
};
total_prompt_tokens += usage1.prompt_tokens;
total_completion_tokens += usage1.completion_tokens;
total_tokens += usage1.total_tokens;
assert_eq!(total_prompt_tokens, 100);
assert_eq!(total_completion_tokens, 50);
assert_eq!(total_tokens, 150);
let usage2 = ChatInvokeUsage {
prompt_tokens: 200,
prompt_cached_tokens: None,
prompt_cache_creation_tokens: None,
prompt_image_tokens: None,
completion_tokens: 100,
total_tokens: 300,
};
total_prompt_tokens += usage2.prompt_tokens;
total_completion_tokens += usage2.completion_tokens;
total_tokens += usage2.total_tokens;
assert_eq!(total_prompt_tokens, 300);
assert_eq!(total_completion_tokens, 150);
assert_eq!(total_tokens, 450);
}
#[test]
fn test_agent_usage_tracking() {
let usage = ChatInvokeUsage {
prompt_tokens: 100,
prompt_cached_tokens: None,
prompt_cache_creation_tokens: None,
prompt_image_tokens: None,
completion_tokens: 50,
total_tokens: 150,
};
assert_eq!(usage.prompt_tokens, 100);
assert_eq!(usage.completion_tokens, 50);
assert_eq!(usage.total_tokens, 150);
assert_eq!(
usage.total_tokens,
usage.prompt_tokens + usage.completion_tokens
);
let summary = UsageSummary {
prompt_tokens: Some(200),
completion_tokens: Some(100),
total_tokens: Some(300),
cost: Some(0.003),
};
assert_eq!(summary.prompt_tokens, Some(200));
assert_eq!(summary.completion_tokens, Some(100));
assert!(summary.cost.is_some() && summary.cost.unwrap() > 0.0);
}
#[test]
fn test_agent_with_custom_settings() {
use browsing::agent::views::VisionMode;
let settings = AgentSettings {
use_vision: VisionMode::Enabled(true),
max_failures: 3,
..Default::default()
};
matches!(settings.use_vision, VisionMode::Enabled(_));
assert_eq!(settings.max_failures, 3);
}
#[test]
fn test_usage_summary_none_values() {
let summary = UsageSummary {
prompt_tokens: None,
completion_tokens: None,
total_tokens: None,
cost: None,
};
assert!(summary.prompt_tokens.is_none());
assert!(summary.completion_tokens.is_none());
assert!(summary.total_tokens.is_none());
assert!(summary.cost.is_none());
}
#[test]
fn test_cost_calculation() {
let prompt_tokens = 1000u32;
let completion_tokens = 500u32;
let prompt_price_per_1k = 0.0001; let completion_price_per_1k = 0.0002;
let prompt_cost = (prompt_tokens as f64 / 1000.0) * prompt_price_per_1k;
let completion_cost = (completion_tokens as f64 / 1000.0) * completion_price_per_1k;
let total_cost = prompt_cost + completion_cost;
assert!((prompt_cost - 0.0001).abs() < 0.00001);
assert!((completion_cost - 0.0001).abs() < 0.00001);
assert!((total_cost - 0.0002).abs() < 0.00001);
}
#[test]
fn test_chat_message_role_validation() {
let valid_roles = vec!["system", "user", "assistant", "tool"];
for role in valid_roles {
assert!(!role.is_empty(), "Role should not be empty");
assert!(role.len() <= 20, "Role should be short");
}
}
#[test]
fn test_chat_message_content_validation() {
let valid_contents = vec![
"You are a helpful assistant.",
"Please navigate to the website and extract information.",
"The task is complete.",
"",
];
for content in valid_contents {
assert!(
content.len() <= 65536,
"Content should be reasonable length"
);
}
}
#[test]
fn test_chat_message_serialization() {
let message = ChatMessage {
role: "user".to_string(),
content: "Test message".to_string(),
};
assert_eq!(message.role, "user");
assert_eq!(message.content, "Test message");
}
#[test]
fn test_agent_state_transitions() {
let states = vec!["idle", "running", "paused", "completed", "error"];
for state in states {
assert!(!state.is_empty(), "State should not be empty");
}
}
#[test]
fn test_agent_state_serialization() {
let state = AgentState {
agent_id: "test-agent-123".to_string(),
n_steps: 5,
..Default::default()
};
assert_eq!(state.agent_id, "test-agent-123");
assert_eq!(state.n_steps, 5);
}
#[test]
fn test_agent_state_with_selector_map() {
use browsing::dom::views::DOMInteractedElement;
let mut selector_map = HashMap::new();
selector_map.insert(
1u32,
DOMInteractedElement {
index: 1,
backend_node_id: Some(123),
tag: "button".to_string(),
text: Some("Click".to_string()),
attributes: HashMap::new(),
selector: None,
},
);
assert_eq!(selector_map.len(), 1);
assert!(selector_map.contains_key(&1));
}
#[test]
fn test_action_model_validation() {
let valid_actions = vec![
"search",
"navigate",
"click",
"input",
"scroll",
"extract_content",
"create_tab",
"switch_tab",
];
for action in valid_actions {
assert!(!action.is_empty(), "Action should not be empty");
}
}
#[test]
fn test_action_parameters_validation() {
let test_cases = vec![
("url", "https://example.com"),
("query", "search term"),
("index", "1"),
("text", "input text"),
("pages", "1.5"),
];
for (key, value) in test_cases {
assert!(!key.is_empty(), "Parameter key should not be empty");
assert!(!value.is_empty(), "Parameter value should not be empty");
}
}
#[test]
fn test_action_result_validation() {
let result = ActionResult {
extracted_content: Some("Content".to_string()),
long_term_memory: Some("Memory".to_string()),
is_done: Some(true),
..Default::default()
};
assert_eq!(result.extracted_content, Some("Content".to_string()));
assert_eq!(result.long_term_memory, Some("Memory".to_string()));
assert_eq!(result.is_done, Some(true));
}
#[test]
fn test_agent_error_max_steps_exceeded() {
let max_steps = 10u32;
let current_step = 11u32;
let exceeded = current_step > max_steps;
assert!(exceeded, "Should detect max steps exceeded");
}
#[test]
fn test_agent_error_invalid_action() {
let invalid_action = "invalid_action";
let valid_actions = vec!["search", "navigate", "click"];
let is_valid = valid_actions.contains(&invalid_action);
assert!(!is_valid, "Should detect invalid action");
}
#[test]
fn test_agent_error_missing_parameter() {
let required_params = vec!["url", "query"];
let provided_params = vec!["url"];
let has_all_required = required_params.iter().all(|p| provided_params.contains(p));
assert!(!has_all_required, "Should detect missing parameter");
}
#[test]
fn test_agent_recovery_from_failure() {
let retry_attempts = vec![1u32, 2, 3];
for attempt in retry_attempts {
assert!(attempt <= 3, "Should limit retry attempts");
}
}
#[test]
fn test_json_extraction_valid() {
let valid_json = r#"{"action": "click", "index": 1}"#;
let parsed: Result<serde_json::Value, _> = serde_json::from_str(valid_json);
assert!(parsed.is_ok(), "Should parse valid JSON");
}
#[test]
fn test_json_extraction_invalid() {
let invalid_json = r#"{action: "click", index: 1}"#;
let parsed: Result<serde_json::Value, _> = serde_json::from_str(invalid_json);
assert!(parsed.is_err(), "Should fail to parse invalid JSON");
}
#[test]
fn test_json_extraction_malformed() {
let malformed_json = r#"{"action": "click", "index": }"#;
let parsed: Result<serde_json::Value, _> = serde_json::from_str(malformed_json);
assert!(parsed.is_err(), "Should fail to parse malformed JSON");
}
#[test]
fn test_json_repair_attempt() {
let broken_json = r#"{action: click, index: 1}"#;
let repaired = broken_json
.replace(r#"{"#, r#"{"action":"#)
.replace(r#":"click"#, r#"","action":"click"#)
.replace(r#"index: "#, r#""index": "#);
assert!(!repaired.is_empty());
}
#[test]
fn test_agent_execution_flow() {
let tasks = vec![
"Navigate to example.com",
"Find the search button",
"Click submit",
];
for task in tasks {
assert!(!task.is_empty(), "Agent task should not be empty");
assert!(task.len() > 5, "Agent task should be descriptive");
}
let action_result = ActionResult::default();
assert!(action_result.extracted_content.is_none());
assert!(action_result.error.is_none());
}
#[test]
fn test_agent_with_multiple_steps() {
use browsing::agent::views::{AgentHistory, StepMetadata};
use browsing::browser::views::BrowserStateHistory;
let history_list = AgentHistoryList {
history: vec![
AgentHistory {
model_output: None,
result: vec![],
state: BrowserStateHistory {
url: "https://example.com".to_string(),
title: "Example".to_string(),
tabs: vec![],
interacted_element: vec![],
screenshot_path: None,
},
metadata: Some(StepMetadata {
step_start_time: chrono::Utc::now().timestamp() as f64,
step_end_time: chrono::Utc::now().timestamp() as f64,
step_number: 1,
}),
state_message: Some("Step 1 completed".to_string()),
},
AgentHistory {
model_output: None,
result: vec![],
state: BrowserStateHistory {
url: "https://example.com".to_string(),
title: "Example".to_string(),
tabs: vec![],
interacted_element: vec![],
screenshot_path: None,
},
metadata: Some(StepMetadata {
step_start_time: chrono::Utc::now().timestamp() as f64,
step_end_time: chrono::Utc::now().timestamp() as f64,
step_number: 2,
}),
state_message: Some("Step 2 completed".to_string()),
},
],
usage: None,
};
assert_eq!(history_list.history.len(), 2);
for history in &history_list.history {
assert!(history.metadata.is_some());
}
}
#[test]
fn test_agent_error_recovery() {
use browsing::agent::views::{AgentHistory, StepMetadata};
use browsing::browser::views::BrowserStateHistory;
let error_messages = vec![
"Navigation failed".to_string(),
"Timeout occurred".to_string(),
"Invalid action".to_string(),
];
for error_msg in error_messages {
assert!(!error_msg.is_empty());
let history = AgentHistory {
model_output: None,
result: vec![],
state: BrowserStateHistory {
url: "https://example.com".to_string(),
title: "Example".to_string(),
tabs: vec![],
interacted_element: vec![],
screenshot_path: None,
},
metadata: None,
state_message: Some(error_msg),
};
assert!(history.state_message.is_some());
}
}
#[test]
fn test_agent_state_snapshots() {
use browsing::agent::views::{AgentHistory, AgentOutput};
use browsing::browser::views::BrowserStateHistory;
use serde_json::json;
let history = AgentHistory {
model_output: Some(AgentOutput {
action: vec![json!({
"action_type": "navigate",
"params": {"url": "https://example.com"}
})],
thinking: Some("Navigate to example.com".to_string()),
evaluation_previous_goal: None,
memory: None,
next_goal: None,
}),
result: vec![ActionResult {
is_done: Some(false),
success: Some(true),
judgement: None,
error: None,
attachments: None,
images: None,
long_term_memory: None,
extracted_content: Some("Navigation successful".to_string()),
include_extracted_content_only_once: false,
metadata: None,
}],
state: BrowserStateHistory {
url: "https://example.com".to_string(),
title: "Example".to_string(),
tabs: vec![],
interacted_element: vec![],
screenshot_path: None,
},
metadata: None,
state_message: None,
};
assert!(history.model_output.is_some());
assert!(!history.result.is_empty());
assert_eq!(history.state.url, "https://example.com");
}