1use crate::result::ProbarResult;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fs;
17use std::path::Path;
18use std::time::{Instant, SystemTime};
19use uuid::Uuid;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct TracingConfig {
24 pub capture_screenshots: bool,
26 pub capture_network: bool,
28 pub capture_console: bool,
30 pub capture_performance: bool,
32 pub max_events: usize,
34 pub include_timestamps: bool,
36}
37
38impl Default for TracingConfig {
39 fn default() -> Self {
40 Self {
41 capture_screenshots: true,
42 capture_network: true,
43 capture_console: true,
44 capture_performance: true,
45 max_events: 10000,
46 include_timestamps: true,
47 }
48 }
49}
50
51impl TracingConfig {
52 #[must_use]
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 #[must_use]
60 pub const fn capture_all(mut self) -> Self {
61 self.capture_screenshots = true;
62 self.capture_network = true;
63 self.capture_console = true;
64 self.capture_performance = true;
65 self
66 }
67
68 #[must_use]
70 pub const fn capture_none(mut self) -> Self {
71 self.capture_screenshots = false;
72 self.capture_network = false;
73 self.capture_console = false;
74 self.capture_performance = false;
75 self
76 }
77
78 #[must_use]
80 pub const fn with_max_events(mut self, max: usize) -> Self {
81 self.max_events = max;
82 self
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct TracedSpan {
89 pub id: String,
91 pub parent_id: Option<String>,
93 pub name: String,
95 pub start_ms: u64,
97 pub end_ms: Option<u64>,
99 pub duration_ms: Option<u64>,
101 pub attributes: HashMap<String, String>,
103 pub status: SpanStatus,
105}
106
107impl TracedSpan {
108 #[must_use]
110 pub fn new(name: &str, start_ms: u64) -> Self {
111 Self {
112 id: Uuid::new_v4().to_string(),
113 parent_id: None,
114 name: name.to_string(),
115 start_ms,
116 end_ms: None,
117 duration_ms: None,
118 attributes: HashMap::new(),
119 status: SpanStatus::Running,
120 }
121 }
122
123 #[must_use]
125 pub fn with_parent(mut self, parent_id: &str) -> Self {
126 self.parent_id = Some(parent_id.to_string());
127 self
128 }
129
130 pub fn add_attribute(&mut self, key: &str, value: &str) {
132 self.attributes.insert(key.to_string(), value.to_string());
133 }
134
135 pub fn end(&mut self, end_ms: u64) {
137 self.end_ms = Some(end_ms);
138 self.duration_ms = Some(end_ms.saturating_sub(self.start_ms));
139 if self.status == SpanStatus::Running {
140 self.status = SpanStatus::Ok;
141 }
142 }
143
144 pub fn mark_error(&mut self, message: &str) {
146 self.status = SpanStatus::Error;
147 self.add_attribute("error.message", message);
148 }
149
150 #[must_use]
152 pub const fn is_complete(&self) -> bool {
153 self.end_ms.is_some()
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
159pub enum SpanStatus {
160 Running,
162 Ok,
164 Error,
166 Cancelled,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct TracedEvent {
173 pub timestamp_ms: u64,
175 pub name: String,
177 pub category: EventCategory,
179 pub level: EventLevel,
181 pub message: String,
183 pub attributes: HashMap<String, serde_json::Value>,
185}
186
187impl TracedEvent {
188 #[must_use]
190 pub fn new(name: &str, category: EventCategory, timestamp_ms: u64) -> Self {
191 Self {
192 timestamp_ms,
193 name: name.to_string(),
194 category,
195 level: EventLevel::Info,
196 message: String::new(),
197 attributes: HashMap::new(),
198 }
199 }
200
201 #[must_use]
203 pub fn with_message(mut self, message: &str) -> Self {
204 self.message = message.to_string();
205 self
206 }
207
208 #[must_use]
210 pub const fn with_level(mut self, level: EventLevel) -> Self {
211 self.level = level;
212 self
213 }
214
215 pub fn add_attribute(&mut self, key: &str, value: serde_json::Value) {
217 self.attributes.insert(key.to_string(), value);
218 }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
223pub enum EventCategory {
224 Test,
226 Interaction,
228 Network,
230 Console,
232 Screenshot,
234 Performance,
236 Assertion,
238 Custom,
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
244pub enum EventLevel {
245 Trace,
247 Debug,
249 Info,
251 Warn,
253 Error,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct NetworkEvent {
260 pub timestamp_ms: u64,
262 pub url: String,
264 pub method: String,
266 pub status: Option<u16>,
268 pub duration_ms: Option<u64>,
270 pub request_size: Option<u64>,
272 pub response_size: Option<u64>,
274 pub content_type: Option<String>,
276 pub failed: bool,
278 pub error: Option<String>,
280}
281
282impl NetworkEvent {
283 #[must_use]
285 pub fn new(url: &str, method: &str, timestamp_ms: u64) -> Self {
286 Self {
287 timestamp_ms,
288 url: url.to_string(),
289 method: method.to_string(),
290 status: None,
291 duration_ms: None,
292 request_size: None,
293 response_size: None,
294 content_type: None,
295 failed: false,
296 error: None,
297 }
298 }
299
300 pub fn complete(&mut self, status: u16, duration_ms: u64) {
302 self.status = Some(status);
303 self.duration_ms = Some(duration_ms);
304 }
305
306 pub fn fail(&mut self, error: &str) {
308 self.failed = true;
309 self.error = Some(error.to_string());
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct ConsoleMessage {
316 pub timestamp_ms: u64,
318 pub level: ConsoleLevel,
320 pub text: String,
322 pub source: Option<String>,
324 pub line: Option<u32>,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
330pub enum ConsoleLevel {
331 Log,
333 Info,
335 Warn,
337 Error,
339 Debug,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct TraceMetadata {
346 pub trace_id: String,
348 pub test_name: String,
350 pub start_time: SystemTime,
352 pub end_time: Option<SystemTime>,
354 pub duration_ms: Option<u64>,
356 pub span_count: usize,
358 pub event_count: usize,
360 pub probar_version: String,
362}
363
364impl TraceMetadata {
365 #[must_use]
367 pub fn new(test_name: &str) -> Self {
368 Self {
369 trace_id: Uuid::new_v4().to_string(),
370 test_name: test_name.to_string(),
371 start_time: SystemTime::now(),
372 end_time: None,
373 duration_ms: None,
374 span_count: 0,
375 event_count: 0,
376 probar_version: env!("CARGO_PKG_VERSION").to_string(),
377 }
378 }
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct TraceArchive {
384 pub metadata: TraceMetadata,
386 pub spans: Vec<TracedSpan>,
388 pub events: Vec<TracedEvent>,
390 pub network_events: Vec<NetworkEvent>,
392 pub console_messages: Vec<ConsoleMessage>,
394}
395
396impl TraceArchive {
397 #[must_use]
399 pub fn new(metadata: TraceMetadata) -> Self {
400 Self {
401 metadata,
402 spans: Vec::new(),
403 events: Vec::new(),
404 network_events: Vec::new(),
405 console_messages: Vec::new(),
406 }
407 }
408
409 pub fn save_json(&self, path: &Path) -> ProbarResult<()> {
411 let json = serde_json::to_string_pretty(self)?;
412
413 if let Some(parent) = path.parent() {
414 fs::create_dir_all(parent)?;
415 }
416
417 fs::write(path, json)?;
418 Ok(())
419 }
420
421 pub fn load_json(path: &Path) -> ProbarResult<Self> {
423 let json = fs::read_to_string(path)?;
424 let archive: TraceArchive = serde_json::from_str(&json)?;
425 Ok(archive)
426 }
427
428 #[must_use]
430 pub fn spans_by_name(&self, name: &str) -> Vec<&TracedSpan> {
431 self.spans.iter().filter(|s| s.name == name).collect()
432 }
433
434 #[must_use]
436 pub fn events_by_category(&self, category: EventCategory) -> Vec<&TracedEvent> {
437 self.events
438 .iter()
439 .filter(|e| e.category == category)
440 .collect()
441 }
442
443 #[must_use]
445 pub fn failed_requests(&self) -> Vec<&NetworkEvent> {
446 self.network_events.iter().filter(|n| n.failed).collect()
447 }
448
449 #[must_use]
451 pub fn error_spans(&self) -> Vec<&TracedSpan> {
452 self.spans
453 .iter()
454 .filter(|s| s.status == SpanStatus::Error)
455 .collect()
456 }
457}
458
459#[derive(Debug)]
461pub struct ExecutionTracer {
462 config: TracingConfig,
463 start_time: Instant,
464 metadata: TraceMetadata,
465 spans: Vec<TracedSpan>,
466 events: Vec<TracedEvent>,
467 network_events: Vec<NetworkEvent>,
468 console_messages: Vec<ConsoleMessage>,
469 current_span_id: Option<String>,
470 running: bool,
471}
472
473impl ExecutionTracer {
474 #[must_use]
476 pub fn new(test_name: &str, config: TracingConfig) -> Self {
477 Self {
478 config,
479 start_time: Instant::now(),
480 metadata: TraceMetadata::new(test_name),
481 spans: Vec::new(),
482 events: Vec::new(),
483 network_events: Vec::new(),
484 console_messages: Vec::new(),
485 current_span_id: None,
486 running: false,
487 }
488 }
489
490 pub fn start(&mut self) {
492 self.running = true;
493 self.start_time = Instant::now();
494 self.metadata.start_time = SystemTime::now();
495 }
496
497 #[must_use]
499 pub fn stop(&mut self) -> TraceArchive {
500 self.running = false;
501 self.metadata.end_time = Some(SystemTime::now());
502 self.metadata.duration_ms = Some(self.elapsed_ms());
503 self.metadata.span_count = self.spans.len();
504 self.metadata.event_count = self.events.len();
505
506 let end_ms = self.elapsed_ms();
508 for span in &mut self.spans {
509 if !span.is_complete() {
510 span.end(end_ms);
511 span.status = SpanStatus::Cancelled;
512 }
513 }
514
515 TraceArchive {
516 metadata: self.metadata.clone(),
517 spans: self.spans.clone(),
518 events: self.events.clone(),
519 network_events: self.network_events.clone(),
520 console_messages: self.console_messages.clone(),
521 }
522 }
523
524 #[must_use]
526 pub fn elapsed_ms(&self) -> u64 {
527 self.start_time.elapsed().as_millis() as u64
528 }
529
530 pub fn start_span(&mut self, name: &str) -> String {
532 let span = TracedSpan::new(name, self.elapsed_ms())
533 .with_parent(self.current_span_id.as_deref().unwrap_or(""));
534
535 let id = span.id.clone();
536 self.current_span_id = Some(id.clone());
537 self.spans.push(span);
538 id
539 }
540
541 pub fn end_span(&mut self, span_id: &str) {
543 let end_ms = self.elapsed_ms();
544 if let Some(span) = self.spans.iter_mut().find(|s| s.id == span_id) {
545 span.end(end_ms);
546
547 if let Some(parent_id) = &span.parent_id {
549 if !parent_id.is_empty() {
550 self.current_span_id = Some(parent_id.clone());
551 } else {
552 self.current_span_id = None;
553 }
554 }
555 }
556 }
557
558 pub fn error_span(&mut self, span_id: &str, message: &str) {
560 if let Some(span) = self.spans.iter_mut().find(|s| s.id == span_id) {
561 span.mark_error(message);
562 }
563 }
564
565 pub fn record_event(&mut self, event: TracedEvent) {
567 if self.events.len() < self.config.max_events {
568 self.events.push(event);
569 }
570 }
571
572 pub fn record_network(&mut self, event: NetworkEvent) {
574 if self.config.capture_network && self.network_events.len() < self.config.max_events {
575 self.network_events.push(event);
576 }
577 }
578
579 pub fn record_console(&mut self, message: ConsoleMessage) {
581 if self.config.capture_console && self.console_messages.len() < self.config.max_events {
582 self.console_messages.push(message);
583 }
584 }
585
586 pub fn info(&mut self, name: &str, message: &str) {
588 let event = TracedEvent::new(name, EventCategory::Custom, self.elapsed_ms())
589 .with_message(message)
590 .with_level(EventLevel::Info);
591 self.record_event(event);
592 }
593
594 pub fn warn(&mut self, name: &str, message: &str) {
596 let event = TracedEvent::new(name, EventCategory::Custom, self.elapsed_ms())
597 .with_message(message)
598 .with_level(EventLevel::Warn);
599 self.record_event(event);
600 }
601
602 pub fn error(&mut self, name: &str, message: &str) {
604 let event = TracedEvent::new(name, EventCategory::Custom, self.elapsed_ms())
605 .with_message(message)
606 .with_level(EventLevel::Error);
607 self.record_event(event);
608 }
609
610 #[must_use]
612 pub const fn is_running(&self) -> bool {
613 self.running
614 }
615
616 #[must_use]
618 pub fn span_count(&self) -> usize {
619 self.spans.len()
620 }
621
622 #[must_use]
624 pub fn event_count(&self) -> usize {
625 self.events.len()
626 }
627}
628
629#[cfg(test)]
630#[allow(clippy::unwrap_used, clippy::expect_used)]
631mod tests {
632 use super::*;
633
634 mod tracing_config_tests {
635 use super::*;
636
637 #[test]
638 fn test_default() {
639 let config = TracingConfig::default();
640 assert!(config.capture_screenshots);
641 assert!(config.capture_network);
642 assert!(config.capture_console);
643 assert!(config.capture_performance);
644 }
645
646 #[test]
647 fn test_capture_all() {
648 let config = TracingConfig::new().capture_none().capture_all();
649 assert!(config.capture_screenshots);
650 assert!(config.capture_network);
651 }
652
653 #[test]
654 fn test_capture_none() {
655 let config = TracingConfig::new().capture_none();
656 assert!(!config.capture_screenshots);
657 assert!(!config.capture_network);
658 assert!(!config.capture_console);
659 assert!(!config.capture_performance);
660 }
661
662 #[test]
663 fn test_with_max_events() {
664 let config = TracingConfig::new().with_max_events(5000);
665 assert_eq!(config.max_events, 5000);
666 }
667 }
668
669 mod traced_span_tests {
670 use super::*;
671
672 #[test]
673 fn test_new() {
674 let span = TracedSpan::new("test_span", 100);
675 assert_eq!(span.name, "test_span");
676 assert_eq!(span.start_ms, 100);
677 assert!(span.parent_id.is_none());
678 assert_eq!(span.status, SpanStatus::Running);
679 }
680
681 #[test]
682 fn test_with_parent() {
683 let span = TracedSpan::new("child", 100).with_parent("parent_id");
684 assert_eq!(span.parent_id, Some("parent_id".to_string()));
685 }
686
687 #[test]
688 fn test_add_attribute() {
689 let mut span = TracedSpan::new("test", 0);
690 span.add_attribute("key", "value");
691 assert_eq!(span.attributes.get("key"), Some(&"value".to_string()));
692 }
693
694 #[test]
695 fn test_end() {
696 let mut span = TracedSpan::new("test", 100);
697 span.end(200);
698
699 assert_eq!(span.end_ms, Some(200));
700 assert_eq!(span.duration_ms, Some(100));
701 assert_eq!(span.status, SpanStatus::Ok);
702 assert!(span.is_complete());
703 }
704
705 #[test]
706 fn test_mark_error() {
707 let mut span = TracedSpan::new("test", 0);
708 span.mark_error("Something went wrong");
709
710 assert_eq!(span.status, SpanStatus::Error);
711 assert_eq!(
712 span.attributes.get("error.message"),
713 Some(&"Something went wrong".to_string())
714 );
715 }
716 }
717
718 mod traced_event_tests {
719 use super::*;
720
721 #[test]
722 fn test_new() {
723 let event = TracedEvent::new("click", EventCategory::Interaction, 500);
724 assert_eq!(event.name, "click");
725 assert_eq!(event.category, EventCategory::Interaction);
726 assert_eq!(event.timestamp_ms, 500);
727 }
728
729 #[test]
730 fn test_with_message() {
731 let event =
732 TracedEvent::new("test", EventCategory::Test, 0).with_message("Test started");
733 assert_eq!(event.message, "Test started");
734 }
735
736 #[test]
737 fn test_with_level() {
738 let event =
739 TracedEvent::new("test", EventCategory::Test, 0).with_level(EventLevel::Error);
740 assert_eq!(event.level, EventLevel::Error);
741 }
742
743 #[test]
744 fn test_add_attribute() {
745 let mut event = TracedEvent::new("test", EventCategory::Test, 0);
746 event.add_attribute("count", serde_json::json!(42));
747 assert_eq!(event.attributes.get("count"), Some(&serde_json::json!(42)));
748 }
749 }
750
751 mod network_event_tests {
752 use super::*;
753
754 #[test]
755 fn test_new() {
756 let event = NetworkEvent::new("https://example.com", "GET", 1000);
757 assert_eq!(event.url, "https://example.com");
758 assert_eq!(event.method, "GET");
759 assert_eq!(event.timestamp_ms, 1000);
760 assert!(!event.failed);
761 }
762
763 #[test]
764 fn test_complete() {
765 let mut event = NetworkEvent::new("https://example.com", "GET", 1000);
766 event.complete(200, 150);
767
768 assert_eq!(event.status, Some(200));
769 assert_eq!(event.duration_ms, Some(150));
770 }
771
772 #[test]
773 fn test_fail() {
774 let mut event = NetworkEvent::new("https://example.com", "GET", 1000);
775 event.fail("Connection timeout");
776
777 assert!(event.failed);
778 assert_eq!(event.error, Some("Connection timeout".to_string()));
779 }
780 }
781
782 mod trace_archive_tests {
783 use super::*;
784 use tempfile::TempDir;
785
786 #[test]
787 fn test_new() {
788 let metadata = TraceMetadata::new("test");
789 let archive = TraceArchive::new(metadata);
790
791 assert!(archive.spans.is_empty());
792 assert!(archive.events.is_empty());
793 }
794
795 #[test]
796 fn test_spans_by_name() {
797 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
798 archive.spans.push(TracedSpan::new("click", 0));
799 archive.spans.push(TracedSpan::new("type", 100));
800 archive.spans.push(TracedSpan::new("click", 200));
801
802 let clicks = archive.spans_by_name("click");
803 assert_eq!(clicks.len(), 2);
804 }
805
806 #[test]
807 fn test_events_by_category() {
808 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
809 archive
810 .events
811 .push(TracedEvent::new("e1", EventCategory::Network, 0));
812 archive
813 .events
814 .push(TracedEvent::new("e2", EventCategory::Console, 100));
815 archive
816 .events
817 .push(TracedEvent::new("e3", EventCategory::Network, 200));
818
819 let network = archive.events_by_category(EventCategory::Network);
820 assert_eq!(network.len(), 2);
821 }
822
823 #[test]
824 fn test_failed_requests() {
825 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
826
827 let mut success = NetworkEvent::new("https://example.com", "GET", 0);
828 success.complete(200, 100);
829 archive.network_events.push(success);
830
831 let mut failure = NetworkEvent::new("https://error.com", "GET", 100);
832 failure.fail("404");
833 archive.network_events.push(failure);
834
835 let failed = archive.failed_requests();
836 assert_eq!(failed.len(), 1);
837 }
838
839 #[test]
840 fn test_error_spans() {
841 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
842
843 let mut ok_span = TracedSpan::new("ok", 0);
844 ok_span.end(100);
845 archive.spans.push(ok_span);
846
847 let mut error_span = TracedSpan::new("error", 100);
848 error_span.mark_error("Failed");
849 archive.spans.push(error_span);
850
851 let errors = archive.error_spans();
852 assert_eq!(errors.len(), 1);
853 }
854
855 #[test]
856 fn test_save_and_load() {
857 let temp_dir = TempDir::new().unwrap();
858 let path = temp_dir.path().join("trace.json");
859
860 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
861 archive.spans.push(TracedSpan::new("span1", 0));
862 archive
863 .events
864 .push(TracedEvent::new("event1", EventCategory::Test, 0));
865
866 archive.save_json(&path).unwrap();
867 assert!(path.exists());
868
869 let loaded = TraceArchive::load_json(&path).unwrap();
870 assert_eq!(loaded.spans.len(), 1);
871 assert_eq!(loaded.events.len(), 1);
872 }
873 }
874
875 mod execution_tracer_tests {
876 use super::*;
877
878 #[test]
879 fn test_new() {
880 let tracer = ExecutionTracer::new("test", TracingConfig::default());
881 assert!(!tracer.is_running());
882 assert_eq!(tracer.span_count(), 0);
883 assert_eq!(tracer.event_count(), 0);
884 }
885
886 #[test]
887 fn test_start_stop() {
888 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
889 tracer.start();
890 assert!(tracer.is_running());
891
892 let archive = tracer.stop();
893 assert!(!tracer.is_running());
894 assert!(archive.metadata.duration_ms.is_some());
895 }
896
897 #[test]
898 fn test_start_and_end_span() {
899 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
900 tracer.start();
901
902 let span_id = tracer.start_span("my_span");
903 assert_eq!(tracer.span_count(), 1);
904
905 tracer.end_span(&span_id);
906
907 let archive = tracer.stop();
908 assert!(archive.spans[0].is_complete());
909 }
910
911 #[test]
912 fn test_nested_spans() {
913 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
914 tracer.start();
915
916 let parent_id = tracer.start_span("parent");
917 let child_id = tracer.start_span("child");
918
919 tracer.end_span(&child_id);
920 tracer.end_span(&parent_id);
921
922 let archive = tracer.stop();
923 assert_eq!(archive.spans.len(), 2);
924
925 let child = &archive.spans[1];
926 assert!(child.parent_id.is_some());
927 }
928
929 #[test]
930 fn test_info_warn_error() {
931 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
932 tracer.start();
933
934 tracer.info("test", "Info message");
935 tracer.warn("test", "Warning message");
936 tracer.error("test", "Error message");
937
938 let archive = tracer.stop();
939 assert_eq!(archive.events.len(), 3);
940 assert_eq!(archive.events[0].level, EventLevel::Info);
941 assert_eq!(archive.events[1].level, EventLevel::Warn);
942 assert_eq!(archive.events[2].level, EventLevel::Error);
943 }
944
945 #[test]
946 fn test_record_network() {
947 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
948 tracer.start();
949
950 let event = NetworkEvent::new("https://example.com", "GET", tracer.elapsed_ms());
951 tracer.record_network(event);
952
953 let archive = tracer.stop();
954 assert_eq!(archive.network_events.len(), 1);
955 }
956
957 #[test]
958 fn test_record_console() {
959 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
960 tracer.start();
961
962 let message = ConsoleMessage {
963 timestamp_ms: tracer.elapsed_ms(),
964 level: ConsoleLevel::Log,
965 text: "Hello".to_string(),
966 source: None,
967 line: None,
968 };
969 tracer.record_console(message);
970
971 let archive = tracer.stop();
972 assert_eq!(archive.console_messages.len(), 1);
973 }
974
975 #[test]
976 fn test_max_events_limit() {
977 let config = TracingConfig::default().with_max_events(3);
978 let mut tracer = ExecutionTracer::new("test", config);
979 tracer.start();
980
981 for i in 0..10 {
982 tracer.info("test", &format!("Event {}", i));
983 }
984
985 let archive = tracer.stop();
986 assert_eq!(archive.events.len(), 3);
987 }
988 }
989
990 mod h0_tracing_config_tests {
995 use super::*;
996
997 #[test]
998 fn h0_trace_01_config_default_capture_screenshots() {
999 let config = TracingConfig::default();
1000 assert!(config.capture_screenshots);
1001 }
1002
1003 #[test]
1004 fn h0_trace_02_config_default_capture_network() {
1005 let config = TracingConfig::default();
1006 assert!(config.capture_network);
1007 }
1008
1009 #[test]
1010 fn h0_trace_03_config_default_capture_console() {
1011 let config = TracingConfig::default();
1012 assert!(config.capture_console);
1013 }
1014
1015 #[test]
1016 fn h0_trace_04_config_default_capture_performance() {
1017 let config = TracingConfig::default();
1018 assert!(config.capture_performance);
1019 }
1020
1021 #[test]
1022 fn h0_trace_05_config_default_max_events() {
1023 let config = TracingConfig::default();
1024 assert_eq!(config.max_events, 10000);
1025 }
1026
1027 #[test]
1028 fn h0_trace_06_config_default_include_timestamps() {
1029 let config = TracingConfig::default();
1030 assert!(config.include_timestamps);
1031 }
1032
1033 #[test]
1034 fn h0_trace_07_config_capture_none() {
1035 let config = TracingConfig::new().capture_none();
1036 assert!(!config.capture_screenshots);
1037 assert!(!config.capture_network);
1038 assert!(!config.capture_console);
1039 assert!(!config.capture_performance);
1040 }
1041
1042 #[test]
1043 fn h0_trace_08_config_capture_all() {
1044 let config = TracingConfig::new().capture_none().capture_all();
1045 assert!(config.capture_screenshots);
1046 assert!(config.capture_network);
1047 }
1048
1049 #[test]
1050 fn h0_trace_09_config_with_max_events() {
1051 let config = TracingConfig::new().with_max_events(500);
1052 assert_eq!(config.max_events, 500);
1053 }
1054
1055 #[test]
1056 fn h0_trace_10_config_new() {
1057 let config = TracingConfig::new();
1058 assert!(config.capture_screenshots);
1059 }
1060 }
1061
1062 mod h0_span_status_tests {
1063 use super::*;
1064
1065 #[test]
1066 fn h0_trace_11_span_status_running() {
1067 let span = TracedSpan::new("test", 0);
1068 assert_eq!(span.status, SpanStatus::Running);
1069 }
1070
1071 #[test]
1072 fn h0_trace_12_span_status_ok_after_end() {
1073 let mut span = TracedSpan::new("test", 0);
1074 span.end(100);
1075 assert_eq!(span.status, SpanStatus::Ok);
1076 }
1077
1078 #[test]
1079 fn h0_trace_13_span_status_error() {
1080 let mut span = TracedSpan::new("test", 0);
1081 span.mark_error("Failed");
1082 assert_eq!(span.status, SpanStatus::Error);
1083 }
1084
1085 #[test]
1086 fn h0_trace_14_span_new_name() {
1087 let span = TracedSpan::new("my_span", 0);
1088 assert_eq!(span.name, "my_span");
1089 }
1090
1091 #[test]
1092 fn h0_trace_15_span_new_start_ms() {
1093 let span = TracedSpan::new("test", 250);
1094 assert_eq!(span.start_ms, 250);
1095 }
1096
1097 #[test]
1098 fn h0_trace_16_span_with_parent() {
1099 let span = TracedSpan::new("child", 0).with_parent("parent_123");
1100 assert_eq!(span.parent_id, Some("parent_123".to_string()));
1101 }
1102
1103 #[test]
1104 fn h0_trace_17_span_add_attribute() {
1105 let mut span = TracedSpan::new("test", 0);
1106 span.add_attribute("action", "click");
1107 assert_eq!(span.attributes.get("action"), Some(&"click".to_string()));
1108 }
1109
1110 #[test]
1111 fn h0_trace_18_span_end_duration() {
1112 let mut span = TracedSpan::new("test", 100);
1113 span.end(300);
1114 assert_eq!(span.duration_ms, Some(200));
1115 }
1116
1117 #[test]
1118 fn h0_trace_19_span_is_complete_false() {
1119 let span = TracedSpan::new("test", 0);
1120 assert!(!span.is_complete());
1121 }
1122
1123 #[test]
1124 fn h0_trace_20_span_is_complete_true() {
1125 let mut span = TracedSpan::new("test", 0);
1126 span.end(100);
1127 assert!(span.is_complete());
1128 }
1129 }
1130
1131 mod h0_traced_event_tests {
1132 use super::*;
1133
1134 #[test]
1135 fn h0_trace_21_event_new_name() {
1136 let event = TracedEvent::new("test_event", EventCategory::Test, 0);
1137 assert_eq!(event.name, "test_event");
1138 }
1139
1140 #[test]
1141 fn h0_trace_22_event_new_category() {
1142 let event = TracedEvent::new("test", EventCategory::Network, 0);
1143 assert_eq!(event.category, EventCategory::Network);
1144 }
1145
1146 #[test]
1147 fn h0_trace_23_event_new_timestamp() {
1148 let event = TracedEvent::new("test", EventCategory::Test, 500);
1149 assert_eq!(event.timestamp_ms, 500);
1150 }
1151
1152 #[test]
1153 fn h0_trace_24_event_default_level() {
1154 let event = TracedEvent::new("test", EventCategory::Test, 0);
1155 assert_eq!(event.level, EventLevel::Info);
1156 }
1157
1158 #[test]
1159 fn h0_trace_25_event_with_message() {
1160 let event =
1161 TracedEvent::new("test", EventCategory::Test, 0).with_message("Hello world");
1162 assert_eq!(event.message, "Hello world");
1163 }
1164
1165 #[test]
1166 fn h0_trace_26_event_with_level() {
1167 let event =
1168 TracedEvent::new("test", EventCategory::Test, 0).with_level(EventLevel::Error);
1169 assert_eq!(event.level, EventLevel::Error);
1170 }
1171
1172 #[test]
1173 fn h0_trace_27_event_add_attribute() {
1174 let mut event = TracedEvent::new("test", EventCategory::Test, 0);
1175 event.add_attribute("count", serde_json::json!(42));
1176 assert!(event.attributes.contains_key("count"));
1177 }
1178
1179 #[test]
1180 fn h0_trace_28_event_category_interaction() {
1181 let event = TracedEvent::new("click", EventCategory::Interaction, 0);
1182 assert_eq!(event.category, EventCategory::Interaction);
1183 }
1184
1185 #[test]
1186 fn h0_trace_29_event_category_console() {
1187 let event = TracedEvent::new("log", EventCategory::Console, 0);
1188 assert_eq!(event.category, EventCategory::Console);
1189 }
1190
1191 #[test]
1192 fn h0_trace_30_event_level_ordering() {
1193 assert!(EventLevel::Trace < EventLevel::Debug);
1194 assert!(EventLevel::Debug < EventLevel::Info);
1195 assert!(EventLevel::Info < EventLevel::Warn);
1196 assert!(EventLevel::Warn < EventLevel::Error);
1197 }
1198 }
1199
1200 mod h0_network_event_tests {
1201 use super::*;
1202
1203 #[test]
1204 fn h0_trace_31_network_event_new_url() {
1205 let event = NetworkEvent::new("https://api.example.com", "POST", 0);
1206 assert_eq!(event.url, "https://api.example.com");
1207 }
1208
1209 #[test]
1210 fn h0_trace_32_network_event_new_method() {
1211 let event = NetworkEvent::new("https://example.com", "PUT", 0);
1212 assert_eq!(event.method, "PUT");
1213 }
1214
1215 #[test]
1216 fn h0_trace_33_network_event_complete() {
1217 let mut event = NetworkEvent::new("https://example.com", "GET", 0);
1218 event.complete(201, 250);
1219 assert_eq!(event.status, Some(201));
1220 assert_eq!(event.duration_ms, Some(250));
1221 }
1222
1223 #[test]
1224 fn h0_trace_34_network_event_fail() {
1225 let mut event = NetworkEvent::new("https://example.com", "GET", 0);
1226 event.fail("Timeout");
1227 assert!(event.failed);
1228 assert_eq!(event.error, Some("Timeout".to_string()));
1229 }
1230
1231 #[test]
1232 fn h0_trace_35_network_event_not_failed_initially() {
1233 let event = NetworkEvent::new("https://example.com", "GET", 0);
1234 assert!(!event.failed);
1235 }
1236
1237 #[test]
1238 fn h0_trace_36_console_level_log() {
1239 let msg = ConsoleMessage {
1240 timestamp_ms: 0,
1241 level: ConsoleLevel::Log,
1242 text: "test".to_string(),
1243 source: None,
1244 line: None,
1245 };
1246 assert_eq!(msg.level, ConsoleLevel::Log);
1247 }
1248
1249 #[test]
1250 fn h0_trace_37_console_level_warn() {
1251 let msg = ConsoleMessage {
1252 timestamp_ms: 0,
1253 level: ConsoleLevel::Warn,
1254 text: "warning".to_string(),
1255 source: None,
1256 line: None,
1257 };
1258 assert_eq!(msg.level, ConsoleLevel::Warn);
1259 }
1260
1261 #[test]
1262 fn h0_trace_38_console_level_error() {
1263 let msg = ConsoleMessage {
1264 timestamp_ms: 0,
1265 level: ConsoleLevel::Error,
1266 text: "error".to_string(),
1267 source: None,
1268 line: None,
1269 };
1270 assert_eq!(msg.level, ConsoleLevel::Error);
1271 }
1272
1273 #[test]
1274 fn h0_trace_39_console_message_source() {
1275 let msg = ConsoleMessage {
1276 timestamp_ms: 0,
1277 level: ConsoleLevel::Log,
1278 text: "test".to_string(),
1279 source: Some("main.js".to_string()),
1280 line: Some(42),
1281 };
1282 assert_eq!(msg.source, Some("main.js".to_string()));
1283 assert_eq!(msg.line, Some(42));
1284 }
1285
1286 #[test]
1287 fn h0_trace_40_trace_metadata_new() {
1288 let metadata = TraceMetadata::new("my_test");
1289 assert_eq!(metadata.test_name, "my_test");
1290 assert!(!metadata.trace_id.is_empty());
1291 }
1292 }
1293
1294 mod h0_execution_tracer_tests {
1295 use super::*;
1296
1297 #[test]
1298 fn h0_trace_41_tracer_new_not_running() {
1299 let tracer = ExecutionTracer::new("test", TracingConfig::default());
1300 assert!(!tracer.is_running());
1301 }
1302
1303 #[test]
1304 fn h0_trace_42_tracer_start_running() {
1305 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1306 tracer.start();
1307 assert!(tracer.is_running());
1308 }
1309
1310 #[test]
1311 fn h0_trace_43_tracer_stop_not_running() {
1312 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1313 tracer.start();
1314 let _ = tracer.stop();
1315 assert!(!tracer.is_running());
1316 }
1317
1318 #[test]
1319 fn h0_trace_44_tracer_span_count_initial() {
1320 let tracer = ExecutionTracer::new("test", TracingConfig::default());
1321 assert_eq!(tracer.span_count(), 0);
1322 }
1323
1324 #[test]
1325 fn h0_trace_45_tracer_event_count_initial() {
1326 let tracer = ExecutionTracer::new("test", TracingConfig::default());
1327 assert_eq!(tracer.event_count(), 0);
1328 }
1329
1330 #[test]
1331 fn h0_trace_46_tracer_start_span() {
1332 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1333 tracer.start();
1334 let span_id = tracer.start_span("action");
1335 assert!(!span_id.is_empty());
1336 assert_eq!(tracer.span_count(), 1);
1337 }
1338
1339 #[test]
1340 fn h0_trace_47_tracer_info_event() {
1341 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1342 tracer.start();
1343 tracer.info("test", "Info message");
1344 assert_eq!(tracer.event_count(), 1);
1345 }
1346
1347 #[test]
1348 fn h0_trace_48_tracer_warn_event() {
1349 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1350 tracer.start();
1351 tracer.warn("test", "Warning message");
1352 let archive = tracer.stop();
1353 assert_eq!(archive.events[0].level, EventLevel::Warn);
1354 }
1355
1356 #[test]
1357 fn h0_trace_49_tracer_error_event() {
1358 let mut tracer = ExecutionTracer::new("test", TracingConfig::default());
1359 tracer.start();
1360 tracer.error("test", "Error message");
1361 let archive = tracer.stop();
1362 assert_eq!(archive.events[0].level, EventLevel::Error);
1363 }
1364
1365 #[test]
1366 fn h0_trace_50_tracer_archive_metadata() {
1367 let mut tracer = ExecutionTracer::new("my_test", TracingConfig::default());
1368 tracer.start();
1369 let archive = tracer.stop();
1370 assert_eq!(archive.metadata.test_name, "my_test");
1371 assert!(archive.metadata.duration_ms.is_some());
1372 }
1373 }
1374
1375 mod h0_archive_tests {
1376 use super::*;
1377
1378 #[test]
1379 fn h0_trace_51_archive_new_empty() {
1380 let archive = TraceArchive::new(TraceMetadata::new("test"));
1381 assert!(archive.spans.is_empty());
1382 assert!(archive.events.is_empty());
1383 assert!(archive.network_events.is_empty());
1384 assert!(archive.console_messages.is_empty());
1385 }
1386
1387 #[test]
1388 fn h0_trace_52_archive_spans_by_name() {
1389 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1390 archive.spans.push(TracedSpan::new("click", 0));
1391 archive.spans.push(TracedSpan::new("click", 100));
1392 let clicks = archive.spans_by_name("click");
1393 assert_eq!(clicks.len(), 2);
1394 }
1395
1396 #[test]
1397 fn h0_trace_53_archive_events_by_category() {
1398 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1399 archive
1400 .events
1401 .push(TracedEvent::new("e1", EventCategory::Network, 0));
1402 archive
1403 .events
1404 .push(TracedEvent::new("e2", EventCategory::Network, 50));
1405 let network = archive.events_by_category(EventCategory::Network);
1406 assert_eq!(network.len(), 2);
1407 }
1408
1409 #[test]
1410 fn h0_trace_54_archive_failed_requests() {
1411 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1412 let mut failed = NetworkEvent::new("https://fail.com", "GET", 0);
1413 failed.fail("404");
1414 archive.network_events.push(failed);
1415 assert_eq!(archive.failed_requests().len(), 1);
1416 }
1417
1418 #[test]
1419 fn h0_trace_55_archive_error_spans() {
1420 let mut archive = TraceArchive::new(TraceMetadata::new("test"));
1421 let mut error_span = TracedSpan::new("err", 0);
1422 error_span.mark_error("Failed");
1423 archive.spans.push(error_span);
1424 assert_eq!(archive.error_spans().len(), 1);
1425 }
1426
1427 #[test]
1428 fn h0_trace_56_event_category_screenshot() {
1429 let event = TracedEvent::new("capture", EventCategory::Screenshot, 0);
1430 assert_eq!(event.category, EventCategory::Screenshot);
1431 }
1432
1433 #[test]
1434 fn h0_trace_57_event_category_performance() {
1435 let event = TracedEvent::new("metric", EventCategory::Performance, 0);
1436 assert_eq!(event.category, EventCategory::Performance);
1437 }
1438
1439 #[test]
1440 fn h0_trace_58_event_category_assertion() {
1441 let event = TracedEvent::new("assert", EventCategory::Assertion, 0);
1442 assert_eq!(event.category, EventCategory::Assertion);
1443 }
1444
1445 #[test]
1446 fn h0_trace_59_event_category_custom() {
1447 let event = TracedEvent::new("custom", EventCategory::Custom, 0);
1448 assert_eq!(event.category, EventCategory::Custom);
1449 }
1450
1451 #[test]
1452 fn h0_trace_60_console_level_debug() {
1453 let msg = ConsoleMessage {
1454 timestamp_ms: 0,
1455 level: ConsoleLevel::Debug,
1456 text: "debug".to_string(),
1457 source: None,
1458 line: None,
1459 };
1460 assert_eq!(msg.level, ConsoleLevel::Debug);
1461 }
1462 }
1463}