1#![warn(missing_docs)]
21#![cfg_attr(test, allow(clippy::large_stack_arrays, clippy::large_stack_frames))]
24
25#[allow(
29 clippy::missing_errors_doc,
30 clippy::must_use_candidate,
31 clippy::missing_const_for_fn,
32 clippy::doc_markdown
33)]
34pub mod brick;
35
36#[allow(
40 clippy::missing_errors_doc,
41 clippy::must_use_candidate,
42 clippy::missing_const_for_fn,
43 clippy::doc_markdown,
44 clippy::expect_used
45)]
46pub mod brick_house;
47
48#[allow(
49 clippy::suboptimal_flops,
50 clippy::cast_precision_loss,
51 clippy::struct_excessive_bools,
52 clippy::missing_errors_doc,
53 clippy::must_use_candidate,
54 clippy::missing_const_for_fn,
55 clippy::unnecessary_wraps,
56 clippy::doc_markdown
57)]
58mod accessibility;
59mod assertion;
60#[allow(
61 clippy::missing_errors_doc,
62 clippy::must_use_candidate,
63 clippy::missing_const_for_fn,
64 clippy::doc_markdown
65)]
66mod bridge;
67mod browser;
68#[allow(
69 clippy::missing_errors_doc,
70 clippy::must_use_candidate,
71 clippy::missing_const_for_fn,
72 clippy::doc_markdown,
73 dead_code
74)]
75mod driver;
76mod event;
77mod fuzzer;
78mod harness;
79#[allow(
80 clippy::missing_errors_doc,
81 clippy::must_use_candidate,
82 clippy::missing_const_for_fn,
83 clippy::unnecessary_wraps,
84 clippy::doc_markdown
85)]
86mod locator;
87#[allow(
88 clippy::missing_errors_doc,
89 clippy::must_use_candidate,
90 clippy::missing_const_for_fn,
91 clippy::doc_markdown,
92 clippy::cast_precision_loss,
93 clippy::format_push_string,
94 clippy::needless_raw_string_hashes
95)]
96mod reporter;
97mod result;
98#[allow(
99 clippy::missing_errors_doc,
100 clippy::must_use_candidate,
101 clippy::missing_const_for_fn,
102 clippy::unnecessary_wraps,
103 clippy::doc_markdown,
104 clippy::if_not_else,
105 clippy::ptr_as_ptr,
106 clippy::expect_used,
107 unsafe_code
108)]
109mod runtime;
110mod simulation;
111mod snapshot;
112#[cfg(feature = "media")]
113mod visual_regression;
114
115#[allow(
119 clippy::missing_errors_doc,
120 clippy::must_use_candidate,
121 clippy::missing_const_for_fn,
122 clippy::doc_markdown
123)]
124pub mod lint;
125
126#[allow(
130 clippy::missing_errors_doc,
131 clippy::must_use_candidate,
132 clippy::missing_const_for_fn,
133 clippy::doc_markdown
134)]
135pub mod mock;
136
137#[allow(
141 clippy::missing_errors_doc,
142 clippy::must_use_candidate,
143 clippy::missing_const_for_fn,
144 clippy::doc_markdown
145)]
146pub mod comply;
147
148#[allow(
150 clippy::missing_errors_doc,
151 clippy::must_use_candidate,
152 clippy::missing_const_for_fn,
153 clippy::doc_markdown
154)]
155mod page_object;
156
157#[allow(
159 clippy::missing_errors_doc,
160 clippy::must_use_candidate,
161 clippy::missing_const_for_fn,
162 clippy::doc_markdown
163)]
164mod fixture;
165
166#[cfg(feature = "tui")]
168#[allow(
169 clippy::missing_errors_doc,
170 clippy::must_use_candidate,
171 clippy::missing_const_for_fn,
172 clippy::doc_markdown
173)]
174pub mod tui;
175
176#[allow(
181 clippy::missing_errors_doc,
182 clippy::must_use_candidate,
183 clippy::missing_const_for_fn,
184 clippy::doc_markdown
185)]
186pub mod tui_load;
187
188#[allow(
190 clippy::missing_errors_doc,
191 clippy::must_use_candidate,
192 clippy::missing_const_for_fn,
193 clippy::doc_markdown
194)]
195pub mod replay;
196
197#[allow(
199 clippy::missing_errors_doc,
200 clippy::must_use_candidate,
201 clippy::missing_const_for_fn,
202 clippy::doc_markdown
203)]
204pub mod ux_coverage;
205
206#[allow(
208 clippy::missing_errors_doc,
209 clippy::must_use_candidate,
210 clippy::missing_const_for_fn,
211 clippy::doc_markdown
212)]
213pub mod emulation;
214
215#[cfg(feature = "media")]
217#[allow(
218 clippy::missing_errors_doc,
219 clippy::must_use_candidate,
220 clippy::missing_const_for_fn,
221 clippy::doc_markdown,
222 clippy::cast_possible_truncation
223)]
224pub mod media;
225
226#[cfg(all(not(target_arch = "wasm32"), feature = "watch"))]
229#[allow(
230 clippy::missing_errors_doc,
231 clippy::must_use_candidate,
232 clippy::missing_const_for_fn,
233 clippy::doc_markdown
234)]
235pub mod watch;
236
237#[allow(
239 clippy::missing_errors_doc,
240 clippy::must_use_candidate,
241 clippy::missing_const_for_fn,
242 clippy::doc_markdown,
243 clippy::cast_possible_truncation
244)]
245pub mod tracing_support;
246
247#[allow(
249 clippy::missing_errors_doc,
250 clippy::must_use_candidate,
251 clippy::missing_const_for_fn,
252 clippy::doc_markdown
253)]
254pub mod network;
255
256#[allow(
258 clippy::missing_errors_doc,
259 clippy::must_use_candidate,
260 clippy::missing_const_for_fn,
261 clippy::doc_markdown
262)]
263pub mod wait;
264
265#[allow(
267 clippy::missing_errors_doc,
268 clippy::must_use_candidate,
269 clippy::missing_const_for_fn,
270 clippy::doc_markdown
271)]
272pub mod websocket;
273
274#[allow(
276 clippy::missing_errors_doc,
277 clippy::must_use_candidate,
278 clippy::missing_const_for_fn,
279 clippy::doc_markdown,
280 clippy::cast_possible_truncation
281)]
282pub mod performance;
283
284#[allow(
286 clippy::missing_errors_doc,
287 clippy::must_use_candidate,
288 clippy::missing_const_for_fn,
289 clippy::doc_markdown
290)]
291pub mod context;
292
293#[allow(
295 clippy::module_name_repetitions,
296 clippy::must_use_candidate,
297 clippy::missing_const_for_fn,
298 clippy::missing_errors_doc,
299 clippy::doc_markdown,
300 clippy::cast_possible_truncation,
301 clippy::cast_precision_loss,
302 clippy::use_self,
303 clippy::inline_always,
304 clippy::similar_names,
305 clippy::missing_panics_doc,
306 clippy::suboptimal_flops,
307 clippy::uninlined_format_args,
308 clippy::redundant_closure_for_method_calls
309)]
310pub mod coverage;
311
312#[allow(
314 clippy::missing_errors_doc,
315 clippy::must_use_candidate,
316 clippy::missing_const_for_fn,
317 clippy::doc_markdown
318)]
319pub mod web;
320
321#[cfg(feature = "media")]
325#[allow(
326 clippy::missing_errors_doc,
327 clippy::must_use_candidate,
328 clippy::missing_const_for_fn,
329 clippy::doc_markdown,
330 clippy::cast_precision_loss
331)]
332pub mod pixel_coverage;
333
334#[allow(
336 clippy::missing_errors_doc,
337 clippy::must_use_candidate,
338 clippy::missing_const_for_fn,
339 clippy::doc_markdown
340)]
341pub mod gpu_pixels;
342
343#[allow(
345 clippy::missing_errors_doc,
346 clippy::must_use_candidate,
347 clippy::missing_const_for_fn,
348 clippy::doc_markdown
349)]
350pub mod runner;
351
352#[allow(
354 clippy::missing_errors_doc,
355 clippy::must_use_candidate,
356 clippy::missing_const_for_fn,
357 clippy::doc_markdown,
358 clippy::cast_precision_loss
359)]
360pub mod perf;
361
362#[allow(
364 clippy::missing_errors_doc,
365 clippy::must_use_candidate,
366 clippy::missing_const_for_fn,
367 clippy::doc_markdown
368)]
369pub mod renacer_integration;
370
371#[allow(
373 clippy::missing_errors_doc,
374 clippy::must_use_candidate,
375 clippy::missing_const_for_fn,
376 clippy::doc_markdown
377)]
378pub mod cdp_coverage;
379
380#[allow(
382 clippy::missing_errors_doc,
383 clippy::must_use_candidate,
384 clippy::missing_const_for_fn,
385 clippy::doc_markdown
386)]
387pub mod shard;
388
389#[allow(
391 clippy::missing_errors_doc,
392 clippy::must_use_candidate,
393 clippy::missing_const_for_fn,
394 clippy::doc_markdown
395)]
396pub mod clock;
397
398#[allow(
400 clippy::missing_errors_doc,
401 clippy::must_use_candidate,
402 clippy::missing_const_for_fn,
403 clippy::doc_markdown
404)]
405pub mod capabilities;
406
407#[allow(
409 clippy::missing_errors_doc,
410 clippy::must_use_candidate,
411 clippy::missing_const_for_fn,
412 clippy::doc_markdown
413)]
414pub mod strict;
415
416#[allow(
418 clippy::missing_errors_doc,
419 clippy::must_use_candidate,
420 clippy::missing_const_for_fn,
421 clippy::doc_markdown
422)]
423pub mod validators;
424
425#[allow(
429 clippy::missing_errors_doc,
430 clippy::must_use_candidate,
431 clippy::missing_const_for_fn,
432 clippy::doc_markdown,
433 clippy::too_long_first_doc_paragraph
434)]
435pub mod zero_js;
436
437#[allow(
441 clippy::missing_errors_doc,
442 clippy::must_use_candidate,
443 clippy::missing_const_for_fn,
444 clippy::doc_markdown,
445 clippy::too_long_first_doc_paragraph
446)]
447pub mod worker_harness;
448
449#[cfg(feature = "docker")]
453#[allow(
454 clippy::missing_errors_doc,
455 clippy::must_use_candidate,
456 clippy::missing_const_for_fn,
457 clippy::doc_markdown,
458 clippy::too_long_first_doc_paragraph
459)]
460pub mod docker;
461
462#[allow(
464 clippy::missing_errors_doc,
465 clippy::must_use_candidate,
466 clippy::missing_const_for_fn,
467 clippy::doc_markdown
468)]
469pub mod dialog;
470
471#[allow(
473 clippy::missing_errors_doc,
474 clippy::must_use_candidate,
475 clippy::missing_const_for_fn,
476 clippy::doc_markdown
477)]
478pub mod file_ops;
479
480#[allow(
482 clippy::missing_docs_in_private_items,
483 clippy::missing_errors_doc,
484 clippy::must_use_candidate,
485 clippy::missing_const_for_fn,
486 clippy::doc_markdown
487)]
488pub mod har;
489
490#[allow(
493 clippy::missing_errors_doc,
494 clippy::must_use_candidate,
495 clippy::missing_const_for_fn,
496 clippy::doc_markdown,
497 clippy::expect_used,
498 clippy::many_single_char_names,
499 clippy::suspicious_operation_groupings,
500 missing_docs,
501 missing_debug_implementations
502)]
503pub mod playbook;
504
505#[allow(
507 clippy::missing_errors_doc,
508 clippy::must_use_candidate,
509 clippy::missing_const_for_fn,
510 clippy::doc_markdown
511)]
512pub mod av_sync;
513
514#[allow(
516 clippy::missing_errors_doc,
517 clippy::must_use_candidate,
518 clippy::missing_const_for_fn,
519 clippy::doc_markdown,
520 clippy::cast_precision_loss
521)]
522pub mod audio_quality;
523
524#[allow(
526 clippy::missing_errors_doc,
527 clippy::must_use_candidate,
528 clippy::missing_const_for_fn,
529 clippy::doc_markdown,
530 clippy::cast_precision_loss
531)]
532pub mod video_quality;
533
534#[allow(
536 clippy::missing_errors_doc,
537 clippy::must_use_candidate,
538 clippy::missing_const_for_fn,
539 clippy::doc_markdown
540)]
541pub mod animation;
542
543#[allow(
548 clippy::missing_errors_doc,
549 clippy::must_use_candidate,
550 clippy::missing_const_for_fn,
551 clippy::doc_markdown
552)]
553pub mod presentar;
554
555pub use accessibility::{
556 AccessibilityAudit, AccessibilityConfig, AccessibilityIssue, AccessibilityValidator, Color,
557 ContrastAnalysis, ContrastPair, FlashDetector, FlashResult, FocusConfig, KeyboardIssue,
558 Severity, MIN_CONTRAST_LARGE, MIN_CONTRAST_NORMAL, MIN_CONTRAST_UI,
559};
560pub use assertion::{
561 retry_contains, retry_eq, retry_none, retry_some, retry_true, Assertion, AssertionCheckResult,
562 AssertionFailure, AssertionMode, AssertionResult, AssertionSummary, EnergyVerifier,
563 EquationContext, EquationResult, EquationVerifier, InvariantVerifier, KinematicVerifier,
564 MomentumVerifier, RetryAssertion, RetryConfig, RetryError, RetryResult, SoftAssertionError,
565 SoftAssertions, Variable,
566};
567pub use bridge::{
568 BridgeConnection, DiffRegion, EntitySnapshot, GameStateData, GameStateSnapshot, SnapshotCache,
569 StateBridge, VisualDiff,
570};
571pub use browser::{Browser, BrowserConfig, BrowserConsoleLevel, BrowserConsoleMessage, Page};
572pub use capabilities::{
573 CapabilityError, CapabilityStatus, RequiredHeaders, WasmThreadCapabilities, WorkerEmulator,
574 WorkerMessage, WorkerState,
575};
576pub use cdp_coverage::{
577 CoverageConfig, CoverageRange, CoverageReport, CoveredFunction, FunctionCoverage, JsCoverage,
578 LineCoverage, ScriptCoverage, SourceMapEntry, WasmCoverage, WasmSourceMap,
579};
580pub use clock::{
581 create_clock, Clock, ClockController, ClockError, ClockOptions, ClockState, FakeClock,
582};
583pub use context::{
584 BrowserContext, ContextConfig, ContextManager, ContextPool, ContextPoolStats, ContextState,
585 Cookie, Geolocation, SameSite, StorageState,
586};
587pub use dialog::{
588 AutoDialogBehavior, Dialog, DialogAction, DialogExpectation, DialogHandler,
589 DialogHandlerBuilder, DialogType,
590};
591#[cfg(feature = "browser")]
592pub use driver::{BrowserController, ProbarDriver};
593pub use driver::{
594 DeviceDescriptor, DriverConfig, ElementHandle, MockDriver, NetworkInterceptor, NetworkResponse,
595 PageMetrics, Screenshot,
596};
597pub use event::{InputEvent, Touch, TouchAction};
598pub use file_ops::{
599 guess_mime_type, Download, DownloadManager, DownloadState, FileChooser, FileInput,
600};
601pub use fixture::{
602 Fixture, FixtureBuilder, FixtureManager, FixtureScope, FixtureState, SimpleFixture,
603};
604pub use fuzzer::{
605 FuzzerConfig, InputFuzzer, InvariantCheck, InvariantChecker, InvariantViolation, Seed,
606};
607pub use har::{
608 Har, HarBrowser, HarCache, HarContent, HarCookie, HarCreator, HarEntry, HarError, HarHeader,
609 HarLog, HarOptions, HarPlayer, HarPostData, HarPostParam, HarQueryParam, HarRecorder,
610 HarRequest, HarResponse, HarTimings, NotFoundBehavior,
611};
612pub use harness::{TestCase, TestHarness, TestResult, TestSuite};
613pub use locator::{
614 expect, BoundingBox, DragBuilder, DragOperation, Expect, ExpectAssertion, Locator,
615 LocatorAction, LocatorOptions, LocatorQuery, Point, Selector, DEFAULT_POLL_INTERVAL_MS,
616 DEFAULT_TIMEOUT_MS,
617};
618pub use network::{
619 CapturedRequest, HttpMethod, MockResponse, NetworkInterception, NetworkInterceptionBuilder,
620 Route, UrlPattern,
621};
622pub use page_object::{
623 PageObject, PageObjectBuilder, PageObjectInfo, PageRegistry, SimplePageObject, UrlMatcher,
624};
625pub use performance::{
626 Measurement, MetricStats, MetricType, PerformanceMonitor, PerformanceProfile,
627 PerformanceProfiler, PerformanceProfilerBuilder, PerformanceSummary, PerformanceThreshold,
628};
629pub use playbook::{
630 calculate_mutation_score, check_complexity_violation, to_dot, Action as PlaybookAction,
631 ActionExecutor, Assertion as PlaybookAssertion, AssertionFailure as PlaybookAssertionFailure,
632 ComplexityAnalyzer, ComplexityClass, ComplexityResult, DeterminismInfo,
633 ExecutionResult as PlaybookExecutionResult, ExecutorError, Invariant, IssueSeverity,
634 MutantResult, MutationClass, MutationGenerator, MutationScore, PerformanceBudget, Playbook,
635 PlaybookError, PlaybookExecutor, ReachabilityInfo, State as PlaybookState, StateMachine,
636 StateMachineValidator, Transition as PlaybookTransition, ValidationIssue, ValidationResult,
637 WaitCondition as PlaybookWaitCondition,
638};
639pub use presentar::{
640 generate_falsification_playbook, parse_and_validate as parse_and_validate_presentar,
641 validate_config as validate_presentar_config, Cell as PresentarCell, Color as PresentarColor,
642 FalsificationCheck, FalsificationResult, KeybindingConfig, LayoutConfig, PanelConfig,
643 PanelConfigs, PanelType, PresentarConfig, PresentarError, TerminalAssertion, TerminalSnapshot,
644 ThemeConfig, ValidationResult as PresentarValidationResult, FALSIFICATION_COUNT,
645 SCHEMA_VERSION,
646};
647pub use renacer_integration::{
648 ChromeTrace, ChromeTraceEvent, TraceCollector, TraceContext, TraceSpan,
649 TracingConfig as RenacerTracingConfig,
650};
651pub use replay::{
652 Replay, ReplayHeader, ReplayPlayer, ReplayRecorder, StateCheckpoint, TimedInput,
653 VerificationResult, REPLAY_FORMAT_VERSION,
654};
655pub use reporter::{
656 AndonCordPulled, FailureMode, Reporter, TestResultEntry, TestStatus, TraceData,
657};
658pub use av_sync::{
659 compare_edl_to_onsets, detect_onsets, default_edl_path, extract_audio, AudioOnset,
660 AudioTickPlacement, AvSyncReport, DetectionConfig, EditDecision, EditDecisionList,
661 SegmentSyncResult, SyncVerdict, TickDelta, DEFAULT_SAMPLE_RATE,
662};
663pub use audio_quality::{
664 analyze_audio, analyze_samples, detect_clipping, detect_silence, AudioLevels,
665 AudioQualityConfig, AudioQualityReport, AudioVerdict, ClippingReport, SilenceRegion,
666 SilenceReport,
667};
668pub use video_quality::{
669 build_ffprobe_args, parse_ffprobe_json, probe_video, validate_video, VideoCheck,
670 VideoExpectations, VideoProbe, VideoQualityReport, VideoVerdict,
671};
672pub use animation::{
673 sample_easing, verify_easing, verify_events, verify_timeline, AnimationEvent,
674 AnimationEventType, AnimationReport, AnimationTimeline, AnimationVerdict, EasingFunction,
675 EasingVerification, EventResult, Keyframe, ObservedEvent,
676};
677pub use result::{ProbarError, ProbarResult};
678pub use runtime::{
679 ComponentId, EntityId, FrameResult, GameHostState, MemoryView, ProbarComponent, ProbarEntity,
680 RuntimeConfig, StateDelta, WasmRuntime,
681};
682pub use shard::{ShardConfig, ShardParseError, ShardReport, ShardedRunner};
683pub use simulation::{
684 run_replay, run_simulation, RandomWalkAgent, RecordedFrame, ReplayResult, SimulatedGameState,
685 SimulationConfig, SimulationRecording,
686};
687pub use snapshot::{Snapshot, SnapshotConfig, SnapshotDiff};
688pub use strict::{
689 ChecklistError, ConsoleCapture, ConsoleSeverity, ConsoleValidationError, E2ETestChecklist,
690 WasmStrictMode,
691};
692pub use tracing_support::{
693 ConsoleLevel, ConsoleMessage, EventCategory, EventLevel, ExecutionTracer, NetworkEvent,
694 SpanStatus, TraceArchive, TraceMetadata, TracedEvent, TracedSpan, TracingConfig,
695};
696#[cfg(feature = "tui")]
697pub use tui::{
698 expect_frame, FrameAssertion, FrameSequence, MultiValueTracker, SnapshotManager, TuiFrame,
699 TuiSnapshot, TuiTestBackend, ValueTracker,
700};
701pub use tui_load::{
702 ComponentTimings, DataGenerator, IntegrationLoadTest, SyntheticItem, TuiFrameMetrics,
703 TuiLoadAssertion, TuiLoadConfig, TuiLoadError, TuiLoadResult, TuiLoadTest,
704};
705pub use ux_coverage::{
706 calculator_coverage, game_coverage, ElementCoverage, ElementId, InteractionType, StateId,
707 TrackedInteraction, UxCoverageBuilder, UxCoverageReport, UxCoverageTracker,
708};
709pub use validators::{
710 CompressionAlgorithm, PartialResult, ScreenshotContent, StateTransition, StreamingMetric,
711 StreamingMetricRecord, StreamingState, StreamingUxValidator, StreamingValidationError,
712 StreamingValidationResult, TestExecutionStats, VuMeterConfig, VuMeterError, VuMeterSample,
713};
714#[cfg(feature = "media")]
715pub use visual_regression::{
716 perceptual_diff, ImageDiffResult, MaskRegion, ScreenshotComparison, VisualRegressionConfig,
717 VisualRegressionTester,
718};
719pub use wait::{
720 wait_timeout, wait_until, FnCondition, LoadState, NavigationOptions, PageEvent, WaitCondition,
721 WaitOptions, WaitResult, Waiter, DEFAULT_WAIT_TIMEOUT_MS, NETWORK_IDLE_THRESHOLD_MS,
722};
723#[cfg(all(not(target_arch = "wasm32"), feature = "watch"))]
724pub use watch::{
725 FileChange, FileChangeKind, FileWatcher, FnWatchHandler, WatchBuilder, WatchConfig,
726 WatchHandler, WatchStats,
727};
728pub use brick::{
730 Brick, BrickAssertion, BrickBudget, BrickError, BrickPhase, BrickResult, BrickVerification,
731 BudgetViolation,
732};
733pub use brick::{
735 AudioBrick, AudioParam, BrickWorkerMessage, BrickWorkerMessageDirection, EventBinding,
736 EventBrick, EventHandler, EventType, FieldType, RingBufferConfig, WorkerBrick,
737 WorkerTransition,
738};
739pub use brick_house::{BrickHouse, BrickHouseBuilder, BrickTiming, BudgetReport, JidokaAlert};
740pub use websocket::{
741 MessageDirection, MessageType, MockWebSocketResponse, WebSocketConnection, WebSocketMessage,
742 WebSocketMock, WebSocketMonitor, WebSocketMonitorBuilder, WebSocketState,
743};
744
745pub mod prelude {
747 pub use super::av_sync::{
748 compare_edl_to_onsets, default_edl_path, detect_onsets, extract_audio, AudioOnset,
749 AudioTickPlacement, AvSyncReport, DetectionConfig, EditDecision, EditDecisionList,
750 SegmentSyncResult, SyncVerdict, TickDelta,
751 };
752 pub use super::audio_quality::{
753 analyze_audio, analyze_samples, detect_clipping, detect_silence, AudioLevels,
754 AudioQualityConfig, AudioQualityReport, AudioVerdict, ClippingReport, SilenceRegion,
755 SilenceReport,
756 };
757 pub use super::video_quality::{
758 build_ffprobe_args, parse_ffprobe_json, probe_video, validate_video, VideoCheck,
759 VideoExpectations, VideoProbe, VideoQualityReport, VideoVerdict,
760 };
761 pub use super::animation::{
762 sample_easing, verify_easing, verify_events, verify_timeline, AnimationEvent,
763 AnimationEventType, AnimationReport, AnimationTimeline, AnimationVerdict, EasingFunction,
764 EasingVerification, EventResult, Keyframe, ObservedEvent,
765 };
766 pub use super::accessibility::*;
767 pub use super::assertion::*;
768 pub use super::brick::*;
770 pub use super::brick_house::*;
771 pub use super::bridge::*;
772 pub use super::browser::*;
773 pub use super::capabilities::*;
774 pub use super::clock::*;
775 pub use super::context::*;
776 pub use super::dialog::*;
777 pub use super::driver::*;
778 pub use super::event::*;
779 pub use super::file_ops::*;
780 pub use super::fixture::*;
781 pub use super::fuzzer::*;
782 pub use super::gpu_pixels::*;
783 pub use super::har::*;
784 pub use super::harness::*;
785 pub use super::locator::*;
786 pub use super::network::*;
787 pub use super::page_object::*;
788 pub use super::perf::*;
789 pub use super::performance::*;
790 #[cfg(feature = "media")]
791 pub use super::pixel_coverage::*;
792 pub use super::replay::*;
793 pub use super::reporter::*;
794 pub use super::result::*;
795 pub use super::runner::*;
796 pub use super::runtime::*;
797 pub use super::shard::*;
798 pub use super::simulation::*;
799 pub use super::snapshot::*;
800 pub use super::strict::{
803 ChecklistError, ConsoleCapture, ConsoleSeverity, ConsoleValidationError, E2ETestChecklist,
804 WasmStrictMode,
805 };
806 pub use super::tracing_support::*;
807 #[cfg(feature = "tui")]
808 pub use super::tui::*;
809 pub use super::tui_load::{
810 ComponentTimings, DataGenerator, IntegrationLoadTest, SyntheticItem, TuiFrameMetrics,
811 TuiLoadAssertion, TuiLoadConfig, TuiLoadError, TuiLoadResult, TuiLoadTest,
812 };
813 pub use super::ux_coverage::*;
814 pub use super::validators::*;
815 #[cfg(feature = "media")]
816 pub use super::visual_regression::*;
817 pub use super::worker_harness::*;
818 pub use super::zero_js::*;
819 pub use super::comply::*;
821 pub use super::lint::*;
822 pub use super::mock::*;
823 #[cfg(feature = "docker")]
825 pub use super::docker::{
826 check_shared_array_buffer_support, validate_coop_coep_headers, Browser as DockerBrowser,
827 ContainerConfig, ContainerState, CoopCoepConfig, DockerConfig, DockerError, DockerResult,
828 DockerTestRunner, DockerTestRunnerBuilder, ParallelRunner, ParallelRunnerBuilder,
829 TestResult as DockerTestResult, TestResults as DockerTestResults,
830 };
831 pub use super::wait::{
832 wait_timeout, wait_until, FnCondition, LoadState, NavigationOptions, PageEvent,
833 WaitCondition, WaitOptions, WaitResult, Waiter, DEFAULT_WAIT_TIMEOUT_MS,
834 NETWORK_IDLE_THRESHOLD_MS,
835 };
836 #[cfg(all(not(target_arch = "wasm32"), feature = "watch"))]
837 pub use super::watch::*;
838 pub use super::web::*;
839 pub use super::websocket::*;
840 pub use super::renacer_integration::{
843 ChromeTrace as RenacerChromeTrace, ChromeTraceEvent, TraceCollector, TraceContext,
844 TraceSpan, TracingConfig as RenacerTracingConfig,
845 };
846}
847
848pub mod standard_invariants {
850 pub use super::fuzzer::standard_invariants::*;
851}
852
853#[cfg(feature = "derive")]
855pub use jugar_probar_derive::{probar_test, ProbarComponent, ProbarEntity, ProbarSelector};
856
857#[cfg(test)]
858#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
859mod tests {
860 use super::*;
861
862 mod browser_tests {
867 use super::*;
868
869 #[test]
870 fn test_browser_config_defaults() {
871 let config = BrowserConfig::default();
872 assert!(config.headless);
873 assert_eq!(config.viewport_width, 800);
874 assert_eq!(config.viewport_height, 600);
875 }
876
877 #[test]
878 fn test_browser_config_builder() {
879 let config = BrowserConfig::default()
880 .with_viewport(1024, 768)
881 .with_headless(false);
882 assert!(!config.headless);
883 assert_eq!(config.viewport_width, 1024);
884 assert_eq!(config.viewport_height, 768);
885 }
886 }
887
888 mod touch_tests {
889 use super::*;
890
891 #[test]
892 fn test_touch_tap() {
893 let touch = Touch::tap(100.0, 200.0);
894 assert!((touch.x - 100.0).abs() < f32::EPSILON);
895 assert!((touch.y - 200.0).abs() < f32::EPSILON);
896 assert!(matches!(touch.action, TouchAction::Tap));
897 }
898
899 #[test]
900 fn test_touch_swipe() {
901 let touch = Touch::swipe(0.0, 0.0, 100.0, 0.0, 300);
902 assert!(matches!(touch.action, TouchAction::Swipe { .. }));
903 }
904
905 #[test]
906 fn test_touch_hold() {
907 let touch = Touch::hold(50.0, 50.0, 500);
908 assert!(matches!(touch.action, TouchAction::Hold { .. }));
909 }
910 }
911
912 mod assertion_tests {
913 use super::*;
914
915 #[test]
916 fn test_assertion_result_pass() {
917 let result = AssertionResult::pass();
918 assert!(result.passed);
919 assert!(result.message.is_empty());
920 }
921
922 #[test]
923 fn test_assertion_result_fail() {
924 let result = AssertionResult::fail("test error message");
925 assert!(!result.passed);
926 assert_eq!(result.message, "test error message");
927 }
928
929 #[test]
930 fn test_assertion_equals_pass() {
931 let result = Assertion::equals(&42, &42);
932 assert!(result.passed);
933 }
934
935 #[test]
936 fn test_assertion_equals_fail() {
937 let result = Assertion::equals(&42, &43);
938 assert!(!result.passed);
939 assert!(result.message.contains("expected"));
940 }
941
942 #[test]
943 fn test_assertion_contains_pass() {
944 let result = Assertion::contains("hello world", "world");
945 assert!(result.passed);
946 }
947
948 #[test]
949 fn test_assertion_contains_fail() {
950 let result = Assertion::contains("hello world", "foo");
951 assert!(!result.passed);
952 assert!(result.message.contains("contain"));
953 }
954
955 #[test]
956 fn test_assertion_in_range_pass() {
957 let result = Assertion::in_range(5.0, 0.0, 10.0);
958 assert!(result.passed);
959 }
960
961 #[test]
962 fn test_assertion_in_range_fail() {
963 let result = Assertion::in_range(15.0, 0.0, 10.0);
964 assert!(!result.passed);
965 assert!(result.message.contains("range"));
966 }
967
968 #[test]
969 fn test_assertion_in_range_at_boundaries() {
970 let result = Assertion::in_range(0.0, 0.0, 10.0);
972 assert!(result.passed);
973 let result = Assertion::in_range(10.0, 0.0, 10.0);
975 assert!(result.passed);
976 }
977
978 #[test]
979 fn test_assertion_is_true_pass() {
980 let result = Assertion::is_true(true, "should be true");
981 assert!(result.passed);
982 }
983
984 #[test]
985 fn test_assertion_is_true_fail() {
986 let result = Assertion::is_true(false, "expected true");
987 assert!(!result.passed);
988 assert_eq!(result.message, "expected true");
989 }
990
991 #[test]
992 fn test_assertion_is_false_pass() {
993 let result = Assertion::is_false(false, "should be false");
994 assert!(result.passed);
995 }
996
997 #[test]
998 fn test_assertion_is_false_fail() {
999 let result = Assertion::is_false(true, "expected false");
1000 assert!(!result.passed);
1001 assert_eq!(result.message, "expected false");
1002 }
1003
1004 #[test]
1005 fn test_assertion_is_some_pass() {
1006 let opt = Some(42);
1007 let result = Assertion::is_some(&opt);
1008 assert!(result.passed);
1009 }
1010
1011 #[test]
1012 fn test_assertion_is_some_fail() {
1013 let opt: Option<i32> = None;
1014 let result = Assertion::is_some(&opt);
1015 assert!(!result.passed);
1016 assert!(result.message.contains("None"));
1017 }
1018
1019 #[test]
1020 fn test_assertion_is_none_pass() {
1021 let opt: Option<i32> = None;
1022 let result = Assertion::is_none(&opt);
1023 assert!(result.passed);
1024 }
1025
1026 #[test]
1027 fn test_assertion_is_none_fail() {
1028 let opt = Some(42);
1029 let result = Assertion::is_none(&opt);
1030 assert!(!result.passed);
1031 assert!(result.message.contains("Some"));
1032 }
1033
1034 #[test]
1035 fn test_assertion_is_ok_pass() {
1036 let res: Result<i32, &str> = Ok(42);
1037 let result = Assertion::is_ok(&res);
1038 assert!(result.passed);
1039 }
1040
1041 #[test]
1042 fn test_assertion_is_ok_fail() {
1043 let res: Result<i32, &str> = Err("error");
1044 let result = Assertion::is_ok(&res);
1045 assert!(!result.passed);
1046 assert!(result.message.contains("Err"));
1047 }
1048
1049 #[test]
1050 fn test_assertion_is_err_pass() {
1051 let res: Result<i32, &str> = Err("error");
1052 let result = Assertion::is_err(&res);
1053 assert!(result.passed);
1054 }
1055
1056 #[test]
1057 fn test_assertion_is_err_fail() {
1058 let res: Result<i32, &str> = Ok(42);
1059 let result = Assertion::is_err(&res);
1060 assert!(!result.passed);
1061 assert!(result.message.contains("Ok"));
1062 }
1063
1064 #[test]
1065 fn test_assertion_approx_eq_pass() {
1066 let result = Assertion::approx_eq(1.0, 1.0001, 0.01);
1067 assert!(result.passed);
1068 }
1069
1070 #[test]
1071 fn test_assertion_approx_eq_fail() {
1072 let result = Assertion::approx_eq(1.0, 2.0, 0.01);
1073 assert!(!result.passed);
1074 assert!(result.message.contains("≈"));
1075 }
1076
1077 #[test]
1078 fn test_assertion_has_length_pass() {
1079 let data = vec![1, 2, 3, 4, 5];
1080 let result = Assertion::has_length(&data, 5);
1081 assert!(result.passed);
1082 }
1083
1084 #[test]
1085 fn test_assertion_has_length_fail() {
1086 let data = vec![1, 2, 3];
1087 let result = Assertion::has_length(&data, 5);
1088 assert!(!result.passed);
1089 assert!(result.message.contains("length"));
1090 }
1091
1092 #[test]
1093 fn test_assertion_has_length_empty() {
1094 let data: Vec<i32> = vec![];
1095 let result = Assertion::has_length(&data, 0);
1096 assert!(result.passed);
1097 }
1098 }
1099
1100 mod snapshot_tests {
1101 use super::*;
1102
1103 #[test]
1104 fn test_snapshot_creation() {
1105 let snapshot = Snapshot::new("test-snapshot", vec![0, 1, 2, 3]);
1106 assert_eq!(snapshot.name, "test-snapshot");
1107 assert_eq!(snapshot.data.len(), 4);
1108 assert_eq!(snapshot.width, 0);
1109 assert_eq!(snapshot.height, 0);
1110 }
1111
1112 #[test]
1113 fn test_snapshot_with_dimensions() {
1114 let snapshot = Snapshot::new("test", vec![1, 2, 3, 4]).with_dimensions(800, 600);
1115 assert_eq!(snapshot.width, 800);
1116 assert_eq!(snapshot.height, 600);
1117 }
1118
1119 #[test]
1120 fn test_snapshot_size() {
1121 let snapshot = Snapshot::new("test", vec![1, 2, 3, 4, 5]);
1122 assert_eq!(snapshot.size(), 5);
1123 }
1124
1125 #[test]
1126 fn test_snapshot_diff_identical() {
1127 let snap1 = Snapshot::new("test", vec![1, 2, 3]);
1128 let snap2 = Snapshot::new("test", vec![1, 2, 3]);
1129 let diff = snap1.diff(&snap2);
1130 assert!(diff.is_identical());
1131 assert_eq!(diff.difference_count, 0);
1132 assert!((diff.difference_percent - 0.0).abs() < f64::EPSILON);
1133 }
1134
1135 #[test]
1136 fn test_snapshot_diff_different() {
1137 let snap1 = Snapshot::new("test", vec![1, 2, 3]);
1138 let snap2 = Snapshot::new("test", vec![1, 2, 4]);
1139 let diff = snap1.diff(&snap2);
1140 assert!(!diff.is_identical());
1141 assert_eq!(diff.difference_count, 1);
1142 }
1143
1144 #[test]
1145 fn test_snapshot_diff_empty() {
1146 let snap1 = Snapshot::new("test", vec![]);
1147 let snap2 = Snapshot::new("test", vec![]);
1148 let diff = snap1.diff(&snap2);
1149 assert!(diff.is_identical());
1150 assert!((diff.difference_percent - 0.0).abs() < f64::EPSILON);
1151 }
1152
1153 #[test]
1154 fn test_snapshot_diff_different_lengths() {
1155 let snap1 = Snapshot::new("test", vec![1, 2, 3]);
1156 let snap2 = Snapshot::new("test", vec![1, 2, 3, 4, 5]);
1157 let diff = snap1.diff(&snap2);
1158 assert!(!diff.is_identical());
1159 assert_eq!(diff.difference_count, 2);
1161 }
1162
1163 #[test]
1164 fn test_snapshot_diff_within_threshold() {
1165 let snap1 = Snapshot::new("test", vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1166 let snap2 = Snapshot::new("test", vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 11]);
1167 let diff = snap1.diff(&snap2);
1168 assert!(diff.within_threshold(0.1)); assert!(!diff.within_threshold(0.05)); }
1172
1173 #[test]
1174 fn test_snapshot_config_default() {
1175 let config = SnapshotConfig::default();
1176 assert!(!config.update_snapshots);
1177 assert!((config.threshold - 0.01).abs() < f64::EPSILON);
1178 assert_eq!(config.snapshot_dir, "__snapshots__");
1179 }
1180
1181 #[test]
1182 fn test_snapshot_config_with_update() {
1183 let config = SnapshotConfig::default().with_update(true);
1184 assert!(config.update_snapshots);
1185 }
1186
1187 #[test]
1188 fn test_snapshot_config_with_threshold() {
1189 let config = SnapshotConfig::default().with_threshold(0.05);
1190 assert!((config.threshold - 0.05).abs() < f64::EPSILON);
1191 }
1192
1193 #[test]
1194 fn test_snapshot_config_with_dir() {
1195 let config = SnapshotConfig::default().with_dir("custom_snapshots");
1196 assert_eq!(config.snapshot_dir, "custom_snapshots");
1197 }
1198
1199 #[test]
1200 fn test_snapshot_config_chained_builders() {
1201 let config = SnapshotConfig::default()
1202 .with_update(true)
1203 .with_threshold(0.02)
1204 .with_dir("my_snaps");
1205 assert!(config.update_snapshots);
1206 assert!((config.threshold - 0.02).abs() < f64::EPSILON);
1207 assert_eq!(config.snapshot_dir, "my_snaps");
1208 }
1209 }
1210
1211 mod harness_tests {
1212 use super::*;
1213 use harness::{SuiteResults, TestCase};
1214 use std::time::Duration;
1215
1216 #[test]
1217 fn test_test_suite_creation() {
1218 let suite = TestSuite::new("Game Tests");
1219 assert_eq!(suite.name, "Game Tests");
1220 assert!(suite.tests.is_empty());
1221 }
1222
1223 #[test]
1224 fn test_test_suite_add_test() {
1225 let mut suite = TestSuite::new("Suite");
1226 suite.add_test(TestCase::new("test1"));
1227 suite.add_test(TestCase::new("test2"));
1228 assert_eq!(suite.test_count(), 2);
1229 }
1230
1231 #[test]
1232 fn test_test_case_creation() {
1233 let case = TestCase::new("my_test");
1234 assert_eq!(case.name, "my_test");
1235 assert_eq!(case.timeout_ms, 30000); }
1237
1238 #[test]
1239 fn test_test_case_with_timeout() {
1240 let case = TestCase::new("my_test").with_timeout(5000);
1241 assert_eq!(case.timeout_ms, 5000);
1242 }
1243
1244 #[test]
1245 fn test_test_result_pass() {
1246 let result = TestResult::pass("test_example");
1247 assert!(result.passed);
1248 assert_eq!(result.name, "test_example");
1249 assert!(result.error.is_none());
1250 assert_eq!(result.duration, Duration::ZERO);
1251 }
1252
1253 #[test]
1254 fn test_test_result_fail() {
1255 let result = TestResult::fail("test_example", "assertion failed");
1256 assert!(!result.passed);
1257 assert!(result.error.is_some());
1258 assert_eq!(result.error.unwrap(), "assertion failed");
1259 }
1260
1261 #[test]
1262 fn test_test_result_with_duration() {
1263 let result = TestResult::pass("test").with_duration(Duration::from_millis(100));
1264 assert_eq!(result.duration, Duration::from_millis(100));
1265 }
1266
1267 #[test]
1268 fn test_suite_results_all_passed() {
1269 let results = SuiteResults {
1270 suite_name: "test".to_string(),
1271 results: vec![TestResult::pass("test1"), TestResult::pass("test2")],
1272 duration: Duration::ZERO,
1273 };
1274 assert!(results.all_passed());
1275 }
1276
1277 #[test]
1278 fn test_suite_results_not_all_passed() {
1279 let results = SuiteResults {
1280 suite_name: "test".to_string(),
1281 results: vec![
1282 TestResult::pass("test1"),
1283 TestResult::fail("test2", "error"),
1284 ],
1285 duration: Duration::ZERO,
1286 };
1287 assert!(!results.all_passed());
1288 }
1289
1290 #[test]
1291 fn test_suite_results_counts() {
1292 let results = SuiteResults {
1293 suite_name: "test".to_string(),
1294 results: vec![
1295 TestResult::pass("test1"),
1296 TestResult::fail("test2", "error"),
1297 TestResult::pass("test3"),
1298 ],
1299 duration: Duration::ZERO,
1300 };
1301 assert_eq!(results.passed_count(), 2);
1302 assert_eq!(results.failed_count(), 1);
1303 assert_eq!(results.total(), 3);
1304 }
1305
1306 #[test]
1307 fn test_suite_results_failures() {
1308 let results = SuiteResults {
1309 suite_name: "test".to_string(),
1310 results: vec![
1311 TestResult::pass("test1"),
1312 TestResult::fail("test2", "error2"),
1313 TestResult::fail("test3", "error3"),
1314 ],
1315 duration: Duration::ZERO,
1316 };
1317 let failures = results.failures();
1318 assert_eq!(failures.len(), 2);
1319 assert_eq!(failures[0].name, "test2");
1320 assert_eq!(failures[1].name, "test3");
1321 }
1322
1323 #[test]
1324 fn test_harness_run_empty_suite() {
1325 let harness = TestHarness::new();
1326 let suite = TestSuite::new("Empty");
1327 let results = harness.run(&suite);
1328 assert!(results.all_passed());
1329 assert_eq!(results.total(), 0);
1330 }
1331
1332 #[test]
1333 fn test_harness_with_fail_fast() {
1334 let harness = TestHarness::new().with_fail_fast();
1335 assert!(harness.fail_fast);
1336 }
1337
1338 #[test]
1339 fn test_harness_with_parallel() {
1340 let harness = TestHarness::new().with_parallel();
1341 assert!(harness.parallel);
1342 }
1343
1344 #[test]
1345 fn test_harness_default() {
1346 let harness = TestHarness::default();
1347 assert!(!harness.fail_fast);
1348 assert!(!harness.parallel);
1349 }
1350 }
1351
1352 mod input_event_tests {
1353 use super::*;
1354
1355 #[test]
1356 fn test_input_event_touch() {
1357 let event = InputEvent::touch(100.0, 200.0);
1358 assert!(
1359 matches!(event, InputEvent::Touch { x, y } if (x - 100.0).abs() < f32::EPSILON && (y - 200.0).abs() < f32::EPSILON)
1360 );
1361 }
1362
1363 #[test]
1364 fn test_input_event_key_press() {
1365 let event = InputEvent::key_press("ArrowUp");
1366 assert!(matches!(event, InputEvent::KeyPress { key } if key == "ArrowUp"));
1367 }
1368
1369 #[test]
1370 fn test_input_event_key_release() {
1371 let event = InputEvent::key_release("Space");
1372 assert!(matches!(event, InputEvent::KeyRelease { key } if key == "Space"));
1373 }
1374
1375 #[test]
1376 fn test_input_event_mouse_click() {
1377 let event = InputEvent::mouse_click(50.0, 75.0);
1378 assert!(
1379 matches!(event, InputEvent::MouseClick { x, y } if (x - 50.0).abs() < f32::EPSILON && (y - 75.0).abs() < f32::EPSILON)
1380 );
1381 }
1382
1383 #[test]
1384 fn test_input_event_mouse_move() {
1385 let event = InputEvent::mouse_move(150.0, 250.0);
1386 assert!(
1387 matches!(event, InputEvent::MouseMove { x, y } if (x - 150.0).abs() < f32::EPSILON && (y - 250.0).abs() < f32::EPSILON)
1388 );
1389 }
1390
1391 #[test]
1392 fn test_input_event_gamepad_button_pressed() {
1393 let event = InputEvent::gamepad_button(0, true);
1394 assert!(matches!(
1395 event,
1396 InputEvent::GamepadButton {
1397 button: 0,
1398 pressed: true
1399 }
1400 ));
1401 }
1402
1403 #[test]
1404 fn test_input_event_gamepad_button_released() {
1405 let event = InputEvent::gamepad_button(1, false);
1406 assert!(matches!(
1407 event,
1408 InputEvent::GamepadButton {
1409 button: 1,
1410 pressed: false
1411 }
1412 ));
1413 }
1414
1415 #[test]
1416 fn test_touch_tap_coordinates() {
1417 let touch = Touch::tap(100.0, 200.0);
1418 assert!((touch.x - 100.0).abs() < f32::EPSILON);
1419 assert!((touch.y - 200.0).abs() < f32::EPSILON);
1420 assert!(matches!(touch.action, TouchAction::Tap));
1421 }
1422
1423 #[test]
1424 fn test_touch_swipe_full_properties() {
1425 let touch = Touch::swipe(10.0, 20.0, 100.0, 200.0, 300);
1426 assert!((touch.x - 10.0).abs() < f32::EPSILON);
1427 assert!((touch.y - 20.0).abs() < f32::EPSILON);
1428 match touch.action {
1429 TouchAction::Swipe {
1430 end_x,
1431 end_y,
1432 duration_ms,
1433 } => {
1434 assert!((end_x - 100.0).abs() < f32::EPSILON);
1435 assert!((end_y - 200.0).abs() < f32::EPSILON);
1436 assert_eq!(duration_ms, 300);
1437 }
1438 _ => panic!("expected Swipe action"),
1439 }
1440 }
1441
1442 #[test]
1443 fn test_touch_hold_full_properties() {
1444 let touch = Touch::hold(50.0, 60.0, 500);
1445 assert!((touch.x - 50.0).abs() < f32::EPSILON);
1446 assert!((touch.y - 60.0).abs() < f32::EPSILON);
1447 match touch.action {
1448 TouchAction::Hold { duration_ms } => {
1449 assert_eq!(duration_ms, 500);
1450 }
1451 _ => panic!("expected Hold action"),
1452 }
1453 }
1454
1455 #[test]
1456 fn test_touch_action_equality() {
1457 assert_eq!(TouchAction::Tap, TouchAction::Tap);
1458 let swipe1 = TouchAction::Swipe {
1459 end_x: 1.0,
1460 end_y: 2.0,
1461 duration_ms: 100,
1462 };
1463 let swipe2 = TouchAction::Swipe {
1464 end_x: 1.0,
1465 end_y: 2.0,
1466 duration_ms: 100,
1467 };
1468 assert_eq!(swipe1, swipe2);
1469 let hold1 = TouchAction::Hold { duration_ms: 500 };
1470 let hold2 = TouchAction::Hold { duration_ms: 500 };
1471 assert_eq!(hold1, hold2);
1472 }
1473
1474 #[test]
1475 fn test_touch_equality() {
1476 let t1 = Touch::tap(100.0, 200.0);
1477 let t2 = Touch::tap(100.0, 200.0);
1478 assert_eq!(t1, t2);
1479 }
1480
1481 #[test]
1482 fn test_input_event_equality() {
1483 let e1 = InputEvent::touch(10.0, 20.0);
1484 let e2 = InputEvent::touch(10.0, 20.0);
1485 assert_eq!(e1, e2);
1486 }
1487 }
1488
1489 mod error_tests {
1490 use super::*;
1491
1492 #[test]
1493 fn test_probar_error_display() {
1494 let err = ProbarError::BrowserNotFound;
1495 let msg = err.to_string();
1496 assert!(msg.contains("browser") || msg.contains("Browser"));
1497 }
1498
1499 #[test]
1500 fn test_probar_error_timeout() {
1501 let err = ProbarError::Timeout { ms: 5000 };
1502 let msg = err.to_string();
1503 assert!(msg.contains("5000"));
1504 }
1505 }
1506}