1#[cfg(feature = "browser")]
31use crate::event::InputEvent;
32use crate::locator::BoundingBox;
33#[cfg(feature = "browser")]
34use crate::result::{ProbarError, ProbarResult};
35use serde::{Deserialize, Serialize};
36use std::time::Duration;
37
38#[cfg(feature = "browser")]
39use async_trait::async_trait;
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct ElementHandle {
44 pub id: String,
46 pub tag_name: String,
48 pub text_content: Option<String>,
50 pub bounding_box: Option<BoundingBox>,
52}
53
54impl ElementHandle {
55 #[must_use]
57 pub fn new(id: impl Into<String>, tag_name: impl Into<String>) -> Self {
58 Self {
59 id: id.into(),
60 tag_name: tag_name.into(),
61 text_content: None,
62 bounding_box: None,
63 }
64 }
65
66 #[must_use]
68 pub const fn is_visible(&self) -> bool {
69 self.bounding_box.is_some()
70 }
71}
72
73#[derive(Debug, Clone, Default, Serialize, Deserialize)]
75pub struct PageMetrics {
76 pub first_paint_ms: Option<f64>,
78 pub first_contentful_paint_ms: Option<f64>,
80 pub dom_content_loaded_ms: Option<f64>,
82 pub load_time_ms: Option<f64>,
84 pub js_heap_size_bytes: Option<u64>,
86 pub js_heap_used_bytes: Option<u64>,
88 pub dom_nodes: Option<u32>,
90 pub frame_count: Option<u32>,
92}
93
94#[derive(Debug, Clone)]
96pub struct Screenshot {
97 pub data: Vec<u8>,
99 pub width: u32,
101 pub height: u32,
103 pub device_pixel_ratio: f64,
105 pub timestamp: std::time::SystemTime,
107}
108
109impl Screenshot {
110 #[must_use]
112 pub fn new(data: Vec<u8>, width: u32, height: u32) -> Self {
113 Self {
114 data,
115 width,
116 height,
117 device_pixel_ratio: 1.0,
118 timestamp: std::time::SystemTime::now(),
119 }
120 }
121
122 #[must_use]
124 pub fn size_bytes(&self) -> usize {
125 self.data.len()
126 }
127
128 #[must_use]
130 pub fn is_valid(&self) -> bool {
131 !self.data.is_empty() && self.width > 0 && self.height > 0
132 }
133}
134
135#[derive(Debug, Clone, Default)]
137pub struct NetworkInterceptor {
138 pub patterns: Vec<String>,
140 pub block: bool,
142 pub response_override: Option<NetworkResponse>,
144}
145
146#[derive(Debug, Clone)]
148pub struct NetworkResponse {
149 pub status: u16,
151 pub headers: Vec<(String, String)>,
153 pub body: Vec<u8>,
155}
156
157impl NetworkResponse {
158 #[must_use]
160 pub fn json(status: u16, body: impl Serialize) -> Self {
161 let body = serde_json::to_vec(&body).unwrap_or_default();
162 Self {
163 status,
164 headers: vec![("Content-Type".to_string(), "application/json".to_string())],
165 body,
166 }
167 }
168
169 #[must_use]
171 pub fn not_found() -> Self {
172 Self {
173 status: 404,
174 headers: vec![],
175 body: b"Not Found".to_vec(),
176 }
177 }
178}
179
180#[derive(Debug, Clone)]
182pub struct DriverConfig {
183 pub headless: bool,
185 pub viewport_width: u32,
187 pub viewport_height: u32,
189 pub device_scale_factor: f64,
191 pub user_agent: Option<String>,
193 pub navigation_timeout: Duration,
195 pub element_timeout: Duration,
197 pub tracing: bool,
199 pub executable_path: Option<String>,
201}
202
203impl Default for DriverConfig {
204 fn default() -> Self {
205 Self {
206 headless: true,
207 viewport_width: 1920,
208 viewport_height: 1080,
209 device_scale_factor: 1.0,
210 user_agent: None,
211 navigation_timeout: Duration::from_secs(30),
212 element_timeout: Duration::from_secs(5),
213 tracing: false,
214 executable_path: None,
215 }
216 }
217}
218
219impl DriverConfig {
220 #[must_use]
222 pub fn new() -> Self {
223 Self::default()
224 }
225
226 #[must_use]
228 pub const fn headless(mut self, headless: bool) -> Self {
229 self.headless = headless;
230 self
231 }
232
233 #[must_use]
235 pub const fn viewport(mut self, width: u32, height: u32) -> Self {
236 self.viewport_width = width;
237 self.viewport_height = height;
238 self
239 }
240
241 #[must_use]
243 pub const fn scale_factor(mut self, factor: f64) -> Self {
244 self.device_scale_factor = factor;
245 self
246 }
247
248 #[must_use]
250 pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
251 self.user_agent = Some(ua.into());
252 self
253 }
254
255 #[must_use]
257 pub const fn navigation_timeout(mut self, timeout: Duration) -> Self {
258 self.navigation_timeout = timeout;
259 self
260 }
261
262 #[must_use]
264 pub const fn with_tracing(mut self, enabled: bool) -> Self {
265 self.tracing = enabled;
266 self
267 }
268}
269
270#[derive(Debug, Clone)]
272pub struct DeviceDescriptor {
273 pub name: &'static str,
275 pub viewport_width: u32,
277 pub viewport_height: u32,
279 pub device_scale_factor: f64,
281 pub is_mobile: bool,
283 pub has_touch: bool,
285 pub user_agent: &'static str,
287}
288
289impl DeviceDescriptor {
290 pub const IPHONE_14_PRO: Self = Self {
292 name: "iPhone 14 Pro",
293 viewport_width: 393,
294 viewport_height: 852,
295 device_scale_factor: 3.0,
296 is_mobile: true,
297 has_touch: true,
298 user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15",
299 };
300
301 pub const IPAD_PRO_12_9: Self = Self {
303 name: "iPad Pro 12.9",
304 viewport_width: 1024,
305 viewport_height: 1366,
306 device_scale_factor: 2.0,
307 is_mobile: true,
308 has_touch: true,
309 user_agent: "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15",
310 };
311
312 pub const DESKTOP_1080P: Self = Self {
314 name: "Desktop 1080p",
315 viewport_width: 1920,
316 viewport_height: 1080,
317 device_scale_factor: 1.0,
318 is_mobile: false,
319 has_touch: false,
320 user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
321 };
322
323 pub const DESKTOP_4K: Self = Self {
325 name: "Desktop 4K",
326 viewport_width: 3840,
327 viewport_height: 2160,
328 device_scale_factor: 2.0,
329 is_mobile: false,
330 has_touch: false,
331 user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
332 };
333
334 #[must_use]
336 pub fn to_config(&self) -> DriverConfig {
337 DriverConfig::default()
338 .viewport(self.viewport_width, self.viewport_height)
339 .scale_factor(self.device_scale_factor)
340 .user_agent(self.user_agent)
341 }
342}
343
344#[cfg(feature = "browser")]
355#[async_trait]
356pub trait ProbarDriver: Send + Sync {
357 async fn navigate(&mut self, url: &str) -> ProbarResult<()>;
359
360 async fn screenshot(&self) -> ProbarResult<Screenshot>;
362
363 async fn execute_js(&self, script: &str) -> ProbarResult<serde_json::Value>;
365
366 async fn query_selector(&self, selector: &str) -> ProbarResult<Option<ElementHandle>>;
368
369 async fn query_selector_all(&self, selector: &str) -> ProbarResult<Vec<ElementHandle>>;
371
372 async fn dispatch_input(&self, event: InputEvent) -> ProbarResult<()>;
374
375 async fn click(&self, selector: &str) -> ProbarResult<()>;
377
378 async fn type_text(&self, selector: &str, text: &str) -> ProbarResult<()>;
380
381 async fn wait_for_selector(
383 &self,
384 selector: &str,
385 timeout: Duration,
386 ) -> ProbarResult<ElementHandle>;
387
388 async fn metrics(&self) -> ProbarResult<PageMetrics>;
390
391 async fn set_network_interceptor(
393 &mut self,
394 interceptor: NetworkInterceptor,
395 ) -> ProbarResult<()>;
396
397 async fn current_url(&self) -> ProbarResult<String>;
399
400 async fn go_back(&mut self) -> ProbarResult<()>;
402
403 async fn go_forward(&mut self) -> ProbarResult<()>;
405
406 async fn reload(&mut self) -> ProbarResult<()>;
408
409 async fn close(&mut self) -> ProbarResult<()>;
411}
412
413#[derive(Debug, Default)]
415pub struct MockDriver {
416 pub current_url: String,
418 pub elements: Vec<ElementHandle>,
420 pub js_results: Vec<serde_json::Value>,
422 pub screenshot_data: Option<Screenshot>,
424 pub call_history: Vec<String>,
426}
427
428impl MockDriver {
429 #[must_use]
431 pub fn new() -> Self {
432 Self::default()
433 }
434
435 pub fn add_element(&mut self, element: ElementHandle) {
437 self.elements.push(element);
438 }
439
440 pub fn set_js_result(&mut self, result: serde_json::Value) {
442 self.js_results.push(result);
443 }
444
445 pub fn set_screenshot(&mut self, screenshot: Screenshot) {
447 self.screenshot_data = Some(screenshot);
448 }
449
450 #[must_use]
452 pub fn history(&self) -> &[String] {
453 &self.call_history
454 }
455
456 #[must_use]
458 pub fn was_called(&self, method: &str) -> bool {
459 self.call_history.iter().any(|c| c.starts_with(method))
460 }
461}
462
463#[cfg(feature = "browser")]
464#[async_trait]
465impl ProbarDriver for MockDriver {
466 async fn navigate(&mut self, url: &str) -> ProbarResult<()> {
467 self.call_history.push(format!("navigate:{url}"));
468 self.current_url = url.to_string();
469 Ok(())
470 }
471
472 async fn screenshot(&self) -> ProbarResult<Screenshot> {
473 self.screenshot_data
474 .clone()
475 .ok_or_else(|| ProbarError::ScreenshotError {
476 message: "No mock screenshot set".to_string(),
477 })
478 }
479
480 async fn execute_js(&self, script: &str) -> ProbarResult<serde_json::Value> {
481 let _ = script; self.js_results
483 .first()
484 .cloned()
485 .ok_or_else(|| ProbarError::WasmError {
486 message: "No mock JS result set".to_string(),
487 })
488 }
489
490 async fn query_selector(&self, selector: &str) -> ProbarResult<Option<ElementHandle>> {
491 Ok(self.elements.iter().find(|e| e.id == selector).cloned())
492 }
493
494 async fn query_selector_all(&self, _selector: &str) -> ProbarResult<Vec<ElementHandle>> {
495 Ok(self.elements.clone())
496 }
497
498 async fn dispatch_input(&self, event: InputEvent) -> ProbarResult<()> {
499 let _ = event; Ok(())
501 }
502
503 async fn click(&self, selector: &str) -> ProbarResult<()> {
504 let _ = selector;
505 Ok(())
506 }
507
508 async fn type_text(&self, selector: &str, text: &str) -> ProbarResult<()> {
509 let _ = (selector, text);
510 Ok(())
511 }
512
513 async fn wait_for_selector(
514 &self,
515 selector: &str,
516 _timeout: Duration,
517 ) -> ProbarResult<ElementHandle> {
518 self.elements
519 .iter()
520 .find(|e| e.id == selector)
521 .cloned()
522 .ok_or_else(|| ProbarError::Timeout { ms: 5000 })
523 }
524
525 async fn metrics(&self) -> ProbarResult<PageMetrics> {
526 Ok(PageMetrics::default())
527 }
528
529 async fn set_network_interceptor(
530 &mut self,
531 _interceptor: NetworkInterceptor,
532 ) -> ProbarResult<()> {
533 Ok(())
534 }
535
536 async fn current_url(&self) -> ProbarResult<String> {
537 Ok(self.current_url.clone())
538 }
539
540 async fn go_back(&mut self) -> ProbarResult<()> {
541 self.call_history.push("go_back".to_string());
542 Ok(())
543 }
544
545 async fn go_forward(&mut self) -> ProbarResult<()> {
546 self.call_history.push("go_forward".to_string());
547 Ok(())
548 }
549
550 async fn reload(&mut self) -> ProbarResult<()> {
551 self.call_history.push("reload".to_string());
552 Ok(())
553 }
554
555 async fn close(&mut self) -> ProbarResult<()> {
556 self.call_history.push("close".to_string());
557 Ok(())
558 }
559}
560
561#[cfg(feature = "browser")]
576pub struct BrowserController<D: ProbarDriver> {
577 driver: D,
578 config: DriverConfig,
579}
580
581#[cfg(feature = "browser")]
582impl<D: ProbarDriver> std::fmt::Debug for BrowserController<D> {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 f.debug_struct("BrowserController")
585 .field("config", &self.config)
586 .finish_non_exhaustive()
587 }
588}
589
590#[cfg(feature = "browser")]
591impl<D: ProbarDriver> BrowserController<D> {
592 pub fn new(driver: D, config: DriverConfig) -> Self {
594 Self { driver, config }
595 }
596
597 pub async fn goto(&mut self, url: &str) -> ProbarResult<()> {
599 self.driver.navigate(url).await
600 }
601
602 pub async fn screenshot(&self) -> ProbarResult<Screenshot> {
604 self.driver.screenshot().await
605 }
606
607 pub async fn evaluate(&self, script: &str) -> ProbarResult<serde_json::Value> {
609 self.driver.execute_js(script).await
610 }
611
612 pub async fn query(&self, selector: &str) -> ProbarResult<Option<ElementHandle>> {
614 self.driver.query_selector(selector).await
615 }
616
617 pub async fn metrics(&self) -> ProbarResult<PageMetrics> {
619 self.driver.metrics().await
620 }
621
622 #[must_use]
624 pub const fn config(&self) -> &DriverConfig {
625 &self.config
626 }
627
628 pub async fn close(&mut self) -> ProbarResult<()> {
630 self.driver.close().await
631 }
632}
633
634#[cfg(test)]
639#[allow(clippy::unwrap_used, clippy::expect_used)]
640mod tests {
641 use super::*;
642
643 mod element_handle_tests {
644 use super::*;
645
646 #[test]
647 fn test_element_handle_creation() {
648 let elem = ElementHandle::new("btn-1", "button");
649 assert_eq!(elem.id, "btn-1");
650 assert_eq!(elem.tag_name, "button");
651 assert!(elem.text_content.is_none());
652 }
653
654 #[test]
655 fn test_element_handle_visibility() {
656 let mut elem = ElementHandle::new("elem", "div");
657 assert!(!elem.is_visible());
658
659 elem.bounding_box = Some(BoundingBox::new(0.0, 0.0, 100.0, 100.0));
660 assert!(elem.is_visible());
661 }
662 }
663
664 mod screenshot_tests {
665 use super::*;
666
667 #[test]
668 fn test_screenshot_creation() {
669 let data = vec![0x89, 0x50, 0x4E, 0x47]; let screenshot = Screenshot::new(data.clone(), 100, 100);
671 assert_eq!(screenshot.width, 100);
672 assert_eq!(screenshot.height, 100);
673 assert_eq!(screenshot.data, data);
674 }
675
676 #[test]
677 fn test_screenshot_size_bytes() {
678 let screenshot = Screenshot::new(vec![0; 1024], 100, 100);
679 assert_eq!(screenshot.size_bytes(), 1024);
680 }
681
682 #[test]
683 fn test_screenshot_is_valid() {
684 let valid = Screenshot::new(vec![1, 2, 3], 100, 100);
685 assert!(valid.is_valid());
686
687 let empty = Screenshot::new(vec![], 100, 100);
688 assert!(!empty.is_valid());
689
690 let zero_width = Screenshot::new(vec![1], 0, 100);
691 assert!(!zero_width.is_valid());
692 }
693 }
694
695 mod network_response_tests {
696 use super::*;
697
698 #[test]
699 fn test_json_response() {
700 let response = NetworkResponse::json(200, serde_json::json!({"ok": true}));
701 assert_eq!(response.status, 200);
702 assert!(response
703 .headers
704 .iter()
705 .any(|(k, v)| k == "Content-Type" && v.contains("json")));
706 }
707
708 #[test]
709 fn test_not_found_response() {
710 let response = NetworkResponse::not_found();
711 assert_eq!(response.status, 404);
712 }
713 }
714
715 mod driver_config_tests {
716 use super::*;
717
718 #[test]
719 fn test_config_default() {
720 let config = DriverConfig::default();
721 assert!(config.headless);
722 assert_eq!(config.viewport_width, 1920);
723 assert_eq!(config.viewport_height, 1080);
724 }
725
726 #[test]
727 fn test_config_builder() {
728 let config = DriverConfig::new()
729 .headless(false)
730 .viewport(800, 600)
731 .scale_factor(2.0)
732 .user_agent("test-agent");
733
734 assert!(!config.headless);
735 assert_eq!(config.viewport_width, 800);
736 assert_eq!(config.viewport_height, 600);
737 assert!((config.device_scale_factor - 2.0).abs() < f64::EPSILON);
738 assert_eq!(config.user_agent, Some("test-agent".to_string()));
739 }
740
741 #[test]
742 fn test_config_with_tracing() {
743 let config = DriverConfig::new().with_tracing(true);
744 assert!(config.tracing);
745 }
746 }
747
748 mod device_descriptor_tests {
749 use super::*;
750
751 #[test]
752 fn test_iphone_14_pro() {
753 let device = DeviceDescriptor::IPHONE_14_PRO;
754 assert_eq!(device.viewport_width, 393);
755 assert!(device.is_mobile);
756 assert!(device.has_touch);
757 }
758
759 #[test]
760 fn test_desktop_1080p() {
761 let device = DeviceDescriptor::DESKTOP_1080P;
762 assert_eq!(device.viewport_width, 1920);
763 assert!(!device.is_mobile);
764 assert!(!device.has_touch);
765 }
766
767 #[test]
768 fn test_device_to_config() {
769 let config = DeviceDescriptor::IPHONE_14_PRO.to_config();
770 assert_eq!(config.viewport_width, 393);
771 assert_eq!(config.viewport_height, 852);
772 }
773 }
774
775 mod mock_driver_tests {
776 use super::*;
777
778 #[test]
779 fn test_mock_driver_creation() {
780 let driver = MockDriver::new();
781 assert!(driver.elements.is_empty());
782 assert!(driver.current_url.is_empty());
783 }
784
785 #[test]
786 fn test_mock_driver_add_element() {
787 let mut driver = MockDriver::new();
788 driver.add_element(ElementHandle::new("test", "div"));
789 assert_eq!(driver.elements.len(), 1);
790 }
791
792 #[test]
793 fn test_mock_driver_history() {
794 let driver = MockDriver::new();
795 assert!(driver.history().is_empty());
796 }
797
798 #[test]
799 fn test_mock_driver_was_called() {
800 let driver = MockDriver::new();
801 assert!(!driver.was_called("navigate"));
802 }
803 }
804
805 mod page_metrics_tests {
806 use super::*;
807
808 #[test]
809 fn test_metrics_default() {
810 let metrics = PageMetrics::default();
811 assert!(metrics.first_paint_ms.is_none());
812 assert!(metrics.dom_nodes.is_none());
813 }
814 }
815
816 #[cfg(feature = "browser")]
817 mod async_driver_tests {
818 use super::*;
819
820 #[tokio::test]
821 async fn test_mock_driver_navigate() {
822 let mut driver = MockDriver::new();
823 driver.navigate("https://example.com").await.unwrap();
824 assert_eq!(driver.current_url, "https://example.com");
825 assert!(driver.was_called("navigate"));
826 }
827
828 #[tokio::test]
829 async fn test_mock_driver_history_tracking() {
830 let mut driver = MockDriver::new();
831 driver.go_back().await.unwrap();
832 driver.go_forward().await.unwrap();
833 driver.reload().await.unwrap();
834 driver.close().await.unwrap();
835
836 assert!(driver.was_called("go_back"));
837 assert!(driver.was_called("go_forward"));
838 assert!(driver.was_called("reload"));
839 assert!(driver.was_called("close"));
840 }
841
842 #[tokio::test]
843 async fn test_browser_controller_with_mock() {
844 let driver = MockDriver::new();
845 let mut controller = BrowserController::new(driver, DriverConfig::default());
846
847 controller.goto("https://test.com").await.unwrap();
848 let url = controller.driver.current_url().await.unwrap();
849 assert_eq!(url, "https://test.com");
850 }
851
852 #[tokio::test]
853 async fn test_mock_driver_screenshot_error() {
854 let driver = MockDriver::new();
855 let result = driver.screenshot().await;
857 assert!(result.is_err());
858 }
859
860 #[tokio::test]
861 async fn test_mock_driver_screenshot_success() {
862 let mut driver = MockDriver::new();
863 let screenshot = Screenshot::new(vec![1, 2, 3], 100, 50);
864 driver.set_screenshot(screenshot);
865 let result = driver.screenshot().await.unwrap();
866 assert_eq!(result.width, 100);
867 assert_eq!(result.height, 50);
868 }
869
870 #[tokio::test]
871 async fn test_mock_driver_execute_js_error() {
872 let driver = MockDriver::new();
873 let result = driver.execute_js("return 1;").await;
875 assert!(result.is_err());
876 }
877
878 #[tokio::test]
879 async fn test_mock_driver_execute_js_success() {
880 let mut driver = MockDriver::new();
881 driver.set_js_result(serde_json::json!({"result": 42}));
882 let result = driver.execute_js("return 42;").await.unwrap();
883 assert_eq!(result["result"], 42);
884 }
885
886 #[tokio::test]
887 async fn test_mock_driver_query_selector() {
888 let mut driver = MockDriver::new();
889 driver.add_element(ElementHandle::new("btn-submit", "button"));
890
891 let found = driver.query_selector("btn-submit").await.unwrap();
892 assert!(found.is_some());
893 assert_eq!(found.unwrap().tag_name, "button");
894
895 let not_found = driver.query_selector("non-existent").await.unwrap();
896 assert!(not_found.is_none());
897 }
898
899 #[tokio::test]
900 async fn test_mock_driver_query_selector_all() {
901 let mut driver = MockDriver::new();
902 driver.add_element(ElementHandle::new("elem1", "div"));
903 driver.add_element(ElementHandle::new("elem2", "span"));
904
905 let elements = driver.query_selector_all("*").await.unwrap();
906 assert_eq!(elements.len(), 2);
907 }
908
909 #[tokio::test]
910 async fn test_mock_driver_dispatch_input() {
911 let driver = MockDriver::new();
912 let event = InputEvent::KeyPress {
913 key: "Enter".to_string(),
914 };
915 let result = driver.dispatch_input(event).await;
916 assert!(result.is_ok());
917 }
918
919 #[tokio::test]
920 async fn test_mock_driver_click() {
921 let driver = MockDriver::new();
922 let result = driver.click("#button").await;
923 assert!(result.is_ok());
924 }
925
926 #[tokio::test]
927 async fn test_mock_driver_type_text() {
928 let driver = MockDriver::new();
929 let result = driver.type_text("#input", "hello world").await;
930 assert!(result.is_ok());
931 }
932
933 #[tokio::test]
934 async fn test_mock_driver_wait_for_selector_found() {
935 let mut driver = MockDriver::new();
936 driver.add_element(ElementHandle::new("target", "div"));
937 let result = driver
938 .wait_for_selector("target", Duration::from_secs(1))
939 .await;
940 assert!(result.is_ok());
941 assert_eq!(result.unwrap().id, "target");
942 }
943
944 #[tokio::test]
945 async fn test_mock_driver_wait_for_selector_not_found() {
946 let driver = MockDriver::new();
947 let result = driver
948 .wait_for_selector("missing", Duration::from_secs(1))
949 .await;
950 assert!(result.is_err());
951 }
952
953 #[tokio::test]
954 async fn test_mock_driver_metrics() {
955 let driver = MockDriver::new();
956 let metrics = driver.metrics().await.unwrap();
957 assert!(metrics.first_paint_ms.is_none());
958 }
959
960 #[tokio::test]
961 async fn test_mock_driver_set_network_interceptor() {
962 let mut driver = MockDriver::new();
963 let interceptor = NetworkInterceptor {
964 patterns: vec!["*.js".to_string()],
965 block: true,
966 response_override: None,
967 };
968 let result = driver.set_network_interceptor(interceptor).await;
969 assert!(result.is_ok());
970 }
971
972 #[tokio::test]
973 async fn test_browser_controller_evaluate() {
974 let mut driver = MockDriver::new();
975 driver.set_js_result(serde_json::json!({"foo": "bar"}));
976 let controller = BrowserController::new(driver, DriverConfig::default());
977 let result = controller.evaluate("return {foo: 'bar'}").await.unwrap();
978 assert_eq!(result["foo"], "bar");
979 }
980
981 #[tokio::test]
982 async fn test_browser_controller_query() {
983 let mut driver = MockDriver::new();
984 driver.add_element(ElementHandle::new("elem", "div"));
985 let controller = BrowserController::new(driver, DriverConfig::default());
986 let result = controller.query("elem").await.unwrap();
987 assert!(result.is_some());
988 }
989
990 #[tokio::test]
991 async fn test_browser_controller_metrics() {
992 let driver = MockDriver::new();
993 let controller = BrowserController::new(driver, DriverConfig::default());
994 let metrics = controller.metrics().await.unwrap();
995 assert!(metrics.first_paint_ms.is_none());
996 }
997
998 #[tokio::test]
999 async fn test_browser_controller_screenshot() {
1000 let mut driver = MockDriver::new();
1001 driver.set_screenshot(Screenshot::new(vec![0x89, 0x50], 640, 480));
1002 let controller = BrowserController::new(driver, DriverConfig::default());
1003 let screenshot = controller.screenshot().await.unwrap();
1004 assert_eq!(screenshot.width, 640);
1005 assert_eq!(screenshot.height, 480);
1006 }
1007
1008 #[tokio::test]
1009 async fn test_browser_controller_close() {
1010 let driver = MockDriver::new();
1011 let mut controller = BrowserController::new(driver, DriverConfig::default());
1012 let result = controller.close().await;
1013 assert!(result.is_ok());
1014 }
1015
1016 #[tokio::test]
1017 async fn test_browser_controller_config() {
1018 let config = DriverConfig::new().viewport(800, 600);
1019 let driver = MockDriver::new();
1020 let controller = BrowserController::new(driver, config);
1021 assert_eq!(controller.config().viewport_width, 800);
1022 assert_eq!(controller.config().viewport_height, 600);
1023 }
1024
1025 #[tokio::test]
1026 async fn test_browser_controller_debug() {
1027 let driver = MockDriver::new();
1028 let controller = BrowserController::new(driver, DriverConfig::default());
1029 let debug_str = format!("{:?}", controller);
1030 assert!(debug_str.contains("BrowserController"));
1031 }
1032 }
1033
1034 mod network_interceptor_tests {
1035 use super::*;
1036
1037 #[test]
1038 fn test_network_interceptor_default() {
1039 let interceptor = NetworkInterceptor::default();
1040 assert!(interceptor.patterns.is_empty());
1041 assert!(!interceptor.block);
1042 assert!(interceptor.response_override.is_none());
1043 }
1044
1045 #[test]
1046 fn test_network_interceptor_with_patterns() {
1047 let interceptor = NetworkInterceptor {
1048 patterns: vec!["*.js".to_string(), "*.css".to_string()],
1049 block: true,
1050 response_override: None,
1051 };
1052 assert_eq!(interceptor.patterns.len(), 2);
1053 assert!(interceptor.block);
1054 }
1055
1056 #[test]
1057 fn test_network_interceptor_with_response_override() {
1058 let response = NetworkResponse::json(200, serde_json::json!({"mock": true}));
1059 let interceptor = NetworkInterceptor {
1060 patterns: vec!["api/*".to_string()],
1061 block: false,
1062 response_override: Some(response),
1063 };
1064 assert!(interceptor.response_override.is_some());
1065 }
1066 }
1067
1068 mod driver_config_extended_tests {
1069 use super::*;
1070
1071 #[test]
1072 fn test_navigation_timeout() {
1073 let config = DriverConfig::new().navigation_timeout(Duration::from_secs(60));
1074 assert_eq!(config.navigation_timeout, Duration::from_secs(60));
1075 }
1076
1077 #[test]
1078 fn test_config_executable_path() {
1079 let mut config = DriverConfig::default();
1080 config.executable_path = Some("/usr/bin/chromium".to_string());
1081 assert_eq!(
1082 config.executable_path,
1083 Some("/usr/bin/chromium".to_string())
1084 );
1085 }
1086
1087 #[test]
1088 fn test_config_element_timeout_default() {
1089 let config = DriverConfig::default();
1090 assert_eq!(config.element_timeout, Duration::from_secs(5));
1091 }
1092 }
1093
1094 mod device_descriptor_extended_tests {
1095 use super::*;
1096
1097 #[test]
1098 fn test_ipad_pro_12_9() {
1099 let device = DeviceDescriptor::IPAD_PRO_12_9;
1100 assert_eq!(device.name, "iPad Pro 12.9");
1101 assert_eq!(device.viewport_width, 1024);
1102 assert_eq!(device.viewport_height, 1366);
1103 assert!((device.device_scale_factor - 2.0).abs() < f64::EPSILON);
1104 assert!(device.is_mobile);
1105 assert!(device.has_touch);
1106 assert!(device.user_agent.contains("iPad"));
1107 }
1108
1109 #[test]
1110 fn test_desktop_4k() {
1111 let device = DeviceDescriptor::DESKTOP_4K;
1112 assert_eq!(device.name, "Desktop 4K");
1113 assert_eq!(device.viewport_width, 3840);
1114 assert_eq!(device.viewport_height, 2160);
1115 assert!((device.device_scale_factor - 2.0).abs() < f64::EPSILON);
1116 assert!(!device.is_mobile);
1117 assert!(!device.has_touch);
1118 }
1119
1120 #[test]
1121 fn test_ipad_to_config() {
1122 let config = DeviceDescriptor::IPAD_PRO_12_9.to_config();
1123 assert_eq!(config.viewport_width, 1024);
1124 assert_eq!(config.viewport_height, 1366);
1125 assert!((config.device_scale_factor - 2.0).abs() < f64::EPSILON);
1126 assert!(config.user_agent.is_some());
1127 }
1128
1129 #[test]
1130 fn test_desktop_4k_to_config() {
1131 let config = DeviceDescriptor::DESKTOP_4K.to_config();
1132 assert_eq!(config.viewport_width, 3840);
1133 assert_eq!(config.viewport_height, 2160);
1134 }
1135 }
1136
1137 mod mock_driver_extended_tests {
1138 use super::*;
1139
1140 #[test]
1141 fn test_mock_driver_set_js_result() {
1142 let mut driver = MockDriver::new();
1143 driver.set_js_result(serde_json::json!({"value": 123}));
1144 assert_eq!(driver.js_results.len(), 1);
1145 assert_eq!(driver.js_results[0]["value"], 123);
1146 }
1147
1148 #[test]
1149 fn test_mock_driver_set_screenshot() {
1150 let mut driver = MockDriver::new();
1151 let screenshot = Screenshot::new(vec![1, 2, 3, 4], 200, 100);
1152 driver.set_screenshot(screenshot);
1153 assert!(driver.screenshot_data.is_some());
1154 let data = driver.screenshot_data.unwrap();
1155 assert_eq!(data.width, 200);
1156 assert_eq!(data.height, 100);
1157 }
1158
1159 #[test]
1160 fn test_mock_driver_debug() {
1161 let driver = MockDriver::new();
1162 let debug_str = format!("{:?}", driver);
1163 assert!(debug_str.contains("MockDriver"));
1164 }
1165
1166 #[test]
1167 fn test_mock_driver_multiple_elements() {
1168 let mut driver = MockDriver::new();
1169 driver.add_element(ElementHandle::new("a", "div"));
1170 driver.add_element(ElementHandle::new("b", "span"));
1171 driver.add_element(ElementHandle::new("c", "button"));
1172 assert_eq!(driver.elements.len(), 3);
1173 }
1174 }
1175
1176 mod element_handle_extended_tests {
1177 use super::*;
1178
1179 #[test]
1180 fn test_element_handle_with_text() {
1181 let mut elem = ElementHandle::new("p1", "p");
1182 elem.text_content = Some("Hello World".to_string());
1183 assert_eq!(elem.text_content, Some("Hello World".to_string()));
1184 }
1185
1186 #[test]
1187 fn test_element_handle_serialization() {
1188 let elem = ElementHandle::new("test-id", "input");
1189 let json = serde_json::to_string(&elem).unwrap();
1190 assert!(json.contains("test-id"));
1191 assert!(json.contains("input"));
1192 }
1193
1194 #[test]
1195 fn test_element_handle_deserialization() {
1196 let json =
1197 r#"{"id":"btn","tag_name":"button","text_content":null,"bounding_box":null}"#;
1198 let elem: ElementHandle = serde_json::from_str(json).unwrap();
1199 assert_eq!(elem.id, "btn");
1200 assert_eq!(elem.tag_name, "button");
1201 }
1202
1203 #[test]
1204 fn test_element_handle_clone() {
1205 let elem = ElementHandle::new("orig", "div");
1206 let cloned = elem.clone();
1207 assert_eq!(elem.id, cloned.id);
1208 assert_eq!(elem.tag_name, cloned.tag_name);
1209 }
1210 }
1211
1212 mod screenshot_extended_tests {
1213 use super::*;
1214
1215 #[test]
1216 fn test_screenshot_device_pixel_ratio() {
1217 let mut screenshot = Screenshot::new(vec![0], 100, 100);
1218 screenshot.device_pixel_ratio = 2.0;
1219 assert!((screenshot.device_pixel_ratio - 2.0).abs() < f64::EPSILON);
1220 }
1221
1222 #[test]
1223 fn test_screenshot_timestamp() {
1224 let screenshot = Screenshot::new(vec![0], 100, 100);
1225 let now = std::time::SystemTime::now();
1227 let duration = now.duration_since(screenshot.timestamp).unwrap();
1228 assert!(duration.as_secs() < 1);
1229 }
1230
1231 #[test]
1232 fn test_screenshot_zero_height_invalid() {
1233 let screenshot = Screenshot::new(vec![1, 2], 100, 0);
1234 assert!(!screenshot.is_valid());
1235 }
1236
1237 #[test]
1238 fn test_screenshot_clone() {
1239 let screenshot = Screenshot::new(vec![1, 2, 3], 50, 50);
1240 let cloned = screenshot.clone();
1241 assert_eq!(screenshot.data, cloned.data);
1242 assert_eq!(screenshot.width, cloned.width);
1243 assert_eq!(screenshot.height, cloned.height);
1244 }
1245 }
1246
1247 mod page_metrics_extended_tests {
1248 use super::*;
1249
1250 #[test]
1251 fn test_page_metrics_with_values() {
1252 let metrics = PageMetrics {
1253 first_paint_ms: Some(100.5),
1254 first_contentful_paint_ms: Some(150.0),
1255 dom_content_loaded_ms: Some(200.0),
1256 load_time_ms: Some(500.0),
1257 js_heap_size_bytes: Some(10_000_000),
1258 js_heap_used_bytes: Some(5_000_000),
1259 dom_nodes: Some(1500),
1260 frame_count: Some(2),
1261 };
1262 assert_eq!(metrics.first_paint_ms, Some(100.5));
1263 assert_eq!(metrics.dom_nodes, Some(1500));
1264 }
1265
1266 #[test]
1267 fn test_page_metrics_serialization() {
1268 let metrics = PageMetrics::default();
1269 let json = serde_json::to_string(&metrics).unwrap();
1270 assert!(json.contains("first_paint_ms"));
1271 }
1272
1273 #[test]
1274 fn test_page_metrics_clone() {
1275 let metrics = PageMetrics {
1276 first_paint_ms: Some(50.0),
1277 ..Default::default()
1278 };
1279 let cloned = metrics.clone();
1280 assert_eq!(metrics.first_paint_ms, cloned.first_paint_ms);
1281 }
1282 }
1283
1284 mod network_response_extended_tests {
1285 use super::*;
1286
1287 #[test]
1288 fn test_network_response_json_serialization_failure() {
1289 let response = NetworkResponse::json(200, serde_json::json!(null));
1291 assert_eq!(response.status, 200);
1292 }
1293
1294 #[test]
1295 fn test_network_response_with_headers() {
1296 let response = NetworkResponse {
1297 status: 201,
1298 headers: vec![
1299 ("Content-Type".to_string(), "text/plain".to_string()),
1300 ("X-Custom".to_string(), "value".to_string()),
1301 ],
1302 body: b"Created".to_vec(),
1303 };
1304 assert_eq!(response.headers.len(), 2);
1305 assert_eq!(response.body, b"Created".to_vec());
1306 }
1307
1308 #[test]
1309 fn test_network_response_not_found_body() {
1310 let response = NetworkResponse::not_found();
1311 assert_eq!(response.body, b"Not Found".to_vec());
1312 assert!(response.headers.is_empty());
1313 }
1314
1315 #[test]
1316 fn test_network_response_clone() {
1317 let response = NetworkResponse::json(200, serde_json::json!({"ok": true}));
1318 let cloned = response.clone();
1319 assert_eq!(response.status, cloned.status);
1320 assert_eq!(response.body, cloned.body);
1321 }
1322 }
1323}