Skip to main content

jugar_probar/
browser.rs

1//! Browser control for headless testing.
2//!
3//! Per spec Section 6.1: Rust-native CDP (Chrome `DevTools` Protocol) implementation.
4//!
5//! This module provides real browser control via the Chrome `DevTools` Protocol.
6//! When compiled with the `browser` feature, it uses chromiumoxide for full CDP support.
7//! Without the feature, it provides a mock implementation for unit testing.
8//!
9//! ## Console Capture (Issue #8)
10//!
11//! Pages can capture console messages from JavaScript/WASM:
12//!
13//! ```ignore
14//! let mut page = browser.new_page().await?;
15//! page.enable_console_capture().await?;
16//! page.goto("http://localhost:8080").await?;
17//!
18//! // Wait for specific message
19//! let msg = page.wait_for_console(|m| m.text.contains("ready"), 5000).await?;
20//!
21//! // Or get all captured messages
22//! for msg in page.console_messages() {
23//!     println!("{}: {}", msg.level, msg.text);
24//! }
25//! ```
26
27use crate::renacer_integration::{
28    ChromeTrace, TraceCollector, TracingConfig as RenacerTracingConfig,
29};
30use crate::result::{ProbarError, ProbarResult};
31
32/// Browser console message level (from CDP)
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum BrowserConsoleLevel {
35    /// console.log
36    Log,
37    /// console.info
38    Info,
39    /// console.warn
40    Warning,
41    /// console.error
42    Error,
43    /// console.debug
44    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/// A captured browser console message (from CDP)
60#[derive(Debug, Clone)]
61pub struct BrowserConsoleMessage {
62    /// Message level (log, warn, error, etc.)
63    pub level: BrowserConsoleLevel,
64    /// Message text
65    pub text: String,
66    /// Timestamp in milliseconds since epoch
67    pub timestamp: u64,
68    /// Source URL (if available)
69    pub source: Option<String>,
70    /// Line number (if available)
71    pub line: Option<u32>,
72}
73
74/// Browser configuration
75#[derive(Debug, Clone)]
76pub struct BrowserConfig {
77    /// Run in headless mode
78    pub headless: bool,
79    /// Viewport width
80    pub viewport_width: u32,
81    /// Viewport height
82    pub viewport_height: u32,
83    /// Path to chromium binary (None = auto-detect)
84    pub chromium_path: Option<String>,
85    /// Remote debugging port (0 = auto-assign)
86    pub debug_port: u16,
87    /// User agent string
88    pub user_agent: Option<String>,
89    /// Enable `DevTools`
90    pub devtools: bool,
91    /// Sandbox mode (disable for containers)
92    pub sandbox: bool,
93    /// Renacer tracing configuration
94    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    /// Set viewport dimensions
115    #[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    /// Set headless mode
123    #[must_use]
124    pub const fn with_headless(mut self, headless: bool) -> Self {
125        self.headless = headless;
126        self
127    }
128
129    /// Set chromium path
130    #[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    /// Set user agent
137    #[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    /// Disable sandbox (for containers/CI)
144    #[must_use]
145    pub const fn with_no_sandbox(mut self) -> Self {
146        self.sandbox = false;
147        self
148    }
149
150    /// Enable renacer tracing
151    #[must_use]
152    pub fn with_tracing(mut self, config: RenacerTracingConfig) -> Self {
153        self.tracing_config = Some(config);
154        self
155    }
156
157    /// Check if tracing is enabled
158    #[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// ============================================================================
165// Real CDP Implementation (when `browser` feature is enabled)
166// ============================================================================
167
168#[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    /// Browser instance with real CDP connection
199    #[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        /// Launch a new browser instance with real CDP
208        ///
209        /// # Errors
210        ///
211        /// Returns error if browser cannot be launched
212        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            // Spawn handler task
240            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        /// Create a new page
256        ///
257        /// # Errors
258        ///
259        /// Returns error if page cannot be created
260        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            // Viewport is configured at browser launch time via window_size
271            // Additional viewport emulation can be done via CDP Emulation domain if needed
272
273            // Initialize trace collector if tracing is enabled
274            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        /// Get the browser configuration
296        #[must_use]
297        pub const fn config(&self) -> &BrowserConfig {
298            &self.config
299        }
300
301        /// Check if the browser handler task is still running
302        #[must_use]
303        pub fn is_handler_running(&self) -> bool {
304            !self.handle.is_finished()
305        }
306
307        /// Close the browser
308        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    /// A browser page with real CDP connection
321    #[derive(Debug)]
322    pub struct Page {
323        /// Page width
324        pub width: u32,
325        /// Page height
326        pub height: u32,
327        /// Current URL
328        pub url: String,
329        /// Whether WASM is ready
330        pub wasm_ready: bool,
331        /// CDP page handle
332        inner: Option<Arc<Mutex<CdpPage>>>,
333        /// Captured console messages
334        console_messages: Arc<Mutex<Vec<BrowserConsoleMessage>>>,
335        /// Whether console capture is enabled
336        console_capture_enabled: bool,
337        /// Renacer trace collector
338        trace_collector: Option<TraceCollector>,
339        /// Whether coverage collection is enabled
340        coverage_enabled: bool,
341    }
342
343    impl Page {
344        /// Create a new mock page (for testing without browser)
345        #[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        /// Navigate to a URL
361        ///
362        /// # Errors
363        ///
364        /// Returns error if navigation fails
365        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        /// Wait for WASM to be ready
380        ///
381        /// # Errors
382        ///
383        /// Returns error if WASM fails to initialize
384        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                // Wait for WASM module to signal readiness
388                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        /// Evaluate JavaScript/WASM expression
404        ///
405        /// # Errors
406        ///
407        /// Returns error if evaluation fails
408        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        /// Simulate touch input
431        ///
432        /// # Errors
433        ///
434        /// Returns error if touch simulation fails
435        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                        // Touch start
442                        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                        // Touch end
463                        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                        // Simulate swipe with multiple move events
483                        let steps = 10;
484                        let step_delay = duration_ms / steps;
485
486                        // Touch start
487                        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                        // Move events
508                        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                        // Touch end
540                        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                        // Touch start
556                        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                        // Wait
577                        tokio::time::sleep(tokio::time::Duration::from_millis(u64::from(
578                            duration_ms,
579                        )))
580                        .await;
581
582                        // Touch end
583                        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        /// Take a screenshot
603        ///
604        /// # Errors
605        ///
606        /// Returns error if screenshot fails
607        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                // Return empty PNG for mock
629                Ok(vec![])
630            }
631        }
632
633        /// Get current URL
634        #[must_use]
635        pub fn current_url(&self) -> &str {
636            &self.url
637        }
638
639        /// Check if WASM is ready
640        #[must_use]
641        pub const fn is_wasm_ready(&self) -> bool {
642            self.wasm_ready
643        }
644
645        // ====================================================================
646        // CDP Access Methods (Issue #18)
647        // ====================================================================
648
649        /// Get access to the underlying CDP page for advanced operations
650        ///
651        /// This allows using CDP-specific methods from capabilities, validators,
652        /// and emulation modules that require direct chromiumoxide::Page access.
653        ///
654        /// # Returns
655        ///
656        /// Returns `Some` with the locked CDP page if a real browser is connected,
657        /// or `None` if this is a mock page.
658        ///
659        /// # Example
660        ///
661        /// ```ignore
662        /// use probar::{Browser, BrowserConfig};
663        /// use probar::capabilities::WasmThreadCapabilities;
664        ///
665        /// let browser = Browser::launch(BrowserConfig::default()).await?;
666        /// let page = browser.new_page().await?;
667        ///
668        /// if let Some(cdp) = page.cdp_page().await {
669        ///     let caps = WasmThreadCapabilities::detect(&*cdp).await?;
670        /// }
671        /// ```
672        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        /// Click an element by CSS selector
681        ///
682        /// # Errors
683        ///
684        /// Returns error if element not found or click fails
685        pub async fn click(&self, selector: &str) -> ProbarResult<()> {
686            if let Some(ref inner) = self.inner {
687                let page = inner.lock().await;
688                // Find element and click it
689                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                // Mock mode - no-op
705                Ok(())
706            }
707        }
708
709        /// Evaluate JavaScript expression and return the result
710        ///
711        /// # Errors
712        ///
713        /// Returns error if evaluation fails
714        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        // ====================================================================
733        // Console Capture Methods (Issue #8)
734        // ====================================================================
735
736        /// Enable console message capture via JavaScript injection
737        ///
738        /// # Errors
739        ///
740        /// Returns error if injection fails
741        pub async fn enable_console_capture(&mut self) -> ProbarResult<()> {
742            // Use the inject method which works without CDP Runtime domain
743            self.inject_console_capture().await
744        }
745
746        /// Check if console capture is enabled
747        #[must_use]
748        pub const fn is_console_capture_enabled(&self) -> bool {
749            self.console_capture_enabled
750        }
751
752        /// Get all captured console messages
753        pub async fn console_messages(&self) -> Vec<BrowserConsoleMessage> {
754            self.console_messages.lock().await.clone()
755        }
756
757        /// Clear captured console messages
758        pub async fn clear_console(&self) {
759            self.console_messages.lock().await.clear();
760        }
761
762        /// Add a console message (used internally by event handler)
763        pub async fn add_console_message(&self, msg: BrowserConsoleMessage) {
764            self.console_messages.lock().await.push(msg);
765        }
766
767        /// Wait for a console message matching the predicate
768        ///
769        /// # Errors
770        ///
771        /// Returns error if timeout is reached before matching message
772        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                // Check existing messages
785                {
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                // Check timeout
793                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                // Poll interval
802                tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
803
804                // If we have a page connection, poll for new console messages
805                if let Some(ref inner) = self.inner {
806                    let page = inner.lock().await;
807
808                    // Execute JS to capture any pending console output
809                    // This triggers console events if there are pending messages
810                    let _ = page
811                        .evaluate("(function() { return window.__probar_console_check || 0; })()")
812                        .await;
813                }
814            }
815        }
816
817        /// Capture console messages by injecting a listener
818        ///
819        /// This injects JavaScript to intercept console calls and store them
820        /// for later retrieval.
821        ///
822        /// # Errors
823        ///
824        /// Returns error if injection fails
825        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                // Inject console interceptor
830                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        /// Fetch console messages from injected capture
863        ///
864        /// # Errors
865        ///
866        /// Returns error if fetch fails
867        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                // Store in internal buffer too
909                {
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        // ====================================================================
928        // Renacer Tracing Methods (Issue #9)
929        // ====================================================================
930
931        /// Check if tracing is enabled for this page
932        #[must_use]
933        pub fn is_tracing_enabled(&self) -> bool {
934            self.trace_collector.is_some()
935        }
936
937        /// Get the traceparent header for W3C Trace Context propagation
938        #[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        /// Start a new trace span
946        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        /// Record a completed span
957        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        /// Record a console message in the trace
964        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        /// Export trace in Chrome trace format
971        #[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        /// Export trace as JSON string
977        ///
978        /// # Errors
979        ///
980        /// Returns error if serialization fails
981        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        /// Inject trace context into the page (for WASM correlation)
997        ///
998        /// This sets `window.__probar_trace_context` with the trace context.
999        ///
1000        /// # Errors
1001        ///
1002        /// Returns error if injection fails
1003        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        // ====================================================================
1022        // CDP Profiler Coverage Methods (Issue #10)
1023        // ====================================================================
1024
1025        /// Start collecting code coverage via CDP Profiler
1026        ///
1027        /// # Errors
1028        ///
1029        /// Returns error if CDP command fails
1030        pub async fn start_coverage(&mut self) -> ProbarResult<()> {
1031            self.start_coverage_with_config(CoverageConfig::default())
1032                .await
1033        }
1034
1035        /// Start collecting code coverage with custom config
1036        ///
1037        /// # Errors
1038        ///
1039        /// Returns error if CDP command fails
1040        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                // Start coverage using JavaScript instrumentation
1048                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        /// Take coverage snapshot without stopping collection
1113        ///
1114        /// # Errors
1115        ///
1116        /// Returns error if CDP command fails
1117        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        /// Stop coverage collection and return final report
1216        ///
1217        /// # Errors
1218        ///
1219        /// Returns error if CDP command fails
1220        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        /// Check if coverage collection is enabled
1237        #[must_use]
1238        pub const fn is_coverage_enabled(&self) -> bool {
1239            self.coverage_enabled
1240        }
1241    }
1242}
1243
1244// ============================================================================
1245// Mock Implementation (when `browser` feature is NOT enabled)
1246// ============================================================================
1247
1248#[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    /// Browser instance for testing (mock when `browser` feature disabled)
1260    #[derive(Debug)]
1261    pub struct Browser {
1262        config: BrowserConfig,
1263    }
1264
1265    impl Browser {
1266        /// Launch a new browser instance (mock)
1267        ///
1268        /// # Errors
1269        ///
1270        /// Returns error if browser cannot be launched
1271        pub fn launch(config: BrowserConfig) -> ProbarResult<Self> {
1272            Ok(Self { config })
1273        }
1274
1275        /// Create a new page
1276        ///
1277        /// # Errors
1278        ///
1279        /// Returns error if page cannot be created
1280        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        /// Get the browser configuration
1297        #[must_use]
1298        pub const fn config(&self) -> &BrowserConfig {
1299            &self.config
1300        }
1301    }
1302
1303    /// A browser page for testing (mock when `browser` feature disabled)
1304    #[derive(Debug)]
1305    pub struct Page {
1306        /// Page width
1307        pub width: u32,
1308        /// Page height
1309        pub height: u32,
1310        /// Current URL
1311        pub url: String,
1312        /// Whether WASM is ready
1313        pub wasm_ready: bool,
1314        /// Captured console messages (mock)
1315        console_messages: Arc<Mutex<Vec<BrowserConsoleMessage>>>,
1316        /// Whether console capture is enabled
1317        console_capture_enabled: bool,
1318        /// Renacer trace collector
1319        trace_collector: Option<TraceCollector>,
1320        /// Whether coverage is enabled (Issue #10)
1321        coverage_enabled: bool,
1322        /// Collected coverage data (mock)
1323        coverage_data: Arc<Mutex<Vec<crate::cdp_coverage::FunctionCoverage>>>,
1324    }
1325
1326    impl Page {
1327        /// Create a new page
1328        #[must_use]
1329        pub fn new(width: u32, height: u32) -> Self {
1330            Self::new_with_tracing(width, height, None)
1331        }
1332
1333        /// Create a new page with optional tracing
1334        #[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        /// Navigate to a URL
1354        ///
1355        /// # Errors
1356        ///
1357        /// Returns error if navigation fails
1358        pub fn goto(&mut self, url: &str) -> ProbarResult<()> {
1359            self.url = url.to_string();
1360            Ok(())
1361        }
1362
1363        /// Wait for WASM to be ready
1364        ///
1365        /// # Errors
1366        ///
1367        /// Returns error if WASM fails to initialize
1368        pub fn wait_for_wasm_ready(&mut self) -> ProbarResult<()> {
1369            self.wasm_ready = true;
1370            Ok(())
1371        }
1372
1373        /// Evaluate WASM expression (mock returns error)
1374        ///
1375        /// # Errors
1376        ///
1377        /// Always returns error in mock mode
1378        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        /// Simulate touch input (mock does nothing)
1387        ///
1388        /// # Errors
1389        ///
1390        /// Returns Ok in mock mode
1391        pub fn touch(&self, _touch: crate::Touch) -> ProbarResult<()> {
1392            Ok(())
1393        }
1394
1395        /// Take a screenshot (mock returns empty)
1396        ///
1397        /// # Errors
1398        ///
1399        /// Returns empty bytes in mock mode
1400        pub fn screenshot(&self) -> ProbarResult<Vec<u8>> {
1401            Ok(vec![])
1402        }
1403
1404        /// Get current URL
1405        #[must_use]
1406        pub fn current_url(&self) -> &str {
1407            &self.url
1408        }
1409
1410        /// Check if WASM is ready
1411        #[must_use]
1412        pub const fn is_wasm_ready(&self) -> bool {
1413            self.wasm_ready
1414        }
1415
1416        // ====================================================================
1417        // Console Capture Methods (Issue #8) - Mock Implementation
1418        // ====================================================================
1419
1420        /// Enable console message capture (mock - always succeeds)
1421        ///
1422        /// # Errors
1423        ///
1424        /// Always returns Ok in mock mode
1425        pub fn enable_console_capture(&mut self) -> ProbarResult<()> {
1426            self.console_capture_enabled = true;
1427            Ok(())
1428        }
1429
1430        /// Check if console capture is enabled
1431        #[must_use]
1432        pub const fn is_console_capture_enabled(&self) -> bool {
1433            self.console_capture_enabled
1434        }
1435
1436        /// Get all captured console messages (mock returns stored messages)
1437        #[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        /// Clear captured console messages
1446        pub fn clear_console(&self) {
1447            if let Ok(mut guard) = self.console_messages.lock() {
1448                guard.clear();
1449            }
1450        }
1451
1452        /// Add a console message (mock - for testing)
1453        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        /// Wait for console message (mock - returns first matching or error)
1460        ///
1461        /// # Errors
1462        ///
1463        /// Returns error if no matching message found
1464        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        /// Inject console capture (mock - always succeeds)
1488        ///
1489        /// # Errors
1490        ///
1491        /// Always returns Ok in mock mode
1492        pub fn inject_console_capture(&mut self) -> ProbarResult<()> {
1493            self.console_capture_enabled = true;
1494            Ok(())
1495        }
1496
1497        /// Fetch console messages (mock - returns stored messages)
1498        ///
1499        /// # Errors
1500        ///
1501        /// Always returns Ok with stored messages
1502        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        // ====================================================================
1511        // Renacer Tracing Methods (Issue #9) - Mock Implementation
1512        // ====================================================================
1513
1514        /// Check if tracing is enabled for this page
1515        #[must_use]
1516        pub fn is_tracing_enabled(&self) -> bool {
1517            self.trace_collector.is_some()
1518        }
1519
1520        /// Get the traceparent header for W3C Trace Context propagation
1521        #[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        /// Start a new trace span
1529        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        /// Record a completed span
1540        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        /// Record a console message in the trace
1547        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        /// Export trace in Chrome trace format
1554        #[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        /// Export trace as JSON string
1560        ///
1561        /// # Errors
1562        ///
1563        /// Returns error if serialization fails
1564        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        /// Inject trace context into the page (mock - does nothing)
1580        ///
1581        /// # Errors
1582        ///
1583        /// Always returns Ok in mock mode
1584        pub fn inject_trace_context(&mut self) -> ProbarResult<()> {
1585            Ok(())
1586        }
1587
1588        // ====================================================================
1589        // CDP Coverage Methods (Issue #10) - Mock Implementation
1590        // ====================================================================
1591
1592        /// Start code coverage collection (mock)
1593        ///
1594        /// # Errors
1595        ///
1596        /// Always returns Ok in mock mode
1597        pub fn start_coverage(&mut self) -> ProbarResult<()> {
1598            self.start_coverage_with_config(CoverageConfig::default())
1599        }
1600
1601        /// Start coverage with custom configuration (mock)
1602        ///
1603        /// # Errors
1604        ///
1605        /// Always returns Ok in mock mode
1606        pub fn start_coverage_with_config(&mut self, _config: CoverageConfig) -> ProbarResult<()> {
1607            self.coverage_enabled = true;
1608            Ok(())
1609        }
1610
1611        /// Take a coverage snapshot (mock - returns simulated data)
1612        ///
1613        /// # Errors
1614        ///
1615        /// Returns error if coverage not enabled
1616        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        /// Stop coverage and return final report (mock)
1643        ///
1644        /// # Errors
1645        ///
1646        /// Returns error if coverage not enabled
1647        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        /// Check if coverage is enabled
1654        #[must_use]
1655        pub const fn is_coverage_enabled(&self) -> bool {
1656            self.coverage_enabled
1657        }
1658
1659        /// Add mock function coverage data (for testing)
1660        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        /// Clear mock coverage data
1667        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// Re-export based on feature
1676#[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()); // Mock returns empty
1833        }
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    // =========================================================================
1844    // Hâ‚€ EXTREME TDD: Browser Tests (Feature F P0)
1845    // =========================================================================
1846
1847    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    // =========================================================================
2227    // Console Capture Tests (Issue #8)
2228    // =========================================================================
2229
2230    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    // =========================================================================
2451    // Renacer Tracing Integration Tests (Issue #9)
2452    // =========================================================================
2453
2454    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            // Mock implementation just returns Ok
2557            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    // =========================================================================
2582    // CDP Coverage Integration Tests (Issue #10)
2583    // =========================================================================
2584
2585    #[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            // First session
2692            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            // Second session
2701            page.start_coverage().unwrap();
2702            let report = page.take_coverage().unwrap();
2703            // Coverage data persists (mock behavior)
2704            assert_eq!(report.scripts[0].functions.len(), 1);
2705        }
2706    }
2707
2708    // =========================================================================
2709    // Additional Comprehensive Coverage Tests
2710    // =========================================================================
2711
2712    mod browser_console_level_comprehensive {
2713        use super::*;
2714
2715        #[test]
2716        fn test_all_levels_display() {
2717            // Test Display for all variants
2718            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            // Both should still be usable (Copy trait)
2736            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            // Each level should only equal itself
2750            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            // Verify all fields match
2827            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            // Without tracing
2888            let config = BrowserConfig::default();
2889            assert!(!config.is_tracing_enabled());
2890
2891            // With enabled tracing
2892            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            // With disabled tracing
2897            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            // Test that Into<String> works with String
2938            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            // HTTP
3022            page.goto("http://example.com").unwrap();
3023            assert_eq!(page.current_url(), "http://example.com");
3024
3025            // HTTPS
3026            page.goto("https://secure.example.com").unwrap();
3027            assert_eq!(page.current_url(), "https://secure.example.com");
3028
3029            // Localhost
3030            page.goto("http://localhost:8080/path").unwrap();
3031            assert_eq!(page.current_url(), "http://localhost:8080/path");
3032
3033            // File URL
3034            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            // Tap
3063            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            // Swipe
3071            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            // Hold
3083            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            // Enable again should still work
3119            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            // Find by multiple criteria
3180            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            // Original should still have messages
3212            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            // W3C traceparent format: version-traceid-spanid-flags
3252            let parts: Vec<&str> = tp.split('-').collect();
3253            assert_eq!(parts.len(), 4);
3254            assert_eq!(parts[0], "00"); // version
3255            assert_eq!(parts[1].len(), 32); // trace-id is 32 hex chars
3256            assert_eq!(parts[2].len(), 16); // span-id is 16 hex chars
3257        }
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            // Create a span from a collector
3281            let mut collector = TraceCollector::new("temp");
3282            let mut span = collector.start_span("test", "cat");
3283            span.end();
3284            // Recording on page without tracing is a no-op
3285            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            // Should be a no-op
3292            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            // Mock just returns Ok
3338            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            // Start
3359            page.start_coverage().unwrap();
3360            assert!(page.is_coverage_enabled());
3361
3362            // Add data
3363            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            // Take (doesn't stop)
3374            let report1 = page.take_coverage().unwrap();
3375            assert!(page.is_coverage_enabled());
3376            assert_eq!(report1.scripts[0].functions.len(), 1);
3377
3378            // Stop
3379            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            // With detailed config
3389            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            // Timestamp should be non-zero (current time)
3415            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            // Navigate
3495            page.goto("http://localhost:8080").unwrap();
3496            assert_eq!(page.current_url(), "http://localhost:8080");
3497
3498            // Wait for WASM
3499            page.wait_for_wasm_ready().unwrap();
3500            assert!(page.is_wasm_ready());
3501
3502            // Console capture
3503            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            // Coverage
3513            page.start_coverage().unwrap();
3514            let report = page.stop_coverage().unwrap();
3515            assert!(!report.scripts.is_empty());
3516
3517            // Screenshot
3518            let screenshot = page.screenshot().unwrap();
3519            assert!(screenshot.is_empty()); // Mock returns empty
3520        }
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            // Start a span
3534            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            // Export
3540            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            // Simulate console output
3551            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            // Simulate coverage
3560            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            // Check console
3571            let messages = page.console_messages();
3572            assert_eq!(messages.len(), 1);
3573
3574            // Check coverage
3575            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            // Touch at (0,0)
3625            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            // Touch at max coords
3633            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            // Negative coords
3641            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            // Predicate that never matches
3662            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            // Predicate that always matches
3678            let result = page.wait_for_console(|_| true, 100);
3679            assert!(result.is_ok());
3680        }
3681    }
3682
3683    // =========================================================================
3684    // Additional Mock Coverage Tests
3685    // =========================================================================
3686
3687    #[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            // Explicitly test the None path for trace_collector
3695            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            // Create a temporary collector to get a span
3714            let mut temp_collector = TraceCollector::new("temp");
3715            let mut span = temp_collector.start_span("test", "cat");
3716            span.end();
3717            // This should be a no-op
3718            page.record_span(span);
3719            // No assertion needed - just ensure no panic
3720        }
3721
3722        #[test]
3723        fn test_page_record_trace_console_noop_without_tracing() {
3724            let mut page = Page::new(800, 600);
3725            // Should be a no-op
3726            page.record_trace_console("test message");
3727            // No assertion needed - just ensure no panic
3728        }
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            // First session
3862            page.start_coverage().unwrap();
3863            assert!(page.is_coverage_enabled());
3864            page.stop_coverage().unwrap();
3865            assert!(!page.is_coverage_enabled());
3866
3867            // Second session
3868            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            // Record console messages
3894            page.record_trace_console("Console 1");
3895            page.record_trace_console("Console 2");
3896
3897            // Record spans
3898            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            // Should have 2 console + 1 span = 3 events
3904            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            // Don't call goto - use default about:blank
3911            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            // Tap at various positions
3945            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            // Swipe with various durations
3957            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            // Hold with various durations
3971            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            // Should not panic
3994            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            // Should not panic
4003            page.clear_mock_coverage();
4004            let report = page.take_coverage().unwrap();
4005            assert!(report.scripts[0].functions.is_empty());
4006        }
4007    }
4008
4009    // =========================================================================
4010    // Error Type and Display Tests
4011    // =========================================================================
4012
4013    mod error_display_tests {
4014        use super::*;
4015
4016        #[test]
4017        fn test_browser_console_level_display_matches_expected() {
4018            // Verify exact string output
4019            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    // =========================================================================
4088    // Property-based-like comprehensive tests
4089    // =========================================================================
4090
4091    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            // Multiple calls should all return empty
4151            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    // =========================================================================
4204    // BrowserConfig Tracing Tests
4205    // =========================================================================
4206
4207    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    // =========================================================================
4246    // Additional Mock Coverage Tests for 99%+ Coverage
4247    // =========================================================================
4248
4249    #[cfg(not(feature = "browser"))]
4250    mod mock_coverage_99_percent {
4251        use super::*;
4252        use crate::cdp_coverage::{CoverageConfig, CoverageRange, FunctionCoverage};
4253
4254        // =====================================================================
4255        // Page::new() and Page::new_with_tracing() edge cases
4256        // =====================================================================
4257
4258        #[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        // =====================================================================
4293        // Console message methods - comprehensive edge cases
4294        // =====================================================================
4295
4296        #[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        // =====================================================================
4398        // Tracing methods - comprehensive edge cases
4399        // =====================================================================
4400
4401        #[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            // Verify W3C format: version-trace_id-parent_id-flags
4415            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            // Create a span from a temporary collector
4445            let mut temp = TraceCollector::new("temp");
4446            let mut span = temp.start_span("span", "cat");
4447            span.end();
4448            // This should be a no-op, not panic
4449            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            // Should be a no-op, not panic
4469            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        // =====================================================================
4538        // Coverage methods - comprehensive edge cases
4539        // =====================================================================
4540
4541        #[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        // =====================================================================
4650        // Browser methods
4651        // =====================================================================
4652
4653        #[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        // =====================================================================
4708        // Page basic methods
4709        // =====================================================================
4710
4711        #[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        // =====================================================================
4786        // Integration scenarios
4787        // =====================================================================
4788
4789        #[test]
4790        fn test_full_mock_page_workflow() {
4791            // Create browser with tracing
4792            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            // Navigate
4800            page.goto("http://localhost:8080/app").unwrap();
4801            assert_eq!(page.current_url(), "http://localhost:8080/app");
4802
4803            // Wait for WASM
4804            page.wait_for_wasm_ready().unwrap();
4805            assert!(page.is_wasm_ready());
4806
4807            // Enable console capture
4808            page.enable_console_capture().unwrap();
4809            assert!(page.is_console_capture_enabled());
4810
4811            // Add console messages
4812            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            // Start coverage
4821            page.start_coverage().unwrap();
4822            assert!(page.is_coverage_enabled());
4823
4824            // Add coverage data
4825            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            // Start a trace span
4836            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            // Record console in trace
4842            page.record_trace_console("Trace console message");
4843
4844            // Get trace JSON
4845            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            // Get coverage report
4851            let report = page.stop_coverage().unwrap();
4852            assert!(!page.is_coverage_enabled());
4853            assert!(!report.scripts.is_empty());
4854
4855            // Verify console messages
4856            let messages = page.console_messages();
4857            assert_eq!(messages.len(), 1);
4858
4859            // Take screenshot
4860            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, // Uncovered branch
4968                    },
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            // Starting again should still work (override)
5008            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            // Tap
5022            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            // Swipe
5031            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            // Hold
5044            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            // Inject again should still be enabled
5061            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            // Enable again should still be enabled
5073            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            // Timestamp should be between before and after
5095            assert!(report.timestamp_ms >= before);
5096            assert!(report.timestamp_ms <= after);
5097        }
5098    }
5099}