1use crate::config::{BrowserConfig, BrowserType};
4use crate::escape::escape_js_string;
5use adk_core::{AdkError, Result};
6use std::sync::Arc;
7use std::time::Duration;
8use thirtyfour::common::print::{PrintOrientation, PrintParameters};
9use thirtyfour::prelude::*;
10use tokio::sync::RwLock;
11
12#[derive(Debug, Clone)]
14pub struct ElementState {
15 pub is_displayed: bool,
17 pub is_enabled: bool,
19 pub is_selected: bool,
21 pub is_clickable: bool,
23}
24
25pub struct BrowserSession {
30 driver: RwLock<Option<WebDriver>>,
31 config: BrowserConfig,
32}
33
34impl BrowserSession {
35 pub fn new(config: BrowserConfig) -> Self {
40 Self { driver: RwLock::new(None), config }
41 }
42
43 pub fn with_defaults() -> Self {
45 Self::new(BrowserConfig::default())
46 }
47
48 pub async fn start(&self) -> Result<()> {
50 let mut driver_guard = self.driver.write().await;
51
52 if driver_guard.is_some() {
53 return Ok(()); }
55
56 let caps = self.build_capabilities()?;
57 let driver = WebDriver::new(&self.config.webdriver_url, caps)
58 .await
59 .map_err(|e| AdkError::Tool(format!("Failed to start browser: {}", e)))?;
60
61 driver
63 .set_page_load_timeout(Duration::from_secs(self.config.page_load_timeout_secs))
64 .await
65 .map_err(|e| AdkError::Tool(format!("Failed to set page load timeout: {}", e)))?;
66
67 driver
68 .set_script_timeout(Duration::from_secs(self.config.script_timeout_secs))
69 .await
70 .map_err(|e| AdkError::Tool(format!("Failed to set script timeout: {}", e)))?;
71
72 driver
73 .set_implicit_wait_timeout(Duration::from_secs(self.config.implicit_wait_secs))
74 .await
75 .map_err(|e| AdkError::Tool(format!("Failed to set implicit wait: {}", e)))?;
76
77 driver
79 .set_window_rect(0, 0, self.config.viewport_width, self.config.viewport_height)
80 .await
81 .map_err(|e| AdkError::Tool(format!("Failed to set viewport: {}", e)))?;
82
83 *driver_guard = Some(driver);
84 Ok(())
85 }
86
87 pub async fn stop(&self) -> Result<()> {
89 let mut driver_guard = self.driver.write().await;
90
91 if let Some(driver) = driver_guard.take() {
92 driver
93 .quit()
94 .await
95 .map_err(|e| AdkError::Tool(format!("Failed to quit browser: {}", e)))?;
96 }
97
98 Ok(())
99 }
100
101 pub async fn is_active(&self) -> bool {
103 let driver_guard = self.driver.read().await;
104 if let Some(ref driver) = *driver_guard {
105 driver.title().await.is_ok()
107 } else {
108 false
109 }
110 }
111
112 pub async fn ensure_started(&self) -> Result<()> {
117 {
118 let driver_guard = self.driver.read().await;
119 if let Some(ref driver) = *driver_guard {
120 if driver.title().await.is_ok() {
122 return Ok(());
123 }
124 }
125 }
126 {
129 let mut driver_guard = self.driver.write().await;
130 *driver_guard = None;
131 }
132 self.start().await
133 }
134
135 async fn live_driver(&self) -> Result<WebDriver> {
141 self.ensure_started().await?;
142
143 let guard = self.driver.read().await;
144 guard.clone().ok_or_else(|| {
145 AdkError::Tool(
146 "Failed to start browser session: driver is None after ensure_started()".into(),
147 )
148 })
149 }
150
151 pub async fn page_context(&self) -> Result<serde_json::Value> {
155 let url = self.current_url().await.unwrap_or_default();
156 let title = self.title().await.unwrap_or_default();
157
158 let page_text = self
160 .execute_script(
161 "return (document.body && document.body.innerText || '').substring(0, 2000);",
162 )
163 .await
164 .ok()
165 .and_then(|v| v.as_str().map(|s| s.to_string()))
166 .unwrap_or_default();
167
168 Ok(serde_json::json!({
169 "url": url,
170 "title": title,
171 "page_text": page_text
172 }))
173 }
174
175 pub fn config(&self) -> &BrowserConfig {
177 &self.config
178 }
179
180 pub async fn navigate(&self, url: &str) -> Result<()> {
182 let driver = self.live_driver().await?;
183
184 driver.goto(url).await.map_err(|e| AdkError::Tool(format!("Navigation failed: {}", e)))?;
185
186 Ok(())
187 }
188
189 pub async fn current_url(&self) -> Result<String> {
191 let driver = self.live_driver().await?;
192
193 driver
194 .current_url()
195 .await
196 .map(|u| u.to_string())
197 .map_err(|e| AdkError::Tool(format!("Failed to get URL: {}", e)))
198 }
199
200 pub async fn title(&self) -> Result<String> {
202 let driver = self.live_driver().await?;
203
204 driver.title().await.map_err(|e| AdkError::Tool(format!("Failed to get title: {}", e)))
205 }
206
207 pub async fn find_element(&self, selector: &str) -> Result<WebElement> {
209 let driver = self.live_driver().await?;
210
211 driver
212 .find(By::Css(selector))
213 .await
214 .map_err(|e| AdkError::Tool(format!("Element not found '{}': {}", selector, e)))
215 }
216
217 pub async fn find_elements(&self, selector: &str) -> Result<Vec<WebElement>> {
219 let driver = self.live_driver().await?;
220
221 driver
222 .find_all(By::Css(selector))
223 .await
224 .map_err(|e| AdkError::Tool(format!("Elements query failed '{}': {}", selector, e)))
225 }
226
227 pub async fn find_by_xpath(&self, xpath: &str) -> Result<WebElement> {
229 let driver = self.live_driver().await?;
230
231 driver
232 .find(By::XPath(xpath))
233 .await
234 .map_err(|e| AdkError::Tool(format!("XPath not found '{}': {}", xpath, e)))
235 }
236
237 pub async fn click(&self, selector: &str) -> Result<()> {
239 let element = self.find_element(selector).await?;
240 element
241 .click()
242 .await
243 .map_err(|e| AdkError::Tool(format!("Click failed on '{}': {}", selector, e)))
244 }
245
246 pub async fn type_text(&self, selector: &str, text: &str) -> Result<()> {
248 let element = self.find_element(selector).await?;
249 element
250 .send_keys(text)
251 .await
252 .map_err(|e| AdkError::Tool(format!("Type failed on '{}': {}", selector, e)))
253 }
254
255 pub async fn clear(&self, selector: &str) -> Result<()> {
257 let element = self.find_element(selector).await?;
258 element
259 .clear()
260 .await
261 .map_err(|e| AdkError::Tool(format!("Clear failed on '{}': {}", selector, e)))
262 }
263
264 pub async fn get_text(&self, selector: &str) -> Result<String> {
266 let element = self.find_element(selector).await?;
267 element
268 .text()
269 .await
270 .map_err(|e| AdkError::Tool(format!("Get text failed on '{}': {}", selector, e)))
271 }
272
273 pub async fn get_attribute(&self, selector: &str, attribute: &str) -> Result<Option<String>> {
275 let element = self.find_element(selector).await?;
276 element
277 .attr(attribute)
278 .await
279 .map_err(|e| AdkError::Tool(format!("Get attribute failed: {}", e)))
280 }
281
282 pub async fn screenshot(&self) -> Result<String> {
284 let driver = self.live_driver().await?;
285
286 let screenshot = driver
287 .screenshot_as_png_base64()
288 .await
289 .map_err(|e| AdkError::Tool(format!("Screenshot failed: {}", e)))?;
290
291 Ok(screenshot)
292 }
293
294 pub async fn screenshot_element(&self, selector: &str) -> Result<String> {
296 let element = self.find_element(selector).await?;
297 let screenshot = element
298 .screenshot_as_png_base64()
299 .await
300 .map_err(|e| AdkError::Tool(format!("Element screenshot failed: {}", e)))?;
301
302 Ok(screenshot)
303 }
304
305 pub async fn execute_script(&self, script: &str) -> Result<serde_json::Value> {
307 let driver = self.live_driver().await?;
308
309 let result = driver
310 .execute(script, vec![])
311 .await
312 .map_err(|e| AdkError::Tool(format!("Script execution failed: {}", e)))?;
313
314 Ok(result.json().clone())
316 }
317
318 pub async fn execute_async_script(&self, script: &str) -> Result<serde_json::Value> {
320 let driver = self.live_driver().await?;
321
322 let result = driver
323 .execute_async(script, vec![])
324 .await
325 .map_err(|e| AdkError::Tool(format!("Async script failed: {}", e)))?;
326
327 Ok(result.json().clone())
328 }
329
330 pub async fn wait_for_element(&self, selector: &str, timeout_secs: u64) -> Result<WebElement> {
332 let driver = self.live_driver().await?;
333
334 driver
335 .query(By::Css(selector))
336 .wait(Duration::from_secs(timeout_secs), Duration::from_millis(100))
337 .first()
338 .await
339 .map_err(|e| {
340 AdkError::Tool(format!(
341 "Timeout waiting for '{}' after {}s: {}",
342 selector, timeout_secs, e
343 ))
344 })
345 }
346
347 pub async fn wait_for_clickable(
349 &self,
350 selector: &str,
351 timeout_secs: u64,
352 ) -> Result<WebElement> {
353 let driver = self.live_driver().await?;
354
355 driver
356 .query(By::Css(selector))
357 .wait(Duration::from_secs(timeout_secs), Duration::from_millis(100))
358 .and_clickable()
359 .first()
360 .await
361 .map_err(|e| {
362 AdkError::Tool(format!("Timeout waiting for clickable '{}': {}", selector, e))
363 })
364 }
365
366 pub async fn page_source(&self) -> Result<String> {
368 let driver = self.live_driver().await?;
369
370 driver
371 .source()
372 .await
373 .map_err(|e| AdkError::Tool(format!("Failed to get page source: {}", e)))
374 }
375
376 pub async fn back(&self) -> Result<()> {
378 let driver = self.live_driver().await?;
379
380 driver.back().await.map_err(|e| AdkError::Tool(format!("Back navigation failed: {}", e)))
381 }
382
383 pub async fn forward(&self) -> Result<()> {
385 let driver = self.live_driver().await?;
386
387 driver
388 .forward()
389 .await
390 .map_err(|e| AdkError::Tool(format!("Forward navigation failed: {}", e)))
391 }
392
393 pub async fn refresh(&self) -> Result<()> {
395 let driver = self.live_driver().await?;
396
397 driver.refresh().await.map_err(|e| AdkError::Tool(format!("Refresh failed: {}", e)))
398 }
399
400 pub async fn get_all_cookies(&self) -> Result<Vec<serde_json::Value>> {
406 let driver = self.live_driver().await?;
407
408 let cookies = driver
409 .get_all_cookies()
410 .await
411 .map_err(|e| AdkError::Tool(format!("Failed to get cookies: {}", e)))?;
412
413 Ok(cookies
414 .into_iter()
415 .map(|c| {
416 serde_json::json!({
417 "name": c.name,
418 "value": c.value,
419 "domain": c.domain,
420 "path": c.path,
421 "secure": c.secure,
422 })
423 })
424 .collect())
425 }
426
427 pub async fn get_cookie(&self, name: &str) -> Result<serde_json::Value> {
429 let driver = self.live_driver().await?;
430
431 let cookie = driver
432 .get_named_cookie(name)
433 .await
434 .map_err(|e| AdkError::Tool(format!("Failed to get cookie '{}': {}", name, e)))?;
435
436 Ok(serde_json::json!({
437 "name": cookie.name,
438 "value": cookie.value,
439 "domain": cookie.domain,
440 "path": cookie.path,
441 "secure": cookie.secure,
442 }))
443 }
444
445 #[allow(clippy::too_many_arguments)]
447 pub async fn add_cookie(
448 &self,
449 name: &str,
450 value: &str,
451 domain: Option<&str>,
452 path: Option<&str>,
453 secure: Option<bool>,
454 expiry: Option<i64>,
455 ) -> Result<()> {
456 let driver = self.live_driver().await?;
457
458 let mut cookie = thirtyfour::Cookie::new(name, value);
459 if let Some(d) = domain {
460 cookie.set_domain(d);
461 }
462 if let Some(p) = path {
463 cookie.set_path(p);
464 }
465 if let Some(s) = secure {
466 cookie.set_secure(s);
467 }
468 if let Some(exp) = expiry {
469 cookie.set_expiry(exp);
471 }
472
473 driver
474 .add_cookie(cookie)
475 .await
476 .map_err(|e| AdkError::Tool(format!("Failed to add cookie: {e}")))
477 }
478
479 pub async fn delete_cookie(&self, name: &str) -> Result<()> {
481 let driver = self.live_driver().await?;
482
483 driver
484 .delete_cookie(name)
485 .await
486 .map_err(|e| AdkError::Tool(format!("Failed to delete cookie: {}", e)))
487 }
488
489 pub async fn delete_all_cookies(&self) -> Result<()> {
491 let driver = self.live_driver().await?;
492
493 driver
494 .delete_all_cookies()
495 .await
496 .map_err(|e| AdkError::Tool(format!("Failed to delete all cookies: {}", e)))
497 }
498
499 pub async fn list_windows(&self) -> Result<(Vec<String>, String)> {
505 let driver = self.live_driver().await?;
506
507 let windows = driver
508 .windows()
509 .await
510 .map_err(|e| AdkError::Tool(format!("Failed to get windows: {}", e)))?;
511
512 let current = driver
513 .window()
514 .await
515 .map_err(|e| AdkError::Tool(format!("Failed to get current window: {}", e)))?;
516
517 Ok((windows.into_iter().map(|w| w.to_string()).collect(), current.to_string()))
518 }
519
520 pub async fn new_tab(&self) -> Result<String> {
522 let driver = self.live_driver().await?;
523
524 let handle = driver
525 .new_tab()
526 .await
527 .map_err(|e| AdkError::Tool(format!("Failed to open new tab: {}", e)))?;
528
529 Ok(handle.to_string())
530 }
531
532 pub async fn new_window(&self) -> Result<String> {
534 let driver = self.live_driver().await?;
535
536 let handle = driver
537 .new_window()
538 .await
539 .map_err(|e| AdkError::Tool(format!("Failed to open new window: {}", e)))?;
540
541 Ok(handle.to_string())
542 }
543
544 pub async fn switch_to_window(&self, handle: &str) -> Result<()> {
546 let driver = self.live_driver().await?;
547
548 let window_handle = thirtyfour::WindowHandle::from(handle.to_string());
549 driver
550 .switch_to_window(window_handle)
551 .await
552 .map_err(|e| AdkError::Tool(format!("Failed to switch window: {}", e)))
553 }
554
555 pub async fn close_window(&self) -> Result<()> {
557 let driver = self.live_driver().await?;
558
559 driver
560 .close_window()
561 .await
562 .map_err(|e| AdkError::Tool(format!("Failed to close window: {}", e)))
563 }
564
565 pub async fn maximize_window(&self) -> Result<()> {
567 let driver = self.live_driver().await?;
568
569 driver
570 .maximize_window()
571 .await
572 .map_err(|e| AdkError::Tool(format!("Failed to maximize window: {}", e)))
573 }
574
575 pub async fn minimize_window(&self) -> Result<()> {
577 let driver = self.live_driver().await?;
578
579 driver
580 .minimize_window()
581 .await
582 .map_err(|e| AdkError::Tool(format!("Failed to minimize window: {}", e)))
583 }
584
585 pub async fn set_window_rect(&self, x: i32, y: i32, width: u32, height: u32) -> Result<()> {
587 let driver = self.live_driver().await?;
588
589 driver
590 .set_window_rect(x as i64, y as i64, width, height)
591 .await
592 .map_err(|e| AdkError::Tool(format!("Failed to set window rect: {}", e)))
593 }
594
595 pub async fn switch_to_frame_by_index(&self, index: u16) -> Result<()> {
601 let driver = self.live_driver().await?;
602
603 driver
604 .enter_frame(index)
605 .await
606 .map_err(|e| AdkError::Tool(format!("Failed to switch to frame {}: {}", index, e)))
607 }
608
609 pub async fn switch_to_frame_by_selector(&self, selector: &str) -> Result<()> {
611 let element = self.find_element(selector).await?;
612
613 element
614 .enter_frame()
615 .await
616 .map_err(|e| AdkError::Tool(format!("Failed to switch to frame: {}", e)))
617 }
618
619 pub async fn switch_to_parent_frame(&self) -> Result<()> {
621 let driver = self.live_driver().await?;
622
623 driver
624 .enter_parent_frame()
625 .await
626 .map_err(|e| AdkError::Tool(format!("Failed to switch to parent frame: {}", e)))
627 }
628
629 pub async fn switch_to_default_content(&self) -> Result<()> {
631 let driver = self.live_driver().await?;
632
633 driver
634 .enter_default_frame()
635 .await
636 .map_err(|e| AdkError::Tool(format!("Failed to switch to default content: {}", e)))
637 }
638
639 pub async fn drag_and_drop(&self, source_selector: &str, target_selector: &str) -> Result<()> {
645 let source = self.find_element(source_selector).await?;
646 let target = self.find_element(target_selector).await?;
647
648 source
650 .js_drag_to(&target)
651 .await
652 .map_err(|e| AdkError::Tool(format!("Drag and drop failed: {}", e)))
653 }
654
655 pub async fn right_click(&self, selector: &str) -> Result<()> {
657 let escaped = escape_js_string(selector);
658 let script = format!(
659 r#"
660 var element = document.querySelector('{escaped}');
661 if (element) {{
662 var event = new MouseEvent('contextmenu', {{
663 'view': window,
664 'bubbles': true,
665 'cancelable': true
666 }});
667 element.dispatchEvent(event);
668 return true;
669 }}
670 return false;
671 "#,
672 );
673
674 let result = self.execute_script(&script).await?;
675 if result.as_bool() != Some(true) {
676 return Err(AdkError::Tool(format!("Element not found: {}", selector)));
677 }
678 Ok(())
679 }
680
681 pub async fn focus_element(&self, selector: &str) -> Result<()> {
683 let element = self.find_element(selector).await?;
684 element.focus().await.map_err(|e| AdkError::Tool(format!("Focus failed: {}", e)))
685 }
686
687 pub async fn get_element_state(&self, selector: &str) -> Result<ElementState> {
689 let element = self.find_element(selector).await?;
690
691 let is_displayed = element.is_displayed().await.unwrap_or(false);
692 let is_enabled = element.is_enabled().await.unwrap_or(false);
693 let is_selected = element.is_selected().await.unwrap_or(false);
694 let is_clickable = element.is_clickable().await.unwrap_or(false);
695
696 Ok(ElementState { is_displayed, is_enabled, is_selected, is_clickable })
697 }
698
699 pub async fn press_key(
701 &self,
702 key: &str,
703 selector: Option<&str>,
704 modifiers: &[&str],
705 ) -> Result<()> {
706 let modifier_codes: Vec<&str> = modifiers
708 .iter()
709 .filter_map(|m| match m.to_lowercase().as_str() {
710 "ctrl" | "control" => Some("\u{E009}"),
711 "alt" => Some("\u{E00A}"),
712 "shift" => Some("\u{E008}"),
713 "meta" | "command" | "cmd" => Some("\u{E03D}"),
714 _ => None,
715 })
716 .collect();
717
718 let key_str = match key.to_lowercase().as_str() {
719 "enter" => "\u{E007}",
720 "tab" => "\u{E004}",
721 "escape" | "esc" => "\u{E00C}",
722 "backspace" => "\u{E003}",
723 "delete" => "\u{E017}",
724 "arrowup" | "up" => "\u{E013}",
725 "arrowdown" | "down" => "\u{E015}",
726 "arrowleft" | "left" => "\u{E012}",
727 "arrowright" | "right" => "\u{E014}",
728 "home" => "\u{E011}",
729 "end" => "\u{E010}",
730 "pageup" => "\u{E00E}",
731 "pagedown" => "\u{E00F}",
732 "space" => " ",
733 _ => key,
734 };
735
736 let mut key_sequence = String::new();
738 for code in &modifier_codes {
739 key_sequence.push_str(code);
740 }
741 key_sequence.push_str(key_str);
742 for code in modifier_codes.iter().rev() {
744 key_sequence.push_str(code);
745 }
746
747 if let Some(sel) = selector {
748 let element = self.find_element(sel).await?;
749 element
750 .send_keys(&key_sequence)
751 .await
752 .map_err(|e| AdkError::Tool(format!("Key press failed: {e}")))?;
753 } else {
754 let driver = self.live_driver().await?;
756
757 let active = driver
758 .active_element()
759 .await
760 .map_err(|e| AdkError::Tool(format!("No active element: {e}")))?;
761 active
762 .send_keys(&key_sequence)
763 .await
764 .map_err(|e| AdkError::Tool(format!("Key press failed: {e}")))?;
765 }
766
767 Ok(())
768 }
769
770 pub async fn upload_file(&self, selector: &str, file_path: &str) -> Result<()> {
772 let element = self.find_element(selector).await?;
773 element
774 .send_keys(file_path)
775 .await
776 .map_err(|e| AdkError::Tool(format!("File upload failed: {}", e)))
777 }
778
779 pub async fn print_to_pdf(&self, landscape: bool, scale: f64) -> Result<String> {
781 let driver = self.live_driver().await?;
782
783 let params = PrintParameters {
784 orientation: if landscape {
785 PrintOrientation::Landscape
786 } else {
787 PrintOrientation::Portrait
788 },
789 scale,
790 ..Default::default()
791 };
792
793 driver
794 .print_page_base64(params)
795 .await
796 .map_err(|e| AdkError::Tool(format!("Print to PDF failed: {}", e)))
797 }
798
799 fn build_capabilities(&self) -> Result<Capabilities> {
801 let caps = match self.config.browser {
802 BrowserType::Chrome => {
803 let mut caps = DesiredCapabilities::chrome();
804 if self.config.headless {
805 caps.add_arg("--headless=new").map_err(|e| {
806 AdkError::Tool(format!("Failed to add headless arg: {}", e))
807 })?;
808 }
809 caps.add_arg("--no-sandbox")
810 .map_err(|e| AdkError::Tool(format!("Failed to add no-sandbox: {}", e)))?;
811 caps.add_arg("--disable-dev-shm-usage")
812 .map_err(|e| AdkError::Tool(format!("Failed to add disable-dev-shm: {}", e)))?;
813
814 if let Some(ref ua) = self.config.user_agent {
815 caps.add_arg(&format!("--user-agent={}", ua))
816 .map_err(|e| AdkError::Tool(format!("Failed to add user-agent: {}", e)))?;
817 }
818
819 for arg in &self.config.browser_args {
820 caps.add_arg(arg).map_err(|e| {
821 AdkError::Tool(format!("Failed to add arg '{}': {}", arg, e))
822 })?;
823 }
824
825 caps.into()
826 }
827 BrowserType::Firefox => {
828 let mut caps = DesiredCapabilities::firefox();
829 if self.config.headless {
830 caps.add_arg("-headless")
831 .map_err(|e| AdkError::Tool(format!("Failed to add headless: {}", e)))?;
832 }
833 caps.into()
834 }
835 BrowserType::Safari => DesiredCapabilities::safari().into(),
836 BrowserType::Edge => {
837 let mut caps = DesiredCapabilities::edge();
838 if self.config.headless {
839 caps.add_arg("--headless")
840 .map_err(|e| AdkError::Tool(format!("Failed to add headless: {}", e)))?;
841 }
842 caps.into()
843 }
844 };
845
846 Ok(caps)
847 }
848}
849
850impl Drop for BrowserSession {
851 fn drop(&mut self) {
852 tracing::debug!("BrowserSession dropped");
854 }
855}
856
857pub fn shared_session(config: BrowserConfig) -> Arc<BrowserSession> {
859 Arc::new(BrowserSession::new(config))
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn test_session_creation() {
868 let session = BrowserSession::with_defaults();
869 assert!(!session.config().headless || session.config().headless); }
871
872 #[tokio::test]
873 async fn test_session_not_started() {
874 let config = BrowserConfig::new().webdriver_url("http://127.0.0.1:1");
876 let session = BrowserSession::new(config);
877 assert!(!session.is_active().await);
878
879 let result = session.navigate("https://example.com").await;
882 assert!(result.is_err());
883 }
884
885 #[test]
886 fn test_build_capabilities_chrome_headless() {
887 let config = BrowserConfig::new().headless(true);
888 let session = BrowserSession::new(config);
889 let caps = session.build_capabilities();
890 assert!(caps.is_ok());
891 }
892
893 #[test]
894 fn test_build_capabilities_firefox() {
895 let config = BrowserConfig::new().browser(BrowserType::Firefox);
896 let session = BrowserSession::new(config);
897 let caps = session.build_capabilities();
898 assert!(caps.is_ok());
899 }
900
901 #[test]
902 fn test_build_capabilities_safari() {
903 let config = BrowserConfig::new().browser(BrowserType::Safari);
904 let session = BrowserSession::new(config);
905 let caps = session.build_capabilities();
906 assert!(caps.is_ok());
907 }
908
909 #[test]
910 fn test_build_capabilities_edge() {
911 let config = BrowserConfig::new().browser(BrowserType::Edge);
912 let session = BrowserSession::new(config);
913 let caps = session.build_capabilities();
914 assert!(caps.is_ok());
915 }
916
917 #[test]
918 fn test_build_capabilities_with_user_agent() {
919 let config = BrowserConfig::new().user_agent("CustomAgent/1.0");
920 let session = BrowserSession::new(config);
921 let caps = session.build_capabilities();
922 assert!(caps.is_ok());
923 }
924
925 #[test]
926 fn test_build_capabilities_with_extra_args() {
927 let config = BrowserConfig::new().add_arg("--disable-gpu").add_arg("--window-size=800,600");
928 let session = BrowserSession::new(config);
929 let caps = session.build_capabilities();
930 assert!(caps.is_ok());
931 }
932}