browser_use/tools/
navigate.rs1use crate::error::Result;
2use crate::tools::{Tool, ToolContext, ToolResult};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6fn normalize_url(url: &str) -> String {
8 let trimmed = url.trim();
9
10 if trimmed.starts_with("http://")
12 || trimmed.starts_with("https://")
13 || trimmed.starts_with("file://")
14 || trimmed.starts_with("data:")
15 || trimmed.starts_with("about:")
16 || trimmed.starts_with("chrome://")
17 || trimmed.starts_with("chrome-extension://")
18 {
19 return trimmed.to_string();
20 }
21
22 if trimmed.starts_with('/') || trimmed.starts_with("./") || trimmed.starts_with("../") {
24 return trimmed.to_string();
25 }
26
27 if trimmed.starts_with("localhost") || trimmed.starts_with("127.0.0.1") {
29 return format!("http://{}", trimmed);
30 }
31
32 if trimmed.contains('.') {
34 return format!("https://{}", trimmed);
36 }
37
38 format!("https://www.{}.com", trimmed)
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
45pub struct NavigateParams {
46 pub url: String,
48
49 #[serde(default = "default_wait")]
51 pub wait_for_load: bool,
52}
53
54fn default_wait() -> bool {
55 true
56}
57
58#[derive(Default)]
60pub struct NavigateTool;
61
62impl Tool for NavigateTool {
63 type Params = NavigateParams;
64
65 fn name(&self) -> &str {
66 "navigate"
67 }
68
69 fn execute_typed(
70 &self,
71 params: NavigateParams,
72 context: &mut ToolContext,
73 ) -> Result<ToolResult> {
74 let normalized_url = normalize_url(¶ms.url);
76
77 context.session.navigate(&normalized_url)?;
79
80 if params.wait_for_load {
82 context.session.wait_for_navigation()?;
83 }
84
85 Ok(ToolResult::success_with(serde_json::json!({
86 "original_url": params.url,
87 "normalized_url": normalized_url,
88 "waited": params.wait_for_load
89 })))
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_navigate_params_default() {
99 let json = serde_json::json!({
100 "url": "https://example.com"
101 });
102
103 let params: NavigateParams = serde_json::from_value(json).unwrap();
104 assert_eq!(params.url, "https://example.com");
105 assert!(params.wait_for_load);
106 }
107
108 #[test]
109 fn test_navigate_params_explicit_wait() {
110 let json = serde_json::json!({
111 "url": "https://example.com",
112 "wait_for_load": false
113 });
114
115 let params: NavigateParams = serde_json::from_value(json).unwrap();
116 assert_eq!(params.url, "https://example.com");
117 assert!(!params.wait_for_load);
118 }
119
120 #[test]
121 fn test_navigate_tool_metadata() {
122 let tool = NavigateTool;
123 assert_eq!(tool.name(), "navigate");
124 let schema = tool.parameters_schema();
125 assert!(schema.is_object());
126 }
127
128 #[test]
129 fn test_normalize_url_complete() {
130 assert_eq!(normalize_url("https://example.com"), "https://example.com");
131 assert_eq!(normalize_url("http://example.com"), "http://example.com");
132 assert_eq!(
133 normalize_url("https://example.com/path"),
134 "https://example.com/path"
135 );
136 }
137
138 #[test]
139 fn test_normalize_url_missing_protocol() {
140 assert_eq!(normalize_url("example.com"), "https://example.com");
141 assert_eq!(
142 normalize_url("example.com/path"),
143 "https://example.com/path"
144 );
145 assert_eq!(
146 normalize_url("sub.example.com"),
147 "https://sub.example.com"
148 );
149 }
150
151 #[test]
152 fn test_normalize_url_partial_domain() {
153 assert_eq!(normalize_url("google"), "https://www.google.com");
154 assert_eq!(normalize_url("github"), "https://www.github.com");
155 assert_eq!(normalize_url("amazon"), "https://www.amazon.com");
156 }
157
158 #[test]
159 fn test_normalize_url_localhost() {
160 assert_eq!(normalize_url("localhost"), "http://localhost");
161 assert_eq!(normalize_url("localhost:3000"), "http://localhost:3000");
162 assert_eq!(normalize_url("127.0.0.1"), "http://127.0.0.1");
163 assert_eq!(normalize_url("127.0.0.1:8080"), "http://127.0.0.1:8080");
164 }
165
166 #[test]
167 fn test_normalize_url_special_protocols() {
168 assert_eq!(normalize_url("about:blank"), "about:blank");
169 assert_eq!(normalize_url("file:///path/to/file"), "file:///path/to/file");
170 assert_eq!(
171 normalize_url("data:text/html,<h1>Test</h1>"),
172 "data:text/html,<h1>Test</h1>"
173 );
174 assert_eq!(normalize_url("chrome://settings"), "chrome://settings");
175 }
176
177 #[test]
178 fn test_normalize_url_relative_paths() {
179 assert_eq!(normalize_url("/path"), "/path");
180 assert_eq!(normalize_url("/path/to/page"), "/path/to/page");
181 assert_eq!(normalize_url("./relative"), "./relative");
182 assert_eq!(normalize_url("../parent"), "../parent");
183 }
184
185 #[test]
186 fn test_normalize_url_whitespace() {
187 assert_eq!(normalize_url(" example.com "), "https://example.com");
188 assert_eq!(
189 normalize_url(" https://example.com "),
190 "https://example.com"
191 );
192 }
193}