1use crate::renacer_integration::{
28 ChromeTrace, TraceCollector, TracingConfig as RenacerTracingConfig,
29};
30use crate::result::{ProbarError, ProbarResult};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum BrowserConsoleLevel {
35 Log,
37 Info,
39 Warning,
41 Error,
43 Debug,
45}
46
47impl std::fmt::Display for BrowserConsoleLevel {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 Self::Log => write!(f, "log"),
51 Self::Info => write!(f, "info"),
52 Self::Warning => write!(f, "warn"),
53 Self::Error => write!(f, "error"),
54 Self::Debug => write!(f, "debug"),
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct BrowserConsoleMessage {
62 pub level: BrowserConsoleLevel,
64 pub text: String,
66 pub timestamp: u64,
68 pub source: Option<String>,
70 pub line: Option<u32>,
72}
73
74#[derive(Debug, Clone)]
76pub struct BrowserConfig {
77 pub headless: bool,
79 pub viewport_width: u32,
81 pub viewport_height: u32,
83 pub chromium_path: Option<String>,
85 pub debug_port: u16,
87 pub user_agent: Option<String>,
89 pub devtools: bool,
91 pub sandbox: bool,
93 pub tracing_config: Option<RenacerTracingConfig>,
95}
96
97impl Default for BrowserConfig {
98 fn default() -> Self {
99 Self {
100 headless: true,
101 viewport_width: 800,
102 viewport_height: 600,
103 chromium_path: None,
104 debug_port: 0,
105 user_agent: None,
106 devtools: false,
107 sandbox: true,
108 tracing_config: None,
109 }
110 }
111}
112
113impl BrowserConfig {
114 #[must_use]
116 pub const fn with_viewport(mut self, width: u32, height: u32) -> Self {
117 self.viewport_width = width;
118 self.viewport_height = height;
119 self
120 }
121
122 #[must_use]
124 pub const fn with_headless(mut self, headless: bool) -> Self {
125 self.headless = headless;
126 self
127 }
128
129 #[must_use]
131 pub fn with_chromium_path(mut self, path: impl Into<String>) -> Self {
132 self.chromium_path = Some(path.into());
133 self
134 }
135
136 #[must_use]
138 pub fn with_user_agent(mut self, ua: impl Into<String>) -> Self {
139 self.user_agent = Some(ua.into());
140 self
141 }
142
143 #[must_use]
145 pub const fn with_no_sandbox(mut self) -> Self {
146 self.sandbox = false;
147 self
148 }
149
150 #[must_use]
152 pub fn with_tracing(mut self, config: RenacerTracingConfig) -> Self {
153 self.tracing_config = Some(config);
154 self
155 }
156
157 #[must_use]
159 pub fn is_tracing_enabled(&self) -> bool {
160 self.tracing_config.as_ref().is_some_and(|c| c.enabled)
161 }
162}
163
164#[cfg(feature = "browser")]
169#[allow(
170 clippy::wildcard_imports,
171 clippy::redundant_clone,
172 clippy::implicit_clone,
173 clippy::significant_drop_tightening,
174 clippy::missing_errors_doc,
175 clippy::items_after_statements,
176 clippy::similar_names,
177 clippy::cast_possible_truncation,
178 clippy::suboptimal_flops
179)]
180mod cdp {
181 use super::*;
182 use crate::cdp_coverage::{
183 CoverageConfig, CoverageRange, CoverageReport, FunctionCoverage, ScriptCoverage,
184 };
185 use crate::renacer_integration::TraceSpan;
186 use chromiumoxide::browser::{Browser as CdpBrowser, BrowserConfig as CdpConfig};
187 use chromiumoxide::cdp::browser_protocol::input::{
188 DispatchTouchEventParams, DispatchTouchEventType, TouchPoint,
189 };
190 use chromiumoxide::cdp::browser_protocol::page::{
191 CaptureScreenshotFormat, CaptureScreenshotParams,
192 };
193 use chromiumoxide::page::Page as CdpPage;
194 use futures::StreamExt;
195 use std::sync::Arc;
196 use tokio::sync::Mutex;
197
198 #[derive(Debug)]
200 pub struct Browser {
201 config: BrowserConfig,
202 inner: Arc<Mutex<CdpBrowser>>,
203 handle: tokio::task::JoinHandle<()>,
204 }
205
206 impl Browser {
207 pub async fn launch(config: BrowserConfig) -> ProbarResult<Self> {
213 let mut builder = CdpConfig::builder();
214
215 if config.headless {
216 builder = builder.with_head();
217 }
218
219 if !config.sandbox {
220 builder = builder.no_sandbox();
221 }
222
223 if let Some(ref path) = config.chromium_path {
224 builder = builder.chrome_executable(path);
225 }
226
227 let cdp_config = builder
228 .build()
229 .map_err(|e| ProbarError::BrowserLaunchError {
230 message: e.to_string(),
231 })?;
232
233 let (browser, mut handler) = CdpBrowser::launch(cdp_config).await.map_err(|e| {
234 ProbarError::BrowserLaunchError {
235 message: e.to_string(),
236 }
237 })?;
238
239 let handle = tokio::spawn(async move {
241 while let Some(h) = handler.next().await {
242 if h.is_err() {
243 break;
244 }
245 }
246 });
247
248 Ok(Self {
249 config,
250 inner: Arc::new(Mutex::new(browser)),
251 handle,
252 })
253 }
254
255 pub async fn new_page(&self) -> ProbarResult<Page> {
261 let browser = self.inner.lock().await;
262 let cdp_page =
263 browser
264 .new_page("about:blank")
265 .await
266 .map_err(|e| ProbarError::PageError {
267 message: e.to_string(),
268 })?;
269
270 let trace_collector = self.config.tracing_config.as_ref().and_then(|tc| {
275 if tc.enabled {
276 Some(TraceCollector::new(&tc.service_name))
277 } else {
278 None
279 }
280 });
281
282 Ok(Page {
283 width: self.config.viewport_width,
284 height: self.config.viewport_height,
285 url: String::from("about:blank"),
286 wasm_ready: false,
287 inner: Some(Arc::new(Mutex::new(cdp_page))),
288 console_messages: Arc::new(Mutex::new(Vec::new())),
289 console_capture_enabled: false,
290 trace_collector,
291 coverage_enabled: false,
292 })
293 }
294
295 #[must_use]
297 pub const fn config(&self) -> &BrowserConfig {
298 &self.config
299 }
300
301 #[must_use]
303 pub fn is_handler_running(&self) -> bool {
304 !self.handle.is_finished()
305 }
306
307 pub async fn close(self) -> ProbarResult<()> {
309 let mut browser = self.inner.lock().await;
310 browser
311 .close()
312 .await
313 .map_err(|e| ProbarError::BrowserLaunchError {
314 message: e.to_string(),
315 })?;
316 Ok(())
317 }
318 }
319
320 #[derive(Debug)]
322 pub struct Page {
323 pub width: u32,
325 pub height: u32,
327 pub url: String,
329 pub wasm_ready: bool,
331 inner: Option<Arc<Mutex<CdpPage>>>,
333 console_messages: Arc<Mutex<Vec<BrowserConsoleMessage>>>,
335 console_capture_enabled: bool,
337 trace_collector: Option<TraceCollector>,
339 coverage_enabled: bool,
341 }
342
343 impl Page {
344 #[must_use]
346 pub fn new(width: u32, height: u32) -> Self {
347 Self {
348 width,
349 height,
350 url: String::from("about:blank"),
351 wasm_ready: false,
352 inner: None,
353 console_messages: Arc::new(Mutex::new(Vec::new())),
354 console_capture_enabled: false,
355 trace_collector: None,
356 coverage_enabled: false,
357 }
358 }
359
360 pub async fn goto(&mut self, url: &str) -> ProbarResult<()> {
366 if let Some(ref inner) = self.inner {
367 let page = inner.lock().await;
368 page.goto(url)
369 .await
370 .map_err(|e| ProbarError::NavigationError {
371 url: url.to_string(),
372 message: e.to_string(),
373 })?;
374 }
375 self.url = url.to_string();
376 Ok(())
377 }
378
379 pub async fn wait_for_wasm_ready(&mut self) -> ProbarResult<()> {
385 if let Some(ref inner) = self.inner {
386 let page = inner.lock().await;
387 page.evaluate(
389 "new Promise(resolve => { \
390 if (window.__wasm_ready) { resolve(true); } \
391 else { window.addEventListener('wasm-ready', () => resolve(true)); } \
392 })",
393 )
394 .await
395 .map_err(|e| ProbarError::WasmError {
396 message: e.to_string(),
397 })?;
398 }
399 self.wasm_ready = true;
400 Ok(())
401 }
402
403 pub async fn eval_wasm<T: serde::de::DeserializeOwned>(
409 &self,
410 expr: &str,
411 ) -> ProbarResult<T> {
412 if let Some(ref inner) = self.inner {
413 let page = inner.lock().await;
414 let result = page
415 .evaluate(expr)
416 .await
417 .map_err(|e| ProbarError::WasmError {
418 message: e.to_string(),
419 })?;
420 result.into_value().map_err(|e| ProbarError::WasmError {
421 message: e.to_string(),
422 })
423 } else {
424 Err(ProbarError::WasmError {
425 message: "No browser connection".to_string(),
426 })
427 }
428 }
429
430 pub async fn touch(&self, touch: crate::Touch) -> ProbarResult<()> {
436 if let Some(ref inner) = self.inner {
437 let page = inner.lock().await;
438
439 match touch.action {
440 crate::TouchAction::Tap => {
441 let start_params = DispatchTouchEventParams::builder()
443 .r#type(DispatchTouchEventType::TouchStart)
444 .touch_points(vec![TouchPoint::builder()
445 .x(f64::from(touch.x))
446 .y(f64::from(touch.y))
447 .build()
448 .map_err(|e| ProbarError::InputError {
449 message: e.to_string(),
450 })?])
451 .build()
452 .map_err(|e| ProbarError::InputError {
453 message: e.to_string(),
454 })?;
455
456 page.execute(start_params)
457 .await
458 .map_err(|e| ProbarError::InputError {
459 message: e.to_string(),
460 })?;
461
462 let end_params = DispatchTouchEventParams::builder()
464 .r#type(DispatchTouchEventType::TouchEnd)
465 .touch_points(Vec::<TouchPoint>::new())
466 .build()
467 .map_err(|e| ProbarError::InputError {
468 message: e.to_string(),
469 })?;
470
471 page.execute(end_params)
472 .await
473 .map_err(|e| ProbarError::InputError {
474 message: e.to_string(),
475 })?;
476 }
477 crate::TouchAction::Swipe {
478 end_x,
479 end_y,
480 duration_ms,
481 } => {
482 let steps = 10;
484 let step_delay = duration_ms / steps;
485
486 let start_params = DispatchTouchEventParams::builder()
488 .r#type(DispatchTouchEventType::TouchStart)
489 .touch_points(vec![TouchPoint::builder()
490 .x(f64::from(touch.x))
491 .y(f64::from(touch.y))
492 .build()
493 .map_err(|e| ProbarError::InputError {
494 message: e.to_string(),
495 })?])
496 .build()
497 .map_err(|e| ProbarError::InputError {
498 message: e.to_string(),
499 })?;
500
501 page.execute(start_params)
502 .await
503 .map_err(|e| ProbarError::InputError {
504 message: e.to_string(),
505 })?;
506
507 for i in 1..=steps {
509 let progress = f32::from(i as u8) / f32::from(steps as u8);
510 let x = touch.x + (end_x - touch.x) * progress;
511 let y = touch.y + (end_y - touch.y) * progress;
512
513 let move_params = DispatchTouchEventParams::builder()
514 .r#type(DispatchTouchEventType::TouchMove)
515 .touch_points(vec![TouchPoint::builder()
516 .x(f64::from(x))
517 .y(f64::from(y))
518 .build()
519 .map_err(|e| ProbarError::InputError {
520 message: e.to_string(),
521 })?])
522 .build()
523 .map_err(|e| ProbarError::InputError {
524 message: e.to_string(),
525 })?;
526
527 page.execute(move_params).await.map_err(|e| {
528 ProbarError::InputError {
529 message: e.to_string(),
530 }
531 })?;
532
533 tokio::time::sleep(tokio::time::Duration::from_millis(u64::from(
534 step_delay,
535 )))
536 .await;
537 }
538
539 let end_params = DispatchTouchEventParams::builder()
541 .r#type(DispatchTouchEventType::TouchEnd)
542 .touch_points(Vec::<TouchPoint>::new())
543 .build()
544 .map_err(|e| ProbarError::InputError {
545 message: e.to_string(),
546 })?;
547
548 page.execute(end_params)
549 .await
550 .map_err(|e| ProbarError::InputError {
551 message: e.to_string(),
552 })?;
553 }
554 crate::TouchAction::Hold { duration_ms } => {
555 let start_params = DispatchTouchEventParams::builder()
557 .r#type(DispatchTouchEventType::TouchStart)
558 .touch_points(vec![TouchPoint::builder()
559 .x(f64::from(touch.x))
560 .y(f64::from(touch.y))
561 .build()
562 .map_err(|e| ProbarError::InputError {
563 message: e.to_string(),
564 })?])
565 .build()
566 .map_err(|e| ProbarError::InputError {
567 message: e.to_string(),
568 })?;
569
570 page.execute(start_params)
571 .await
572 .map_err(|e| ProbarError::InputError {
573 message: e.to_string(),
574 })?;
575
576 tokio::time::sleep(tokio::time::Duration::from_millis(u64::from(
578 duration_ms,
579 )))
580 .await;
581
582 let end_params = DispatchTouchEventParams::builder()
584 .r#type(DispatchTouchEventType::TouchEnd)
585 .touch_points(Vec::<TouchPoint>::new())
586 .build()
587 .map_err(|e| ProbarError::InputError {
588 message: e.to_string(),
589 })?;
590
591 page.execute(end_params)
592 .await
593 .map_err(|e| ProbarError::InputError {
594 message: e.to_string(),
595 })?;
596 }
597 }
598 }
599 Ok(())
600 }
601
602 pub async fn screenshot(&self) -> ProbarResult<Vec<u8>> {
608 if let Some(ref inner) = self.inner {
609 let page = inner.lock().await;
610 let params = CaptureScreenshotParams::builder()
611 .format(CaptureScreenshotFormat::Png)
612 .build();
613
614 let screenshot =
615 page.execute(params)
616 .await
617 .map_err(|e| ProbarError::ScreenshotError {
618 message: e.to_string(),
619 })?;
620
621 use base64::Engine;
622 base64::engine::general_purpose::STANDARD
623 .decode(&screenshot.data)
624 .map_err(|e| ProbarError::ScreenshotError {
625 message: e.to_string(),
626 })
627 } else {
628 Ok(vec![])
630 }
631 }
632
633 #[must_use]
635 pub fn current_url(&self) -> &str {
636 &self.url
637 }
638
639 #[must_use]
641 pub const fn is_wasm_ready(&self) -> bool {
642 self.wasm_ready
643 }
644
645 pub async fn cdp_page(&self) -> Option<tokio::sync::MutexGuard<'_, CdpPage>> {
673 if let Some(ref inner) = self.inner {
674 Some(inner.lock().await)
675 } else {
676 None
677 }
678 }
679
680 pub async fn click(&self, selector: &str) -> ProbarResult<()> {
686 if let Some(ref inner) = self.inner {
687 let page = inner.lock().await;
688 let element = page.find_element(selector).await.map_err(|e| {
690 ProbarError::ElementNotFound {
691 selector: selector.to_string(),
692 message: e.to_string(),
693 }
694 })?;
695 element
696 .click()
697 .await
698 .map_err(|e| ProbarError::ElementNotFound {
699 selector: selector.to_string(),
700 message: format!("Click failed: {e}"),
701 })?;
702 Ok(())
703 } else {
704 Ok(())
706 }
707 }
708
709 pub async fn evaluate(
715 &self,
716 expression: &str,
717 ) -> ProbarResult<chromiumoxide::js::EvaluationResult> {
718 if let Some(ref inner) = self.inner {
719 let page = inner.lock().await;
720 page.evaluate(expression)
721 .await
722 .map_err(|e| ProbarError::WasmError {
723 message: format!("Evaluate failed: {e}"),
724 })
725 } else {
726 Err(ProbarError::WasmError {
727 message: "Cannot evaluate on mock page".to_string(),
728 })
729 }
730 }
731
732 pub async fn enable_console_capture(&mut self) -> ProbarResult<()> {
742 self.inject_console_capture().await
744 }
745
746 #[must_use]
748 pub const fn is_console_capture_enabled(&self) -> bool {
749 self.console_capture_enabled
750 }
751
752 pub async fn console_messages(&self) -> Vec<BrowserConsoleMessage> {
754 self.console_messages.lock().await.clone()
755 }
756
757 pub async fn clear_console(&self) {
759 self.console_messages.lock().await.clear();
760 }
761
762 pub async fn add_console_message(&self, msg: BrowserConsoleMessage) {
764 self.console_messages.lock().await.push(msg);
765 }
766
767 pub async fn wait_for_console<F>(
773 &self,
774 predicate: F,
775 timeout_ms: u64,
776 ) -> ProbarResult<BrowserConsoleMessage>
777 where
778 F: Fn(&BrowserConsoleMessage) -> bool,
779 {
780 let start = std::time::Instant::now();
781 let timeout = std::time::Duration::from_millis(timeout_ms);
782
783 loop {
784 {
786 let messages = self.console_messages.lock().await;
787 if let Some(msg) = messages.iter().find(|m| predicate(m)) {
788 return Ok(msg.clone());
789 }
790 }
791
792 if start.elapsed() >= timeout {
794 return Err(ProbarError::TimeoutError {
795 message: format!(
796 "Timeout waiting for console message after {timeout_ms}ms"
797 ),
798 });
799 }
800
801 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
803
804 if let Some(ref inner) = self.inner {
806 let page = inner.lock().await;
807
808 let _ = page
811 .evaluate("(function() { return window.__probar_console_check || 0; })()")
812 .await;
813 }
814 }
815 }
816
817 pub async fn inject_console_capture(&mut self) -> ProbarResult<()> {
826 if let Some(ref inner) = self.inner {
827 let page = inner.lock().await;
828
829 page.evaluate(
831 r#"
832 (function() {
833 if (window.__probar_console_hooked) return;
834 window.__probar_console_hooked = true;
835 window.__probar_console_messages = [];
836
837 const levels = ['log', 'info', 'warn', 'error', 'debug'];
838 levels.forEach(level => {
839 const original = console[level];
840 console[level] = function(...args) {
841 window.__probar_console_messages.push({
842 level: level,
843 text: args.map(a => String(a)).join(' '),
844 timestamp: Date.now()
845 });
846 original.apply(console, args);
847 };
848 });
849 })();
850 "#,
851 )
852 .await
853 .map_err(|e| ProbarError::WasmError {
854 message: format!("Failed to inject console capture: {e}"),
855 })?;
856
857 self.console_capture_enabled = true;
858 }
859 Ok(())
860 }
861
862 pub async fn fetch_console_messages(&self) -> ProbarResult<Vec<BrowserConsoleMessage>> {
868 if let Some(ref inner) = self.inner {
869 let page = inner.lock().await;
870
871 let result: serde_json::Value = page
872 .evaluate("window.__probar_console_messages || []")
873 .await
874 .map_err(|e| ProbarError::WasmError {
875 message: format!("Failed to fetch console messages: {e}"),
876 })?
877 .into_value()
878 .map_err(|e| ProbarError::WasmError {
879 message: format!("Failed to parse console messages: {e}"),
880 })?;
881
882 let messages: Vec<BrowserConsoleMessage> = result
883 .as_array()
884 .map(|arr| {
885 arr.iter()
886 .filter_map(|v| {
887 let level_str = v.get("level")?.as_str()?;
888 let level = match level_str {
889 "log" => BrowserConsoleLevel::Log,
890 "info" => BrowserConsoleLevel::Info,
891 "warn" => BrowserConsoleLevel::Warning,
892 "error" => BrowserConsoleLevel::Error,
893 "debug" => BrowserConsoleLevel::Debug,
894 _ => BrowserConsoleLevel::Log,
895 };
896 Some(BrowserConsoleMessage {
897 level,
898 text: v.get("text")?.as_str()?.to_string(),
899 timestamp: v.get("timestamp")?.as_u64().unwrap_or(0),
900 source: None,
901 line: None,
902 })
903 })
904 .collect()
905 })
906 .unwrap_or_default();
907
908 {
910 let mut internal = self.console_messages.lock().await;
911 for msg in &messages {
912 if !internal
913 .iter()
914 .any(|m| m.timestamp == msg.timestamp && m.text == msg.text)
915 {
916 internal.push(msg.clone());
917 }
918 }
919 }
920
921 Ok(messages)
922 } else {
923 Ok(vec![])
924 }
925 }
926
927 #[must_use]
933 pub fn is_tracing_enabled(&self) -> bool {
934 self.trace_collector.is_some()
935 }
936
937 #[must_use]
939 pub fn traceparent(&self) -> Option<String> {
940 self.trace_collector
941 .as_ref()
942 .and_then(|tc| tc.traceparent())
943 }
944
945 pub fn start_span(
947 &mut self,
948 name: impl Into<String>,
949 category: impl Into<String>,
950 ) -> Option<TraceSpan> {
951 self.trace_collector
952 .as_mut()
953 .map(|tc| tc.start_span(name, category))
954 }
955
956 pub fn record_span(&mut self, span: TraceSpan) {
958 if let Some(tc) = &mut self.trace_collector {
959 tc.record_span(span);
960 }
961 }
962
963 pub fn record_trace_console(&mut self, message: impl Into<String>) {
965 if let Some(tc) = &mut self.trace_collector {
966 tc.record_console(message);
967 }
968 }
969
970 #[must_use]
972 pub fn export_chrome_trace(&self) -> Option<ChromeTrace> {
973 self.trace_collector.as_ref().map(|tc| tc.to_chrome_trace())
974 }
975
976 pub fn export_trace_json(&self) -> ProbarResult<Option<String>> {
982 match self.trace_collector.as_ref() {
983 Some(tc) => {
984 let chrome_trace = tc.to_chrome_trace();
985 chrome_trace
986 .to_json()
987 .map(Some)
988 .map_err(|e| ProbarError::SerializationError {
989 message: format!("Failed to serialize trace: {e}"),
990 })
991 }
992 None => Ok(None),
993 }
994 }
995
996 pub async fn inject_trace_context(&mut self) -> ProbarResult<()> {
1004 if let Some(traceparent) = self.traceparent() {
1005 if let Some(ref inner) = self.inner {
1006 let page = inner.lock().await;
1007 let script = format!(
1008 r#"window.__probar_trace_context = {{ traceparent: "{}" }};"#,
1009 traceparent
1010 );
1011 page.evaluate(script.as_str())
1012 .await
1013 .map_err(|e| ProbarError::WasmError {
1014 message: format!("Failed to inject trace context: {e}"),
1015 })?;
1016 }
1017 }
1018 Ok(())
1019 }
1020
1021 pub async fn start_coverage(&mut self) -> ProbarResult<()> {
1031 self.start_coverage_with_config(CoverageConfig::default())
1032 .await
1033 }
1034
1035 pub async fn start_coverage_with_config(
1041 &mut self,
1042 _config: CoverageConfig,
1043 ) -> ProbarResult<()> {
1044 if let Some(ref inner) = self.inner {
1045 let page = inner.lock().await;
1046
1047 let coverage_script = r#"
1049 (function() {
1050 window.__probar_coverage = {
1051 enabled: true,
1052 functions: {},
1053 start_time: performance.now()
1054 };
1055
1056 // Track all function calls via console
1057 const originalLog = console.log;
1058 const originalInfo = console.info;
1059 const originalWarn = console.warn;
1060 const originalError = console.error;
1061 const originalDebug = console.debug;
1062
1063 function trackCall(name, args) {
1064 if (!window.__probar_coverage.functions[name]) {
1065 window.__probar_coverage.functions[name] = { count: 0, first_call: performance.now() };
1066 }
1067 window.__probar_coverage.functions[name].count++;
1068 window.__probar_coverage.functions[name].last_call = performance.now();
1069 }
1070
1071 console.log = function(...args) {
1072 trackCall('console.log', args);
1073 return originalLog.apply(console, args);
1074 };
1075 console.info = function(...args) {
1076 trackCall('console.info', args);
1077 return originalInfo.apply(console, args);
1078 };
1079 console.warn = function(...args) {
1080 trackCall('console.warn', args);
1081 return originalWarn.apply(console, args);
1082 };
1083 console.error = function(...args) {
1084 trackCall('console.error', args);
1085 return originalError.apply(console, args);
1086 };
1087 console.debug = function(...args) {
1088 trackCall('console.debug', args);
1089 return originalDebug.apply(console, args);
1090 };
1091
1092 // Track WASM availability
1093 if (typeof wasm_bindgen !== 'undefined') {
1094 window.__probar_coverage.wasm_available = true;
1095 }
1096
1097 return true;
1098 })();
1099 "#;
1100
1101 page.evaluate(coverage_script)
1102 .await
1103 .map_err(|e| ProbarError::WasmError {
1104 message: format!("Failed to start coverage: {e}"),
1105 })?;
1106
1107 self.coverage_enabled = true;
1108 }
1109 Ok(())
1110 }
1111
1112 pub async fn take_coverage(&self) -> ProbarResult<CoverageReport> {
1118 if let Some(ref inner) = self.inner {
1119 let page = inner.lock().await;
1120
1121 let result: serde_json::Value = page
1122 .evaluate(
1123 r#"
1124 (function() {
1125 const cov = window.__probar_coverage || { functions: {} };
1126 const funcs = [];
1127
1128 for (const [name, data] of Object.entries(cov.functions)) {
1129 funcs.push({
1130 function_name: name,
1131 ranges: [{
1132 start_offset: 0,
1133 end_offset: 100,
1134 count: data.count || 0
1135 }],
1136 is_block_coverage: false
1137 });
1138 }
1139
1140 const url = window.location.href;
1141
1142 return {
1143 scripts: [{
1144 script_id: '1',
1145 url: url,
1146 functions: funcs
1147 }],
1148 timestamp_ms: Date.now(),
1149 wasm_available: cov.wasm_available || false
1150 };
1151 })()
1152 "#,
1153 )
1154 .await
1155 .map_err(|e| ProbarError::WasmError {
1156 message: format!("Failed to take coverage: {e}"),
1157 })?
1158 .into_value()
1159 .map_err(|e| ProbarError::WasmError {
1160 message: format!("Failed to parse coverage result: {e}"),
1161 })?;
1162
1163 let mut report = CoverageReport::new();
1164 report.timestamp_ms = result["timestamp_ms"].as_u64().unwrap_or(0);
1165
1166 if let Some(scripts) = result["scripts"].as_array() {
1167 for script in scripts {
1168 let mut script_cov = ScriptCoverage {
1169 script_id: script["script_id"].as_str().unwrap_or("").to_string(),
1170 url: script["url"].as_str().unwrap_or("").to_string(),
1171 functions: vec![],
1172 };
1173
1174 if let Some(functions) = script["functions"].as_array() {
1175 for func in functions {
1176 let mut func_cov = FunctionCoverage {
1177 function_name: func["function_name"]
1178 .as_str()
1179 .unwrap_or("")
1180 .to_string(),
1181 ranges: vec![],
1182 is_block_coverage: func["is_block_coverage"]
1183 .as_bool()
1184 .unwrap_or(false),
1185 };
1186
1187 if let Some(ranges) = func["ranges"].as_array() {
1188 for range in ranges {
1189 func_cov.ranges.push(CoverageRange {
1190 start_offset: range["start_offset"]
1191 .as_u64()
1192 .unwrap_or(0)
1193 as u32,
1194 end_offset: range["end_offset"].as_u64().unwrap_or(0)
1195 as u32,
1196 count: range["count"].as_u64().unwrap_or(0) as u32,
1197 });
1198 }
1199 }
1200
1201 script_cov.functions.push(func_cov);
1202 }
1203 }
1204
1205 report.add_script(script_cov);
1206 }
1207 }
1208
1209 return Ok(report);
1210 }
1211
1212 Ok(CoverageReport::new())
1213 }
1214
1215 pub async fn stop_coverage(&mut self) -> ProbarResult<CoverageReport> {
1221 let report = self.take_coverage().await?;
1222
1223 if let Some(ref inner) = self.inner {
1224 let page = inner.lock().await;
1225 page.evaluate("window.__probar_coverage = null;")
1226 .await
1227 .map_err(|e| ProbarError::WasmError {
1228 message: format!("Failed to stop coverage: {e}"),
1229 })?;
1230 }
1231
1232 self.coverage_enabled = false;
1233 Ok(report)
1234 }
1235
1236 #[must_use]
1238 pub const fn is_coverage_enabled(&self) -> bool {
1239 self.coverage_enabled
1240 }
1241 }
1242}
1243
1244#[cfg(not(feature = "browser"))]
1249#[allow(clippy::missing_const_for_fn)]
1250mod mock {
1251 use super::{
1252 BrowserConfig, BrowserConsoleMessage, ChromeTrace, ProbarError, ProbarResult,
1253 TraceCollector,
1254 };
1255 use crate::cdp_coverage::{CoverageConfig, CoverageReport};
1256 use crate::renacer_integration::TraceSpan;
1257 use std::sync::{Arc, Mutex};
1258
1259 #[derive(Debug)]
1261 pub struct Browser {
1262 config: BrowserConfig,
1263 }
1264
1265 impl Browser {
1266 pub fn launch(config: BrowserConfig) -> ProbarResult<Self> {
1272 Ok(Self { config })
1273 }
1274
1275 pub fn new_page(&self) -> ProbarResult<Page> {
1281 let trace_collector = self.config.tracing_config.as_ref().and_then(|tc| {
1282 if tc.enabled {
1283 Some(TraceCollector::new(&tc.service_name))
1284 } else {
1285 None
1286 }
1287 });
1288
1289 Ok(Page::new_with_tracing(
1290 self.config.viewport_width,
1291 self.config.viewport_height,
1292 trace_collector,
1293 ))
1294 }
1295
1296 #[must_use]
1298 pub const fn config(&self) -> &BrowserConfig {
1299 &self.config
1300 }
1301 }
1302
1303 #[derive(Debug)]
1305 pub struct Page {
1306 pub width: u32,
1308 pub height: u32,
1310 pub url: String,
1312 pub wasm_ready: bool,
1314 console_messages: Arc<Mutex<Vec<BrowserConsoleMessage>>>,
1316 console_capture_enabled: bool,
1318 trace_collector: Option<TraceCollector>,
1320 coverage_enabled: bool,
1322 coverage_data: Arc<Mutex<Vec<crate::cdp_coverage::FunctionCoverage>>>,
1324 }
1325
1326 impl Page {
1327 #[must_use]
1329 pub fn new(width: u32, height: u32) -> Self {
1330 Self::new_with_tracing(width, height, None)
1331 }
1332
1333 #[must_use]
1335 pub fn new_with_tracing(
1336 width: u32,
1337 height: u32,
1338 trace_collector: Option<TraceCollector>,
1339 ) -> Self {
1340 Self {
1341 width,
1342 height,
1343 url: String::from("about:blank"),
1344 wasm_ready: false,
1345 console_messages: Arc::new(Mutex::new(Vec::new())),
1346 console_capture_enabled: false,
1347 trace_collector,
1348 coverage_enabled: false,
1349 coverage_data: Arc::new(Mutex::new(Vec::new())),
1350 }
1351 }
1352
1353 pub fn goto(&mut self, url: &str) -> ProbarResult<()> {
1359 self.url = url.to_string();
1360 Ok(())
1361 }
1362
1363 pub fn wait_for_wasm_ready(&mut self) -> ProbarResult<()> {
1369 self.wasm_ready = true;
1370 Ok(())
1371 }
1372
1373 pub fn eval_wasm<T: serde::de::DeserializeOwned>(&self, _expr: &str) -> ProbarResult<T> {
1379 Err(ProbarError::WasmError {
1380 message:
1381 "Browser feature not enabled. Enable 'browser' feature for real CDP support."
1382 .to_string(),
1383 })
1384 }
1385
1386 pub fn touch(&self, _touch: crate::Touch) -> ProbarResult<()> {
1392 Ok(())
1393 }
1394
1395 pub fn screenshot(&self) -> ProbarResult<Vec<u8>> {
1401 Ok(vec![])
1402 }
1403
1404 #[must_use]
1406 pub fn current_url(&self) -> &str {
1407 &self.url
1408 }
1409
1410 #[must_use]
1412 pub const fn is_wasm_ready(&self) -> bool {
1413 self.wasm_ready
1414 }
1415
1416 pub fn enable_console_capture(&mut self) -> ProbarResult<()> {
1426 self.console_capture_enabled = true;
1427 Ok(())
1428 }
1429
1430 #[must_use]
1432 pub const fn is_console_capture_enabled(&self) -> bool {
1433 self.console_capture_enabled
1434 }
1435
1436 #[must_use]
1438 pub fn console_messages(&self) -> Vec<BrowserConsoleMessage> {
1439 self.console_messages
1440 .lock()
1441 .map(|guard| guard.clone())
1442 .unwrap_or_default()
1443 }
1444
1445 pub fn clear_console(&self) {
1447 if let Ok(mut guard) = self.console_messages.lock() {
1448 guard.clear();
1449 }
1450 }
1451
1452 pub fn add_console_message(&self, msg: BrowserConsoleMessage) {
1454 if let Ok(mut guard) = self.console_messages.lock() {
1455 guard.push(msg);
1456 }
1457 }
1458
1459 pub fn wait_for_console<F>(
1465 &self,
1466 predicate: F,
1467 _timeout_ms: u64,
1468 ) -> ProbarResult<BrowserConsoleMessage>
1469 where
1470 F: Fn(&BrowserConsoleMessage) -> bool,
1471 {
1472 let messages = self
1473 .console_messages
1474 .lock()
1475 .map_err(|e| ProbarError::InvalidState {
1476 message: format!("Lock poisoned: {e}"),
1477 })?;
1478 messages
1479 .iter()
1480 .find(|m| predicate(m))
1481 .cloned()
1482 .ok_or_else(|| ProbarError::TimeoutError {
1483 message: "No matching console message found (mock)".to_string(),
1484 })
1485 }
1486
1487 pub fn inject_console_capture(&mut self) -> ProbarResult<()> {
1493 self.console_capture_enabled = true;
1494 Ok(())
1495 }
1496
1497 pub fn fetch_console_messages(&self) -> ProbarResult<Vec<BrowserConsoleMessage>> {
1503 Ok(self
1504 .console_messages
1505 .lock()
1506 .map(|guard| guard.clone())
1507 .unwrap_or_default())
1508 }
1509
1510 #[must_use]
1516 pub fn is_tracing_enabled(&self) -> bool {
1517 self.trace_collector.is_some()
1518 }
1519
1520 #[must_use]
1522 pub fn traceparent(&self) -> Option<String> {
1523 self.trace_collector
1524 .as_ref()
1525 .and_then(|tc| tc.traceparent())
1526 }
1527
1528 pub fn start_span(
1530 &mut self,
1531 name: impl Into<String>,
1532 category: impl Into<String>,
1533 ) -> Option<TraceSpan> {
1534 self.trace_collector
1535 .as_mut()
1536 .map(|tc| tc.start_span(name, category))
1537 }
1538
1539 pub fn record_span(&mut self, span: TraceSpan) {
1541 if let Some(tc) = &mut self.trace_collector {
1542 tc.record_span(span);
1543 }
1544 }
1545
1546 pub fn record_trace_console(&mut self, message: impl Into<String>) {
1548 if let Some(tc) = &mut self.trace_collector {
1549 tc.record_console(message);
1550 }
1551 }
1552
1553 #[must_use]
1555 pub fn export_chrome_trace(&self) -> Option<ChromeTrace> {
1556 self.trace_collector.as_ref().map(|tc| tc.to_chrome_trace())
1557 }
1558
1559 pub fn export_trace_json(&self) -> ProbarResult<Option<String>> {
1565 match self.trace_collector.as_ref() {
1566 Some(tc) => {
1567 let chrome_trace = tc.to_chrome_trace();
1568 chrome_trace
1569 .to_json()
1570 .map(Some)
1571 .map_err(|e| ProbarError::SerializationError {
1572 message: format!("Failed to serialize trace: {e}"),
1573 })
1574 }
1575 None => Ok(None),
1576 }
1577 }
1578
1579 pub fn inject_trace_context(&mut self) -> ProbarResult<()> {
1585 Ok(())
1586 }
1587
1588 pub fn start_coverage(&mut self) -> ProbarResult<()> {
1598 self.start_coverage_with_config(CoverageConfig::default())
1599 }
1600
1601 pub fn start_coverage_with_config(&mut self, _config: CoverageConfig) -> ProbarResult<()> {
1607 self.coverage_enabled = true;
1608 Ok(())
1609 }
1610
1611 pub fn take_coverage(&self) -> ProbarResult<CoverageReport> {
1617 if !self.coverage_enabled {
1618 return Err(ProbarError::InvalidState {
1619 message: "Coverage not enabled. Call start_coverage() first.".to_string(),
1620 });
1621 }
1622
1623 let functions = self
1624 .coverage_data
1625 .lock()
1626 .map(|guard| guard.clone())
1627 .unwrap_or_default();
1628
1629 Ok(CoverageReport {
1630 scripts: vec![crate::cdp_coverage::ScriptCoverage {
1631 script_id: "mock-script-1".to_string(),
1632 url: self.url.clone(),
1633 functions,
1634 }],
1635 timestamp_ms: std::time::SystemTime::now()
1636 .duration_since(std::time::UNIX_EPOCH)
1637 .map(|d| d.as_millis() as u64)
1638 .unwrap_or(0),
1639 })
1640 }
1641
1642 pub fn stop_coverage(&mut self) -> ProbarResult<CoverageReport> {
1648 let report = self.take_coverage()?;
1649 self.coverage_enabled = false;
1650 Ok(report)
1651 }
1652
1653 #[must_use]
1655 pub const fn is_coverage_enabled(&self) -> bool {
1656 self.coverage_enabled
1657 }
1658
1659 pub fn add_mock_coverage(&self, func: crate::cdp_coverage::FunctionCoverage) {
1661 if let Ok(mut guard) = self.coverage_data.lock() {
1662 guard.push(func);
1663 }
1664 }
1665
1666 pub fn clear_mock_coverage(&self) {
1668 if let Ok(mut guard) = self.coverage_data.lock() {
1669 guard.clear();
1670 }
1671 }
1672 }
1673}
1674
1675#[cfg(feature = "browser")]
1677pub use cdp::{Browser, Page};
1678
1679#[cfg(not(feature = "browser"))]
1680pub use mock::{Browser, Page};
1681
1682#[cfg(test)]
1683#[allow(clippy::unwrap_used, clippy::expect_used)]
1684mod tests {
1685 use super::*;
1686
1687 mod browser_config_tests {
1688 use super::*;
1689
1690 #[test]
1691 fn test_default() {
1692 let config = BrowserConfig::default();
1693 assert!(config.headless);
1694 assert_eq!(config.viewport_width, 800);
1695 assert_eq!(config.viewport_height, 600);
1696 assert!(config.chromium_path.is_none());
1697 assert_eq!(config.debug_port, 0);
1698 assert!(config.user_agent.is_none());
1699 assert!(!config.devtools);
1700 assert!(config.sandbox);
1701 }
1702
1703 #[test]
1704 fn test_with_viewport() {
1705 let config = BrowserConfig::default().with_viewport(1920, 1080);
1706 assert_eq!(config.viewport_width, 1920);
1707 assert_eq!(config.viewport_height, 1080);
1708 }
1709
1710 #[test]
1711 fn test_with_headless() {
1712 let config = BrowserConfig::default().with_headless(false);
1713 assert!(!config.headless);
1714 }
1715
1716 #[test]
1717 fn test_with_chromium_path() {
1718 let config = BrowserConfig::default().with_chromium_path("/usr/bin/chromium");
1719 assert_eq!(config.chromium_path, Some("/usr/bin/chromium".to_string()));
1720 }
1721
1722 #[test]
1723 fn test_with_user_agent() {
1724 let config = BrowserConfig::default().with_user_agent("Custom UA");
1725 assert_eq!(config.user_agent, Some("Custom UA".to_string()));
1726 }
1727
1728 #[test]
1729 fn test_with_no_sandbox() {
1730 let config = BrowserConfig::default().with_no_sandbox();
1731 assert!(!config.sandbox);
1732 }
1733
1734 #[test]
1735 fn test_clone() {
1736 let config = BrowserConfig::default()
1737 .with_viewport(1024, 768)
1738 .with_headless(false);
1739 let cloned = config.clone();
1740 assert_eq!(config.viewport_width, cloned.viewport_width);
1741 assert_eq!(config.headless, cloned.headless);
1742 }
1743
1744 #[test]
1745 fn test_debug() {
1746 let config = BrowserConfig::default();
1747 let debug = format!("{:?}", config);
1748 assert!(debug.contains("BrowserConfig"));
1749 assert!(debug.contains("headless"));
1750 }
1751 }
1752
1753 #[cfg(not(feature = "browser"))]
1754 mod mock_browser_tests {
1755 use super::*;
1756
1757 #[test]
1758 fn test_browser_launch() {
1759 let config = BrowserConfig::default();
1760 let browser = Browser::launch(config).unwrap();
1761 assert_eq!(browser.config().viewport_width, 800);
1762 }
1763
1764 #[test]
1765 fn test_browser_new_page() {
1766 let config = BrowserConfig::default().with_viewport(1024, 768);
1767 let browser = Browser::launch(config).unwrap();
1768 let page = browser.new_page().unwrap();
1769 assert_eq!(page.width, 1024);
1770 assert_eq!(page.height, 768);
1771 }
1772
1773 #[test]
1774 fn test_browser_debug() {
1775 let config = BrowserConfig::default();
1776 let browser = Browser::launch(config).unwrap();
1777 let debug = format!("{:?}", browser);
1778 assert!(debug.contains("Browser"));
1779 }
1780 }
1781
1782 #[cfg(not(feature = "browser"))]
1783 mod mock_page_tests {
1784 use super::*;
1785
1786 #[test]
1787 fn test_page_new() {
1788 let page = Page::new(800, 600);
1789 assert_eq!(page.width, 800);
1790 assert_eq!(page.height, 600);
1791 assert_eq!(page.url, "about:blank");
1792 assert!(!page.wasm_ready);
1793 }
1794
1795 #[test]
1796 fn test_page_goto() {
1797 let mut page = Page::new(800, 600);
1798 page.goto("https://example.com").unwrap();
1799 assert_eq!(page.current_url(), "https://example.com");
1800 }
1801
1802 #[test]
1803 fn test_page_wait_for_wasm_ready() {
1804 let mut page = Page::new(800, 600);
1805 assert!(!page.is_wasm_ready());
1806 page.wait_for_wasm_ready().unwrap();
1807 assert!(page.is_wasm_ready());
1808 }
1809
1810 #[test]
1811 fn test_page_eval_wasm_error() {
1812 let page = Page::new(800, 600);
1813 let result: Result<String, _> = page.eval_wasm("test");
1814 assert!(result.is_err());
1815 }
1816
1817 #[test]
1818 fn test_page_touch() {
1819 let page = Page::new(800, 600);
1820 let touch = crate::Touch {
1821 x: 100.0,
1822 y: 100.0,
1823 action: crate::TouchAction::Tap,
1824 };
1825 page.touch(touch).unwrap();
1826 }
1827
1828 #[test]
1829 fn test_page_screenshot() {
1830 let page = Page::new(800, 600);
1831 let screenshot = page.screenshot().unwrap();
1832 assert!(screenshot.is_empty()); }
1834
1835 #[test]
1836 fn test_page_debug() {
1837 let page = Page::new(800, 600);
1838 let debug = format!("{:?}", page);
1839 assert!(debug.contains("Page"));
1840 }
1841 }
1842
1843 mod h0_browser_config_tests {
1848 use super::*;
1849
1850 #[test]
1851 fn h0_browser_01_config_default_headless() {
1852 let config = BrowserConfig::default();
1853 assert!(config.headless);
1854 }
1855
1856 #[test]
1857 fn h0_browser_02_config_default_viewport_width() {
1858 let config = BrowserConfig::default();
1859 assert_eq!(config.viewport_width, 800);
1860 }
1861
1862 #[test]
1863 fn h0_browser_03_config_default_viewport_height() {
1864 let config = BrowserConfig::default();
1865 assert_eq!(config.viewport_height, 600);
1866 }
1867
1868 #[test]
1869 fn h0_browser_04_config_default_no_chromium_path() {
1870 let config = BrowserConfig::default();
1871 assert!(config.chromium_path.is_none());
1872 }
1873
1874 #[test]
1875 fn h0_browser_05_config_default_debug_port() {
1876 let config = BrowserConfig::default();
1877 assert_eq!(config.debug_port, 0);
1878 }
1879
1880 #[test]
1881 fn h0_browser_06_config_default_no_user_agent() {
1882 let config = BrowserConfig::default();
1883 assert!(config.user_agent.is_none());
1884 }
1885
1886 #[test]
1887 fn h0_browser_07_config_default_devtools_off() {
1888 let config = BrowserConfig::default();
1889 assert!(!config.devtools);
1890 }
1891
1892 #[test]
1893 fn h0_browser_08_config_default_sandbox_on() {
1894 let config = BrowserConfig::default();
1895 assert!(config.sandbox);
1896 }
1897
1898 #[test]
1899 fn h0_browser_09_config_with_viewport() {
1900 let config = BrowserConfig::default().with_viewport(1920, 1080);
1901 assert_eq!(config.viewport_width, 1920);
1902 assert_eq!(config.viewport_height, 1080);
1903 }
1904
1905 #[test]
1906 fn h0_browser_10_config_with_headless_false() {
1907 let config = BrowserConfig::default().with_headless(false);
1908 assert!(!config.headless);
1909 }
1910 }
1911
1912 mod h0_browser_config_builder_tests {
1913 use super::*;
1914
1915 #[test]
1916 fn h0_browser_11_config_with_chromium_path() {
1917 let config = BrowserConfig::default().with_chromium_path("/path/to/chromium");
1918 assert_eq!(config.chromium_path, Some("/path/to/chromium".to_string()));
1919 }
1920
1921 #[test]
1922 fn h0_browser_12_config_with_user_agent() {
1923 let config = BrowserConfig::default().with_user_agent("Test UA");
1924 assert_eq!(config.user_agent, Some("Test UA".to_string()));
1925 }
1926
1927 #[test]
1928 fn h0_browser_13_config_with_no_sandbox() {
1929 let config = BrowserConfig::default().with_no_sandbox();
1930 assert!(!config.sandbox);
1931 }
1932
1933 #[test]
1934 fn h0_browser_14_config_builder_chain() {
1935 let config = BrowserConfig::default()
1936 .with_viewport(1024, 768)
1937 .with_headless(false)
1938 .with_no_sandbox()
1939 .with_user_agent("Custom");
1940 assert_eq!(config.viewport_width, 1024);
1941 assert!(!config.headless);
1942 assert!(!config.sandbox);
1943 assert_eq!(config.user_agent, Some("Custom".to_string()));
1944 }
1945
1946 #[test]
1947 fn h0_browser_15_config_clone() {
1948 let config = BrowserConfig::default().with_viewport(800, 600);
1949 let cloned = config;
1950 assert_eq!(cloned.viewport_width, 800);
1951 }
1952
1953 #[test]
1954 fn h0_browser_16_config_string_conversion() {
1955 let config =
1956 BrowserConfig::default().with_chromium_path(String::from("/usr/bin/chrome"));
1957 assert!(config.chromium_path.is_some());
1958 }
1959
1960 #[test]
1961 fn h0_browser_17_config_small_viewport() {
1962 let config = BrowserConfig::default().with_viewport(320, 240);
1963 assert_eq!(config.viewport_width, 320);
1964 assert_eq!(config.viewport_height, 240);
1965 }
1966
1967 #[test]
1968 fn h0_browser_18_config_large_viewport() {
1969 let config = BrowserConfig::default().with_viewport(3840, 2160);
1970 assert_eq!(config.viewport_width, 3840);
1971 }
1972
1973 #[test]
1974 fn h0_browser_19_config_debug_format() {
1975 let config = BrowserConfig::default();
1976 let debug = format!("{:?}", config);
1977 assert!(debug.contains("headless"));
1978 }
1979
1980 #[test]
1981 fn h0_browser_20_config_user_agent_unicode() {
1982 let config = BrowserConfig::default().with_user_agent("UA/テスト");
1983 assert_eq!(config.user_agent, Some("UA/テスト".to_string()));
1984 }
1985 }
1986
1987 #[cfg(not(feature = "browser"))]
1988 mod h0_mock_browser_tests {
1989 use super::*;
1990
1991 #[test]
1992 fn h0_browser_21_launch() {
1993 let config = BrowserConfig::default();
1994 let browser = Browser::launch(config);
1995 assert!(browser.is_ok());
1996 }
1997
1998 #[test]
1999 fn h0_browser_22_launch_config_preserved() {
2000 let config = BrowserConfig::default().with_viewport(1024, 768);
2001 let browser = Browser::launch(config).unwrap();
2002 assert_eq!(browser.config().viewport_width, 1024);
2003 }
2004
2005 #[test]
2006 fn h0_browser_23_new_page() {
2007 let browser = Browser::launch(BrowserConfig::default()).unwrap();
2008 let page = browser.new_page();
2009 assert!(page.is_ok());
2010 }
2011
2012 #[test]
2013 fn h0_browser_24_new_page_dimensions() {
2014 let config = BrowserConfig::default().with_viewport(1280, 720);
2015 let browser = Browser::launch(config).unwrap();
2016 let page = browser.new_page().unwrap();
2017 assert_eq!(page.width, 1280);
2018 assert_eq!(page.height, 720);
2019 }
2020
2021 #[test]
2022 fn h0_browser_25_debug_format() {
2023 let browser = Browser::launch(BrowserConfig::default()).unwrap();
2024 let debug = format!("{:?}", browser);
2025 assert!(debug.contains("Browser"));
2026 }
2027 }
2028
2029 #[cfg(not(feature = "browser"))]
2030 mod h0_mock_page_tests {
2031 use super::*;
2032
2033 #[test]
2034 fn h0_browser_26_page_new() {
2035 let page = Page::new(800, 600);
2036 assert_eq!(page.width, 800);
2037 }
2038
2039 #[test]
2040 fn h0_browser_27_page_initial_url() {
2041 let page = Page::new(800, 600);
2042 assert_eq!(page.url, "about:blank");
2043 }
2044
2045 #[test]
2046 fn h0_browser_28_page_initial_wasm_not_ready() {
2047 let page = Page::new(800, 600);
2048 assert!(!page.wasm_ready);
2049 }
2050
2051 #[test]
2052 fn h0_browser_29_page_goto() {
2053 let mut page = Page::new(800, 600);
2054 let result = page.goto("http://localhost:8080");
2055 assert!(result.is_ok());
2056 }
2057
2058 #[test]
2059 fn h0_browser_30_page_goto_updates_url() {
2060 let mut page = Page::new(800, 600);
2061 page.goto("http://test.com").unwrap();
2062 assert_eq!(page.current_url(), "http://test.com");
2063 }
2064
2065 #[test]
2066 fn h0_browser_31_page_wait_for_wasm() {
2067 let mut page = Page::new(800, 600);
2068 let result = page.wait_for_wasm_ready();
2069 assert!(result.is_ok());
2070 }
2071
2072 #[test]
2073 fn h0_browser_32_page_wasm_ready_after_wait() {
2074 let mut page = Page::new(800, 600);
2075 page.wait_for_wasm_ready().unwrap();
2076 assert!(page.is_wasm_ready());
2077 }
2078
2079 #[test]
2080 fn h0_browser_33_page_eval_wasm_fails() {
2081 let page = Page::new(800, 600);
2082 let result: Result<i32, _> = page.eval_wasm("1 + 1");
2083 assert!(result.is_err());
2084 }
2085
2086 #[test]
2087 fn h0_browser_34_page_touch_tap() {
2088 let page = Page::new(800, 600);
2089 let touch = crate::Touch {
2090 x: 50.0,
2091 y: 50.0,
2092 action: crate::TouchAction::Tap,
2093 };
2094 assert!(page.touch(touch).is_ok());
2095 }
2096
2097 #[test]
2098 fn h0_browser_35_page_screenshot_empty() {
2099 let page = Page::new(800, 600);
2100 let screenshot = page.screenshot().unwrap();
2101 assert!(screenshot.is_empty());
2102 }
2103 }
2104
2105 #[cfg(not(feature = "browser"))]
2106 mod h0_mock_page_advanced_tests {
2107 use super::*;
2108
2109 #[test]
2110 fn h0_browser_36_page_touch_swipe() {
2111 let page = Page::new(800, 600);
2112 let touch = crate::Touch {
2113 x: 100.0,
2114 y: 100.0,
2115 action: crate::TouchAction::Swipe {
2116 end_x: 200.0,
2117 end_y: 200.0,
2118 duration_ms: 100,
2119 },
2120 };
2121 assert!(page.touch(touch).is_ok());
2122 }
2123
2124 #[test]
2125 fn h0_browser_37_page_touch_hold() {
2126 let page = Page::new(800, 600);
2127 let touch = crate::Touch {
2128 x: 100.0,
2129 y: 100.0,
2130 action: crate::TouchAction::Hold { duration_ms: 500 },
2131 };
2132 assert!(page.touch(touch).is_ok());
2133 }
2134
2135 #[test]
2136 fn h0_browser_38_page_debug() {
2137 let page = Page::new(800, 600);
2138 let debug = format!("{:?}", page);
2139 assert!(debug.contains("Page"));
2140 }
2141
2142 #[test]
2143 fn h0_browser_39_page_current_url_method() {
2144 let page = Page::new(800, 600);
2145 assert_eq!(page.current_url(), "about:blank");
2146 }
2147
2148 #[test]
2149 fn h0_browser_40_page_is_wasm_ready_method() {
2150 let page = Page::new(800, 600);
2151 assert!(!page.is_wasm_ready());
2152 }
2153
2154 #[test]
2155 fn h0_browser_41_page_multiple_goto() {
2156 let mut page = Page::new(800, 600);
2157 page.goto("http://first.com").unwrap();
2158 page.goto("http://second.com").unwrap();
2159 assert_eq!(page.current_url(), "http://second.com");
2160 }
2161
2162 #[test]
2163 fn h0_browser_42_page_zero_dimensions() {
2164 let page = Page::new(0, 0);
2165 assert_eq!(page.width, 0);
2166 assert_eq!(page.height, 0);
2167 }
2168
2169 #[test]
2170 fn h0_browser_43_page_large_dimensions() {
2171 let page = Page::new(7680, 4320);
2172 assert_eq!(page.width, 7680);
2173 }
2174
2175 #[test]
2176 fn h0_browser_44_config_overwrite_viewport() {
2177 let config = BrowserConfig::default()
2178 .with_viewport(800, 600)
2179 .with_viewport(1024, 768);
2180 assert_eq!(config.viewport_width, 1024);
2181 }
2182
2183 #[test]
2184 fn h0_browser_45_config_overwrite_headless() {
2185 let config = BrowserConfig::default()
2186 .with_headless(false)
2187 .with_headless(true);
2188 assert!(config.headless);
2189 }
2190 }
2191
2192 mod h0_browser_edge_cases {
2193 use super::*;
2194
2195 #[test]
2196 fn h0_browser_46_config_empty_chromium_path() {
2197 let config = BrowserConfig::default().with_chromium_path("");
2198 assert_eq!(config.chromium_path, Some(String::new()));
2199 }
2200
2201 #[test]
2202 fn h0_browser_47_config_empty_user_agent() {
2203 let config = BrowserConfig::default().with_user_agent("");
2204 assert_eq!(config.user_agent, Some(String::new()));
2205 }
2206
2207 #[test]
2208 fn h0_browser_48_config_viewport_square() {
2209 let config = BrowserConfig::default().with_viewport(1000, 1000);
2210 assert_eq!(config.viewport_width, config.viewport_height);
2211 }
2212
2213 #[test]
2214 fn h0_browser_49_config_viewport_portrait() {
2215 let config = BrowserConfig::default().with_viewport(600, 800);
2216 assert!(config.viewport_height > config.viewport_width);
2217 }
2218
2219 #[test]
2220 fn h0_browser_50_config_viewport_landscape() {
2221 let config = BrowserConfig::default().with_viewport(1920, 1080);
2222 assert!(config.viewport_width > config.viewport_height);
2223 }
2224 }
2225
2226 mod console_capture_tests {
2231 use super::*;
2232
2233 #[test]
2234 fn test_browser_console_level_display() {
2235 assert_eq!(format!("{}", BrowserConsoleLevel::Log), "log");
2236 assert_eq!(format!("{}", BrowserConsoleLevel::Info), "info");
2237 assert_eq!(format!("{}", BrowserConsoleLevel::Warning), "warn");
2238 assert_eq!(format!("{}", BrowserConsoleLevel::Error), "error");
2239 assert_eq!(format!("{}", BrowserConsoleLevel::Debug), "debug");
2240 }
2241
2242 #[test]
2243 fn test_browser_console_level_eq() {
2244 assert_eq!(BrowserConsoleLevel::Log, BrowserConsoleLevel::Log);
2245 assert_ne!(BrowserConsoleLevel::Log, BrowserConsoleLevel::Error);
2246 }
2247
2248 #[test]
2249 fn test_browser_console_level_clone() {
2250 let level = BrowserConsoleLevel::Warning;
2251 let cloned = level;
2252 assert_eq!(level, cloned);
2253 }
2254
2255 #[test]
2256 fn test_browser_console_level_debug() {
2257 let level = BrowserConsoleLevel::Error;
2258 let debug = format!("{:?}", level);
2259 assert!(debug.contains("Error"));
2260 }
2261
2262 #[test]
2263 fn test_browser_console_message_create() {
2264 let msg = BrowserConsoleMessage {
2265 level: BrowserConsoleLevel::Log,
2266 text: "test message".to_string(),
2267 timestamp: 1234567890,
2268 source: Some("test.js".to_string()),
2269 line: Some(42),
2270 };
2271 assert_eq!(msg.level, BrowserConsoleLevel::Log);
2272 assert_eq!(msg.text, "test message");
2273 assert_eq!(msg.timestamp, 1234567890);
2274 assert_eq!(msg.source, Some("test.js".to_string()));
2275 assert_eq!(msg.line, Some(42));
2276 }
2277
2278 #[test]
2279 fn test_browser_console_message_without_source() {
2280 let msg = BrowserConsoleMessage {
2281 level: BrowserConsoleLevel::Error,
2282 text: "error".to_string(),
2283 timestamp: 0,
2284 source: None,
2285 line: None,
2286 };
2287 assert!(msg.source.is_none());
2288 assert!(msg.line.is_none());
2289 }
2290
2291 #[test]
2292 fn test_browser_console_message_clone() {
2293 let msg = BrowserConsoleMessage {
2294 level: BrowserConsoleLevel::Info,
2295 text: "info".to_string(),
2296 timestamp: 100,
2297 source: None,
2298 line: None,
2299 };
2300 let cloned = msg.clone();
2301 assert_eq!(msg.text, cloned.text);
2302 assert_eq!(msg.timestamp, cloned.timestamp);
2303 }
2304
2305 #[test]
2306 fn test_browser_console_message_debug() {
2307 let msg = BrowserConsoleMessage {
2308 level: BrowserConsoleLevel::Debug,
2309 text: "debug msg".to_string(),
2310 timestamp: 0,
2311 source: None,
2312 line: None,
2313 };
2314 let debug = format!("{:?}", msg);
2315 assert!(debug.contains("BrowserConsoleMessage"));
2316 assert!(debug.contains("debug msg"));
2317 }
2318 }
2319
2320 #[cfg(not(feature = "browser"))]
2321 mod mock_console_capture_tests {
2322 use super::*;
2323
2324 #[test]
2325 fn test_page_enable_console_capture() {
2326 let mut page = Page::new(800, 600);
2327 assert!(!page.is_console_capture_enabled());
2328 page.enable_console_capture().unwrap();
2329 assert!(page.is_console_capture_enabled());
2330 }
2331
2332 #[test]
2333 fn test_page_console_messages_empty() {
2334 let page = Page::new(800, 600);
2335 let messages = page.console_messages();
2336 assert!(messages.is_empty());
2337 }
2338
2339 #[test]
2340 fn test_page_add_console_message() {
2341 let page = Page::new(800, 600);
2342 let msg = BrowserConsoleMessage {
2343 level: BrowserConsoleLevel::Log,
2344 text: "test".to_string(),
2345 timestamp: 123,
2346 source: None,
2347 line: None,
2348 };
2349 page.add_console_message(msg);
2350 let messages = page.console_messages();
2351 assert_eq!(messages.len(), 1);
2352 assert_eq!(messages[0].text, "test");
2353 }
2354
2355 #[test]
2356 fn test_page_clear_console() {
2357 let page = Page::new(800, 600);
2358 page.add_console_message(BrowserConsoleMessage {
2359 level: BrowserConsoleLevel::Log,
2360 text: "msg".to_string(),
2361 timestamp: 0,
2362 source: None,
2363 line: None,
2364 });
2365 assert_eq!(page.console_messages().len(), 1);
2366 page.clear_console();
2367 assert!(page.console_messages().is_empty());
2368 }
2369
2370 #[test]
2371 fn test_page_wait_for_console_found() {
2372 let page = Page::new(800, 600);
2373 page.add_console_message(BrowserConsoleMessage {
2374 level: BrowserConsoleLevel::Info,
2375 text: "ready".to_string(),
2376 timestamp: 100,
2377 source: None,
2378 line: None,
2379 });
2380 let result = page.wait_for_console(|m| m.text.contains("ready"), 1000);
2381 assert!(result.is_ok());
2382 assert_eq!(result.unwrap().text, "ready");
2383 }
2384
2385 #[test]
2386 fn test_page_wait_for_console_not_found() {
2387 let page = Page::new(800, 600);
2388 let result = page.wait_for_console(|m| m.text.contains("missing"), 1000);
2389 assert!(result.is_err());
2390 }
2391
2392 #[test]
2393 fn test_page_wait_for_console_by_level() {
2394 let page = Page::new(800, 600);
2395 page.add_console_message(BrowserConsoleMessage {
2396 level: BrowserConsoleLevel::Error,
2397 text: "error occurred".to_string(),
2398 timestamp: 0,
2399 source: None,
2400 line: None,
2401 });
2402 let result = page.wait_for_console(|m| m.level == BrowserConsoleLevel::Error, 1000);
2403 assert!(result.is_ok());
2404 }
2405
2406 #[test]
2407 fn test_page_inject_console_capture() {
2408 let mut page = Page::new(800, 600);
2409 assert!(!page.is_console_capture_enabled());
2410 page.inject_console_capture().unwrap();
2411 assert!(page.is_console_capture_enabled());
2412 }
2413
2414 #[test]
2415 fn test_page_fetch_console_messages() {
2416 let page = Page::new(800, 600);
2417 page.add_console_message(BrowserConsoleMessage {
2418 level: BrowserConsoleLevel::Warning,
2419 text: "warning".to_string(),
2420 timestamp: 0,
2421 source: None,
2422 line: None,
2423 });
2424 let result = page.fetch_console_messages();
2425 assert!(result.is_ok());
2426 let messages = result.unwrap();
2427 assert_eq!(messages.len(), 1);
2428 assert_eq!(messages[0].level, BrowserConsoleLevel::Warning);
2429 }
2430
2431 #[test]
2432 fn test_page_multiple_console_messages() {
2433 let page = Page::new(800, 600);
2434 for i in 0..5 {
2435 page.add_console_message(BrowserConsoleMessage {
2436 level: BrowserConsoleLevel::Log,
2437 text: format!("message {i}"),
2438 timestamp: i as u64,
2439 source: None,
2440 line: None,
2441 });
2442 }
2443 let messages = page.console_messages();
2444 assert_eq!(messages.len(), 5);
2445 assert_eq!(messages[0].text, "message 0");
2446 assert_eq!(messages[4].text, "message 4");
2447 }
2448 }
2449
2450 mod renacer_tracing_tests {
2455 use super::*;
2456
2457 #[test]
2458 fn test_browser_config_with_tracing() {
2459 let tracing_config = RenacerTracingConfig::new("test-service");
2460 let config = BrowserConfig::default().with_tracing(tracing_config);
2461 assert!(config.tracing_config.is_some());
2462 assert!(config.is_tracing_enabled());
2463 }
2464
2465 #[test]
2466 fn test_browser_config_without_tracing() {
2467 let config = BrowserConfig::default();
2468 assert!(config.tracing_config.is_none());
2469 assert!(!config.is_tracing_enabled());
2470 }
2471
2472 #[test]
2473 fn test_browser_config_disabled_tracing() {
2474 let tracing_config = RenacerTracingConfig::disabled();
2475 let config = BrowserConfig::default().with_tracing(tracing_config);
2476 assert!(config.tracing_config.is_some());
2477 assert!(!config.is_tracing_enabled());
2478 }
2479 }
2480
2481 #[cfg(not(feature = "browser"))]
2482 mod mock_renacer_tracing_tests {
2483 use super::*;
2484
2485 #[test]
2486 fn test_page_tracing_disabled_by_default() {
2487 let page = Page::new(800, 600);
2488 assert!(!page.is_tracing_enabled());
2489 assert!(page.traceparent().is_none());
2490 assert!(page.export_chrome_trace().is_none());
2491 }
2492
2493 #[test]
2494 fn test_page_with_tracing_enabled() {
2495 let trace_collector = TraceCollector::new("test-service");
2496 let page = Page::new_with_tracing(800, 600, Some(trace_collector));
2497 assert!(page.is_tracing_enabled());
2498 assert!(page.traceparent().is_some());
2499 }
2500
2501 #[test]
2502 fn test_page_traceparent_format() {
2503 let trace_collector = TraceCollector::new("test-service");
2504 let page = Page::new_with_tracing(800, 600, Some(trace_collector));
2505 let traceparent = page.traceparent().unwrap();
2506 assert!(traceparent.starts_with("00-"));
2507 let parts: Vec<&str> = traceparent.split('-').collect();
2508 assert_eq!(parts.len(), 4);
2509 }
2510
2511 #[test]
2512 fn test_page_start_and_record_span() {
2513 let trace_collector = TraceCollector::new("test-service");
2514 let mut page = Page::new_with_tracing(800, 600, Some(trace_collector));
2515
2516 let mut span = page.start_span("test-span", "browser").unwrap();
2517 span.add_attribute("key", "value");
2518 span.end();
2519 page.record_span(span);
2520
2521 let chrome_trace = page.export_chrome_trace().unwrap();
2522 assert_eq!(chrome_trace.trace_events.len(), 1);
2523 assert_eq!(chrome_trace.trace_events[0].name, "test-span");
2524 }
2525
2526 #[test]
2527 fn test_page_record_trace_console() {
2528 let trace_collector = TraceCollector::new("test-service");
2529 let mut page = Page::new_with_tracing(800, 600, Some(trace_collector));
2530
2531 page.record_trace_console("test message");
2532
2533 let chrome_trace = page.export_chrome_trace().unwrap();
2534 assert_eq!(chrome_trace.trace_events.len(), 1);
2535 assert_eq!(chrome_trace.trace_events[0].cat, "console");
2536 }
2537
2538 #[test]
2539 fn test_page_export_trace_json() {
2540 let trace_collector = TraceCollector::new("test-service");
2541 let mut page = Page::new_with_tracing(800, 600, Some(trace_collector));
2542
2543 let mut span = page.start_span("json-test", "browser").unwrap();
2544 span.end();
2545 page.record_span(span);
2546
2547 let json = page.export_trace_json().unwrap().unwrap();
2548 assert!(json.contains("traceEvents"));
2549 assert!(json.contains("json-test"));
2550 }
2551
2552 #[test]
2553 fn test_page_inject_trace_context() {
2554 let trace_collector = TraceCollector::new("test-service");
2555 let mut page = Page::new_with_tracing(800, 600, Some(trace_collector));
2556 let result = page.inject_trace_context();
2558 assert!(result.is_ok());
2559 }
2560
2561 #[test]
2562 fn test_browser_new_page_with_tracing() {
2563 let tracing_config = RenacerTracingConfig::new("test-service");
2564 let config = BrowserConfig::default().with_tracing(tracing_config);
2565 let browser = Browser::launch(config).unwrap();
2566 let page = browser.new_page().unwrap();
2567 assert!(page.is_tracing_enabled());
2568 assert!(page.traceparent().is_some());
2569 }
2570
2571 #[test]
2572 fn test_browser_new_page_without_tracing() {
2573 let config = BrowserConfig::default();
2574 let browser = Browser::launch(config).unwrap();
2575 let page = browser.new_page().unwrap();
2576 assert!(!page.is_tracing_enabled());
2577 assert!(page.traceparent().is_none());
2578 }
2579 }
2580
2581 #[cfg(not(feature = "browser"))]
2586 mod mock_coverage_tests {
2587 use super::*;
2588 use crate::cdp_coverage::{CoverageConfig, CoverageRange, FunctionCoverage};
2589
2590 #[test]
2591 fn test_coverage_disabled_by_default() {
2592 let page = Page::new(800, 600);
2593 assert!(!page.is_coverage_enabled());
2594 }
2595
2596 #[test]
2597 fn test_start_coverage() {
2598 let mut page = Page::new(800, 600);
2599 assert!(page.start_coverage().is_ok());
2600 assert!(page.is_coverage_enabled());
2601 }
2602
2603 #[test]
2604 fn test_take_coverage_without_start_fails() {
2605 let page = Page::new(800, 600);
2606 let result = page.take_coverage();
2607 assert!(result.is_err());
2608 }
2609
2610 #[test]
2611 fn test_take_coverage_returns_report() {
2612 let mut page = Page::new(800, 600);
2613 page.goto("http://localhost:8080/test.html").unwrap();
2614 page.start_coverage().unwrap();
2615
2616 let report = page.take_coverage().unwrap();
2617 assert_eq!(report.scripts.len(), 1);
2618 assert_eq!(report.scripts[0].url, "http://localhost:8080/test.html");
2619 assert!(report.timestamp_ms > 0);
2620 }
2621
2622 #[test]
2623 fn test_stop_coverage_returns_report_and_disables() {
2624 let mut page = Page::new(800, 600);
2625 page.start_coverage().unwrap();
2626
2627 let report = page.stop_coverage().unwrap();
2628 assert_eq!(report.scripts.len(), 1);
2629 assert!(!page.is_coverage_enabled());
2630 }
2631
2632 #[test]
2633 fn test_add_mock_coverage() {
2634 let mut page = Page::new(800, 600);
2635 page.start_coverage().unwrap();
2636
2637 page.add_mock_coverage(FunctionCoverage {
2638 function_name: "test_func".to_string(),
2639 ranges: vec![CoverageRange {
2640 start_offset: 0,
2641 end_offset: 100,
2642 count: 5,
2643 }],
2644 is_block_coverage: false,
2645 });
2646
2647 let report = page.take_coverage().unwrap();
2648 assert_eq!(report.scripts[0].functions.len(), 1);
2649 assert_eq!(report.scripts[0].functions[0].function_name, "test_func");
2650 assert_eq!(report.scripts[0].functions[0].ranges[0].count, 5);
2651 }
2652
2653 #[test]
2654 fn test_clear_mock_coverage() {
2655 let mut page = Page::new(800, 600);
2656 page.start_coverage().unwrap();
2657
2658 page.add_mock_coverage(FunctionCoverage {
2659 function_name: "func1".to_string(),
2660 ranges: vec![],
2661 is_block_coverage: false,
2662 });
2663 page.add_mock_coverage(FunctionCoverage {
2664 function_name: "func2".to_string(),
2665 ranges: vec![],
2666 is_block_coverage: false,
2667 });
2668
2669 page.clear_mock_coverage();
2670
2671 let report = page.take_coverage().unwrap();
2672 assert!(report.scripts[0].functions.is_empty());
2673 }
2674
2675 #[test]
2676 fn test_coverage_with_config() {
2677 let mut page = Page::new(800, 600);
2678 let config = CoverageConfig {
2679 call_count: true,
2680 detailed: true,
2681 allow_triggered_updates: false,
2682 };
2683 assert!(page.start_coverage_with_config(config).is_ok());
2684 assert!(page.is_coverage_enabled());
2685 }
2686
2687 #[test]
2688 fn test_multiple_coverage_sessions() {
2689 let mut page = Page::new(800, 600);
2690
2691 page.start_coverage().unwrap();
2693 page.add_mock_coverage(FunctionCoverage {
2694 function_name: "session1_func".to_string(),
2695 ranges: vec![],
2696 is_block_coverage: false,
2697 });
2698 page.stop_coverage().unwrap();
2699
2700 page.start_coverage().unwrap();
2702 let report = page.take_coverage().unwrap();
2703 assert_eq!(report.scripts[0].functions.len(), 1);
2705 }
2706 }
2707
2708 mod browser_console_level_comprehensive {
2713 use super::*;
2714
2715 #[test]
2716 fn test_all_levels_display() {
2717 let levels = [
2719 (BrowserConsoleLevel::Log, "log"),
2720 (BrowserConsoleLevel::Info, "info"),
2721 (BrowserConsoleLevel::Warning, "warn"),
2722 (BrowserConsoleLevel::Error, "error"),
2723 (BrowserConsoleLevel::Debug, "debug"),
2724 ];
2725 for (level, expected) in levels {
2726 assert_eq!(format!("{}", level), expected);
2727 }
2728 }
2729
2730 #[test]
2731 fn test_level_copy_semantics() {
2732 let level = BrowserConsoleLevel::Warning;
2733 let copied = level;
2734 assert_eq!(level, copied);
2735 assert_eq!(format!("{}", level), "warn");
2737 assert_eq!(format!("{}", copied), "warn");
2738 }
2739
2740 #[test]
2741 fn test_level_equality_all_pairs() {
2742 let levels = [
2743 BrowserConsoleLevel::Log,
2744 BrowserConsoleLevel::Info,
2745 BrowserConsoleLevel::Warning,
2746 BrowserConsoleLevel::Error,
2747 BrowserConsoleLevel::Debug,
2748 ];
2749 for (i, level_a) in levels.iter().enumerate() {
2751 for (j, level_b) in levels.iter().enumerate() {
2752 if i == j {
2753 assert_eq!(level_a, level_b);
2754 } else {
2755 assert_ne!(level_a, level_b);
2756 }
2757 }
2758 }
2759 }
2760
2761 #[test]
2762 fn test_level_debug_all_variants() {
2763 assert!(format!("{:?}", BrowserConsoleLevel::Log).contains("Log"));
2764 assert!(format!("{:?}", BrowserConsoleLevel::Info).contains("Info"));
2765 assert!(format!("{:?}", BrowserConsoleLevel::Warning).contains("Warning"));
2766 assert!(format!("{:?}", BrowserConsoleLevel::Error).contains("Error"));
2767 assert!(format!("{:?}", BrowserConsoleLevel::Debug).contains("Debug"));
2768 }
2769 }
2770
2771 mod browser_console_message_comprehensive {
2772 use super::*;
2773
2774 #[test]
2775 fn test_message_all_fields() {
2776 let msg = BrowserConsoleMessage {
2777 level: BrowserConsoleLevel::Warning,
2778 text: "Test warning message".to_string(),
2779 timestamp: 9999999999,
2780 source: Some("file.js".to_string()),
2781 line: Some(123),
2782 };
2783 assert_eq!(msg.level, BrowserConsoleLevel::Warning);
2784 assert_eq!(msg.text, "Test warning message");
2785 assert_eq!(msg.timestamp, 9999999999);
2786 assert_eq!(msg.source.as_deref(), Some("file.js"));
2787 assert_eq!(msg.line, Some(123));
2788 }
2789
2790 #[test]
2791 fn test_message_empty_text() {
2792 let msg = BrowserConsoleMessage {
2793 level: BrowserConsoleLevel::Log,
2794 text: String::new(),
2795 timestamp: 0,
2796 source: None,
2797 line: None,
2798 };
2799 assert!(msg.text.is_empty());
2800 }
2801
2802 #[test]
2803 fn test_message_unicode_text() {
2804 let msg = BrowserConsoleMessage {
2805 level: BrowserConsoleLevel::Info,
2806 text: "Unicode: \u{1F600} \u{1F4BB}".to_string(),
2807 timestamp: 100,
2808 source: Some("/path/\u{65E5}\u{672C}\u{8A9E}.js".to_string()),
2809 line: Some(1),
2810 };
2811 assert!(msg.text.contains("\u{1F600}"));
2812 assert!(msg.source.as_ref().unwrap().contains("\u{65E5}"));
2813 }
2814
2815 #[test]
2816 fn test_message_clone_deep() {
2817 let original = BrowserConsoleMessage {
2818 level: BrowserConsoleLevel::Error,
2819 text: "Error message".to_string(),
2820 timestamp: 12345,
2821 source: Some("source.js".to_string()),
2822 line: Some(42),
2823 };
2824 let cloned = original.clone();
2825
2826 assert_eq!(original.level, cloned.level);
2828 assert_eq!(original.text, cloned.text);
2829 assert_eq!(original.timestamp, cloned.timestamp);
2830 assert_eq!(original.source, cloned.source);
2831 assert_eq!(original.line, cloned.line);
2832 }
2833
2834 #[test]
2835 fn test_message_debug_format_comprehensive() {
2836 let msg = BrowserConsoleMessage {
2837 level: BrowserConsoleLevel::Debug,
2838 text: "debug text".to_string(),
2839 timestamp: 555,
2840 source: Some("test.js".to_string()),
2841 line: Some(10),
2842 };
2843 let debug = format!("{:?}", msg);
2844 assert!(debug.contains("BrowserConsoleMessage"));
2845 assert!(debug.contains("debug text"));
2846 assert!(debug.contains("555"));
2847 assert!(debug.contains("test.js"));
2848 assert!(debug.contains("10"));
2849 }
2850
2851 #[test]
2852 fn test_message_max_values() {
2853 let msg = BrowserConsoleMessage {
2854 level: BrowserConsoleLevel::Log,
2855 text: "max".to_string(),
2856 timestamp: u64::MAX,
2857 source: None,
2858 line: Some(u32::MAX),
2859 };
2860 assert_eq!(msg.timestamp, u64::MAX);
2861 assert_eq!(msg.line, Some(u32::MAX));
2862 }
2863 }
2864
2865 mod browser_config_comprehensive {
2866 use super::*;
2867
2868 #[test]
2869 fn test_config_all_builder_methods() {
2870 let config = BrowserConfig::default()
2871 .with_viewport(1920, 1080)
2872 .with_headless(false)
2873 .with_chromium_path("/custom/path")
2874 .with_user_agent("Custom Agent")
2875 .with_no_sandbox();
2876
2877 assert_eq!(config.viewport_width, 1920);
2878 assert_eq!(config.viewport_height, 1080);
2879 assert!(!config.headless);
2880 assert_eq!(config.chromium_path, Some("/custom/path".to_string()));
2881 assert_eq!(config.user_agent, Some("Custom Agent".to_string()));
2882 assert!(!config.sandbox);
2883 }
2884
2885 #[test]
2886 fn test_config_tracing_enabled_check() {
2887 let config = BrowserConfig::default();
2889 assert!(!config.is_tracing_enabled());
2890
2891 let tracing = RenacerTracingConfig::new("test");
2893 let config_with_tracing = BrowserConfig::default().with_tracing(tracing);
2894 assert!(config_with_tracing.is_tracing_enabled());
2895
2896 let disabled_tracing = RenacerTracingConfig::disabled();
2898 let config_disabled = BrowserConfig::default().with_tracing(disabled_tracing);
2899 assert!(!config_disabled.is_tracing_enabled());
2900 }
2901
2902 #[test]
2903 fn test_config_debug_format() {
2904 let config = BrowserConfig::default()
2905 .with_viewport(800, 600)
2906 .with_headless(true);
2907 let debug = format!("{:?}", config);
2908 assert!(debug.contains("BrowserConfig"));
2909 assert!(debug.contains("800"));
2910 assert!(debug.contains("600"));
2911 assert!(debug.contains("headless"));
2912 }
2913
2914 #[test]
2915 fn test_config_clone_all_fields() {
2916 let tracing = RenacerTracingConfig::new("service");
2917 let config = BrowserConfig::default()
2918 .with_viewport(1024, 768)
2919 .with_headless(false)
2920 .with_chromium_path("/path")
2921 .with_user_agent("Agent")
2922 .with_no_sandbox()
2923 .with_tracing(tracing);
2924
2925 let cloned = config.clone();
2926 assert_eq!(config.viewport_width, cloned.viewport_width);
2927 assert_eq!(config.viewport_height, cloned.viewport_height);
2928 assert_eq!(config.headless, cloned.headless);
2929 assert_eq!(config.chromium_path, cloned.chromium_path);
2930 assert_eq!(config.user_agent, cloned.user_agent);
2931 assert_eq!(config.sandbox, cloned.sandbox);
2932 assert!(cloned.tracing_config.is_some());
2933 }
2934
2935 #[test]
2936 fn test_config_with_into_string() {
2937 let config = BrowserConfig::default()
2939 .with_chromium_path(String::from("/path"))
2940 .with_user_agent(String::from("UA"));
2941 assert!(config.chromium_path.is_some());
2942 assert!(config.user_agent.is_some());
2943 }
2944
2945 #[test]
2946 fn test_config_default_values() {
2947 let config = BrowserConfig::default();
2948 assert!(config.headless);
2949 assert_eq!(config.viewport_width, 800);
2950 assert_eq!(config.viewport_height, 600);
2951 assert!(config.chromium_path.is_none());
2952 assert_eq!(config.debug_port, 0);
2953 assert!(config.user_agent.is_none());
2954 assert!(!config.devtools);
2955 assert!(config.sandbox);
2956 assert!(config.tracing_config.is_none());
2957 }
2958 }
2959
2960 #[cfg(not(feature = "browser"))]
2961 mod mock_browser_comprehensive {
2962 use super::*;
2963
2964 #[test]
2965 fn test_browser_launch_with_all_config() {
2966 let tracing = RenacerTracingConfig::new("test");
2967 let config = BrowserConfig::default()
2968 .with_viewport(1280, 720)
2969 .with_headless(false)
2970 .with_no_sandbox()
2971 .with_tracing(tracing);
2972
2973 let browser = Browser::launch(config).unwrap();
2974 assert_eq!(browser.config().viewport_width, 1280);
2975 assert!(!browser.config().headless);
2976 assert!(!browser.config().sandbox);
2977 }
2978
2979 #[test]
2980 fn test_browser_multiple_pages() {
2981 let browser = Browser::launch(BrowserConfig::default()).unwrap();
2982 let page1 = browser.new_page().unwrap();
2983 let page2 = browser.new_page().unwrap();
2984 assert_eq!(page1.width, 800);
2985 assert_eq!(page2.width, 800);
2986 }
2987
2988 #[test]
2989 fn test_browser_config_accessor() {
2990 let config = BrowserConfig::default().with_viewport(1920, 1080);
2991 let browser = Browser::launch(config).unwrap();
2992 let returned_config = browser.config();
2993 assert_eq!(returned_config.viewport_width, 1920);
2994 assert_eq!(returned_config.viewport_height, 1080);
2995 }
2996 }
2997
2998 #[cfg(not(feature = "browser"))]
2999 mod mock_page_comprehensive {
3000 use super::*;
3001
3002 #[test]
3003 fn test_page_new_with_tracing() {
3004 let collector = TraceCollector::new("test");
3005 let page = Page::new_with_tracing(1024, 768, Some(collector));
3006 assert_eq!(page.width, 1024);
3007 assert_eq!(page.height, 768);
3008 assert!(page.is_tracing_enabled());
3009 }
3010
3011 #[test]
3012 fn test_page_new_without_tracing() {
3013 let page = Page::new_with_tracing(800, 600, None);
3014 assert!(!page.is_tracing_enabled());
3015 }
3016
3017 #[test]
3018 fn test_page_goto_various_urls() {
3019 let mut page = Page::new(800, 600);
3020
3021 page.goto("http://example.com").unwrap();
3023 assert_eq!(page.current_url(), "http://example.com");
3024
3025 page.goto("https://secure.example.com").unwrap();
3027 assert_eq!(page.current_url(), "https://secure.example.com");
3028
3029 page.goto("http://localhost:8080/path").unwrap();
3031 assert_eq!(page.current_url(), "http://localhost:8080/path");
3032
3033 page.goto("file:///path/to/file.html").unwrap();
3035 assert_eq!(page.current_url(), "file:///path/to/file.html");
3036 }
3037
3038 #[test]
3039 fn test_page_wasm_ready_lifecycle() {
3040 let mut page = Page::new(800, 600);
3041 assert!(!page.is_wasm_ready());
3042 assert!(!page.wasm_ready);
3043
3044 page.wait_for_wasm_ready().unwrap();
3045 assert!(page.is_wasm_ready());
3046 assert!(page.wasm_ready);
3047 }
3048
3049 #[test]
3050 fn test_page_eval_wasm_error_message() {
3051 let page = Page::new(800, 600);
3052 let result: Result<String, _> = page.eval_wasm("window.test");
3053 let err = result.unwrap_err();
3054 let err_str = format!("{}", err);
3055 assert!(err_str.contains("Browser feature not enabled"));
3056 }
3057
3058 #[test]
3059 fn test_page_all_touch_actions() {
3060 let page = Page::new(800, 600);
3061
3062 let tap = crate::Touch {
3064 x: 100.0,
3065 y: 200.0,
3066 action: crate::TouchAction::Tap,
3067 };
3068 assert!(page.touch(tap).is_ok());
3069
3070 let swipe = crate::Touch {
3072 x: 50.0,
3073 y: 50.0,
3074 action: crate::TouchAction::Swipe {
3075 end_x: 250.0,
3076 end_y: 250.0,
3077 duration_ms: 200,
3078 },
3079 };
3080 assert!(page.touch(swipe).is_ok());
3081
3082 let hold = crate::Touch {
3084 x: 300.0,
3085 y: 300.0,
3086 action: crate::TouchAction::Hold { duration_ms: 1000 },
3087 };
3088 assert!(page.touch(hold).is_ok());
3089 }
3090
3091 #[test]
3092 fn test_page_screenshot_returns_empty() {
3093 let page = Page::new(800, 600);
3094 let screenshot = page.screenshot().unwrap();
3095 assert!(screenshot.is_empty());
3096 }
3097
3098 #[test]
3099 fn test_page_debug_includes_fields() {
3100 let mut page = Page::new(1024, 768);
3101 page.goto("http://test.com").unwrap();
3102 let debug = format!("{:?}", page);
3103 assert!(debug.contains("Page"));
3104 assert!(debug.contains("1024"));
3105 assert!(debug.contains("768"));
3106 }
3107 }
3108
3109 #[cfg(not(feature = "browser"))]
3110 mod mock_console_comprehensive {
3111 use super::*;
3112
3113 #[test]
3114 fn test_enable_console_capture_idempotent() {
3115 let mut page = Page::new(800, 600);
3116 page.enable_console_capture().unwrap();
3117 assert!(page.is_console_capture_enabled());
3118 page.enable_console_capture().unwrap();
3120 assert!(page.is_console_capture_enabled());
3121 }
3122
3123 #[test]
3124 fn test_add_multiple_console_messages() {
3125 let page = Page::new(800, 600);
3126 for i in 0..10 {
3127 page.add_console_message(BrowserConsoleMessage {
3128 level: BrowserConsoleLevel::Log,
3129 text: format!("Message {}", i),
3130 timestamp: i as u64 * 100,
3131 source: None,
3132 line: None,
3133 });
3134 }
3135 assert_eq!(page.console_messages().len(), 10);
3136 }
3137
3138 #[test]
3139 fn test_clear_console_after_messages() {
3140 let page = Page::new(800, 600);
3141 page.add_console_message(BrowserConsoleMessage {
3142 level: BrowserConsoleLevel::Error,
3143 text: "Error 1".to_string(),
3144 timestamp: 0,
3145 source: None,
3146 line: None,
3147 });
3148 page.add_console_message(BrowserConsoleMessage {
3149 level: BrowserConsoleLevel::Error,
3150 text: "Error 2".to_string(),
3151 timestamp: 1,
3152 source: None,
3153 line: None,
3154 });
3155 assert_eq!(page.console_messages().len(), 2);
3156
3157 page.clear_console();
3158 assert!(page.console_messages().is_empty());
3159 }
3160
3161 #[test]
3162 fn test_wait_for_console_complex_predicate() {
3163 let page = Page::new(800, 600);
3164 page.add_console_message(BrowserConsoleMessage {
3165 level: BrowserConsoleLevel::Log,
3166 text: "startup complete".to_string(),
3167 timestamp: 100,
3168 source: Some("main.js".to_string()),
3169 line: Some(1),
3170 });
3171 page.add_console_message(BrowserConsoleMessage {
3172 level: BrowserConsoleLevel::Error,
3173 text: "error: failed".to_string(),
3174 timestamp: 200,
3175 source: Some("error.js".to_string()),
3176 line: Some(42),
3177 });
3178
3179 let result = page.wait_for_console(
3181 |m| m.level == BrowserConsoleLevel::Error && m.text.contains("failed"),
3182 1000,
3183 );
3184 assert!(result.is_ok());
3185 let msg = result.unwrap();
3186 assert_eq!(msg.text, "error: failed");
3187 }
3188
3189 #[test]
3190 fn test_inject_console_capture_sets_flag() {
3191 let mut page = Page::new(800, 600);
3192 assert!(!page.is_console_capture_enabled());
3193 page.inject_console_capture().unwrap();
3194 assert!(page.is_console_capture_enabled());
3195 }
3196
3197 #[test]
3198 fn test_fetch_console_messages_returns_copy() {
3199 let page = Page::new(800, 600);
3200 page.add_console_message(BrowserConsoleMessage {
3201 level: BrowserConsoleLevel::Info,
3202 text: "info".to_string(),
3203 timestamp: 0,
3204 source: None,
3205 line: None,
3206 });
3207
3208 let fetched = page.fetch_console_messages().unwrap();
3209 assert_eq!(fetched.len(), 1);
3210
3211 assert_eq!(page.console_messages().len(), 1);
3213 }
3214
3215 #[test]
3216 fn test_console_messages_with_all_levels() {
3217 let page = Page::new(800, 600);
3218 let levels = [
3219 BrowserConsoleLevel::Log,
3220 BrowserConsoleLevel::Info,
3221 BrowserConsoleLevel::Warning,
3222 BrowserConsoleLevel::Error,
3223 BrowserConsoleLevel::Debug,
3224 ];
3225
3226 for level in levels {
3227 page.add_console_message(BrowserConsoleMessage {
3228 level,
3229 text: format!("{:?}", level),
3230 timestamp: 0,
3231 source: None,
3232 line: None,
3233 });
3234 }
3235
3236 let messages = page.console_messages();
3237 assert_eq!(messages.len(), 5);
3238 }
3239 }
3240
3241 #[cfg(not(feature = "browser"))]
3242 mod mock_tracing_comprehensive {
3243 use super::*;
3244
3245 #[test]
3246 fn test_traceparent_format_w3c() {
3247 let collector = TraceCollector::new("test");
3248 let page = Page::new_with_tracing(800, 600, Some(collector));
3249 let tp = page.traceparent().unwrap();
3250
3251 let parts: Vec<&str> = tp.split('-').collect();
3253 assert_eq!(parts.len(), 4);
3254 assert_eq!(parts[0], "00"); assert_eq!(parts[1].len(), 32); assert_eq!(parts[2].len(), 16); }
3258
3259 #[test]
3260 fn test_start_span_without_tracing() {
3261 let mut page = Page::new(800, 600);
3262 let span = page.start_span("test", "category");
3263 assert!(span.is_none());
3264 }
3265
3266 #[test]
3267 fn test_start_span_with_tracing() {
3268 let collector = TraceCollector::new("test");
3269 let mut page = Page::new_with_tracing(800, 600, Some(collector));
3270 let span = page.start_span("operation", "http");
3271 assert!(span.is_some());
3272 let mut span = span.unwrap();
3273 span.end();
3274 page.record_span(span);
3275 }
3276
3277 #[test]
3278 fn test_record_span_without_tracing() {
3279 let mut page = Page::new(800, 600);
3280 let mut collector = TraceCollector::new("temp");
3282 let mut span = collector.start_span("test", "cat");
3283 span.end();
3284 page.record_span(span);
3286 }
3287
3288 #[test]
3289 fn test_record_trace_console_without_tracing() {
3290 let mut page = Page::new(800, 600);
3291 page.record_trace_console("test message");
3293 }
3294
3295 #[test]
3296 fn test_record_trace_console_with_tracing() {
3297 let collector = TraceCollector::new("test");
3298 let mut page = Page::new_with_tracing(800, 600, Some(collector));
3299 page.record_trace_console("console log entry");
3300
3301 let trace = page.export_chrome_trace().unwrap();
3302 assert!(!trace.trace_events.is_empty());
3303 }
3304
3305 #[test]
3306 fn test_export_chrome_trace_without_tracing() {
3307 let page = Page::new(800, 600);
3308 assert!(page.export_chrome_trace().is_none());
3309 }
3310
3311 #[test]
3312 fn test_export_trace_json_without_tracing() {
3313 let page = Page::new(800, 600);
3314 let json = page.export_trace_json().unwrap();
3315 assert!(json.is_none());
3316 }
3317
3318 #[test]
3319 fn test_export_trace_json_with_tracing() {
3320 let collector = TraceCollector::new("test");
3321 let mut page = Page::new_with_tracing(800, 600, Some(collector));
3322
3323 let mut span = page.start_span("test-op", "test-cat").unwrap();
3324 span.end();
3325 page.record_span(span);
3326
3327 let json = page.export_trace_json().unwrap();
3328 assert!(json.is_some());
3329 let json_str = json.unwrap();
3330 assert!(json_str.contains("traceEvents"));
3331 }
3332
3333 #[test]
3334 fn test_inject_trace_context_mock() {
3335 let collector = TraceCollector::new("test");
3336 let mut page = Page::new_with_tracing(800, 600, Some(collector));
3337 assert!(page.inject_trace_context().is_ok());
3339 }
3340
3341 #[test]
3342 fn test_inject_trace_context_without_tracing() {
3343 let mut page = Page::new(800, 600);
3344 assert!(page.inject_trace_context().is_ok());
3345 }
3346 }
3347
3348 #[cfg(not(feature = "browser"))]
3349 mod mock_coverage_comprehensive {
3350 use super::*;
3351 use crate::cdp_coverage::{CoverageConfig, CoverageRange, FunctionCoverage};
3352
3353 #[test]
3354 fn test_coverage_lifecycle_complete() {
3355 let mut page = Page::new(800, 600);
3356 assert!(!page.is_coverage_enabled());
3357
3358 page.start_coverage().unwrap();
3360 assert!(page.is_coverage_enabled());
3361
3362 page.add_mock_coverage(FunctionCoverage {
3364 function_name: "testFn".to_string(),
3365 ranges: vec![CoverageRange {
3366 start_offset: 0,
3367 end_offset: 50,
3368 count: 3,
3369 }],
3370 is_block_coverage: true,
3371 });
3372
3373 let report1 = page.take_coverage().unwrap();
3375 assert!(page.is_coverage_enabled());
3376 assert_eq!(report1.scripts[0].functions.len(), 1);
3377
3378 let report2 = page.stop_coverage().unwrap();
3380 assert!(!page.is_coverage_enabled());
3381 assert_eq!(report2.scripts[0].functions.len(), 1);
3382 }
3383
3384 #[test]
3385 fn test_coverage_config_options() {
3386 let mut page = Page::new(800, 600);
3387
3388 let config = CoverageConfig {
3390 call_count: false,
3391 detailed: false,
3392 allow_triggered_updates: true,
3393 };
3394 page.start_coverage_with_config(config).unwrap();
3395 assert!(page.is_coverage_enabled());
3396 }
3397
3398 #[test]
3399 fn test_coverage_report_url() {
3400 let mut page = Page::new(800, 600);
3401 page.goto("http://localhost:8080/app.html").unwrap();
3402 page.start_coverage().unwrap();
3403
3404 let report = page.take_coverage().unwrap();
3405 assert_eq!(report.scripts[0].url, "http://localhost:8080/app.html");
3406 }
3407
3408 #[test]
3409 fn test_coverage_report_timestamp() {
3410 let mut page = Page::new(800, 600);
3411 page.start_coverage().unwrap();
3412
3413 let report = page.take_coverage().unwrap();
3414 assert!(report.timestamp_ms > 0);
3416 }
3417
3418 #[test]
3419 fn test_clear_mock_coverage() {
3420 let mut page = Page::new(800, 600);
3421 page.start_coverage().unwrap();
3422
3423 page.add_mock_coverage(FunctionCoverage {
3424 function_name: "fn1".to_string(),
3425 ranges: vec![],
3426 is_block_coverage: false,
3427 });
3428 page.add_mock_coverage(FunctionCoverage {
3429 function_name: "fn2".to_string(),
3430 ranges: vec![],
3431 is_block_coverage: false,
3432 });
3433
3434 let report1 = page.take_coverage().unwrap();
3435 assert_eq!(report1.scripts[0].functions.len(), 2);
3436
3437 page.clear_mock_coverage();
3438
3439 let report2 = page.take_coverage().unwrap();
3440 assert!(report2.scripts[0].functions.is_empty());
3441 }
3442
3443 #[test]
3444 fn test_coverage_multiple_functions() {
3445 let mut page = Page::new(800, 600);
3446 page.start_coverage().unwrap();
3447
3448 for i in 0..5 {
3449 page.add_mock_coverage(FunctionCoverage {
3450 function_name: format!("function_{}", i),
3451 ranges: vec![CoverageRange {
3452 start_offset: i * 100,
3453 end_offset: (i + 1) * 100,
3454 count: i + 1,
3455 }],
3456 is_block_coverage: i % 2 == 0,
3457 });
3458 }
3459
3460 let report = page.take_coverage().unwrap();
3461 assert_eq!(report.scripts[0].functions.len(), 5);
3462 }
3463
3464 #[test]
3465 fn test_take_coverage_error_when_disabled() {
3466 let page = Page::new(800, 600);
3467 let result = page.take_coverage();
3468 assert!(result.is_err());
3469 let err = result.unwrap_err();
3470 let err_str = format!("{}", err);
3471 assert!(err_str.contains("Coverage not enabled"));
3472 }
3473
3474 #[test]
3475 fn test_stop_coverage_error_when_disabled() {
3476 let mut page = Page::new(800, 600);
3477 let result = page.stop_coverage();
3478 assert!(result.is_err());
3479 }
3480 }
3481
3482 #[cfg(not(feature = "browser"))]
3483 mod mock_integration_tests {
3484 use super::*;
3485
3486 #[test]
3487 fn test_full_page_lifecycle() {
3488 let config = BrowserConfig::default()
3489 .with_viewport(1280, 720)
3490 .with_headless(true);
3491 let browser = Browser::launch(config).unwrap();
3492 let mut page = browser.new_page().unwrap();
3493
3494 page.goto("http://localhost:8080").unwrap();
3496 assert_eq!(page.current_url(), "http://localhost:8080");
3497
3498 page.wait_for_wasm_ready().unwrap();
3500 assert!(page.is_wasm_ready());
3501
3502 page.enable_console_capture().unwrap();
3504 page.add_console_message(BrowserConsoleMessage {
3505 level: BrowserConsoleLevel::Log,
3506 text: "ready".to_string(),
3507 timestamp: 100,
3508 source: None,
3509 line: None,
3510 });
3511
3512 page.start_coverage().unwrap();
3514 let report = page.stop_coverage().unwrap();
3515 assert!(!report.scripts.is_empty());
3516
3517 let screenshot = page.screenshot().unwrap();
3519 assert!(screenshot.is_empty()); }
3521
3522 #[test]
3523 fn test_browser_with_tracing_creates_traced_pages() {
3524 let tracing = RenacerTracingConfig::new("integration-test");
3525 let config = BrowserConfig::default().with_tracing(tracing);
3526 let browser = Browser::launch(config).unwrap();
3527 let mut page = browser.new_page().unwrap();
3528
3529 assert!(page.is_tracing_enabled());
3530 let traceparent = page.traceparent();
3531 assert!(traceparent.is_some());
3532
3533 let mut span = page.start_span("test-op", "test-cat").unwrap();
3535 span.add_attribute("key", "value");
3536 span.end();
3537 page.record_span(span);
3538
3539 let json = page.export_trace_json().unwrap();
3541 assert!(json.is_some());
3542 }
3543
3544 #[test]
3545 fn test_console_and_coverage_together() {
3546 let mut page = Page::new(800, 600);
3547 page.enable_console_capture().unwrap();
3548 page.start_coverage().unwrap();
3549
3550 page.add_console_message(BrowserConsoleMessage {
3552 level: BrowserConsoleLevel::Log,
3553 text: "Starting app...".to_string(),
3554 timestamp: 1,
3555 source: None,
3556 line: None,
3557 });
3558
3559 page.add_mock_coverage(crate::cdp_coverage::FunctionCoverage {
3561 function_name: "init".to_string(),
3562 ranges: vec![crate::cdp_coverage::CoverageRange {
3563 start_offset: 0,
3564 end_offset: 100,
3565 count: 1,
3566 }],
3567 is_block_coverage: false,
3568 });
3569
3570 let messages = page.console_messages();
3572 assert_eq!(messages.len(), 1);
3573
3574 let report = page.stop_coverage().unwrap();
3576 assert_eq!(report.scripts[0].functions.len(), 1);
3577 }
3578 }
3579
3580 mod edge_cases {
3581 use super::*;
3582
3583 #[test]
3584 fn test_browser_config_zero_viewport() {
3585 let config = BrowserConfig::default().with_viewport(0, 0);
3586 assert_eq!(config.viewport_width, 0);
3587 assert_eq!(config.viewport_height, 0);
3588 }
3589
3590 #[test]
3591 fn test_browser_config_max_viewport() {
3592 let config = BrowserConfig::default().with_viewport(u32::MAX, u32::MAX);
3593 assert_eq!(config.viewport_width, u32::MAX);
3594 assert_eq!(config.viewport_height, u32::MAX);
3595 }
3596
3597 #[test]
3598 fn test_browser_config_long_path() {
3599 let long_path = "a".repeat(10000);
3600 let config = BrowserConfig::default().with_chromium_path(&long_path);
3601 assert_eq!(config.chromium_path.as_ref().unwrap().len(), 10000);
3602 }
3603
3604 #[test]
3605 fn test_browser_config_special_chars_user_agent() {
3606 let ua = "Mozilla/5.0 (Test) <script>alert(1)</script> \n\r\t";
3607 let config = BrowserConfig::default().with_user_agent(ua);
3608 assert_eq!(config.user_agent.as_ref().unwrap(), ua);
3609 }
3610
3611 #[cfg(not(feature = "browser"))]
3612 #[test]
3613 fn test_page_empty_url() {
3614 let mut page = Page::new(800, 600);
3615 page.goto("").unwrap();
3616 assert_eq!(page.current_url(), "");
3617 }
3618
3619 #[cfg(not(feature = "browser"))]
3620 #[test]
3621 fn test_page_touch_at_boundaries() {
3622 let page = Page::new(800, 600);
3623
3624 let tap_origin = crate::Touch {
3626 x: 0.0,
3627 y: 0.0,
3628 action: crate::TouchAction::Tap,
3629 };
3630 assert!(page.touch(tap_origin).is_ok());
3631
3632 let tap_max = crate::Touch {
3634 x: f32::MAX,
3635 y: f32::MAX,
3636 action: crate::TouchAction::Tap,
3637 };
3638 assert!(page.touch(tap_max).is_ok());
3639
3640 let tap_neg = crate::Touch {
3642 x: -100.0,
3643 y: -100.0,
3644 action: crate::TouchAction::Tap,
3645 };
3646 assert!(page.touch(tap_neg).is_ok());
3647 }
3648
3649 #[test]
3650 fn test_console_level_copy() {
3651 let level = BrowserConsoleLevel::Error;
3652 let copied: BrowserConsoleLevel = level;
3653 let another: BrowserConsoleLevel = level;
3654 assert_eq!(copied, another);
3655 }
3656
3657 #[cfg(not(feature = "browser"))]
3658 #[test]
3659 fn test_wait_for_console_empty_predicate() {
3660 let page = Page::new(800, 600);
3661 let result = page.wait_for_console(|_| false, 100);
3663 assert!(result.is_err());
3664 }
3665
3666 #[cfg(not(feature = "browser"))]
3667 #[test]
3668 fn test_wait_for_console_always_matches() {
3669 let page = Page::new(800, 600);
3670 page.add_console_message(BrowserConsoleMessage {
3671 level: BrowserConsoleLevel::Log,
3672 text: "any".to_string(),
3673 timestamp: 0,
3674 source: None,
3675 line: None,
3676 });
3677 let result = page.wait_for_console(|_| true, 100);
3679 assert!(result.is_ok());
3680 }
3681 }
3682
3683 #[cfg(not(feature = "browser"))]
3688 mod additional_mock_coverage_tests {
3689 use super::*;
3690 use crate::cdp_coverage::{CoverageRange, FunctionCoverage};
3691
3692 #[test]
3693 fn test_page_new_with_tracing_none_explicit() {
3694 let page = Page::new_with_tracing(640, 480, None);
3696 assert_eq!(page.width, 640);
3697 assert_eq!(page.height, 480);
3698 assert!(!page.is_tracing_enabled());
3699 assert!(page.traceparent().is_none());
3700 assert!(page.export_chrome_trace().is_none());
3701 }
3702
3703 #[test]
3704 fn test_page_start_span_returns_none_without_tracing() {
3705 let mut page = Page::new(800, 600);
3706 let span = page.start_span("operation", "category");
3707 assert!(span.is_none());
3708 }
3709
3710 #[test]
3711 fn test_page_record_span_noop_without_tracing() {
3712 let mut page = Page::new(800, 600);
3713 let mut temp_collector = TraceCollector::new("temp");
3715 let mut span = temp_collector.start_span("test", "cat");
3716 span.end();
3717 page.record_span(span);
3719 }
3721
3722 #[test]
3723 fn test_page_record_trace_console_noop_without_tracing() {
3724 let mut page = Page::new(800, 600);
3725 page.record_trace_console("test message");
3727 }
3729
3730 #[test]
3731 fn test_page_export_trace_json_none_without_tracing() {
3732 let page = Page::new(800, 600);
3733 let result = page.export_trace_json();
3734 assert!(result.is_ok());
3735 assert!(result.unwrap().is_none());
3736 }
3737
3738 #[test]
3739 fn test_page_inject_trace_context_ok_without_tracing() {
3740 let mut page = Page::new(800, 600);
3741 let result = page.inject_trace_context();
3742 assert!(result.is_ok());
3743 }
3744
3745 #[test]
3746 fn test_browser_launch_preserves_all_config_fields() {
3747 let config = BrowserConfig {
3748 headless: false,
3749 viewport_width: 1920,
3750 viewport_height: 1080,
3751 chromium_path: Some("/usr/bin/chromium".to_string()),
3752 debug_port: 9222,
3753 user_agent: Some("TestAgent".to_string()),
3754 devtools: true,
3755 sandbox: false,
3756 tracing_config: Some(RenacerTracingConfig::new("test")),
3757 };
3758 let browser = Browser::launch(config).unwrap();
3759 let cfg = browser.config();
3760 assert!(!cfg.headless);
3761 assert_eq!(cfg.viewport_width, 1920);
3762 assert_eq!(cfg.viewport_height, 1080);
3763 assert_eq!(cfg.chromium_path, Some("/usr/bin/chromium".to_string()));
3764 assert_eq!(cfg.debug_port, 9222);
3765 assert_eq!(cfg.user_agent, Some("TestAgent".to_string()));
3766 assert!(cfg.devtools);
3767 assert!(!cfg.sandbox);
3768 assert!(cfg.tracing_config.is_some());
3769 }
3770
3771 #[test]
3772 fn test_page_add_mock_coverage_multiple_ranges() {
3773 let mut page = Page::new(800, 600);
3774 page.start_coverage().unwrap();
3775
3776 page.add_mock_coverage(FunctionCoverage {
3777 function_name: "multi_range_func".to_string(),
3778 ranges: vec![
3779 CoverageRange {
3780 start_offset: 0,
3781 end_offset: 50,
3782 count: 10,
3783 },
3784 CoverageRange {
3785 start_offset: 50,
3786 end_offset: 100,
3787 count: 5,
3788 },
3789 CoverageRange {
3790 start_offset: 100,
3791 end_offset: 200,
3792 count: 0,
3793 },
3794 ],
3795 is_block_coverage: true,
3796 });
3797
3798 let report = page.take_coverage().unwrap();
3799 assert_eq!(report.scripts[0].functions[0].ranges.len(), 3);
3800 }
3801
3802 #[test]
3803 fn test_console_message_with_source_and_line() {
3804 let page = Page::new(800, 600);
3805 page.add_console_message(BrowserConsoleMessage {
3806 level: BrowserConsoleLevel::Error,
3807 text: "Error occurred".to_string(),
3808 timestamp: 12345678,
3809 source: Some("/path/to/script.js".to_string()),
3810 line: Some(42),
3811 });
3812
3813 let messages = page.console_messages();
3814 assert_eq!(messages.len(), 1);
3815 assert_eq!(messages[0].source, Some("/path/to/script.js".to_string()));
3816 assert_eq!(messages[0].line, Some(42));
3817 }
3818
3819 #[test]
3820 fn test_wait_for_console_predicate_by_timestamp() {
3821 let page = Page::new(800, 600);
3822 page.add_console_message(BrowserConsoleMessage {
3823 level: BrowserConsoleLevel::Log,
3824 text: "early".to_string(),
3825 timestamp: 100,
3826 source: None,
3827 line: None,
3828 });
3829 page.add_console_message(BrowserConsoleMessage {
3830 level: BrowserConsoleLevel::Log,
3831 text: "late".to_string(),
3832 timestamp: 500,
3833 source: None,
3834 line: None,
3835 });
3836
3837 let result = page.wait_for_console(|m| m.timestamp > 200, 1000);
3838 assert!(result.is_ok());
3839 assert_eq!(result.unwrap().text, "late");
3840 }
3841
3842 #[test]
3843 fn test_wait_for_console_predicate_by_source() {
3844 let page = Page::new(800, 600);
3845 page.add_console_message(BrowserConsoleMessage {
3846 level: BrowserConsoleLevel::Warning,
3847 text: "warning from main".to_string(),
3848 timestamp: 0,
3849 source: Some("main.js".to_string()),
3850 line: Some(10),
3851 });
3852
3853 let result = page.wait_for_console(|m| m.source.as_deref() == Some("main.js"), 1000);
3854 assert!(result.is_ok());
3855 }
3856
3857 #[test]
3858 fn test_coverage_restart_after_stop() {
3859 let mut page = Page::new(800, 600);
3860
3861 page.start_coverage().unwrap();
3863 assert!(page.is_coverage_enabled());
3864 page.stop_coverage().unwrap();
3865 assert!(!page.is_coverage_enabled());
3866
3867 page.start_coverage().unwrap();
3869 assert!(page.is_coverage_enabled());
3870 }
3871
3872 #[test]
3873 fn test_page_with_tracing_multiple_spans() {
3874 let collector = TraceCollector::new("multi-span-test");
3875 let mut page = Page::new_with_tracing(800, 600, Some(collector));
3876
3877 for i in 0..5 {
3878 let mut span = page.start_span(format!("span_{}", i), "test").unwrap();
3879 span.add_attribute("index", i.to_string());
3880 span.end();
3881 page.record_span(span);
3882 }
3883
3884 let trace = page.export_chrome_trace().unwrap();
3885 assert_eq!(trace.trace_events.len(), 5);
3886 }
3887
3888 #[test]
3889 fn test_page_with_tracing_console_and_spans() {
3890 let collector = TraceCollector::new("mixed-test");
3891 let mut page = Page::new_with_tracing(800, 600, Some(collector));
3892
3893 page.record_trace_console("Console 1");
3895 page.record_trace_console("Console 2");
3896
3897 let mut span = page.start_span("operation", "http").unwrap();
3899 span.end();
3900 page.record_span(span);
3901
3902 let trace = page.export_chrome_trace().unwrap();
3903 assert_eq!(trace.trace_events.len(), 3);
3905 }
3906
3907 #[test]
3908 fn test_coverage_with_about_blank_url() {
3909 let mut page = Page::new(800, 600);
3910 page.start_coverage().unwrap();
3912
3913 let report = page.take_coverage().unwrap();
3914 assert_eq!(report.scripts[0].url, "about:blank");
3915 }
3916
3917 #[test]
3918 fn test_page_current_url_after_multiple_navigations() {
3919 let mut page = Page::new(800, 600);
3920 assert_eq!(page.current_url(), "about:blank");
3921
3922 let urls = ["http://first.com", "http://second.com", "http://third.com"];
3923 for url in &urls {
3924 page.goto(url).unwrap();
3925 }
3926 assert_eq!(page.current_url(), "http://third.com");
3927 }
3928
3929 #[test]
3930 fn test_browser_disabled_tracing_creates_untraced_pages() {
3931 let disabled_tracing = RenacerTracingConfig::disabled();
3932 let config = BrowserConfig::default().with_tracing(disabled_tracing);
3933 let browser = Browser::launch(config).unwrap();
3934 let page = browser.new_page().unwrap();
3935
3936 assert!(!page.is_tracing_enabled());
3937 assert!(page.traceparent().is_none());
3938 }
3939
3940 #[test]
3941 fn test_all_touch_actions_mock() {
3942 let page = Page::new(800, 600);
3943
3944 for x in [0.0f32, 400.0, 800.0] {
3946 for y in [0.0f32, 300.0, 600.0] {
3947 let tap = crate::Touch {
3948 x,
3949 y,
3950 action: crate::TouchAction::Tap,
3951 };
3952 assert!(page.touch(tap).is_ok());
3953 }
3954 }
3955
3956 for duration in [0u32, 100, 500, 1000] {
3958 let swipe = crate::Touch {
3959 x: 100.0,
3960 y: 100.0,
3961 action: crate::TouchAction::Swipe {
3962 end_x: 200.0,
3963 end_y: 200.0,
3964 duration_ms: duration,
3965 },
3966 };
3967 assert!(page.touch(swipe).is_ok());
3968 }
3969
3970 for duration in [0u32, 100, 500, 2000] {
3972 let hold = crate::Touch {
3973 x: 300.0,
3974 y: 300.0,
3975 action: crate::TouchAction::Hold {
3976 duration_ms: duration,
3977 },
3978 };
3979 assert!(page.touch(hold).is_ok());
3980 }
3981 }
3982
3983 #[test]
3984 fn test_console_messages_empty_initially() {
3985 let page = Page::new(800, 600);
3986 let fetched = page.fetch_console_messages().unwrap();
3987 assert!(fetched.is_empty());
3988 }
3989
3990 #[test]
3991 fn test_clear_console_when_empty() {
3992 let page = Page::new(800, 600);
3993 page.clear_console();
3995 assert!(page.console_messages().is_empty());
3996 }
3997
3998 #[test]
3999 fn test_clear_mock_coverage_when_empty() {
4000 let mut page = Page::new(800, 600);
4001 page.start_coverage().unwrap();
4002 page.clear_mock_coverage();
4004 let report = page.take_coverage().unwrap();
4005 assert!(report.scripts[0].functions.is_empty());
4006 }
4007 }
4008
4009 mod error_display_tests {
4014 use super::*;
4015
4016 #[test]
4017 fn test_browser_console_level_display_matches_expected() {
4018 assert_eq!(BrowserConsoleLevel::Log.to_string(), "log");
4020 assert_eq!(BrowserConsoleLevel::Info.to_string(), "info");
4021 assert_eq!(BrowserConsoleLevel::Warning.to_string(), "warn");
4022 assert_eq!(BrowserConsoleLevel::Error.to_string(), "error");
4023 assert_eq!(BrowserConsoleLevel::Debug.to_string(), "debug");
4024 }
4025
4026 #[test]
4027 fn test_browser_console_level_debug_format() {
4028 assert_eq!(format!("{:?}", BrowserConsoleLevel::Log), "Log");
4029 assert_eq!(format!("{:?}", BrowserConsoleLevel::Info), "Info");
4030 assert_eq!(format!("{:?}", BrowserConsoleLevel::Warning), "Warning");
4031 assert_eq!(format!("{:?}", BrowserConsoleLevel::Error), "Error");
4032 assert_eq!(format!("{:?}", BrowserConsoleLevel::Debug), "Debug");
4033 }
4034
4035 #[test]
4036 fn test_browser_console_message_debug_format_complete() {
4037 let msg = BrowserConsoleMessage {
4038 level: BrowserConsoleLevel::Error,
4039 text: "test error".to_string(),
4040 timestamp: 999,
4041 source: Some("test.js".to_string()),
4042 line: Some(99),
4043 };
4044 let debug_str = format!("{:?}", msg);
4045 assert!(debug_str.contains("BrowserConsoleMessage"));
4046 assert!(debug_str.contains("Error"));
4047 assert!(debug_str.contains("test error"));
4048 assert!(debug_str.contains("999"));
4049 assert!(debug_str.contains("test.js"));
4050 assert!(debug_str.contains("99"));
4051 }
4052
4053 #[test]
4054 fn test_browser_config_debug_format_complete() {
4055 let config = BrowserConfig::default()
4056 .with_viewport(1280, 720)
4057 .with_chromium_path("/path/to/chrome")
4058 .with_user_agent("Test UA")
4059 .with_no_sandbox();
4060 let debug_str = format!("{:?}", config);
4061 assert!(debug_str.contains("BrowserConfig"));
4062 assert!(debug_str.contains("1280"));
4063 assert!(debug_str.contains("720"));
4064 assert!(debug_str.contains("/path/to/chrome"));
4065 assert!(debug_str.contains("Test UA"));
4066 }
4067
4068 #[cfg(not(feature = "browser"))]
4069 #[test]
4070 fn test_browser_debug_format() {
4071 let browser = Browser::launch(BrowserConfig::default()).unwrap();
4072 let debug_str = format!("{:?}", browser);
4073 assert!(debug_str.contains("Browser"));
4074 }
4075
4076 #[cfg(not(feature = "browser"))]
4077 #[test]
4078 fn test_page_debug_format() {
4079 let page = Page::new(1024, 768);
4080 let debug_str = format!("{:?}", page);
4081 assert!(debug_str.contains("Page"));
4082 assert!(debug_str.contains("1024"));
4083 assert!(debug_str.contains("768"));
4084 }
4085 }
4086
4087 mod comprehensive_property_tests {
4092 use super::*;
4093
4094 #[test]
4095 fn test_browser_config_viewport_dimensions_preserved() {
4096 for (w, h) in [
4097 (100u32, 100u32),
4098 (800, 600),
4099 (1920, 1080),
4100 (3840, 2160),
4101 (1, 1),
4102 ] {
4103 let config = BrowserConfig::default().with_viewport(w, h);
4104 assert_eq!(config.viewport_width, w);
4105 assert_eq!(config.viewport_height, h);
4106 }
4107 }
4108
4109 #[test]
4110 fn test_browser_config_headless_toggle() {
4111 let config_headless = BrowserConfig::default().with_headless(true);
4112 assert!(config_headless.headless);
4113
4114 let config_not_headless = BrowserConfig::default().with_headless(false);
4115 assert!(!config_not_headless.headless);
4116 }
4117
4118 #[test]
4119 fn test_browser_console_level_equality_reflexive() {
4120 let levels = [
4121 BrowserConsoleLevel::Log,
4122 BrowserConsoleLevel::Info,
4123 BrowserConsoleLevel::Warning,
4124 BrowserConsoleLevel::Error,
4125 BrowserConsoleLevel::Debug,
4126 ];
4127 for level in levels {
4128 assert_eq!(level, level);
4129 }
4130 }
4131
4132 #[test]
4133 fn test_browser_console_level_clone_equals_original() {
4134 let levels = [
4135 BrowserConsoleLevel::Log,
4136 BrowserConsoleLevel::Info,
4137 BrowserConsoleLevel::Warning,
4138 BrowserConsoleLevel::Error,
4139 BrowserConsoleLevel::Debug,
4140 ];
4141 for level in levels {
4142 let cloned = level;
4143 assert_eq!(level, cloned);
4144 }
4145 }
4146
4147 #[cfg(not(feature = "browser"))]
4148 #[test]
4149 fn test_page_screenshot_always_returns_empty() {
4150 let page = Page::new(800, 600);
4152 for _ in 0..5 {
4153 let result = page.screenshot();
4154 assert!(result.is_ok());
4155 assert!(result.unwrap().is_empty());
4156 }
4157 }
4158
4159 #[cfg(not(feature = "browser"))]
4160 #[test]
4161 fn test_page_eval_wasm_always_fails() {
4162 let page = Page::new(800, 600);
4163 let expressions = ["1 + 1", "window.test", "document.body", ""];
4164 for expr in expressions {
4165 let result: Result<String, _> = page.eval_wasm(expr);
4166 assert!(result.is_err());
4167 }
4168 }
4169
4170 #[cfg(not(feature = "browser"))]
4171 #[test]
4172 fn test_page_goto_accepts_any_string() {
4173 let mut page = Page::new(800, 600);
4174 let urls = [
4175 "",
4176 "http://example.com",
4177 "https://secure.example.com",
4178 "file:///path/to/file",
4179 "about:blank",
4180 "javascript:void(0)",
4181 "data:text/html,<h1>Test</h1>",
4182 ];
4183 for url in urls {
4184 let result = page.goto(url);
4185 assert!(result.is_ok());
4186 assert_eq!(page.current_url(), url);
4187 }
4188 }
4189
4190 #[cfg(not(feature = "browser"))]
4191 #[test]
4192 fn test_page_wasm_ready_idempotent() {
4193 let mut page = Page::new(800, 600);
4194 assert!(!page.is_wasm_ready());
4195
4196 for _ in 0..5 {
4197 page.wait_for_wasm_ready().unwrap();
4198 assert!(page.is_wasm_ready());
4199 }
4200 }
4201 }
4202
4203 mod browser_config_tracing_tests {
4208 use super::*;
4209
4210 #[test]
4211 fn test_is_tracing_enabled_none() {
4212 let config = BrowserConfig::default();
4213 assert!(!config.is_tracing_enabled());
4214 }
4215
4216 #[test]
4217 fn test_is_tracing_enabled_some_enabled() {
4218 let tracing = RenacerTracingConfig::new("test-service");
4219 let config = BrowserConfig::default().with_tracing(tracing);
4220 assert!(config.is_tracing_enabled());
4221 }
4222
4223 #[test]
4224 fn test_is_tracing_enabled_some_disabled() {
4225 let tracing = RenacerTracingConfig::disabled();
4226 let config = BrowserConfig::default().with_tracing(tracing);
4227 assert!(!config.is_tracing_enabled());
4228 }
4229
4230 #[test]
4231 fn test_with_tracing_replaces_previous() {
4232 let tracing1 = RenacerTracingConfig::new("service1");
4233 let tracing2 = RenacerTracingConfig::new("service2");
4234
4235 let config = BrowserConfig::default()
4236 .with_tracing(tracing1)
4237 .with_tracing(tracing2);
4238
4239 assert!(config.is_tracing_enabled());
4240 let service_name = &config.tracing_config.as_ref().unwrap().service_name;
4241 assert_eq!(service_name, "service2");
4242 }
4243 }
4244
4245 #[cfg(not(feature = "browser"))]
4250 mod mock_coverage_99_percent {
4251 use super::*;
4252 use crate::cdp_coverage::{CoverageConfig, CoverageRange, FunctionCoverage};
4253
4254 #[test]
4259 fn test_page_new_default_console_capture_disabled() {
4260 let page = Page::new(800, 600);
4261 assert!(!page.is_console_capture_enabled());
4262 }
4263
4264 #[test]
4265 fn test_page_new_default_coverage_disabled() {
4266 let page = Page::new(800, 600);
4267 assert!(!page.is_coverage_enabled());
4268 }
4269
4270 #[test]
4271 fn test_page_new_with_tracing_default_fields() {
4272 let collector = TraceCollector::new("test");
4273 let page = Page::new_with_tracing(640, 480, Some(collector));
4274 assert_eq!(page.url, "about:blank");
4275 assert!(!page.wasm_ready);
4276 assert!(!page.is_console_capture_enabled());
4277 assert!(!page.is_coverage_enabled());
4278 }
4279
4280 #[test]
4281 fn test_page_new_with_tracing_none_all_defaults() {
4282 let page = Page::new_with_tracing(320, 240, None);
4283 assert_eq!(page.width, 320);
4284 assert_eq!(page.height, 240);
4285 assert_eq!(page.url, "about:blank");
4286 assert!(!page.wasm_ready);
4287 assert!(!page.is_console_capture_enabled());
4288 assert!(!page.is_tracing_enabled());
4289 assert!(!page.is_coverage_enabled());
4290 }
4291
4292 #[test]
4297 fn test_console_messages_returns_empty_vec_initially() {
4298 let page = Page::new(800, 600);
4299 let messages = page.console_messages();
4300 assert!(messages.is_empty());
4301 assert_eq!(messages.len(), 0);
4302 }
4303
4304 #[test]
4305 fn test_fetch_console_messages_empty() {
4306 let page = Page::new(800, 600);
4307 let result = page.fetch_console_messages();
4308 assert!(result.is_ok());
4309 assert!(result.unwrap().is_empty());
4310 }
4311
4312 #[test]
4313 fn test_add_console_message_then_fetch() {
4314 let page = Page::new(800, 600);
4315 page.add_console_message(BrowserConsoleMessage {
4316 level: BrowserConsoleLevel::Log,
4317 text: "test".to_string(),
4318 timestamp: 100,
4319 source: None,
4320 line: None,
4321 });
4322 let fetched = page.fetch_console_messages().unwrap();
4323 assert_eq!(fetched.len(), 1);
4324 assert_eq!(fetched[0].text, "test");
4325 }
4326
4327 #[test]
4328 fn test_clear_console_empties_messages() {
4329 let page = Page::new(800, 600);
4330 page.add_console_message(BrowserConsoleMessage {
4331 level: BrowserConsoleLevel::Error,
4332 text: "error".to_string(),
4333 timestamp: 0,
4334 source: None,
4335 line: None,
4336 });
4337 assert_eq!(page.console_messages().len(), 1);
4338 page.clear_console();
4339 assert_eq!(page.console_messages().len(), 0);
4340 }
4341
4342 #[test]
4343 fn test_wait_for_console_finds_first_match() {
4344 let page = Page::new(800, 600);
4345 page.add_console_message(BrowserConsoleMessage {
4346 level: BrowserConsoleLevel::Log,
4347 text: "first".to_string(),
4348 timestamp: 1,
4349 source: None,
4350 line: None,
4351 });
4352 page.add_console_message(BrowserConsoleMessage {
4353 level: BrowserConsoleLevel::Log,
4354 text: "second".to_string(),
4355 timestamp: 2,
4356 source: None,
4357 line: None,
4358 });
4359 let result = page.wait_for_console(|m| m.text == "first", 1000);
4360 assert!(result.is_ok());
4361 assert_eq!(result.unwrap().text, "first");
4362 }
4363
4364 #[test]
4365 fn test_wait_for_console_no_match_returns_timeout_error() {
4366 let page = Page::new(800, 600);
4367 page.add_console_message(BrowserConsoleMessage {
4368 level: BrowserConsoleLevel::Log,
4369 text: "exists".to_string(),
4370 timestamp: 0,
4371 source: None,
4372 line: None,
4373 });
4374 let result = page.wait_for_console(|m| m.text == "does_not_exist", 100);
4375 assert!(result.is_err());
4376 let err = result.unwrap_err();
4377 let err_str = format!("{}", err);
4378 assert!(err_str.contains("No matching console message"));
4379 }
4380
4381 #[test]
4382 fn test_enable_console_capture_returns_ok() {
4383 let mut page = Page::new(800, 600);
4384 let result = page.enable_console_capture();
4385 assert!(result.is_ok());
4386 assert!(page.is_console_capture_enabled());
4387 }
4388
4389 #[test]
4390 fn test_inject_console_capture_returns_ok() {
4391 let mut page = Page::new(800, 600);
4392 let result = page.inject_console_capture();
4393 assert!(result.is_ok());
4394 assert!(page.is_console_capture_enabled());
4395 }
4396
4397 #[test]
4402 fn test_traceparent_returns_none_without_collector() {
4403 let page = Page::new(800, 600);
4404 assert!(page.traceparent().is_none());
4405 }
4406
4407 #[test]
4408 fn test_traceparent_returns_some_with_collector() {
4409 let collector = TraceCollector::new("test");
4410 let page = Page::new_with_tracing(800, 600, Some(collector));
4411 let tp = page.traceparent();
4412 assert!(tp.is_some());
4413 let traceparent = tp.unwrap();
4414 let parts: Vec<&str> = traceparent.split('-').collect();
4416 assert_eq!(parts.len(), 4);
4417 assert_eq!(parts[0], "00");
4418 assert_eq!(parts[1].len(), 32);
4419 assert_eq!(parts[2].len(), 16);
4420 assert_eq!(parts[3].len(), 2);
4421 }
4422
4423 #[test]
4424 fn test_start_span_returns_none_without_collector() {
4425 let mut page = Page::new(800, 600);
4426 let span = page.start_span("test-span", "test-category");
4427 assert!(span.is_none());
4428 }
4429
4430 #[test]
4431 fn test_start_span_returns_some_with_collector() {
4432 let collector = TraceCollector::new("test");
4433 let mut page = Page::new_with_tracing(800, 600, Some(collector));
4434 let span = page.start_span("test-span", "test-category");
4435 assert!(span.is_some());
4436 let span = span.unwrap();
4437 assert_eq!(span.name, "test-span");
4438 assert_eq!(span.category, "test-category");
4439 }
4440
4441 #[test]
4442 fn test_record_span_noop_without_collector() {
4443 let mut page = Page::new(800, 600);
4444 let mut temp = TraceCollector::new("temp");
4446 let mut span = temp.start_span("span", "cat");
4447 span.end();
4448 page.record_span(span);
4450 }
4451
4452 #[test]
4453 fn test_record_span_with_collector() {
4454 let collector = TraceCollector::new("test");
4455 let mut page = Page::new_with_tracing(800, 600, Some(collector));
4456 let mut span = page.start_span("recorded-span", "browser").unwrap();
4457 span.end();
4458 page.record_span(span);
4459
4460 let trace = page.export_chrome_trace().unwrap();
4461 assert!(!trace.trace_events.is_empty());
4462 assert_eq!(trace.trace_events[0].name, "recorded-span");
4463 }
4464
4465 #[test]
4466 fn test_record_trace_console_noop_without_collector() {
4467 let mut page = Page::new(800, 600);
4468 page.record_trace_console("test message");
4470 }
4471
4472 #[test]
4473 fn test_record_trace_console_with_collector() {
4474 let collector = TraceCollector::new("test");
4475 let mut page = Page::new_with_tracing(800, 600, Some(collector));
4476 page.record_trace_console("console message 1");
4477 page.record_trace_console("console message 2");
4478
4479 let trace = page.export_chrome_trace().unwrap();
4480 assert_eq!(trace.trace_events.len(), 2);
4481 }
4482
4483 #[test]
4484 fn test_export_chrome_trace_returns_none_without_collector() {
4485 let page = Page::new(800, 600);
4486 assert!(page.export_chrome_trace().is_none());
4487 }
4488
4489 #[test]
4490 fn test_export_chrome_trace_returns_some_with_collector() {
4491 let collector = TraceCollector::new("test");
4492 let page = Page::new_with_tracing(800, 600, Some(collector));
4493 let trace = page.export_chrome_trace();
4494 assert!(trace.is_some());
4495 }
4496
4497 #[test]
4498 fn test_export_trace_json_returns_ok_none_without_collector() {
4499 let page = Page::new(800, 600);
4500 let result = page.export_trace_json();
4501 assert!(result.is_ok());
4502 assert!(result.unwrap().is_none());
4503 }
4504
4505 #[test]
4506 fn test_export_trace_json_returns_ok_some_with_collector() {
4507 let collector = TraceCollector::new("test");
4508 let mut page = Page::new_with_tracing(800, 600, Some(collector));
4509 let mut span = page.start_span("json-span", "cat").unwrap();
4510 span.end();
4511 page.record_span(span);
4512
4513 let result = page.export_trace_json();
4514 assert!(result.is_ok());
4515 let json = result.unwrap();
4516 assert!(json.is_some());
4517 let json_str = json.unwrap();
4518 assert!(json_str.contains("traceEvents"));
4519 assert!(json_str.contains("json-span"));
4520 }
4521
4522 #[test]
4523 fn test_inject_trace_context_returns_ok() {
4524 let mut page = Page::new(800, 600);
4525 let result = page.inject_trace_context();
4526 assert!(result.is_ok());
4527 }
4528
4529 #[test]
4530 fn test_inject_trace_context_with_tracing_returns_ok() {
4531 let collector = TraceCollector::new("test");
4532 let mut page = Page::new_with_tracing(800, 600, Some(collector));
4533 let result = page.inject_trace_context();
4534 assert!(result.is_ok());
4535 }
4536
4537 #[test]
4542 fn test_start_coverage_enables_coverage() {
4543 let mut page = Page::new(800, 600);
4544 assert!(!page.is_coverage_enabled());
4545 let result = page.start_coverage();
4546 assert!(result.is_ok());
4547 assert!(page.is_coverage_enabled());
4548 }
4549
4550 #[test]
4551 fn test_start_coverage_with_config_enables_coverage() {
4552 let mut page = Page::new(800, 600);
4553 let config = CoverageConfig {
4554 call_count: false,
4555 detailed: true,
4556 allow_triggered_updates: true,
4557 };
4558 let result = page.start_coverage_with_config(config);
4559 assert!(result.is_ok());
4560 assert!(page.is_coverage_enabled());
4561 }
4562
4563 #[test]
4564 fn test_take_coverage_error_when_not_enabled() {
4565 let page = Page::new(800, 600);
4566 let result = page.take_coverage();
4567 assert!(result.is_err());
4568 let err = result.unwrap_err();
4569 let err_str = format!("{}", err);
4570 assert!(err_str.contains("Coverage not enabled"));
4571 }
4572
4573 #[test]
4574 fn test_take_coverage_returns_report_when_enabled() {
4575 let mut page = Page::new(800, 600);
4576 page.goto("http://test.com").unwrap();
4577 page.start_coverage().unwrap();
4578 let result = page.take_coverage();
4579 assert!(result.is_ok());
4580 let report = result.unwrap();
4581 assert_eq!(report.scripts.len(), 1);
4582 assert_eq!(report.scripts[0].url, "http://test.com");
4583 assert!(report.timestamp_ms > 0);
4584 }
4585
4586 #[test]
4587 fn test_stop_coverage_error_when_not_enabled() {
4588 let mut page = Page::new(800, 600);
4589 let result = page.stop_coverage();
4590 assert!(result.is_err());
4591 }
4592
4593 #[test]
4594 fn test_stop_coverage_returns_report_and_disables() {
4595 let mut page = Page::new(800, 600);
4596 page.start_coverage().unwrap();
4597 assert!(page.is_coverage_enabled());
4598
4599 let result = page.stop_coverage();
4600 assert!(result.is_ok());
4601 assert!(!page.is_coverage_enabled());
4602 }
4603
4604 #[test]
4605 fn test_add_mock_coverage_adds_function() {
4606 let mut page = Page::new(800, 600);
4607 page.start_coverage().unwrap();
4608
4609 page.add_mock_coverage(FunctionCoverage {
4610 function_name: "myFunction".to_string(),
4611 ranges: vec![CoverageRange {
4612 start_offset: 0,
4613 end_offset: 100,
4614 count: 5,
4615 }],
4616 is_block_coverage: true,
4617 });
4618
4619 let report = page.take_coverage().unwrap();
4620 assert_eq!(report.scripts[0].functions.len(), 1);
4621 assert_eq!(report.scripts[0].functions[0].function_name, "myFunction");
4622 }
4623
4624 #[test]
4625 fn test_clear_mock_coverage_clears_functions() {
4626 let mut page = Page::new(800, 600);
4627 page.start_coverage().unwrap();
4628
4629 page.add_mock_coverage(FunctionCoverage {
4630 function_name: "fn1".to_string(),
4631 ranges: vec![],
4632 is_block_coverage: false,
4633 });
4634 page.add_mock_coverage(FunctionCoverage {
4635 function_name: "fn2".to_string(),
4636 ranges: vec![],
4637 is_block_coverage: false,
4638 });
4639
4640 let report = page.take_coverage().unwrap();
4641 assert_eq!(report.scripts[0].functions.len(), 2);
4642
4643 page.clear_mock_coverage();
4644
4645 let report2 = page.take_coverage().unwrap();
4646 assert!(report2.scripts[0].functions.is_empty());
4647 }
4648
4649 #[test]
4654 fn test_browser_launch_returns_ok() {
4655 let config = BrowserConfig::default();
4656 let result = Browser::launch(config);
4657 assert!(result.is_ok());
4658 }
4659
4660 #[test]
4661 fn test_browser_config_accessor() {
4662 let config = BrowserConfig::default()
4663 .with_viewport(1920, 1080)
4664 .with_headless(false);
4665 let browser = Browser::launch(config).unwrap();
4666 let cfg = browser.config();
4667 assert_eq!(cfg.viewport_width, 1920);
4668 assert_eq!(cfg.viewport_height, 1080);
4669 assert!(!cfg.headless);
4670 }
4671
4672 #[test]
4673 fn test_browser_new_page_returns_page_with_config_dimensions() {
4674 let config = BrowserConfig::default().with_viewport(1280, 720);
4675 let browser = Browser::launch(config).unwrap();
4676 let page = browser.new_page().unwrap();
4677 assert_eq!(page.width, 1280);
4678 assert_eq!(page.height, 720);
4679 }
4680
4681 #[test]
4682 fn test_browser_new_page_with_tracing_enabled() {
4683 let tracing = RenacerTracingConfig::new("test-service");
4684 let config = BrowserConfig::default().with_tracing(tracing);
4685 let browser = Browser::launch(config).unwrap();
4686 let page = browser.new_page().unwrap();
4687 assert!(page.is_tracing_enabled());
4688 }
4689
4690 #[test]
4691 fn test_browser_new_page_with_tracing_disabled() {
4692 let tracing = RenacerTracingConfig::disabled();
4693 let config = BrowserConfig::default().with_tracing(tracing);
4694 let browser = Browser::launch(config).unwrap();
4695 let page = browser.new_page().unwrap();
4696 assert!(!page.is_tracing_enabled());
4697 }
4698
4699 #[test]
4700 fn test_browser_new_page_without_tracing_config() {
4701 let config = BrowserConfig::default();
4702 let browser = Browser::launch(config).unwrap();
4703 let page = browser.new_page().unwrap();
4704 assert!(!page.is_tracing_enabled());
4705 }
4706
4707 #[test]
4712 fn test_page_goto_returns_ok() {
4713 let mut page = Page::new(800, 600);
4714 let result = page.goto("http://example.com");
4715 assert!(result.is_ok());
4716 }
4717
4718 #[test]
4719 fn test_page_goto_updates_url() {
4720 let mut page = Page::new(800, 600);
4721 page.goto("http://new-url.com").unwrap();
4722 assert_eq!(page.current_url(), "http://new-url.com");
4723 assert_eq!(page.url, "http://new-url.com");
4724 }
4725
4726 #[test]
4727 fn test_page_wait_for_wasm_ready_returns_ok() {
4728 let mut page = Page::new(800, 600);
4729 let result = page.wait_for_wasm_ready();
4730 assert!(result.is_ok());
4731 }
4732
4733 #[test]
4734 fn test_page_wait_for_wasm_ready_sets_flag() {
4735 let mut page = Page::new(800, 600);
4736 assert!(!page.wasm_ready);
4737 page.wait_for_wasm_ready().unwrap();
4738 assert!(page.wasm_ready);
4739 assert!(page.is_wasm_ready());
4740 }
4741
4742 #[test]
4743 fn test_page_eval_wasm_returns_error() {
4744 let page = Page::new(800, 600);
4745 let result: Result<i32, _> = page.eval_wasm("expression");
4746 assert!(result.is_err());
4747 let err = result.unwrap_err();
4748 let err_str = format!("{}", err);
4749 assert!(err_str.contains("Browser feature not enabled"));
4750 }
4751
4752 #[test]
4753 fn test_page_touch_returns_ok() {
4754 let page = Page::new(800, 600);
4755 let touch = crate::Touch {
4756 x: 100.0,
4757 y: 100.0,
4758 action: crate::TouchAction::Tap,
4759 };
4760 let result = page.touch(touch);
4761 assert!(result.is_ok());
4762 }
4763
4764 #[test]
4765 fn test_page_screenshot_returns_empty_bytes() {
4766 let page = Page::new(800, 600);
4767 let result = page.screenshot();
4768 assert!(result.is_ok());
4769 let bytes = result.unwrap();
4770 assert!(bytes.is_empty());
4771 }
4772
4773 #[test]
4774 fn test_page_current_url_returns_url() {
4775 let page = Page::new(800, 600);
4776 assert_eq!(page.current_url(), "about:blank");
4777 }
4778
4779 #[test]
4780 fn test_page_is_wasm_ready_returns_bool() {
4781 let page = Page::new(800, 600);
4782 assert!(!page.is_wasm_ready());
4783 }
4784
4785 #[test]
4790 fn test_full_mock_page_workflow() {
4791 let tracing = RenacerTracingConfig::new("integration-test");
4793 let config = BrowserConfig::default()
4794 .with_viewport(1920, 1080)
4795 .with_tracing(tracing);
4796 let browser = Browser::launch(config).unwrap();
4797 let mut page = browser.new_page().unwrap();
4798
4799 page.goto("http://localhost:8080/app").unwrap();
4801 assert_eq!(page.current_url(), "http://localhost:8080/app");
4802
4803 page.wait_for_wasm_ready().unwrap();
4805 assert!(page.is_wasm_ready());
4806
4807 page.enable_console_capture().unwrap();
4809 assert!(page.is_console_capture_enabled());
4810
4811 page.add_console_message(BrowserConsoleMessage {
4813 level: BrowserConsoleLevel::Log,
4814 text: "App started".to_string(),
4815 timestamp: 1000,
4816 source: Some("main.js".to_string()),
4817 line: Some(10),
4818 });
4819
4820 page.start_coverage().unwrap();
4822 assert!(page.is_coverage_enabled());
4823
4824 page.add_mock_coverage(FunctionCoverage {
4826 function_name: "init".to_string(),
4827 ranges: vec![CoverageRange {
4828 start_offset: 0,
4829 end_offset: 100,
4830 count: 1,
4831 }],
4832 is_block_coverage: true,
4833 });
4834
4835 let mut span = page.start_span("test-operation", "test").unwrap();
4837 span.add_attribute("key", "value");
4838 span.end();
4839 page.record_span(span);
4840
4841 page.record_trace_console("Trace console message");
4843
4844 let trace_json = page.export_trace_json().unwrap();
4846 assert!(trace_json.is_some());
4847 let json_str = trace_json.unwrap();
4848 assert!(json_str.contains("traceEvents"));
4849
4850 let report = page.stop_coverage().unwrap();
4852 assert!(!page.is_coverage_enabled());
4853 assert!(!report.scripts.is_empty());
4854
4855 let messages = page.console_messages();
4857 assert_eq!(messages.len(), 1);
4858
4859 let screenshot = page.screenshot().unwrap();
4861 assert!(screenshot.is_empty());
4862 }
4863
4864 #[test]
4865 fn test_coverage_report_script_id() {
4866 let mut page = Page::new(800, 600);
4867 page.start_coverage().unwrap();
4868 let report = page.take_coverage().unwrap();
4869 assert_eq!(report.scripts[0].script_id, "mock-script-1");
4870 }
4871
4872 #[test]
4873 fn test_coverage_report_empty_functions() {
4874 let mut page = Page::new(800, 600);
4875 page.start_coverage().unwrap();
4876 let report = page.take_coverage().unwrap();
4877 assert!(report.scripts[0].functions.is_empty());
4878 }
4879
4880 #[test]
4881 fn test_multiple_console_message_sources() {
4882 let page = Page::new(800, 600);
4883
4884 page.add_console_message(BrowserConsoleMessage {
4885 level: BrowserConsoleLevel::Log,
4886 text: "from main".to_string(),
4887 timestamp: 1,
4888 source: Some("main.js".to_string()),
4889 line: Some(10),
4890 });
4891 page.add_console_message(BrowserConsoleMessage {
4892 level: BrowserConsoleLevel::Warning,
4893 text: "from utils".to_string(),
4894 timestamp: 2,
4895 source: Some("utils.js".to_string()),
4896 line: Some(20),
4897 });
4898 page.add_console_message(BrowserConsoleMessage {
4899 level: BrowserConsoleLevel::Error,
4900 text: "no source".to_string(),
4901 timestamp: 3,
4902 source: None,
4903 line: None,
4904 });
4905
4906 let messages = page.console_messages();
4907 assert_eq!(messages.len(), 3);
4908 assert_eq!(messages[0].source, Some("main.js".to_string()));
4909 assert_eq!(messages[1].source, Some("utils.js".to_string()));
4910 assert!(messages[2].source.is_none());
4911 }
4912
4913 #[test]
4914 fn test_wait_for_console_by_line() {
4915 let page = Page::new(800, 600);
4916 page.add_console_message(BrowserConsoleMessage {
4917 level: BrowserConsoleLevel::Error,
4918 text: "error on line 42".to_string(),
4919 timestamp: 0,
4920 source: Some("app.js".to_string()),
4921 line: Some(42),
4922 });
4923
4924 let result = page.wait_for_console(|m| m.line == Some(42), 1000);
4925 assert!(result.is_ok());
4926 assert_eq!(result.unwrap().line, Some(42));
4927 }
4928
4929 #[test]
4930 fn test_tracing_span_with_multiple_attributes() {
4931 let collector = TraceCollector::new("test");
4932 let mut page = Page::new_with_tracing(800, 600, Some(collector));
4933
4934 let mut span = page.start_span("complex-span", "http").unwrap();
4935 span.add_attribute("method", "GET");
4936 span.add_attribute("url", "http://api.example.com/data");
4937 span.add_attribute("status", "200");
4938 span.end();
4939 page.record_span(span);
4940
4941 let trace = page.export_chrome_trace().unwrap();
4942 assert_eq!(trace.trace_events.len(), 1);
4943 assert_eq!(trace.trace_events[0].name, "complex-span");
4944 }
4945
4946 #[test]
4947 fn test_coverage_with_multiple_ranges() {
4948 let mut page = Page::new(800, 600);
4949 page.start_coverage().unwrap();
4950
4951 page.add_mock_coverage(FunctionCoverage {
4952 function_name: "complex_function".to_string(),
4953 ranges: vec![
4954 CoverageRange {
4955 start_offset: 0,
4956 end_offset: 50,
4957 count: 10,
4958 },
4959 CoverageRange {
4960 start_offset: 50,
4961 end_offset: 100,
4962 count: 5,
4963 },
4964 CoverageRange {
4965 start_offset: 100,
4966 end_offset: 150,
4967 count: 0, },
4969 ],
4970 is_block_coverage: true,
4971 });
4972
4973 let report = page.take_coverage().unwrap();
4974 let func = &report.scripts[0].functions[0];
4975 assert_eq!(func.ranges.len(), 3);
4976 assert_eq!(func.ranges[2].count, 0);
4977 }
4978
4979 #[test]
4980 fn test_browser_debug_format() {
4981 let config = BrowserConfig::default();
4982 let browser = Browser::launch(config).unwrap();
4983 let debug_str = format!("{:?}", browser);
4984 assert!(debug_str.contains("Browser"));
4985 assert!(debug_str.contains("config"));
4986 }
4987
4988 #[test]
4989 fn test_page_debug_format_comprehensive() {
4990 let mut page = Page::new(1024, 768);
4991 page.goto("http://test.com").unwrap();
4992 page.enable_console_capture().unwrap();
4993 page.start_coverage().unwrap();
4994
4995 let debug_str = format!("{:?}", page);
4996 assert!(debug_str.contains("Page"));
4997 assert!(debug_str.contains("1024"));
4998 assert!(debug_str.contains("768"));
4999 }
5000
5001 #[test]
5002 fn test_start_coverage_then_start_again() {
5003 let mut page = Page::new(800, 600);
5004 page.start_coverage().unwrap();
5005 assert!(page.is_coverage_enabled());
5006
5007 let config = CoverageConfig {
5009 call_count: false,
5010 detailed: false,
5011 allow_triggered_updates: true,
5012 };
5013 page.start_coverage_with_config(config).unwrap();
5014 assert!(page.is_coverage_enabled());
5015 }
5016
5017 #[test]
5018 fn test_page_touch_all_variants() {
5019 let page = Page::new(800, 600);
5020
5021 assert!(page
5023 .touch(crate::Touch {
5024 x: 0.0,
5025 y: 0.0,
5026 action: crate::TouchAction::Tap
5027 })
5028 .is_ok());
5029
5030 assert!(page
5032 .touch(crate::Touch {
5033 x: 0.0,
5034 y: 0.0,
5035 action: crate::TouchAction::Swipe {
5036 end_x: 100.0,
5037 end_y: 100.0,
5038 duration_ms: 200
5039 }
5040 })
5041 .is_ok());
5042
5043 assert!(page
5045 .touch(crate::Touch {
5046 x: 50.0,
5047 y: 50.0,
5048 action: crate::TouchAction::Hold { duration_ms: 1000 }
5049 })
5050 .is_ok());
5051 }
5052
5053 #[test]
5054 fn test_console_capture_enabled_after_inject() {
5055 let mut page = Page::new(800, 600);
5056 assert!(!page.is_console_capture_enabled());
5057 page.inject_console_capture().unwrap();
5058 assert!(page.is_console_capture_enabled());
5059
5060 page.inject_console_capture().unwrap();
5062 assert!(page.is_console_capture_enabled());
5063 }
5064
5065 #[test]
5066 fn test_console_capture_enabled_after_enable() {
5067 let mut page = Page::new(800, 600);
5068 assert!(!page.is_console_capture_enabled());
5069 page.enable_console_capture().unwrap();
5070 assert!(page.is_console_capture_enabled());
5071
5072 page.enable_console_capture().unwrap();
5074 assert!(page.is_console_capture_enabled());
5075 }
5076
5077 #[test]
5078 fn test_coverage_timestamp_is_current_time() {
5079 let mut page = Page::new(800, 600);
5080 page.start_coverage().unwrap();
5081
5082 let before = std::time::SystemTime::now()
5083 .duration_since(std::time::UNIX_EPOCH)
5084 .unwrap()
5085 .as_millis() as u64;
5086
5087 let report = page.take_coverage().unwrap();
5088
5089 let after = std::time::SystemTime::now()
5090 .duration_since(std::time::UNIX_EPOCH)
5091 .unwrap()
5092 .as_millis() as u64;
5093
5094 assert!(report.timestamp_ms >= before);
5096 assert!(report.timestamp_ms <= after);
5097 }
5098 }
5099}