browser_use/browser/
session.rs1use crate::browser::config::{ConnectionOptions, LaunchOptions};
2use crate::dom::DomTree;
3use crate::error::{BrowserError, Result};
4use crate::tools::{ToolContext, ToolRegistry};
5use headless_chrome::{Browser, Tab};
6use std::ffi::OsStr;
7use std::sync::Arc;
8use std::time::Duration;
9
10pub struct TabElement<'a> {
12 pub tab: Arc<Tab>,
13 pub element: headless_chrome::Element<'a>,
14}
15
16pub struct BrowserSession {
18 browser: Browser,
20
21 tool_registry: ToolRegistry,
23}
24
25impl BrowserSession {
26 pub fn launch(options: LaunchOptions) -> Result<Self> {
28 let mut launch_opts = headless_chrome::LaunchOptions::default();
29
30 launch_opts
32 .ignore_default_args
33 .push(OsStr::new("--enable-automation"));
34 launch_opts
35 .args
36 .push(OsStr::new("--disable-blink-features=AutomationControlled"));
37
38 launch_opts.idle_browser_timeout = Duration::from_secs(60 * 60);
40
41 launch_opts.headless = options.headless;
43
44 launch_opts.window_size = Some((options.window_width, options.window_height));
46
47 if let Some(path) = options.chrome_path {
49 launch_opts.path = Some(path);
50 }
51
52 if let Some(dir) = options.user_data_dir {
54 launch_opts.user_data_dir = Some(dir);
55 }
56
57 launch_opts.sandbox = options.sandbox;
59
60 let browser =
62 Browser::new(launch_opts).map_err(|e| BrowserError::LaunchFailed(e.to_string()))?;
63
64 browser
65 .new_tab()
66 .map_err(|e| BrowserError::LaunchFailed(format!("Failed to create tab: {}", e)))?;
67
68 Ok(Self {
69 browser,
70 tool_registry: ToolRegistry::with_defaults(),
71 })
72 }
73
74 pub fn connect(options: ConnectionOptions) -> Result<Self> {
76 let browser = Browser::connect(options.ws_url)
77 .map_err(|e| BrowserError::ConnectionFailed(e.to_string()))?;
78
79 Ok(Self {
80 browser,
81 tool_registry: ToolRegistry::with_defaults(),
82 })
83 }
84
85 pub fn new() -> Result<Self> {
87 Self::launch(LaunchOptions::default())
88 }
89
90 pub fn tab(&self) -> Result<Arc<Tab>> {
92 self.get_active_tab()
93 }
94
95 pub fn new_tab(&mut self) -> Result<Arc<Tab>> {
97 let tab = self.browser.new_tab().map_err(|e| {
98 BrowserError::TabOperationFailed(format!("Failed to create tab: {}", e))
99 })?;
100 Ok(tab)
101 }
102
103 pub fn get_tabs(&self) -> Result<Vec<Arc<Tab>>> {
105 let tabs = self
106 .browser
107 .get_tabs()
108 .lock()
109 .map_err(|e| BrowserError::TabOperationFailed(format!("Failed to get tabs: {}", e)))?
110 .clone();
111
112 Ok(tabs)
113 }
114
115 pub fn get_active_tab(&self) -> Result<Arc<Tab>> {
117 let tabs = self.get_tabs()?;
118
119 for tab in &tabs {
121 let result = tab.evaluate(
122 "document.visibilityState === 'visible' && document.hasFocus()",
123 false,
124 );
125 match result {
126 Ok(remote_object) => {
127 if let Some(value) = remote_object.value {
128 if value.as_bool().unwrap_or(false) {
129 return Ok(tab.clone());
130 }
131 }
132 }
133 Err(e) => {
134 log::debug!("Failed to check tab status: {}", e);
135 continue;
136 }
137 }
138 }
139
140 for tab in &tabs {
142 let result = tab.evaluate("document.visibilityState === 'visible'", false);
143 match result {
144 Ok(remote_object) => {
145 if let Some(value) = remote_object.value {
146 if value.as_bool().unwrap_or(false) {
147 return Ok(tab.clone());
148 }
149 }
150 }
151 Err(_) => continue,
152 }
153 }
154
155 Err(BrowserError::TabOperationFailed(
156 "No active tab found".to_string(),
157 ))
158 }
159
160 pub fn close_active_tab(&mut self) -> Result<()> {
162 self.tab()?
163 .close(true)
164 .map_err(|e| BrowserError::TabOperationFailed(format!("Failed to close tab: {}", e)))?;
165
166 Ok(())
167 }
168
169 pub fn browser(&self) -> &Browser {
171 &self.browser
172 }
173
174 pub fn navigate(&self, url: &str) -> Result<()> {
176 self.tab()?.navigate_to(url).map_err(|e| {
177 BrowserError::NavigationFailed(format!("Failed to navigate to {}: {}", url, e))
178 })?;
179
180 Ok(())
181 }
182
183 pub fn wait_for_navigation(&self) -> Result<()> {
185 self.tab()?
186 .wait_until_navigated()
187 .map_err(|e| BrowserError::NavigationFailed(format!("Navigation timeout: {}", e)))?;
188
189 Ok(())
190 }
191
192 pub fn extract_dom(&self) -> Result<DomTree> {
194 DomTree::from_tab(&self.tab()?)
195 }
196
197 pub fn extract_dom_with_prefix(&self, prefix: &str) -> Result<DomTree> {
199 DomTree::from_tab_with_prefix(&self.tab()?, prefix)
200 }
201
202 pub fn find_element<'a>(
204 &self,
205 tab: &'a Arc<Tab>,
206 css_selector: &str,
207 ) -> Result<headless_chrome::Element<'a>> {
208 tab.find_element(css_selector).map_err(|e| {
209 BrowserError::ElementNotFound(format!("Element '{}' not found: {}", css_selector, e))
210 })
211 }
212
213 pub fn tool_registry(&self) -> &ToolRegistry {
215 &self.tool_registry
216 }
217
218 pub fn tool_registry_mut(&mut self) -> &mut ToolRegistry {
220 &mut self.tool_registry
221 }
222
223 pub fn execute_tool(
225 &self,
226 name: &str,
227 params: serde_json::Value,
228 ) -> Result<crate::tools::ToolResult> {
229 let mut context = ToolContext::new(self);
230 self.tool_registry.execute(name, params, &mut context)
231 }
232
233 pub fn go_back(&self) -> Result<()> {
235 let go_back_js = r#"
236 (function() {
237 window.history.back();
238 return true;
239 })()
240 "#;
241
242 self.tab()?
243 .evaluate(go_back_js, false)
244 .map_err(|e| BrowserError::NavigationFailed(format!("Failed to go back: {}", e)))?;
245
246 std::thread::sleep(std::time::Duration::from_millis(300));
248
249 Ok(())
250 }
251
252 pub fn go_forward(&self) -> Result<()> {
254 let go_forward_js = r#"
255 (function() {
256 window.history.forward();
257 return true;
258 })()
259 "#;
260
261 self.tab()?
262 .evaluate(go_forward_js, false)
263 .map_err(|e| BrowserError::NavigationFailed(format!("Failed to go forward: {}", e)))?;
264
265 std::thread::sleep(std::time::Duration::from_millis(300));
267
268 Ok(())
269 }
270
271 pub fn close(&self) -> Result<()> {
273 let tabs = self.get_tabs()?;
277 for tab in tabs {
278 let _ = tab.close(false); }
280 Ok(())
281 }
282}
283
284impl Default for BrowserSession {
285 fn default() -> Self {
286 Self::new().expect("Failed to create default browser session")
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_launch_options_builder() {
296 let opts = LaunchOptions::new().headless(true).window_size(800, 600);
297
298 assert!(opts.headless);
299 assert_eq!(opts.window_width, 800);
300 assert_eq!(opts.window_height, 600);
301 }
302
303 #[test]
304 fn test_connection_options() {
305 let opts = ConnectionOptions::new("ws://localhost:9222").timeout(5000);
306
307 assert_eq!(opts.ws_url, "ws://localhost:9222");
308 assert_eq!(opts.timeout, 5000);
309 }
310
311 #[test]
312 #[ignore]
313 fn test_get_active_tab() {
314 let session = BrowserSession::launch(LaunchOptions::new().headless(true))
315 .expect("Failed to launch browser");
316
317 let tab = session.get_active_tab();
318 assert!(tab.is_ok());
319 }
320
321 #[test]
323 #[ignore] fn test_launch_browser() {
325 let result = BrowserSession::launch(LaunchOptions::new().headless(true));
326 assert!(result.is_ok());
327 }
328
329 #[test]
330 #[ignore]
331 fn test_navigate() {
332 let session = BrowserSession::launch(LaunchOptions::new().headless(true))
333 .expect("Failed to launch browser");
334
335 let result = session.navigate("about:blank");
336 assert!(result.is_ok());
337 }
338
339 #[test]
340 #[ignore]
341 fn test_new_tab() {
342 let mut session = BrowserSession::launch(LaunchOptions::new().headless(true))
343 .expect("Failed to launch browser");
344
345 let result = session.new_tab();
346 assert!(result.is_ok());
347
348 let tabs = session.get_tabs().expect("Failed to get tabs");
349 assert!(tabs.len() >= 2);
350 }
351}