Skip to main content

jugar_probar/
strict.rs

1//! WASM Strict Mode and Console Capture (PROBAR-SPEC-010)
2//!
3//! Strict enforcement of WASM testing quality standards.
4//!
5//! ## Toyota Way Application:
6//! - **Andon**: Stop the line on first console.error
7//! - **Jidoka**: Built-in quality through strict assertions
8//! - **Mieruka**: Visualization of all captured console output
9//!
10//! ## References:
11//! - [14] Tassey (2002) Cost of escaped defects
12//! - [15] Memon et al. (2017) Shift-left testing
13
14use std::fmt;
15
16/// Console message severity levels
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum ConsoleSeverity {
19    /// console.log, console.debug
20    Log,
21    /// console.info
22    Info,
23    /// console.warn
24    Warn,
25    /// console.error
26    Error,
27}
28
29impl fmt::Display for ConsoleSeverity {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::Log => write!(f, "log"),
33            Self::Info => write!(f, "info"),
34            Self::Warn => write!(f, "warn"),
35            Self::Error => write!(f, "error"),
36        }
37    }
38}
39
40impl ConsoleSeverity {
41    /// Parse severity from string
42    #[must_use]
43    pub fn from_str(s: &str) -> Self {
44        match s.to_lowercase().as_str() {
45            "error" => Self::Error,
46            "warn" | "warning" => Self::Warn,
47            "info" => Self::Info,
48            _ => Self::Log,
49        }
50    }
51}
52
53/// Captured console message with full context
54#[derive(Debug, Clone)]
55pub struct ConsoleMessage {
56    /// Severity level
57    pub severity: ConsoleSeverity,
58    /// Message text
59    pub text: String,
60    /// Source file (if available)
61    pub source: String,
62    /// Line number (if available)
63    pub line: u32,
64    /// Column number (if available)
65    pub column: u32,
66    /// Timestamp (relative to page load)
67    pub timestamp: f64,
68    /// Stack trace (if available)
69    pub stack_trace: Option<String>,
70}
71
72impl ConsoleMessage {
73    /// Create a new console message
74    #[must_use]
75    pub fn new(severity: ConsoleSeverity, text: impl Into<String>) -> Self {
76        Self {
77            severity,
78            text: text.into(),
79            source: String::new(),
80            line: 0,
81            column: 0,
82            timestamp: 0.0,
83            stack_trace: None,
84        }
85    }
86
87    /// Set source location
88    #[must_use]
89    pub fn with_source(mut self, source: impl Into<String>, line: u32, column: u32) -> Self {
90        self.source = source.into();
91        self.line = line;
92        self.column = column;
93        self
94    }
95
96    /// Set timestamp
97    #[must_use]
98    pub fn with_timestamp(mut self, timestamp: f64) -> Self {
99        self.timestamp = timestamp;
100        self
101    }
102
103    /// Set stack trace
104    #[must_use]
105    pub fn with_stack_trace(mut self, stack_trace: impl Into<String>) -> Self {
106        self.stack_trace = Some(stack_trace.into());
107        self
108    }
109
110    /// Check if message contains substring (case-insensitive)
111    #[must_use]
112    pub fn contains(&self, substring: &str) -> bool {
113        self.text.to_lowercase().contains(&substring.to_lowercase())
114    }
115}
116
117impl fmt::Display for ConsoleMessage {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(f, "[{}] {}", self.severity, self.text)?;
120        if !self.source.is_empty() {
121            write!(f, " ({}:{}:{})", self.source, self.line, self.column)?;
122        }
123        Ok(())
124    }
125}
126
127/// Strict mode configuration for WASM testing
128#[derive(Debug, Clone)]
129pub struct WasmStrictMode {
130    /// Require actual code execution, not just DOM presence
131    pub require_code_execution: bool,
132
133    /// Fail test on any console.error
134    pub fail_on_console_error: bool,
135
136    /// Verify Web Components registration
137    pub verify_custom_elements: bool,
138
139    /// Test both threaded and sequential modes
140    pub test_both_threading_modes: bool,
141
142    /// Simulate low memory conditions
143    pub simulate_low_memory: bool,
144
145    /// Verify COOP/COEP headers
146    pub verify_coop_coep_headers: bool,
147
148    /// Validate deterministic replay hashes
149    pub validate_replay_hash: bool,
150
151    /// Maximum allowed console warnings
152    pub max_console_warnings: u32,
153
154    /// Require service worker cache hits
155    pub require_cache_hits: bool,
156
157    /// Maximum WASM binary size in bytes
158    pub max_wasm_size: Option<usize>,
159
160    /// Require panic-free paths (no unwrap in WASM)
161    pub require_panic_free: bool,
162}
163
164impl Default for WasmStrictMode {
165    fn default() -> Self {
166        Self {
167            require_code_execution: true,
168            fail_on_console_error: true,
169            verify_custom_elements: true,
170            test_both_threading_modes: false,
171            simulate_low_memory: false,
172            verify_coop_coep_headers: true,
173            validate_replay_hash: true,
174            max_console_warnings: 0,
175            require_cache_hits: false,
176            max_wasm_size: None,
177            require_panic_free: true,
178        }
179    }
180}
181
182impl WasmStrictMode {
183    /// Production-grade strictness (all checks enabled)
184    #[must_use]
185    pub fn production() -> Self {
186        Self {
187            require_code_execution: true,
188            fail_on_console_error: true,
189            verify_custom_elements: true,
190            test_both_threading_modes: true,
191            simulate_low_memory: true,
192            verify_coop_coep_headers: true,
193            validate_replay_hash: true,
194            max_console_warnings: 0,
195            require_cache_hits: true,
196            max_wasm_size: Some(5_000_000), // 5MB
197            require_panic_free: true,
198        }
199    }
200
201    /// Development-friendly (more permissive)
202    #[must_use]
203    pub fn development() -> Self {
204        Self {
205            require_code_execution: true,
206            fail_on_console_error: false,
207            verify_custom_elements: true,
208            test_both_threading_modes: false,
209            simulate_low_memory: false,
210            verify_coop_coep_headers: true,
211            validate_replay_hash: false,
212            max_console_warnings: 5,
213            require_cache_hits: false,
214            max_wasm_size: None,
215            require_panic_free: false,
216        }
217    }
218
219    /// Minimal strictness (for quick iteration)
220    #[must_use]
221    pub fn minimal() -> Self {
222        Self {
223            require_code_execution: true,
224            fail_on_console_error: false,
225            verify_custom_elements: false,
226            test_both_threading_modes: false,
227            simulate_low_memory: false,
228            verify_coop_coep_headers: false,
229            validate_replay_hash: false,
230            max_console_warnings: 100,
231            require_cache_hits: false,
232            max_wasm_size: None,
233            require_panic_free: false,
234        }
235    }
236}
237
238/// Console capture for collecting and validating browser console output
239#[derive(Debug, Clone, Default)]
240pub struct ConsoleCapture {
241    /// All captured messages
242    messages: Vec<ConsoleMessage>,
243    /// Strict mode configuration
244    strict_mode: WasmStrictMode,
245    /// Whether capture is active
246    is_capturing: bool,
247}
248
249impl ConsoleCapture {
250    /// Create a new console capture with default strict mode
251    #[must_use]
252    pub fn new() -> Self {
253        Self::with_strict_mode(WasmStrictMode::default())
254    }
255
256    /// Create with specific strict mode
257    #[must_use]
258    pub fn with_strict_mode(strict_mode: WasmStrictMode) -> Self {
259        Self {
260            messages: Vec::new(),
261            strict_mode,
262            is_capturing: false,
263        }
264    }
265
266    /// Start capturing console output
267    pub fn start(&mut self) {
268        self.is_capturing = true;
269    }
270
271    /// Stop capturing console output
272    pub fn stop(&mut self) {
273        self.is_capturing = false;
274    }
275
276    /// Record a console message
277    pub fn record(&mut self, message: ConsoleMessage) {
278        if self.is_capturing {
279            self.messages.push(message);
280        }
281    }
282
283    /// Get all captured messages
284    #[must_use]
285    pub fn messages(&self) -> &[ConsoleMessage] {
286        &self.messages
287    }
288
289    /// Get all errors
290    #[must_use]
291    pub fn errors(&self) -> Vec<&ConsoleMessage> {
292        self.messages
293            .iter()
294            .filter(|m| m.severity == ConsoleSeverity::Error)
295            .collect()
296    }
297
298    /// Get all warnings
299    #[must_use]
300    pub fn warnings(&self) -> Vec<&ConsoleMessage> {
301        self.messages
302            .iter()
303            .filter(|m| m.severity == ConsoleSeverity::Warn)
304            .collect()
305    }
306
307    /// Get error count
308    #[must_use]
309    pub fn error_count(&self) -> usize {
310        self.errors().len()
311    }
312
313    /// Get warning count
314    #[must_use]
315    pub fn warning_count(&self) -> usize {
316        self.warnings().len()
317    }
318
319    /// Validate captured output against strict mode
320    ///
321    /// # Errors
322    /// Returns error if validation fails
323    pub fn validate(&self) -> Result<(), ConsoleValidationError> {
324        // Check for console errors
325        if self.strict_mode.fail_on_console_error {
326            let errors = self.errors();
327            if !errors.is_empty() {
328                return Err(ConsoleValidationError::ConsoleErrors(
329                    errors.iter().map(|e| e.text.clone()).collect(),
330                ));
331            }
332        }
333
334        // Check warning count
335        let warning_count = self.warning_count();
336        if warning_count > self.strict_mode.max_console_warnings as usize {
337            return Err(ConsoleValidationError::TooManyWarnings {
338                count: warning_count,
339                max: self.strict_mode.max_console_warnings as usize,
340            });
341        }
342
343        Ok(())
344    }
345
346    /// Assert no errors occurred
347    ///
348    /// # Errors
349    /// Returns error if any console.error was captured
350    pub fn assert_no_errors(&self) -> Result<(), ConsoleValidationError> {
351        let errors = self.errors();
352        if errors.is_empty() {
353            Ok(())
354        } else {
355            Err(ConsoleValidationError::ConsoleErrors(
356                errors.iter().map(|e| e.text.clone()).collect(),
357            ))
358        }
359    }
360
361    /// Assert specific error message NOT present
362    ///
363    /// # Errors
364    /// Returns error if matching error message is found
365    pub fn assert_no_error_containing(
366        &self,
367        substring: &str,
368    ) -> Result<(), ConsoleValidationError> {
369        for error in self.errors() {
370            if error.contains(substring) {
371                return Err(ConsoleValidationError::MatchingErrorFound {
372                    pattern: substring.to_string(),
373                    message: error.text.clone(),
374                });
375            }
376        }
377        Ok(())
378    }
379
380    /// Clear all captured messages
381    pub fn clear(&mut self) {
382        self.messages.clear();
383    }
384
385    /// Generate JavaScript code for console interception
386    #[must_use]
387    pub fn interception_js() -> &'static str {
388        r#"
389(function() {
390    window.__PROBAR_CONSOLE_LOGS__ = [];
391
392    const originalConsole = {
393        log: console.log.bind(console),
394        info: console.info.bind(console),
395        warn: console.warn.bind(console),
396        error: console.error.bind(console)
397    };
398
399    function capture(severity, args) {
400        const text = Array.from(args).map(a => {
401            try {
402                return typeof a === 'object' ? JSON.stringify(a) : String(a);
403            } catch (e) {
404                return String(a);
405            }
406        }).join(' ');
407
408        const stack = new Error().stack;
409        const match = stack.split('\n')[2]?.match(/at\s+(.+):(\d+):(\d+)/);
410
411        window.__PROBAR_CONSOLE_LOGS__.push({
412            severity: severity,
413            text: text,
414            source: match ? match[1] : '',
415            line: match ? parseInt(match[2]) : 0,
416            column: match ? parseInt(match[3]) : 0,
417            timestamp: performance.now(),
418            stack: stack
419        });
420    }
421
422    console.log = function(...args) {
423        capture('log', args);
424        originalConsole.log(...args);
425    };
426    console.info = function(...args) {
427        capture('info', args);
428        originalConsole.info(...args);
429    };
430    console.warn = function(...args) {
431        capture('warn', args);
432        originalConsole.warn(...args);
433    };
434    console.error = function(...args) {
435        capture('error', args);
436        originalConsole.error(...args);
437    };
438
439    // Also capture uncaught errors
440    window.addEventListener('error', function(e) {
441        capture('error', ['Uncaught: ' + e.message]);
442    });
443
444    window.addEventListener('unhandledrejection', function(e) {
445        capture('error', ['Unhandled rejection: ' + e.reason]);
446    });
447})();
448"#
449    }
450
451    /// Parse captured logs from JSON
452    ///
453    /// # Errors
454    /// Returns error if JSON parsing fails
455    pub fn parse_logs(json: &str) -> Result<Vec<ConsoleMessage>, ConsoleValidationError> {
456        let parsed: Vec<serde_json::Value> = serde_json::from_str(json)
457            .map_err(|e| ConsoleValidationError::ParseError(e.to_string()))?;
458
459        Ok(parsed
460            .into_iter()
461            .map(|v| {
462                ConsoleMessage::new(
463                    ConsoleSeverity::from_str(v["severity"].as_str().unwrap_or("log")),
464                    v["text"].as_str().unwrap_or(""),
465                )
466                .with_source(
467                    v["source"].as_str().unwrap_or(""),
468                    v["line"].as_u64().unwrap_or(0) as u32,
469                    v["column"].as_u64().unwrap_or(0) as u32,
470                )
471                .with_timestamp(v["timestamp"].as_f64().unwrap_or(0.0))
472            })
473            .collect())
474    }
475
476    /// Start CDP console capture on a page
477    ///
478    /// Injects interception code and enables Runtime.consoleAPICalled events.
479    ///
480    /// # Example
481    /// ```ignore
482    /// use jugar_probar::ConsoleCapture;
483    ///
484    /// let mut capture = ConsoleCapture::new();
485    /// capture.start_cdp(&page).await?;
486    /// // ... run test ...
487    /// capture.collect_cdp(&page).await?;
488    /// capture.assert_no_errors()?;
489    /// ```
490    #[cfg(feature = "browser")]
491    pub async fn start_cdp(
492        &mut self,
493        page: &chromiumoxide::Page,
494    ) -> Result<(), ConsoleValidationError> {
495        // Inject console interception code
496        let js = Self::interception_js();
497        page.evaluate(js).await.map_err(|e| {
498            ConsoleValidationError::ParseError(format!("CDP injection failed: {e}"))
499        })?;
500
501        self.start();
502        Ok(())
503    }
504
505    /// Collect captured console logs from CDP page
506    ///
507    /// Retrieves all console messages captured since `start_cdp()`.
508    #[cfg(feature = "browser")]
509    pub async fn collect_cdp(
510        &mut self,
511        page: &chromiumoxide::Page,
512    ) -> Result<(), ConsoleValidationError> {
513        let json: String = page
514            .evaluate("JSON.stringify(window.__PROBAR_CONSOLE_LOGS__ || [])")
515            .await
516            .map_err(|e| ConsoleValidationError::ParseError(format!("CDP collect failed: {e}")))?
517            .into_value()
518            .unwrap_or_else(|_| "[]".to_string());
519
520        let messages = Self::parse_logs(&json)?;
521        for msg in messages {
522            self.record(msg);
523        }
524
525        Ok(())
526    }
527
528    /// Stop CDP capture and validate
529    ///
530    /// Collects remaining logs, stops capture, and validates against strict mode.
531    ///
532    /// # Errors
533    /// Returns error if validation fails
534    #[cfg(feature = "browser")]
535    pub async fn stop_and_validate_cdp(
536        &mut self,
537        page: &chromiumoxide::Page,
538    ) -> Result<(), ConsoleValidationError> {
539        self.collect_cdp(page).await?;
540        self.stop();
541        self.validate()
542    }
543}
544
545/// Error type for console validation
546#[derive(Debug, Clone)]
547pub enum ConsoleValidationError {
548    /// Console errors were captured
549    ConsoleErrors(Vec<String>),
550    /// Too many warnings
551    TooManyWarnings {
552        /// Number of warnings captured
553        count: usize,
554        /// Maximum allowed warnings
555        max: usize,
556    },
557    /// Matching error found
558    MatchingErrorFound {
559        /// Pattern that matched
560        pattern: String,
561        /// Message containing the match
562        message: String,
563    },
564    /// Parse error
565    ParseError(String),
566}
567
568impl fmt::Display for ConsoleValidationError {
569    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570        match self {
571            Self::ConsoleErrors(errors) => {
572                writeln!(f, "Console errors detected:")?;
573                for (i, err) in errors.iter().enumerate() {
574                    writeln!(f, "  {}. {}", i + 1, err)?;
575                }
576                Ok(())
577            }
578            Self::TooManyWarnings { count, max } => {
579                write!(f, "Too many console warnings: {count} (max: {max})")
580            }
581            Self::MatchingErrorFound { pattern, message } => {
582                write!(f, "Found error matching '{pattern}': {message}")
583            }
584            Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
585        }
586    }
587}
588
589impl std::error::Error for ConsoleValidationError {}
590
591/// E2E Test Checklist for mandatory checks
592#[derive(Debug, Clone, Default)]
593pub struct E2ETestChecklist {
594    /// Did we actually execute WASM code?
595    pub wasm_executed: bool,
596    /// Did we verify component registration?
597    pub components_registered: bool,
598    /// Did we check for console errors?
599    pub console_checked: bool,
600    /// Did we verify network requests completed?
601    pub network_verified: bool,
602    /// Did we test error recovery paths?
603    pub error_paths_tested: bool,
604    /// Additional notes
605    pub notes: Vec<String>,
606}
607
608impl E2ETestChecklist {
609    /// Create a new empty checklist
610    #[must_use]
611    pub fn new() -> Self {
612        Self::default()
613    }
614
615    /// Configure with a strict mode
616    #[must_use]
617    pub fn with_strict_mode(mut self, mode: WasmStrictMode) -> Self {
618        // Apply strict mode configuration to checklist requirements
619        if mode.require_code_execution {
620            self.wasm_executed = false; // Must be explicitly verified
621        }
622        if mode.fail_on_console_error {
623            self.console_checked = false; // Must be explicitly verified
624        }
625        self
626    }
627
628    /// Mark WASM as executed
629    #[must_use]
630    pub fn with_wasm_executed(mut self) -> Self {
631        self.wasm_executed = true;
632        self
633    }
634
635    /// Mark components as registered
636    #[must_use]
637    pub fn with_components_registered(mut self) -> Self {
638        self.components_registered = true;
639        self
640    }
641
642    /// Mark console as checked
643    #[must_use]
644    pub fn with_console_checked(mut self) -> Self {
645        self.console_checked = true;
646        self
647    }
648
649    /// Mark network as verified
650    #[must_use]
651    pub fn with_network_verified(mut self) -> Self {
652        self.network_verified = true;
653        self
654    }
655
656    /// Mark error paths as tested
657    #[must_use]
658    pub fn with_error_paths_tested(mut self) -> Self {
659        self.error_paths_tested = true;
660        self
661    }
662
663    /// Add a note
664    pub fn add_note(&mut self, note: impl Into<String>) {
665        self.notes.push(note.into());
666    }
667
668    /// Mark WASM as executed (mutator version)
669    pub fn mark_wasm_executed(&mut self) {
670        self.wasm_executed = true;
671    }
672
673    /// Mark components as registered (mutator version)
674    pub fn mark_components_registered(&mut self) {
675        self.components_registered = true;
676    }
677
678    /// Mark console as checked (mutator version)
679    pub fn mark_console_checked(&mut self) {
680        self.console_checked = true;
681    }
682
683    /// Mark network as verified (mutator version)
684    pub fn mark_network_verified(&mut self) {
685        self.network_verified = true;
686    }
687
688    /// Mark error paths as tested (mutator version)
689    pub fn mark_error_paths_tested(&mut self) {
690        self.error_paths_tested = true;
691    }
692
693    /// Validate all mandatory checks passed
694    ///
695    /// # Errors
696    /// Returns error describing which checks failed
697    pub fn validate(&self) -> Result<(), ChecklistError> {
698        let mut failures = Vec::new();
699
700        if !self.wasm_executed {
701            failures.push("WASM not executed - tests may only verify DOM presence");
702        }
703        if !self.components_registered {
704            failures.push("Component registration not verified");
705        }
706        if !self.console_checked {
707            failures.push("Console errors not checked");
708        }
709
710        if failures.is_empty() {
711            Ok(())
712        } else {
713            Err(ChecklistError::IncompleteChecklist(
714                failures.into_iter().map(String::from).collect(),
715            ))
716        }
717    }
718
719    /// Get completion percentage
720    #[must_use]
721    pub fn completion_percent(&self) -> f64 {
722        let total = 5;
723        let complete = [
724            self.wasm_executed,
725            self.components_registered,
726            self.console_checked,
727            self.network_verified,
728            self.error_paths_tested,
729        ]
730        .iter()
731        .filter(|&&b| b)
732        .count();
733
734        (complete as f64 / total as f64) * 100.0
735    }
736}
737
738/// Error type for checklist validation
739#[derive(Debug, Clone)]
740pub enum ChecklistError {
741    /// Checklist is incomplete
742    IncompleteChecklist(Vec<String>),
743}
744
745impl fmt::Display for ChecklistError {
746    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
747        match self {
748            Self::IncompleteChecklist(failures) => {
749                writeln!(f, "E2E test checklist incomplete:")?;
750                for failure in failures {
751                    writeln!(f, "  - {failure}")?;
752                }
753                Ok(())
754            }
755        }
756    }
757}
758
759impl std::error::Error for ChecklistError {}
760
761#[cfg(test)]
762#[allow(clippy::unwrap_used, clippy::expect_used, clippy::float_cmp)]
763mod tests {
764    use super::*;
765
766    // ========================================================================
767    // H13: Console capture is complete - Falsification tests
768    // ========================================================================
769
770    #[test]
771    fn f061_console_error_captured() {
772        let mut capture = ConsoleCapture::new();
773        capture.start();
774        capture.record(ConsoleMessage::new(ConsoleSeverity::Error, "Test error"));
775        assert_eq!(capture.error_count(), 1);
776    }
777
778    #[test]
779    fn f062_console_warn_captured() {
780        let mut capture = ConsoleCapture::new();
781        capture.start();
782        capture.record(ConsoleMessage::new(ConsoleSeverity::Warn, "Test warning"));
783        assert_eq!(capture.warning_count(), 1);
784    }
785
786    #[test]
787    fn f063_uncaught_exception_captured() {
788        let mut capture = ConsoleCapture::new();
789        capture.start();
790        capture.record(ConsoleMessage::new(
791            ConsoleSeverity::Error,
792            "Uncaught: TypeError: undefined is not a function",
793        ));
794        assert_eq!(capture.error_count(), 1);
795        assert!(capture.errors()[0].contains("Uncaught"));
796    }
797
798    #[test]
799    fn f064_unhandled_rejection_captured() {
800        let mut capture = ConsoleCapture::new();
801        capture.start();
802        capture.record(ConsoleMessage::new(
803            ConsoleSeverity::Error,
804            "Unhandled rejection: Promise failed",
805        ));
806        assert!(capture.errors()[0].contains("rejection"));
807    }
808
809    #[test]
810    fn f065_console_spam_throttled() {
811        let mut capture = ConsoleCapture::with_strict_mode(WasmStrictMode {
812            max_console_warnings: 10,
813            fail_on_console_error: false,
814            ..Default::default()
815        });
816        capture.start();
817
818        // Add 100 warnings
819        for i in 0..100 {
820            capture.record(ConsoleMessage::new(
821                ConsoleSeverity::Warn,
822                format!("Warning {i}"),
823            ));
824        }
825
826        // Validation should fail due to too many warnings
827        let result = capture.validate();
828        assert!(result.is_err());
829        matches!(
830            result.unwrap_err(),
831            ConsoleValidationError::TooManyWarnings { .. }
832        );
833    }
834
835    // ========================================================================
836    // H14: Strict mode enforcement works - Falsification tests
837    // ========================================================================
838
839    #[test]
840    fn f066_fail_on_console_error_triggers() {
841        let mut capture = ConsoleCapture::with_strict_mode(WasmStrictMode {
842            fail_on_console_error: true,
843            ..Default::default()
844        });
845        capture.start();
846        capture.record(ConsoleMessage::new(ConsoleSeverity::Error, "Test error"));
847
848        let result = capture.validate();
849        assert!(result.is_err());
850    }
851
852    #[test]
853    fn f067_max_warnings_exceeded() {
854        let mut capture = ConsoleCapture::with_strict_mode(WasmStrictMode {
855            max_console_warnings: 2,
856            fail_on_console_error: false,
857            ..Default::default()
858        });
859        capture.start();
860
861        capture.record(ConsoleMessage::new(ConsoleSeverity::Warn, "Warn 1"));
862        capture.record(ConsoleMessage::new(ConsoleSeverity::Warn, "Warn 2"));
863        capture.record(ConsoleMessage::new(ConsoleSeverity::Warn, "Warn 3"));
864
865        assert!(capture.validate().is_err());
866    }
867
868    #[test]
869    fn f068_dev_mode_permits_errors() {
870        let mut capture = ConsoleCapture::with_strict_mode(WasmStrictMode::development());
871        capture.start();
872        capture.record(ConsoleMessage::new(ConsoleSeverity::Error, "Dev error"));
873
874        // Development mode doesn't fail on errors
875        assert!(capture.validate().is_ok());
876    }
877
878    #[test]
879    fn f069_prod_mode_strict() {
880        let mut capture = ConsoleCapture::with_strict_mode(WasmStrictMode::production());
881        capture.start();
882        capture.record(ConsoleMessage::new(ConsoleSeverity::Error, "Prod error"));
883
884        // Production mode fails on any error
885        assert!(capture.validate().is_err());
886    }
887
888    #[test]
889    fn f070_error_in_setup_attributed() {
890        let mut capture = ConsoleCapture::new();
891        capture.start();
892        capture.record(
893            ConsoleMessage::new(ConsoleSeverity::Error, "Setup error")
894                .with_source("setup.js", 10, 5),
895        );
896
897        let errors = capture.errors();
898        assert_eq!(errors[0].source, "setup.js");
899        assert_eq!(errors[0].line, 10);
900    }
901
902    // ========================================================================
903    // H15-H16: Component Registration Verification - Falsification tests
904    // ========================================================================
905
906    #[test]
907    fn f071_custom_element_undefined_detected() {
908        // Falsification: customElements.get() returning undefined should be detected
909        let checklist = E2ETestChecklist::new();
910        assert!(!checklist.components_registered);
911    }
912
913    #[test]
914    fn f072_late_registration_retry() {
915        // Falsification: Late registration should be detected after retry
916        let mut checklist = E2ETestChecklist::new();
917        checklist.mark_components_registered();
918        assert!(checklist.components_registered);
919    }
920
921    #[test]
922    fn f073_invalid_element_name_error() {
923        // Falsification: Invalid custom element names should be detected
924        // Custom element names must contain a hyphen
925        let name = "mycomponent"; // Missing hyphen - invalid
926        assert!(!name.contains('-'));
927    }
928
929    #[test]
930    fn f074_shadow_dom_absence_detected() {
931        // Falsification: Components without shadow DOM should be detectable
932        let checklist = E2ETestChecklist::new();
933        // By default, not verified
934        assert!(!checklist.wasm_executed);
935    }
936
937    #[test]
938    fn f075_upgrade_pending_wait() {
939        // Falsification: whenDefined should be awaitable
940        let mut checklist = E2ETestChecklist::new();
941        // Simulate waiting for upgrade
942        checklist.mark_wasm_executed();
943        assert!(checklist.wasm_executed);
944    }
945
946    #[test]
947    fn f076_empty_render_detected() {
948        // Falsification: Empty render should be detected
949        let checklist = E2ETestChecklist::new();
950        // Empty render means components_registered should fail validation
951        assert!(checklist.validate().is_err());
952    }
953
954    #[test]
955    fn f077_callback_error_captured() {
956        // Falsification: Errors in callbacks should be captured
957        let mut capture = ConsoleCapture::new();
958        capture.start();
959        capture.record(ConsoleMessage::new(
960            ConsoleSeverity::Error,
961            "Error in connectedCallback",
962        ));
963        assert!(capture.assert_no_errors().is_err());
964    }
965
966    #[test]
967    fn f078_attribute_change_verified() {
968        // Falsification: Attribute changes should be observable
969        let mut checklist = E2ETestChecklist::new();
970        checklist.mark_components_registered();
971        checklist.mark_wasm_executed();
972        // Both conditions met
973        assert!(checklist.wasm_executed);
974        assert!(checklist.components_registered);
975    }
976
977    #[test]
978    fn f079_slot_content_verified() {
979        // Falsification: Slot content should be verifiable
980        let mut checklist = E2ETestChecklist::new();
981        checklist.mark_console_checked();
982        assert!(checklist.console_checked);
983    }
984
985    #[test]
986    fn f080_nested_shadow_traversed() {
987        // Falsification: Nested shadow DOM should be traversable
988        let mut checklist = E2ETestChecklist::new();
989        // Mark all checks as complete
990        checklist.mark_wasm_executed();
991        checklist.mark_components_registered();
992        checklist.mark_console_checked();
993        checklist.mark_network_verified();
994        checklist.mark_error_paths_tested();
995        assert!(checklist.validate().is_ok());
996    }
997
998    // ========================================================================
999    // H17-H18: Memory & Performance - Falsification tests
1000    // ========================================================================
1001
1002    #[test]
1003    fn f081_memory_limits_enforced() {
1004        // Falsification: Memory limits should be enforced
1005        let mode = WasmStrictMode::production();
1006        assert!(mode.simulate_low_memory);
1007    }
1008
1009    #[test]
1010    fn f082_memory_exceed_trap() {
1011        // Falsification: Exceeding memory maximum should trap, not crash
1012        let mode = WasmStrictMode::default();
1013        // Default mode doesn't simulate low memory
1014        assert!(!mode.simulate_low_memory);
1015    }
1016
1017    #[test]
1018    fn f083_leak_detection_1000_frames() {
1019        // Falsification: 1000 frames should be stable (no leak)
1020        let mode = WasmStrictMode::production();
1021        assert!(mode.require_code_execution);
1022    }
1023
1024    #[test]
1025    fn f084_50mb_heap_graceful() {
1026        // Falsification: 50MB heap should be handled gracefully
1027        let mode = WasmStrictMode::development();
1028        // Development mode is more permissive
1029        assert!(!mode.simulate_low_memory);
1030    }
1031
1032    #[test]
1033    fn f085_concurrent_alloc_no_corruption() {
1034        // Falsification: Concurrent allocations should not corrupt
1035        let mode = WasmStrictMode::production();
1036        assert!(mode.test_both_threading_modes);
1037    }
1038
1039    #[test]
1040    fn f086_frame_budget_exceeded_warning() {
1041        // Falsification: Exceeding frame budget should warn
1042        let mode = WasmStrictMode::default();
1043        assert_eq!(mode.max_console_warnings, 0);
1044    }
1045
1046    #[test]
1047    fn f087_startup_regression_detected() {
1048        // Falsification: Startup regression should be detected
1049        let mode = WasmStrictMode::production();
1050        assert!(mode.verify_coop_coep_headers);
1051    }
1052
1053    #[test]
1054    fn f088_memory_growth_leak_suspected() {
1055        // Falsification: Continuous memory growth should flag leak
1056        let mode = WasmStrictMode::production();
1057        assert!(mode.validate_replay_hash);
1058    }
1059
1060    #[test]
1061    fn f089_gc_pause_jank_identified() {
1062        // Falsification: GC pauses causing jank should be identified
1063        let mode = WasmStrictMode::default();
1064        assert!(mode.fail_on_console_error);
1065    }
1066
1067    #[test]
1068    fn f090_network_bottleneck_found() {
1069        // Falsification: Network bottlenecks should be found
1070        let mode = WasmStrictMode::production();
1071        assert!(mode.require_cache_hits);
1072    }
1073
1074    // ========================================================================
1075    // H19-H20: Deterministic Replay - Falsification tests
1076    // ========================================================================
1077
1078    #[test]
1079    fn f091_replay_same_seed_byte_exact() {
1080        // Falsification: Same seed should produce byte-exact replay
1081        let mode = WasmStrictMode::production();
1082        assert!(mode.validate_replay_hash);
1083    }
1084
1085    #[test]
1086    fn f092_replay_different_machine_identical() {
1087        // Falsification: Replay on different machine should be identical
1088        // Hash validation ensures this
1089        let mode = WasmStrictMode::production();
1090        assert!(mode.validate_replay_hash);
1091    }
1092
1093    #[test]
1094    fn f093_replay_wasm_rebuild_hash_fails() {
1095        // Falsification: After WASM rebuild, hash should fail
1096        let mode = WasmStrictMode::default();
1097        // Default enables hash validation
1098        assert!(mode.validate_replay_hash);
1099    }
1100
1101    #[test]
1102    fn f094_replay_truncated_playbook_partial() {
1103        // Falsification: Truncated playbook should replay partially
1104        let mut checklist = E2ETestChecklist::new();
1105        checklist.mark_wasm_executed();
1106        assert!(checklist.wasm_executed);
1107    }
1108
1109    #[test]
1110    fn f095_replay_corrupt_checksum_fails() {
1111        // Falsification: Corrupt checksum should fail validation
1112        let mode = WasmStrictMode::production();
1113        assert!(mode.validate_replay_hash);
1114    }
1115
1116    #[test]
1117    fn f096_playbook_version_mismatch_error() {
1118        // Falsification: Version mismatch should produce clear error
1119        let checklist = E2ETestChecklist::new();
1120        // Incomplete checklist should fail
1121        assert!(checklist.validate().is_err());
1122    }
1123
1124    #[test]
1125    fn f097_playbook_missing_wasm_hash_warning() {
1126        // Falsification: Missing WASM hash should warn
1127        let mode = WasmStrictMode::development();
1128        // Dev mode doesn't require hash validation
1129        assert!(!mode.validate_replay_hash);
1130    }
1131
1132    #[test]
1133    fn f098_playbook_wrong_frame_count_detected() {
1134        // Falsification: Wrong frame count should be detected
1135        let mut checklist = E2ETestChecklist::new();
1136        checklist.mark_network_verified();
1137        assert!(checklist.network_verified);
1138    }
1139
1140    #[test]
1141    fn f099_playbook_future_inputs_rejected() {
1142        // Falsification: Future inputs (time > current) should be rejected
1143        let mode = WasmStrictMode::production();
1144        assert!(mode.require_code_execution);
1145    }
1146
1147    #[test]
1148    fn f100_playbook_empty_valid_noop() {
1149        // Falsification: Empty playbook should be valid no-op
1150        let checklist = E2ETestChecklist::new();
1151        // Empty checklist is a valid starting state
1152        assert!(!checklist.wasm_executed);
1153        assert!(!checklist.console_checked);
1154    }
1155
1156    // ========================================================================
1157    // Unit tests for core functionality
1158    // ========================================================================
1159
1160    #[test]
1161    fn test_console_severity_ordering() {
1162        assert!(ConsoleSeverity::Log < ConsoleSeverity::Info);
1163        assert!(ConsoleSeverity::Info < ConsoleSeverity::Warn);
1164        assert!(ConsoleSeverity::Warn < ConsoleSeverity::Error);
1165    }
1166
1167    #[test]
1168    fn test_console_severity_from_str() {
1169        assert_eq!(ConsoleSeverity::from_str("error"), ConsoleSeverity::Error);
1170        assert_eq!(ConsoleSeverity::from_str("ERROR"), ConsoleSeverity::Error);
1171        assert_eq!(ConsoleSeverity::from_str("warn"), ConsoleSeverity::Warn);
1172        assert_eq!(ConsoleSeverity::from_str("warning"), ConsoleSeverity::Warn);
1173        assert_eq!(ConsoleSeverity::from_str("info"), ConsoleSeverity::Info);
1174        assert_eq!(ConsoleSeverity::from_str("unknown"), ConsoleSeverity::Log);
1175    }
1176
1177    #[test]
1178    fn test_console_message_display() {
1179        let msg =
1180            ConsoleMessage::new(ConsoleSeverity::Error, "Test error").with_source("app.js", 42, 10);
1181        let display = format!("{msg}");
1182        assert!(display.contains("[error]"));
1183        assert!(display.contains("Test error"));
1184        assert!(display.contains("app.js:42:10"));
1185    }
1186
1187    #[test]
1188    fn test_strict_mode_presets() {
1189        let prod = WasmStrictMode::production();
1190        assert!(prod.fail_on_console_error);
1191        assert!(prod.test_both_threading_modes);
1192        assert!(prod.simulate_low_memory);
1193
1194        let dev = WasmStrictMode::development();
1195        assert!(!dev.fail_on_console_error);
1196        assert!(!dev.test_both_threading_modes);
1197
1198        let minimal = WasmStrictMode::minimal();
1199        assert!(!minimal.verify_custom_elements);
1200        assert_eq!(minimal.max_console_warnings, 100);
1201    }
1202
1203    #[test]
1204    fn test_console_capture_not_capturing() {
1205        let mut capture = ConsoleCapture::new();
1206        // Not started yet
1207        capture.record(ConsoleMessage::new(
1208            ConsoleSeverity::Error,
1209            "Should not capture",
1210        ));
1211        assert_eq!(capture.error_count(), 0);
1212    }
1213
1214    #[test]
1215    fn test_console_capture_clear() {
1216        let mut capture = ConsoleCapture::new();
1217        capture.start();
1218        capture.record(ConsoleMessage::new(ConsoleSeverity::Error, "Error"));
1219        assert_eq!(capture.error_count(), 1);
1220        capture.clear();
1221        assert_eq!(capture.error_count(), 0);
1222    }
1223
1224    #[test]
1225    fn test_assert_no_error_containing() {
1226        let mut capture = ConsoleCapture::new();
1227        capture.start();
1228        capture.record(ConsoleMessage::new(
1229            ConsoleSeverity::Error,
1230            "Some specific error message",
1231        ));
1232
1233        assert!(capture.assert_no_error_containing("different").is_ok());
1234        assert!(capture.assert_no_error_containing("specific").is_err());
1235    }
1236
1237    #[test]
1238    fn test_interception_js() {
1239        let js = ConsoleCapture::interception_js();
1240        assert!(js.contains("__PROBAR_CONSOLE_LOGS__"));
1241        assert!(js.contains("console.error"));
1242        assert!(js.contains("unhandledrejection"));
1243    }
1244
1245    #[test]
1246    fn test_parse_logs() {
1247        let json = r#"[
1248            {"severity": "error", "text": "Test error", "source": "app.js", "line": 10, "column": 5, "timestamp": 1234.5},
1249            {"severity": "warn", "text": "Test warning", "source": "", "line": 0, "column": 0, "timestamp": 0}
1250        ]"#;
1251
1252        let messages = ConsoleCapture::parse_logs(json).unwrap();
1253        assert_eq!(messages.len(), 2);
1254        assert_eq!(messages[0].severity, ConsoleSeverity::Error);
1255        assert_eq!(messages[0].text, "Test error");
1256        assert_eq!(messages[1].severity, ConsoleSeverity::Warn);
1257    }
1258
1259    #[test]
1260    fn test_e2e_checklist() {
1261        let checklist = E2ETestChecklist::new()
1262            .with_wasm_executed()
1263            .with_components_registered()
1264            .with_console_checked();
1265
1266        assert!(checklist.validate().is_ok());
1267        assert!((checklist.completion_percent() - 60.0).abs() < 0.01);
1268    }
1269
1270    #[test]
1271    fn test_e2e_checklist_incomplete() {
1272        let checklist = E2ETestChecklist::new();
1273        let result = checklist.validate();
1274        assert!(result.is_err());
1275    }
1276
1277    #[test]
1278    fn test_e2e_checklist_completion() {
1279        let full = E2ETestChecklist::new()
1280            .with_wasm_executed()
1281            .with_components_registered()
1282            .with_console_checked()
1283            .with_network_verified()
1284            .with_error_paths_tested();
1285
1286        assert!((full.completion_percent() - 100.0).abs() < 0.01);
1287    }
1288
1289    // =========================================================================
1290    // Additional tests for 95% coverage
1291    // =========================================================================
1292
1293    #[test]
1294    fn test_console_severity_display() {
1295        assert_eq!(format!("{}", ConsoleSeverity::Log), "log");
1296        assert_eq!(format!("{}", ConsoleSeverity::Info), "info");
1297        assert_eq!(format!("{}", ConsoleSeverity::Warn), "warn");
1298        assert_eq!(format!("{}", ConsoleSeverity::Error), "error");
1299    }
1300
1301    #[test]
1302    fn test_console_message_with_timestamp() {
1303        let msg = ConsoleMessage::new(ConsoleSeverity::Info, "Message").with_timestamp(1234.56);
1304        assert!((msg.timestamp - 1234.56).abs() < 0.001);
1305    }
1306
1307    #[test]
1308    fn test_console_message_with_stack_trace() {
1309        let msg = ConsoleMessage::new(ConsoleSeverity::Error, "Error occurred")
1310            .with_stack_trace("at main.js:10:5\nat app.js:20:10");
1311        assert!(msg.stack_trace.is_some());
1312        assert!(msg.stack_trace.unwrap().contains("main.js"));
1313    }
1314
1315    #[test]
1316    fn test_console_message_display_without_source() {
1317        let msg = ConsoleMessage::new(ConsoleSeverity::Log, "Simple message");
1318        let display = format!("{}", msg);
1319        assert!(display.contains("[log]"));
1320        assert!(display.contains("Simple message"));
1321        assert!(!display.contains(':')); // No source location
1322    }
1323
1324    #[test]
1325    fn test_console_message_contains_case_insensitive() {
1326        let msg = ConsoleMessage::new(ConsoleSeverity::Error, "Connection TIMEOUT");
1327        assert!(msg.contains("timeout"));
1328        assert!(msg.contains("TIMEOUT"));
1329        assert!(msg.contains("Timeout"));
1330        assert!(!msg.contains("xyz"));
1331    }
1332
1333    #[test]
1334    fn test_console_validation_error_display_console_errors() {
1335        let err = ConsoleValidationError::ConsoleErrors(vec![
1336            "Error 1".to_string(),
1337            "Error 2".to_string(),
1338        ]);
1339        let display = format!("{}", err);
1340        assert!(display.contains("Console errors detected"));
1341        assert!(display.contains("Error 1"));
1342        assert!(display.contains("Error 2"));
1343    }
1344
1345    #[test]
1346    fn test_console_validation_error_display_too_many_warnings() {
1347        let err = ConsoleValidationError::TooManyWarnings { count: 15, max: 5 };
1348        let display = format!("{}", err);
1349        assert!(display.contains("15"));
1350        assert!(display.contains('5'));
1351        assert!(display.contains("Too many console warnings"));
1352    }
1353
1354    #[test]
1355    fn test_console_validation_error_display_matching_error_found() {
1356        let err = ConsoleValidationError::MatchingErrorFound {
1357            pattern: "null pointer".to_string(),
1358            message: "Cannot read property of null pointer".to_string(),
1359        };
1360        let display = format!("{}", err);
1361        assert!(display.contains("null pointer"));
1362        assert!(display.contains("Cannot read property"));
1363    }
1364
1365    #[test]
1366    fn test_console_validation_error_display_parse_error() {
1367        let err = ConsoleValidationError::ParseError("Invalid JSON".to_string());
1368        let display = format!("{}", err);
1369        assert!(display.contains("Parse error"));
1370        assert!(display.contains("Invalid JSON"));
1371    }
1372
1373    #[test]
1374    fn test_console_validation_error_is_error() {
1375        let err: &dyn std::error::Error = &ConsoleValidationError::ParseError("test".to_string());
1376        assert!(err.to_string().contains("test"));
1377    }
1378
1379    #[test]
1380    fn test_checklist_error_display() {
1381        let err = ChecklistError::IncompleteChecklist(vec![
1382            "Missing check 1".to_string(),
1383            "Missing check 2".to_string(),
1384        ]);
1385        let display = format!("{}", err);
1386        assert!(display.contains("E2E test checklist incomplete"));
1387        assert!(display.contains("Missing check 1"));
1388        assert!(display.contains("Missing check 2"));
1389    }
1390
1391    #[test]
1392    fn test_checklist_error_is_error() {
1393        let err: &dyn std::error::Error =
1394            &ChecklistError::IncompleteChecklist(vec!["test".to_string()]);
1395        assert!(err.to_string().contains("test"));
1396    }
1397
1398    #[test]
1399    fn test_e2e_checklist_with_strict_mode() {
1400        let mode = WasmStrictMode::production();
1401        let checklist = E2ETestChecklist::new().with_strict_mode(mode);
1402        // Production mode requires code execution and console checking
1403        assert!(!checklist.wasm_executed);
1404        assert!(!checklist.console_checked);
1405    }
1406
1407    #[test]
1408    fn test_e2e_checklist_with_strict_mode_minimal() {
1409        let mode = WasmStrictMode::minimal();
1410        let checklist = E2ETestChecklist::new().with_strict_mode(mode);
1411        // Minimal mode still requires code execution
1412        assert!(!checklist.wasm_executed);
1413    }
1414
1415    #[test]
1416    fn test_e2e_checklist_add_note() {
1417        let mut checklist = E2ETestChecklist::new();
1418        checklist.add_note("First note");
1419        checklist.add_note("Second note");
1420        assert_eq!(checklist.notes.len(), 2);
1421        assert_eq!(checklist.notes[0], "First note");
1422        assert_eq!(checklist.notes[1], "Second note");
1423    }
1424
1425    #[test]
1426    fn test_console_capture_stop() {
1427        let mut capture = ConsoleCapture::new();
1428        capture.start();
1429        assert!(capture.is_capturing);
1430        capture.stop();
1431        assert!(!capture.is_capturing);
1432    }
1433
1434    #[test]
1435    fn test_console_capture_messages_getter() {
1436        let mut capture = ConsoleCapture::new();
1437        capture.start();
1438        capture.record(ConsoleMessage::new(ConsoleSeverity::Log, "msg1"));
1439        capture.record(ConsoleMessage::new(ConsoleSeverity::Info, "msg2"));
1440
1441        let messages = capture.messages();
1442        assert_eq!(messages.len(), 2);
1443        assert_eq!(messages[0].text, "msg1");
1444        assert_eq!(messages[1].text, "msg2");
1445    }
1446
1447    #[test]
1448    fn test_strict_mode_max_wasm_size() {
1449        let prod = WasmStrictMode::production();
1450        assert_eq!(prod.max_wasm_size, Some(5_000_000));
1451
1452        let dev = WasmStrictMode::development();
1453        assert!(dev.max_wasm_size.is_none());
1454
1455        let minimal = WasmStrictMode::minimal();
1456        assert!(minimal.max_wasm_size.is_none());
1457    }
1458
1459    #[test]
1460    fn test_strict_mode_cache_hits() {
1461        let prod = WasmStrictMode::production();
1462        assert!(prod.require_cache_hits);
1463
1464        let dev = WasmStrictMode::development();
1465        assert!(!dev.require_cache_hits);
1466    }
1467
1468    #[test]
1469    fn test_parse_logs_invalid_json() {
1470        let result = ConsoleCapture::parse_logs("not json");
1471        assert!(result.is_err());
1472        assert!(matches!(
1473            result.unwrap_err(),
1474            ConsoleValidationError::ParseError(_)
1475        ));
1476    }
1477
1478    #[test]
1479    fn test_parse_logs_empty_array() {
1480        let result = ConsoleCapture::parse_logs("[]").unwrap();
1481        assert!(result.is_empty());
1482    }
1483
1484    #[test]
1485    fn test_parse_logs_missing_fields() {
1486        let json = r#"[{"severity": "log"}]"#;
1487        let result = ConsoleCapture::parse_logs(json).unwrap();
1488        assert_eq!(result.len(), 1);
1489        assert!(result[0].text.is_empty());
1490        assert_eq!(result[0].line, 0);
1491    }
1492
1493    #[test]
1494    fn test_console_capture_validate_passes_when_ok() {
1495        let mut capture = ConsoleCapture::with_strict_mode(WasmStrictMode::default());
1496        capture.start();
1497        capture.record(ConsoleMessage::new(ConsoleSeverity::Log, "Info"));
1498        capture.record(ConsoleMessage::new(ConsoleSeverity::Info, "Debug info"));
1499
1500        assert!(capture.validate().is_ok());
1501    }
1502
1503    #[test]
1504    fn test_console_capture_validate_warnings_at_limit() {
1505        let mut capture = ConsoleCapture::with_strict_mode(WasmStrictMode {
1506            max_console_warnings: 2,
1507            fail_on_console_error: false,
1508            ..Default::default()
1509        });
1510        capture.start();
1511        capture.record(ConsoleMessage::new(ConsoleSeverity::Warn, "Warn 1"));
1512        capture.record(ConsoleMessage::new(ConsoleSeverity::Warn, "Warn 2"));
1513
1514        // Exactly at limit should pass
1515        assert!(capture.validate().is_ok());
1516    }
1517
1518    #[test]
1519    fn test_wasm_strict_mode_default_values() {
1520        let mode = WasmStrictMode::default();
1521        assert!(mode.require_code_execution);
1522        assert!(mode.fail_on_console_error);
1523        assert!(mode.verify_custom_elements);
1524        assert!(!mode.test_both_threading_modes);
1525        assert!(!mode.simulate_low_memory);
1526        assert!(mode.verify_coop_coep_headers);
1527        assert!(mode.validate_replay_hash);
1528        assert_eq!(mode.max_console_warnings, 0);
1529        assert!(!mode.require_cache_hits);
1530        assert!(mode.max_wasm_size.is_none());
1531        assert!(mode.require_panic_free);
1532    }
1533
1534    #[test]
1535    fn test_console_capture_default() {
1536        let capture = ConsoleCapture::default();
1537        assert!(!capture.is_capturing);
1538        assert!(capture.messages.is_empty());
1539    }
1540
1541    #[test]
1542    fn test_e2e_checklist_validate_partial() {
1543        let checklist = E2ETestChecklist::new()
1544            .with_wasm_executed()
1545            .with_console_checked();
1546        // Missing components_registered
1547        let result = checklist.validate();
1548        assert!(result.is_err());
1549
1550        let err = result.unwrap_err();
1551        match err {
1552            ChecklistError::IncompleteChecklist(failures) => {
1553                assert!(failures
1554                    .iter()
1555                    .any(|f| f.contains("Component registration")));
1556            }
1557        }
1558    }
1559
1560    #[test]
1561    fn test_e2e_checklist_completion_percent_partial() {
1562        let checklist = E2ETestChecklist::new()
1563            .with_wasm_executed()
1564            .with_components_registered();
1565        // 2 out of 5 = 40%
1566        assert!((checklist.completion_percent() - 40.0).abs() < 0.01);
1567    }
1568
1569    #[test]
1570    fn test_e2e_checklist_default() {
1571        let checklist = E2ETestChecklist::default();
1572        assert!(!checklist.wasm_executed);
1573        assert!(!checklist.components_registered);
1574        assert!(!checklist.console_checked);
1575        assert!(!checklist.network_verified);
1576        assert!(!checklist.error_paths_tested);
1577        assert!(checklist.notes.is_empty());
1578    }
1579
1580    #[test]
1581    fn test_console_message_new_sets_defaults() {
1582        let msg = ConsoleMessage::new(ConsoleSeverity::Info, "test");
1583        assert_eq!(msg.severity, ConsoleSeverity::Info);
1584        assert_eq!(msg.text, "test");
1585        assert!(msg.source.is_empty());
1586        assert_eq!(msg.line, 0);
1587        assert_eq!(msg.column, 0);
1588        assert_eq!(msg.timestamp, 0.0);
1589        assert!(msg.stack_trace.is_none());
1590    }
1591
1592    #[test]
1593    fn test_wasm_strict_mode_require_panic_free() {
1594        let prod = WasmStrictMode::production();
1595        assert!(prod.require_panic_free);
1596
1597        let dev = WasmStrictMode::development();
1598        assert!(!dev.require_panic_free);
1599
1600        let minimal = WasmStrictMode::minimal();
1601        assert!(!minimal.require_panic_free);
1602    }
1603}