1use std::fmt;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum ConsoleSeverity {
19 Log,
21 Info,
23 Warn,
25 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 #[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#[derive(Debug, Clone)]
55pub struct ConsoleMessage {
56 pub severity: ConsoleSeverity,
58 pub text: String,
60 pub source: String,
62 pub line: u32,
64 pub column: u32,
66 pub timestamp: f64,
68 pub stack_trace: Option<String>,
70}
71
72impl ConsoleMessage {
73 #[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 #[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 #[must_use]
98 pub fn with_timestamp(mut self, timestamp: f64) -> Self {
99 self.timestamp = timestamp;
100 self
101 }
102
103 #[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 #[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#[derive(Debug, Clone)]
129pub struct WasmStrictMode {
130 pub require_code_execution: bool,
132
133 pub fail_on_console_error: bool,
135
136 pub verify_custom_elements: bool,
138
139 pub test_both_threading_modes: bool,
141
142 pub simulate_low_memory: bool,
144
145 pub verify_coop_coep_headers: bool,
147
148 pub validate_replay_hash: bool,
150
151 pub max_console_warnings: u32,
153
154 pub require_cache_hits: bool,
156
157 pub max_wasm_size: Option<usize>,
159
160 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 #[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), require_panic_free: true,
198 }
199 }
200
201 #[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 #[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#[derive(Debug, Clone, Default)]
240pub struct ConsoleCapture {
241 messages: Vec<ConsoleMessage>,
243 strict_mode: WasmStrictMode,
245 is_capturing: bool,
247}
248
249impl ConsoleCapture {
250 #[must_use]
252 pub fn new() -> Self {
253 Self::with_strict_mode(WasmStrictMode::default())
254 }
255
256 #[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 pub fn start(&mut self) {
268 self.is_capturing = true;
269 }
270
271 pub fn stop(&mut self) {
273 self.is_capturing = false;
274 }
275
276 pub fn record(&mut self, message: ConsoleMessage) {
278 if self.is_capturing {
279 self.messages.push(message);
280 }
281 }
282
283 #[must_use]
285 pub fn messages(&self) -> &[ConsoleMessage] {
286 &self.messages
287 }
288
289 #[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 #[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 #[must_use]
309 pub fn error_count(&self) -> usize {
310 self.errors().len()
311 }
312
313 #[must_use]
315 pub fn warning_count(&self) -> usize {
316 self.warnings().len()
317 }
318
319 pub fn validate(&self) -> Result<(), ConsoleValidationError> {
324 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 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 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 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 pub fn clear(&mut self) {
382 self.messages.clear();
383 }
384
385 #[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 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 #[cfg(feature = "browser")]
491 pub async fn start_cdp(
492 &mut self,
493 page: &chromiumoxide::Page,
494 ) -> Result<(), ConsoleValidationError> {
495 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 #[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 #[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#[derive(Debug, Clone)]
547pub enum ConsoleValidationError {
548 ConsoleErrors(Vec<String>),
550 TooManyWarnings {
552 count: usize,
554 max: usize,
556 },
557 MatchingErrorFound {
559 pattern: String,
561 message: String,
563 },
564 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#[derive(Debug, Clone, Default)]
593pub struct E2ETestChecklist {
594 pub wasm_executed: bool,
596 pub components_registered: bool,
598 pub console_checked: bool,
600 pub network_verified: bool,
602 pub error_paths_tested: bool,
604 pub notes: Vec<String>,
606}
607
608impl E2ETestChecklist {
609 #[must_use]
611 pub fn new() -> Self {
612 Self::default()
613 }
614
615 #[must_use]
617 pub fn with_strict_mode(mut self, mode: WasmStrictMode) -> Self {
618 if mode.require_code_execution {
620 self.wasm_executed = false; }
622 if mode.fail_on_console_error {
623 self.console_checked = false; }
625 self
626 }
627
628 #[must_use]
630 pub fn with_wasm_executed(mut self) -> Self {
631 self.wasm_executed = true;
632 self
633 }
634
635 #[must_use]
637 pub fn with_components_registered(mut self) -> Self {
638 self.components_registered = true;
639 self
640 }
641
642 #[must_use]
644 pub fn with_console_checked(mut self) -> Self {
645 self.console_checked = true;
646 self
647 }
648
649 #[must_use]
651 pub fn with_network_verified(mut self) -> Self {
652 self.network_verified = true;
653 self
654 }
655
656 #[must_use]
658 pub fn with_error_paths_tested(mut self) -> Self {
659 self.error_paths_tested = true;
660 self
661 }
662
663 pub fn add_note(&mut self, note: impl Into<String>) {
665 self.notes.push(note.into());
666 }
667
668 pub fn mark_wasm_executed(&mut self) {
670 self.wasm_executed = true;
671 }
672
673 pub fn mark_components_registered(&mut self) {
675 self.components_registered = true;
676 }
677
678 pub fn mark_console_checked(&mut self) {
680 self.console_checked = true;
681 }
682
683 pub fn mark_network_verified(&mut self) {
685 self.network_verified = true;
686 }
687
688 pub fn mark_error_paths_tested(&mut self) {
690 self.error_paths_tested = true;
691 }
692
693 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 #[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#[derive(Debug, Clone)]
740pub enum ChecklistError {
741 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 #[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 for i in 0..100 {
820 capture.record(ConsoleMessage::new(
821 ConsoleSeverity::Warn,
822 format!("Warning {i}"),
823 ));
824 }
825
826 let result = capture.validate();
828 assert!(result.is_err());
829 matches!(
830 result.unwrap_err(),
831 ConsoleValidationError::TooManyWarnings { .. }
832 );
833 }
834
835 #[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 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 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 #[test]
907 fn f071_custom_element_undefined_detected() {
908 let checklist = E2ETestChecklist::new();
910 assert!(!checklist.components_registered);
911 }
912
913 #[test]
914 fn f072_late_registration_retry() {
915 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 let name = "mycomponent"; assert!(!name.contains('-'));
927 }
928
929 #[test]
930 fn f074_shadow_dom_absence_detected() {
931 let checklist = E2ETestChecklist::new();
933 assert!(!checklist.wasm_executed);
935 }
936
937 #[test]
938 fn f075_upgrade_pending_wait() {
939 let mut checklist = E2ETestChecklist::new();
941 checklist.mark_wasm_executed();
943 assert!(checklist.wasm_executed);
944 }
945
946 #[test]
947 fn f076_empty_render_detected() {
948 let checklist = E2ETestChecklist::new();
950 assert!(checklist.validate().is_err());
952 }
953
954 #[test]
955 fn f077_callback_error_captured() {
956 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 let mut checklist = E2ETestChecklist::new();
970 checklist.mark_components_registered();
971 checklist.mark_wasm_executed();
972 assert!(checklist.wasm_executed);
974 assert!(checklist.components_registered);
975 }
976
977 #[test]
978 fn f079_slot_content_verified() {
979 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 let mut checklist = E2ETestChecklist::new();
989 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 #[test]
1003 fn f081_memory_limits_enforced() {
1004 let mode = WasmStrictMode::production();
1006 assert!(mode.simulate_low_memory);
1007 }
1008
1009 #[test]
1010 fn f082_memory_exceed_trap() {
1011 let mode = WasmStrictMode::default();
1013 assert!(!mode.simulate_low_memory);
1015 }
1016
1017 #[test]
1018 fn f083_leak_detection_1000_frames() {
1019 let mode = WasmStrictMode::production();
1021 assert!(mode.require_code_execution);
1022 }
1023
1024 #[test]
1025 fn f084_50mb_heap_graceful() {
1026 let mode = WasmStrictMode::development();
1028 assert!(!mode.simulate_low_memory);
1030 }
1031
1032 #[test]
1033 fn f085_concurrent_alloc_no_corruption() {
1034 let mode = WasmStrictMode::production();
1036 assert!(mode.test_both_threading_modes);
1037 }
1038
1039 #[test]
1040 fn f086_frame_budget_exceeded_warning() {
1041 let mode = WasmStrictMode::default();
1043 assert_eq!(mode.max_console_warnings, 0);
1044 }
1045
1046 #[test]
1047 fn f087_startup_regression_detected() {
1048 let mode = WasmStrictMode::production();
1050 assert!(mode.verify_coop_coep_headers);
1051 }
1052
1053 #[test]
1054 fn f088_memory_growth_leak_suspected() {
1055 let mode = WasmStrictMode::production();
1057 assert!(mode.validate_replay_hash);
1058 }
1059
1060 #[test]
1061 fn f089_gc_pause_jank_identified() {
1062 let mode = WasmStrictMode::default();
1064 assert!(mode.fail_on_console_error);
1065 }
1066
1067 #[test]
1068 fn f090_network_bottleneck_found() {
1069 let mode = WasmStrictMode::production();
1071 assert!(mode.require_cache_hits);
1072 }
1073
1074 #[test]
1079 fn f091_replay_same_seed_byte_exact() {
1080 let mode = WasmStrictMode::production();
1082 assert!(mode.validate_replay_hash);
1083 }
1084
1085 #[test]
1086 fn f092_replay_different_machine_identical() {
1087 let mode = WasmStrictMode::production();
1090 assert!(mode.validate_replay_hash);
1091 }
1092
1093 #[test]
1094 fn f093_replay_wasm_rebuild_hash_fails() {
1095 let mode = WasmStrictMode::default();
1097 assert!(mode.validate_replay_hash);
1099 }
1100
1101 #[test]
1102 fn f094_replay_truncated_playbook_partial() {
1103 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 let mode = WasmStrictMode::production();
1113 assert!(mode.validate_replay_hash);
1114 }
1115
1116 #[test]
1117 fn f096_playbook_version_mismatch_error() {
1118 let checklist = E2ETestChecklist::new();
1120 assert!(checklist.validate().is_err());
1122 }
1123
1124 #[test]
1125 fn f097_playbook_missing_wasm_hash_warning() {
1126 let mode = WasmStrictMode::development();
1128 assert!(!mode.validate_replay_hash);
1130 }
1131
1132 #[test]
1133 fn f098_playbook_wrong_frame_count_detected() {
1134 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 let mode = WasmStrictMode::production();
1144 assert!(mode.require_code_execution);
1145 }
1146
1147 #[test]
1148 fn f100_playbook_empty_valid_noop() {
1149 let checklist = E2ETestChecklist::new();
1151 assert!(!checklist.wasm_executed);
1153 assert!(!checklist.console_checked);
1154 }
1155
1156 #[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 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 #[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(':')); }
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 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 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 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 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 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}