1use crate::config::{BrowserConfig, BrowserType};
4use adk_core::{AdkError, Result};
5use std::sync::Arc;
6use std::time::Duration;
7use thirtyfour::common::print::{PrintOrientation, PrintParameters};
8use thirtyfour::prelude::*;
9use tokio::sync::RwLock;
10
11#[derive(Debug, Clone)]
13pub struct ElementState {
14 pub is_displayed: bool,
16 pub is_enabled: bool,
18 pub is_selected: bool,
20 pub is_clickable: bool,
22}
23
24pub struct BrowserSession {
29 driver: RwLock<Option<WebDriver>>,
30 config: BrowserConfig,
31}
32
33impl BrowserSession {
34 pub fn new(config: BrowserConfig) -> Self {
39 Self { driver: RwLock::new(None), config }
40 }
41
42 pub fn with_defaults() -> Self {
44 Self::new(BrowserConfig::default())
45 }
46
47 pub async fn start(&self) -> Result<()> {
49 let mut driver_guard = self.driver.write().await;
50
51 if driver_guard.is_some() {
52 return Ok(()); }
54
55 let caps = self.build_capabilities()?;
56 let driver = WebDriver::new(&self.config.webdriver_url, caps)
57 .await
58 .map_err(|e| AdkError::Tool(format!("Failed to start browser: {}", e)))?;
59
60 driver
62 .set_page_load_timeout(Duration::from_secs(self.config.page_load_timeout_secs))
63 .await
64 .map_err(|e| AdkError::Tool(format!("Failed to set page load timeout: {}", e)))?;
65
66 driver
67 .set_script_timeout(Duration::from_secs(self.config.script_timeout_secs))
68 .await
69 .map_err(|e| AdkError::Tool(format!("Failed to set script timeout: {}", e)))?;
70
71 driver
72 .set_implicit_wait_timeout(Duration::from_secs(self.config.implicit_wait_secs))
73 .await
74 .map_err(|e| AdkError::Tool(format!("Failed to set implicit wait: {}", e)))?;
75
76 driver
78 .set_window_rect(0, 0, self.config.viewport_width, self.config.viewport_height)
79 .await
80 .map_err(|e| AdkError::Tool(format!("Failed to set viewport: {}", e)))?;
81
82 *driver_guard = Some(driver);
83 Ok(())
84 }
85
86 pub async fn stop(&self) -> Result<()> {
88 let mut driver_guard = self.driver.write().await;
89
90 if let Some(driver) = driver_guard.take() {
91 driver
92 .quit()
93 .await
94 .map_err(|e| AdkError::Tool(format!("Failed to quit browser: {}", e)))?;
95 }
96
97 Ok(())
98 }
99
100 pub async fn is_active(&self) -> bool {
102 self.driver.read().await.is_some()
103 }
104
105 pub fn config(&self) -> &BrowserConfig {
107 &self.config
108 }
109
110 pub async fn navigate(&self, url: &str) -> Result<()> {
112 let driver_guard = self.driver.read().await;
113 let driver = driver_guard
114 .as_ref()
115 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
116
117 driver.goto(url).await.map_err(|e| AdkError::Tool(format!("Navigation failed: {}", e)))?;
118
119 Ok(())
120 }
121
122 pub async fn current_url(&self) -> Result<String> {
124 let driver_guard = self.driver.read().await;
125 let driver = driver_guard
126 .as_ref()
127 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
128
129 driver
130 .current_url()
131 .await
132 .map(|u| u.to_string())
133 .map_err(|e| AdkError::Tool(format!("Failed to get URL: {}", e)))
134 }
135
136 pub async fn title(&self) -> Result<String> {
138 let driver_guard = self.driver.read().await;
139 let driver = driver_guard
140 .as_ref()
141 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
142
143 driver.title().await.map_err(|e| AdkError::Tool(format!("Failed to get title: {}", e)))
144 }
145
146 pub async fn find_element(&self, selector: &str) -> Result<WebElement> {
148 let driver_guard = self.driver.read().await;
149 let driver = driver_guard
150 .as_ref()
151 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
152
153 driver
154 .find(By::Css(selector))
155 .await
156 .map_err(|e| AdkError::Tool(format!("Element not found '{}': {}", selector, e)))
157 }
158
159 pub async fn find_elements(&self, selector: &str) -> Result<Vec<WebElement>> {
161 let driver_guard = self.driver.read().await;
162 let driver = driver_guard
163 .as_ref()
164 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
165
166 driver
167 .find_all(By::Css(selector))
168 .await
169 .map_err(|e| AdkError::Tool(format!("Elements query failed '{}': {}", selector, e)))
170 }
171
172 pub async fn find_by_xpath(&self, xpath: &str) -> Result<WebElement> {
174 let driver_guard = self.driver.read().await;
175 let driver = driver_guard
176 .as_ref()
177 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
178
179 driver
180 .find(By::XPath(xpath))
181 .await
182 .map_err(|e| AdkError::Tool(format!("XPath not found '{}': {}", xpath, e)))
183 }
184
185 pub async fn click(&self, selector: &str) -> Result<()> {
187 let element = self.find_element(selector).await?;
188 element
189 .click()
190 .await
191 .map_err(|e| AdkError::Tool(format!("Click failed on '{}': {}", selector, e)))
192 }
193
194 pub async fn type_text(&self, selector: &str, text: &str) -> Result<()> {
196 let element = self.find_element(selector).await?;
197 element
198 .send_keys(text)
199 .await
200 .map_err(|e| AdkError::Tool(format!("Type failed on '{}': {}", selector, e)))
201 }
202
203 pub async fn clear(&self, selector: &str) -> Result<()> {
205 let element = self.find_element(selector).await?;
206 element
207 .clear()
208 .await
209 .map_err(|e| AdkError::Tool(format!("Clear failed on '{}': {}", selector, e)))
210 }
211
212 pub async fn get_text(&self, selector: &str) -> Result<String> {
214 let element = self.find_element(selector).await?;
215 element
216 .text()
217 .await
218 .map_err(|e| AdkError::Tool(format!("Get text failed on '{}': {}", selector, e)))
219 }
220
221 pub async fn get_attribute(&self, selector: &str, attribute: &str) -> Result<Option<String>> {
223 let element = self.find_element(selector).await?;
224 element
225 .attr(attribute)
226 .await
227 .map_err(|e| AdkError::Tool(format!("Get attribute failed: {}", e)))
228 }
229
230 pub async fn screenshot(&self) -> Result<String> {
232 let driver_guard = self.driver.read().await;
233 let driver = driver_guard
234 .as_ref()
235 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
236
237 let screenshot = driver
238 .screenshot_as_png_base64()
239 .await
240 .map_err(|e| AdkError::Tool(format!("Screenshot failed: {}", e)))?;
241
242 Ok(screenshot)
243 }
244
245 pub async fn screenshot_element(&self, selector: &str) -> Result<String> {
247 let element = self.find_element(selector).await?;
248 let screenshot = element
249 .screenshot_as_png_base64()
250 .await
251 .map_err(|e| AdkError::Tool(format!("Element screenshot failed: {}", e)))?;
252
253 Ok(screenshot)
254 }
255
256 pub async fn execute_script(&self, script: &str) -> Result<serde_json::Value> {
258 let driver_guard = self.driver.read().await;
259 let driver = driver_guard
260 .as_ref()
261 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
262
263 let result = driver
264 .execute(script, vec![])
265 .await
266 .map_err(|e| AdkError::Tool(format!("Script execution failed: {}", e)))?;
267
268 Ok(result.json().clone())
270 }
271
272 pub async fn execute_async_script(&self, script: &str) -> Result<serde_json::Value> {
274 let driver_guard = self.driver.read().await;
275 let driver = driver_guard
276 .as_ref()
277 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
278
279 let result = driver
280 .execute_async(script, vec![])
281 .await
282 .map_err(|e| AdkError::Tool(format!("Async script failed: {}", e)))?;
283
284 Ok(result.json().clone())
285 }
286
287 pub async fn wait_for_element(&self, selector: &str, timeout_secs: u64) -> Result<WebElement> {
289 let driver_guard = self.driver.read().await;
290 let driver = driver_guard
291 .as_ref()
292 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
293
294 driver
295 .query(By::Css(selector))
296 .wait(Duration::from_secs(timeout_secs), Duration::from_millis(100))
297 .first()
298 .await
299 .map_err(|e| {
300 AdkError::Tool(format!(
301 "Timeout waiting for '{}' after {}s: {}",
302 selector, timeout_secs, e
303 ))
304 })
305 }
306
307 pub async fn wait_for_clickable(
309 &self,
310 selector: &str,
311 timeout_secs: u64,
312 ) -> Result<WebElement> {
313 let driver_guard = self.driver.read().await;
314 let driver = driver_guard
315 .as_ref()
316 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
317
318 driver
319 .query(By::Css(selector))
320 .wait(Duration::from_secs(timeout_secs), Duration::from_millis(100))
321 .and_clickable()
322 .first()
323 .await
324 .map_err(|e| {
325 AdkError::Tool(format!("Timeout waiting for clickable '{}': {}", selector, e))
326 })
327 }
328
329 pub async fn page_source(&self) -> Result<String> {
331 let driver_guard = self.driver.read().await;
332 let driver = driver_guard
333 .as_ref()
334 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
335
336 driver
337 .source()
338 .await
339 .map_err(|e| AdkError::Tool(format!("Failed to get page source: {}", e)))
340 }
341
342 pub async fn back(&self) -> Result<()> {
344 let driver_guard = self.driver.read().await;
345 let driver = driver_guard
346 .as_ref()
347 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
348
349 driver.back().await.map_err(|e| AdkError::Tool(format!("Back navigation failed: {}", e)))
350 }
351
352 pub async fn forward(&self) -> Result<()> {
354 let driver_guard = self.driver.read().await;
355 let driver = driver_guard
356 .as_ref()
357 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
358
359 driver
360 .forward()
361 .await
362 .map_err(|e| AdkError::Tool(format!("Forward navigation failed: {}", e)))
363 }
364
365 pub async fn refresh(&self) -> Result<()> {
367 let driver_guard = self.driver.read().await;
368 let driver = driver_guard
369 .as_ref()
370 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
371
372 driver.refresh().await.map_err(|e| AdkError::Tool(format!("Refresh failed: {}", e)))
373 }
374
375 pub async fn get_all_cookies(&self) -> Result<Vec<serde_json::Value>> {
381 let driver_guard = self.driver.read().await;
382 let driver = driver_guard
383 .as_ref()
384 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
385
386 let cookies = driver
387 .get_all_cookies()
388 .await
389 .map_err(|e| AdkError::Tool(format!("Failed to get cookies: {}", e)))?;
390
391 Ok(cookies
392 .into_iter()
393 .map(|c| {
394 serde_json::json!({
395 "name": c.name,
396 "value": c.value,
397 "domain": c.domain,
398 "path": c.path,
399 "secure": c.secure,
400 })
401 })
402 .collect())
403 }
404
405 pub async fn get_cookie(&self, name: &str) -> Result<serde_json::Value> {
407 let driver_guard = self.driver.read().await;
408 let driver = driver_guard
409 .as_ref()
410 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
411
412 let cookie = driver
413 .get_named_cookie(name)
414 .await
415 .map_err(|e| AdkError::Tool(format!("Failed to get cookie '{}': {}", name, e)))?;
416
417 Ok(serde_json::json!({
418 "name": cookie.name,
419 "value": cookie.value,
420 "domain": cookie.domain,
421 "path": cookie.path,
422 "secure": cookie.secure,
423 }))
424 }
425
426 #[allow(clippy::too_many_arguments)]
428 pub async fn add_cookie(
429 &self,
430 name: &str,
431 value: &str,
432 domain: Option<&str>,
433 path: Option<&str>,
434 secure: Option<bool>,
435 _expiry: Option<i64>,
436 ) -> Result<()> {
437 let driver_guard = self.driver.read().await;
438 let driver = driver_guard
439 .as_ref()
440 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
441
442 let mut cookie = thirtyfour::Cookie::new(name, value);
443 if let Some(d) = domain {
444 cookie.set_domain(d);
445 }
446 if let Some(p) = path {
447 cookie.set_path(p);
448 }
449 if let Some(s) = secure {
450 cookie.set_secure(s);
451 }
452
453 driver
454 .add_cookie(cookie)
455 .await
456 .map_err(|e| AdkError::Tool(format!("Failed to add cookie: {}", e)))
457 }
458
459 pub async fn delete_cookie(&self, name: &str) -> Result<()> {
461 let driver_guard = self.driver.read().await;
462 let driver = driver_guard
463 .as_ref()
464 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
465
466 driver
467 .delete_cookie(name)
468 .await
469 .map_err(|e| AdkError::Tool(format!("Failed to delete cookie: {}", e)))
470 }
471
472 pub async fn delete_all_cookies(&self) -> Result<()> {
474 let driver_guard = self.driver.read().await;
475 let driver = driver_guard
476 .as_ref()
477 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
478
479 driver
480 .delete_all_cookies()
481 .await
482 .map_err(|e| AdkError::Tool(format!("Failed to delete all cookies: {}", e)))
483 }
484
485 pub async fn list_windows(&self) -> Result<(Vec<String>, String)> {
491 let driver_guard = self.driver.read().await;
492 let driver = driver_guard
493 .as_ref()
494 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
495
496 let windows = driver
497 .windows()
498 .await
499 .map_err(|e| AdkError::Tool(format!("Failed to get windows: {}", e)))?;
500
501 let current = driver
502 .window()
503 .await
504 .map_err(|e| AdkError::Tool(format!("Failed to get current window: {}", e)))?;
505
506 Ok((windows.into_iter().map(|w| w.to_string()).collect(), current.to_string()))
507 }
508
509 pub async fn new_tab(&self) -> Result<String> {
511 let driver_guard = self.driver.read().await;
512 let driver = driver_guard
513 .as_ref()
514 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
515
516 let handle = driver
517 .new_tab()
518 .await
519 .map_err(|e| AdkError::Tool(format!("Failed to open new tab: {}", e)))?;
520
521 Ok(handle.to_string())
522 }
523
524 pub async fn new_window(&self) -> Result<String> {
526 let driver_guard = self.driver.read().await;
527 let driver = driver_guard
528 .as_ref()
529 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
530
531 let handle = driver
532 .new_window()
533 .await
534 .map_err(|e| AdkError::Tool(format!("Failed to open new window: {}", e)))?;
535
536 Ok(handle.to_string())
537 }
538
539 pub async fn switch_to_window(&self, handle: &str) -> Result<()> {
541 let driver_guard = self.driver.read().await;
542 let driver = driver_guard
543 .as_ref()
544 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
545
546 let window_handle = thirtyfour::WindowHandle::from(handle.to_string());
547 driver
548 .switch_to_window(window_handle)
549 .await
550 .map_err(|e| AdkError::Tool(format!("Failed to switch window: {}", e)))
551 }
552
553 pub async fn close_window(&self) -> Result<()> {
555 let driver_guard = self.driver.read().await;
556 let driver = driver_guard
557 .as_ref()
558 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
559
560 driver
561 .close_window()
562 .await
563 .map_err(|e| AdkError::Tool(format!("Failed to close window: {}", e)))
564 }
565
566 pub async fn maximize_window(&self) -> Result<()> {
568 let driver_guard = self.driver.read().await;
569 let driver = driver_guard
570 .as_ref()
571 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
572
573 driver
574 .maximize_window()
575 .await
576 .map_err(|e| AdkError::Tool(format!("Failed to maximize window: {}", e)))
577 }
578
579 pub async fn minimize_window(&self) -> Result<()> {
581 let driver_guard = self.driver.read().await;
582 let driver = driver_guard
583 .as_ref()
584 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
585
586 driver
587 .minimize_window()
588 .await
589 .map_err(|e| AdkError::Tool(format!("Failed to minimize window: {}", e)))
590 }
591
592 pub async fn set_window_rect(&self, x: i32, y: i32, width: u32, height: u32) -> Result<()> {
594 let driver_guard = self.driver.read().await;
595 let driver = driver_guard
596 .as_ref()
597 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
598
599 driver
600 .set_window_rect(x as i64, y as i64, width, height)
601 .await
602 .map_err(|e| AdkError::Tool(format!("Failed to set window rect: {}", e)))
603 }
604
605 pub async fn switch_to_frame_by_index(&self, index: u16) -> Result<()> {
611 let driver_guard = self.driver.read().await;
612 let driver = driver_guard
613 .as_ref()
614 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
615
616 driver
617 .enter_frame(index)
618 .await
619 .map_err(|e| AdkError::Tool(format!("Failed to switch to frame {}: {}", index, e)))
620 }
621
622 pub async fn switch_to_frame_by_selector(&self, selector: &str) -> Result<()> {
624 let element = self.find_element(selector).await?;
625
626 element
627 .enter_frame()
628 .await
629 .map_err(|e| AdkError::Tool(format!("Failed to switch to frame: {}", e)))
630 }
631
632 pub async fn switch_to_parent_frame(&self) -> Result<()> {
634 let driver_guard = self.driver.read().await;
635 let driver = driver_guard
636 .as_ref()
637 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
638
639 driver
640 .enter_parent_frame()
641 .await
642 .map_err(|e| AdkError::Tool(format!("Failed to switch to parent frame: {}", e)))
643 }
644
645 pub async fn switch_to_default_content(&self) -> Result<()> {
647 let driver_guard = self.driver.read().await;
648 let driver = driver_guard
649 .as_ref()
650 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
651
652 driver
653 .enter_default_frame()
654 .await
655 .map_err(|e| AdkError::Tool(format!("Failed to switch to default content: {}", e)))
656 }
657
658 pub async fn drag_and_drop(&self, source_selector: &str, target_selector: &str) -> Result<()> {
664 let source = self.find_element(source_selector).await?;
665 let target = self.find_element(target_selector).await?;
666
667 source
669 .js_drag_to(&target)
670 .await
671 .map_err(|e| AdkError::Tool(format!("Drag and drop failed: {}", e)))
672 }
673
674 pub async fn right_click(&self, selector: &str) -> Result<()> {
676 let script = format!(
677 r#"
678 var element = document.querySelector('{}');
679 if (element) {{
680 var event = new MouseEvent('contextmenu', {{
681 'view': window,
682 'bubbles': true,
683 'cancelable': true
684 }});
685 element.dispatchEvent(event);
686 return true;
687 }}
688 return false;
689 "#,
690 selector.replace('\'', "\\'")
691 );
692
693 let result = self.execute_script(&script).await?;
694 if result.as_bool() != Some(true) {
695 return Err(AdkError::Tool(format!("Element not found: {}", selector)));
696 }
697 Ok(())
698 }
699
700 pub async fn focus_element(&self, selector: &str) -> Result<()> {
702 let element = self.find_element(selector).await?;
703 element.focus().await.map_err(|e| AdkError::Tool(format!("Focus failed: {}", e)))
704 }
705
706 pub async fn get_element_state(&self, selector: &str) -> Result<ElementState> {
708 let element = self.find_element(selector).await?;
709
710 let is_displayed = element.is_displayed().await.unwrap_or(false);
711 let is_enabled = element.is_enabled().await.unwrap_or(false);
712 let is_selected = element.is_selected().await.unwrap_or(false);
713 let is_clickable = element.is_clickable().await.unwrap_or(false);
714
715 Ok(ElementState { is_displayed, is_enabled, is_selected, is_clickable })
716 }
717
718 pub async fn press_key(
720 &self,
721 key: &str,
722 selector: Option<&str>,
723 _modifiers: &[&str],
724 ) -> Result<()> {
725 let key_str = match key.to_lowercase().as_str() {
726 "enter" => "\u{E007}",
727 "tab" => "\u{E004}",
728 "escape" | "esc" => "\u{E00C}",
729 "backspace" => "\u{E003}",
730 "delete" => "\u{E017}",
731 "arrowup" | "up" => "\u{E013}",
732 "arrowdown" | "down" => "\u{E015}",
733 "arrowleft" | "left" => "\u{E012}",
734 "arrowright" | "right" => "\u{E014}",
735 "home" => "\u{E011}",
736 "end" => "\u{E010}",
737 "pageup" => "\u{E00E}",
738 "pagedown" => "\u{E00F}",
739 "space" => " ",
740 _ => key,
741 };
742
743 if let Some(sel) = selector {
744 let element = self.find_element(sel).await?;
745 element
746 .send_keys(key_str)
747 .await
748 .map_err(|e| AdkError::Tool(format!("Key press failed: {}", e)))?;
749 } else {
750 let script = format!(
752 "document.activeElement.dispatchEvent(new KeyboardEvent('keydown', {{'key': '{}'}}));",
753 key
754 );
755 self.execute_script(&script).await?;
756 }
757
758 Ok(())
759 }
760
761 pub async fn upload_file(&self, selector: &str, file_path: &str) -> Result<()> {
763 let element = self.find_element(selector).await?;
764 element
765 .send_keys(file_path)
766 .await
767 .map_err(|e| AdkError::Tool(format!("File upload failed: {}", e)))
768 }
769
770 pub async fn print_to_pdf(&self, landscape: bool, scale: f64) -> Result<String> {
772 let driver_guard = self.driver.read().await;
773 let driver = driver_guard
774 .as_ref()
775 .ok_or_else(|| AdkError::Tool("Browser session not started".to_string()))?;
776
777 let params = PrintParameters {
778 orientation: if landscape {
779 PrintOrientation::Landscape
780 } else {
781 PrintOrientation::Portrait
782 },
783 scale,
784 ..Default::default()
785 };
786
787 driver
788 .print_page_base64(params)
789 .await
790 .map_err(|e| AdkError::Tool(format!("Print to PDF failed: {}", e)))
791 }
792
793 fn build_capabilities(&self) -> Result<Capabilities> {
795 let caps = match self.config.browser {
796 BrowserType::Chrome => {
797 let mut caps = DesiredCapabilities::chrome();
798 if self.config.headless {
799 caps.add_arg("--headless=new").map_err(|e| {
800 AdkError::Tool(format!("Failed to add headless arg: {}", e))
801 })?;
802 }
803 caps.add_arg("--no-sandbox")
804 .map_err(|e| AdkError::Tool(format!("Failed to add no-sandbox: {}", e)))?;
805 caps.add_arg("--disable-dev-shm-usage")
806 .map_err(|e| AdkError::Tool(format!("Failed to add disable-dev-shm: {}", e)))?;
807
808 if let Some(ref ua) = self.config.user_agent {
809 caps.add_arg(&format!("--user-agent={}", ua))
810 .map_err(|e| AdkError::Tool(format!("Failed to add user-agent: {}", e)))?;
811 }
812
813 for arg in &self.config.browser_args {
814 caps.add_arg(arg).map_err(|e| {
815 AdkError::Tool(format!("Failed to add arg '{}': {}", arg, e))
816 })?;
817 }
818
819 caps.into()
820 }
821 BrowserType::Firefox => {
822 let mut caps = DesiredCapabilities::firefox();
823 if self.config.headless {
824 caps.add_arg("-headless")
825 .map_err(|e| AdkError::Tool(format!("Failed to add headless: {}", e)))?;
826 }
827 caps.into()
828 }
829 BrowserType::Safari => DesiredCapabilities::safari().into(),
830 BrowserType::Edge => {
831 let mut caps = DesiredCapabilities::edge();
832 if self.config.headless {
833 caps.add_arg("--headless")
834 .map_err(|e| AdkError::Tool(format!("Failed to add headless: {}", e)))?;
835 }
836 caps.into()
837 }
838 };
839
840 Ok(caps)
841 }
842}
843
844impl Drop for BrowserSession {
845 fn drop(&mut self) {
846 tracing::debug!("BrowserSession dropped");
848 }
849}
850
851pub fn shared_session(config: BrowserConfig) -> Arc<BrowserSession> {
853 Arc::new(BrowserSession::new(config))
854}
855
856#[cfg(test)]
857mod tests {
858 use super::*;
859
860 #[test]
861 fn test_session_creation() {
862 let session = BrowserSession::with_defaults();
863 assert!(!session.config().headless || session.config().headless); }
865
866 #[tokio::test]
867 async fn test_session_not_started() {
868 let session = BrowserSession::with_defaults();
869 assert!(!session.is_active().await);
870
871 let result = session.navigate("https://example.com").await;
872 assert!(result.is_err());
873 }
874}