1use crate::network::UrlPattern;
14use crate::result::{ProbarError, ProbarResult};
15use std::time::{Duration, Instant};
16
17pub const DEFAULT_WAIT_TIMEOUT_MS: u64 = 30_000;
23
24pub const DEFAULT_POLL_INTERVAL_MS: u64 = 50;
26
27pub const NETWORK_IDLE_THRESHOLD_MS: u64 = 500;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum LoadState {
37 Load,
39 DomContentLoaded,
41 NetworkIdle,
43}
44
45impl LoadState {
46 #[must_use]
48 pub const fn event_name(&self) -> &'static str {
49 match self {
50 Self::Load => "load",
51 Self::DomContentLoaded => "DOMContentLoaded",
52 Self::NetworkIdle => "networkidle",
53 }
54 }
55
56 #[must_use]
58 pub const fn default_timeout_ms(&self) -> u64 {
59 match self {
60 Self::Load => 30_000,
61 Self::DomContentLoaded => 30_000,
62 Self::NetworkIdle => 60_000, }
64 }
65}
66
67impl Default for LoadState {
68 fn default() -> Self {
69 Self::Load
70 }
71}
72
73impl std::fmt::Display for LoadState {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{}", self.event_name())
76 }
77}
78
79#[derive(Debug, Clone)]
85pub struct WaitOptions {
86 pub timeout_ms: u64,
88 pub poll_interval_ms: u64,
90 pub wait_until: LoadState,
92}
93
94impl Default for WaitOptions {
95 fn default() -> Self {
96 Self {
97 timeout_ms: DEFAULT_WAIT_TIMEOUT_MS,
98 poll_interval_ms: DEFAULT_POLL_INTERVAL_MS,
99 wait_until: LoadState::Load,
100 }
101 }
102}
103
104impl WaitOptions {
105 #[must_use]
107 pub fn new() -> Self {
108 Self::default()
109 }
110
111 #[must_use]
113 pub const fn with_timeout(mut self, timeout_ms: u64) -> Self {
114 self.timeout_ms = timeout_ms;
115 self
116 }
117
118 #[must_use]
120 pub const fn with_poll_interval(mut self, poll_interval_ms: u64) -> Self {
121 self.poll_interval_ms = poll_interval_ms;
122 self
123 }
124
125 #[must_use]
127 pub const fn with_wait_until(mut self, state: LoadState) -> Self {
128 self.wait_until = state;
129 self
130 }
131
132 #[must_use]
134 pub const fn timeout(&self) -> Duration {
135 Duration::from_millis(self.timeout_ms)
136 }
137
138 #[must_use]
140 pub const fn poll_interval(&self) -> Duration {
141 Duration::from_millis(self.poll_interval_ms)
142 }
143}
144
145#[derive(Debug, Clone)]
151pub struct NavigationOptions {
152 pub timeout_ms: u64,
154 pub wait_until: LoadState,
156 pub url_pattern: Option<UrlPattern>,
158}
159
160impl Default for NavigationOptions {
161 fn default() -> Self {
162 Self {
163 timeout_ms: DEFAULT_WAIT_TIMEOUT_MS,
164 wait_until: LoadState::Load,
165 url_pattern: None,
166 }
167 }
168}
169
170impl NavigationOptions {
171 #[must_use]
173 pub fn new() -> Self {
174 Self::default()
175 }
176
177 #[must_use]
179 pub const fn with_timeout(mut self, timeout_ms: u64) -> Self {
180 self.timeout_ms = timeout_ms;
181 self
182 }
183
184 #[must_use]
186 pub const fn with_wait_until(mut self, state: LoadState) -> Self {
187 self.wait_until = state;
188 self
189 }
190
191 #[must_use]
193 pub fn with_url(mut self, pattern: UrlPattern) -> Self {
194 self.url_pattern = Some(pattern);
195 self
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub enum PageEvent {
206 Close,
208 Console,
210 Crash,
212 Dialog,
214 Download,
216 FileChooser,
218 FrameAttached,
220 FrameDetached,
222 FrameNavigated,
224 Load,
226 DomContentLoaded,
228 PageError,
230 Popup,
232 Request,
234 RequestFailed,
236 RequestFinished,
238 Response,
240 WebSocket,
242 Worker,
244}
245
246impl PageEvent {
247 #[must_use]
249 pub const fn as_str(&self) -> &'static str {
250 match self {
251 Self::Close => "close",
252 Self::Console => "console",
253 Self::Crash => "crash",
254 Self::Dialog => "dialog",
255 Self::Download => "download",
256 Self::FileChooser => "filechooser",
257 Self::FrameAttached => "frameattached",
258 Self::FrameDetached => "framedetached",
259 Self::FrameNavigated => "framenavigated",
260 Self::Load => "load",
261 Self::DomContentLoaded => "domcontentloaded",
262 Self::PageError => "pageerror",
263 Self::Popup => "popup",
264 Self::Request => "request",
265 Self::RequestFailed => "requestfailed",
266 Self::RequestFinished => "requestfinished",
267 Self::Response => "response",
268 Self::WebSocket => "websocket",
269 Self::Worker => "worker",
270 }
271 }
272}
273
274impl std::fmt::Display for PageEvent {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 write!(f, "{}", self.as_str())
277 }
278}
279
280pub trait WaitCondition: Send + Sync {
286 fn check(&self) -> bool;
288
289 fn description(&self) -> String;
291}
292
293pub struct FnCondition<F: Fn() -> bool + Send + Sync> {
295 func: F,
296 description: String,
297}
298
299impl<F: Fn() -> bool + Send + Sync> std::fmt::Debug for FnCondition<F> {
300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301 f.debug_struct("FnCondition")
302 .field("description", &self.description)
303 .finish_non_exhaustive()
304 }
305}
306
307impl<F: Fn() -> bool + Send + Sync> FnCondition<F> {
308 pub fn new(func: F, description: impl Into<String>) -> Self {
310 Self {
311 func,
312 description: description.into(),
313 }
314 }
315}
316
317impl<F: Fn() -> bool + Send + Sync> WaitCondition for FnCondition<F> {
318 fn check(&self) -> bool {
319 (self.func)()
320 }
321
322 fn description(&self) -> String {
323 self.description.clone()
324 }
325}
326
327#[derive(Debug, Clone)]
333pub struct WaitResult {
334 pub success: bool,
336 pub elapsed: Duration,
338 pub waited_for: String,
340}
341
342impl WaitResult {
343 #[must_use]
345 pub fn success(elapsed: Duration, waited_for: impl Into<String>) -> Self {
346 Self {
347 success: true,
348 elapsed,
349 waited_for: waited_for.into(),
350 }
351 }
352
353 #[must_use]
355 pub fn timeout(elapsed: Duration, waited_for: impl Into<String>) -> Self {
356 Self {
357 success: false,
358 elapsed,
359 waited_for: waited_for.into(),
360 }
361 }
362}
363
364#[derive(Debug, Clone, Default)]
370pub struct Waiter {
371 #[allow(dead_code)]
373 options: WaitOptions,
374 current_url: Option<String>,
376 load_state: LoadState,
378 pending_requests: usize,
380 last_network_activity: Option<Instant>,
382 events: Vec<PageEvent>,
384}
385
386impl Waiter {
387 #[must_use]
389 pub fn new() -> Self {
390 Self::default()
391 }
392
393 #[must_use]
395 pub fn with_options(options: WaitOptions) -> Self {
396 Self {
397 options,
398 ..Default::default()
399 }
400 }
401
402 pub fn set_url(&mut self, url: impl Into<String>) {
404 self.current_url = Some(url.into());
405 }
406
407 pub fn set_load_state(&mut self, state: LoadState) {
409 self.load_state = state;
410 }
411
412 pub fn set_pending_requests(&mut self, count: usize) {
414 self.pending_requests = count;
415 if count > 0 {
416 self.last_network_activity = Some(Instant::now());
417 }
418 }
419
420 pub fn record_event(&mut self, event: PageEvent) {
422 self.events.push(event);
423 }
424
425 pub fn clear_events(&mut self) {
427 self.events.clear();
428 }
429
430 pub fn wait_for<C: WaitCondition>(
432 &self,
433 condition: &C,
434 options: &WaitOptions,
435 ) -> ProbarResult<WaitResult> {
436 let start = Instant::now();
437 let timeout = Duration::from_millis(options.timeout_ms);
438 let poll_interval = Duration::from_millis(options.poll_interval_ms);
439
440 while start.elapsed() < timeout {
441 if condition.check() {
442 return Ok(WaitResult::success(
443 start.elapsed(),
444 condition.description(),
445 ));
446 }
447 std::thread::sleep(poll_interval);
448 }
449
450 Err(ProbarError::Timeout {
451 ms: options.timeout_ms,
452 })
453 }
454
455 pub fn wait_for_url(
457 &self,
458 pattern: &UrlPattern,
459 options: &WaitOptions,
460 ) -> ProbarResult<WaitResult> {
461 let start = Instant::now();
462 let timeout = Duration::from_millis(options.timeout_ms);
463 let poll_interval = Duration::from_millis(options.poll_interval_ms);
464
465 while start.elapsed() < timeout {
466 if let Some(ref url) = self.current_url {
467 if pattern.matches(url) {
468 return Ok(WaitResult::success(
469 start.elapsed(),
470 format!("URL matching {:?}", pattern),
471 ));
472 }
473 }
474 std::thread::sleep(poll_interval);
475 }
476
477 Err(ProbarError::Timeout {
478 ms: options.timeout_ms,
479 })
480 }
481
482 pub fn wait_for_load_state(
484 &self,
485 state: LoadState,
486 options: &WaitOptions,
487 ) -> ProbarResult<WaitResult> {
488 let start = Instant::now();
489 let timeout = Duration::from_millis(options.timeout_ms);
490 let poll_interval = Duration::from_millis(options.poll_interval_ms);
491
492 while start.elapsed() < timeout {
493 let state_reached = match state {
494 LoadState::Load => self.load_state == LoadState::Load,
495 LoadState::DomContentLoaded => {
496 self.load_state == LoadState::DomContentLoaded
497 || self.load_state == LoadState::Load
498 }
499 LoadState::NetworkIdle => self.is_network_idle(),
500 };
501
502 if state_reached {
503 return Ok(WaitResult::success(
504 start.elapsed(),
505 format!("Load state: {}", state),
506 ));
507 }
508 std::thread::sleep(poll_interval);
509 }
510
511 Err(ProbarError::Timeout {
512 ms: options.timeout_ms,
513 })
514 }
515
516 fn is_network_idle(&self) -> bool {
518 if self.pending_requests > 0 {
519 return false;
520 }
521
522 match self.last_network_activity {
523 Some(last) => last.elapsed() >= Duration::from_millis(NETWORK_IDLE_THRESHOLD_MS),
524 None => true, }
526 }
527
528 pub fn wait_for_navigation(&self, options: &NavigationOptions) -> ProbarResult<WaitResult> {
530 let wait_options = WaitOptions::new()
531 .with_timeout(options.timeout_ms)
532 .with_wait_until(options.wait_until);
533
534 if let Some(ref pattern) = options.url_pattern {
536 self.wait_for_url(pattern, &wait_options)?;
537 }
538
539 self.wait_for_load_state(options.wait_until, &wait_options)
541 }
542
543 pub fn wait_for_event(
545 &self,
546 event: &PageEvent,
547 options: &WaitOptions,
548 ) -> ProbarResult<WaitResult> {
549 let start = Instant::now();
550 let timeout = Duration::from_millis(options.timeout_ms);
551 let poll_interval = Duration::from_millis(options.poll_interval_ms);
552
553 while start.elapsed() < timeout {
554 if self.events.contains(event) {
555 return Ok(WaitResult::success(
556 start.elapsed(),
557 format!("Event: {}", event),
558 ));
559 }
560 std::thread::sleep(poll_interval);
561 }
562
563 Err(ProbarError::Timeout {
564 ms: options.timeout_ms,
565 })
566 }
567
568 pub fn wait_for_function<F>(
570 &self,
571 predicate: F,
572 options: &WaitOptions,
573 ) -> ProbarResult<WaitResult>
574 where
575 F: Fn() -> bool,
576 {
577 let start = Instant::now();
578 let timeout = Duration::from_millis(options.timeout_ms);
579 let poll_interval = Duration::from_millis(options.poll_interval_ms);
580
581 while start.elapsed() < timeout {
582 if predicate() {
583 return Ok(WaitResult::success(start.elapsed(), "custom function"));
584 }
585 std::thread::sleep(poll_interval);
586 }
587
588 Err(ProbarError::Timeout {
589 ms: options.timeout_ms,
590 })
591 }
592}
593
594pub fn wait_until<F>(predicate: F, timeout_ms: u64) -> ProbarResult<()>
600where
601 F: Fn() -> bool,
602{
603 let waiter = Waiter::new();
604 let options = WaitOptions::new().with_timeout(timeout_ms);
605 waiter.wait_for_function(predicate, &options)?;
606 Ok(())
607}
608
609pub fn wait_timeout(duration_ms: u64) {
611 std::thread::sleep(Duration::from_millis(duration_ms));
612}
613
614#[cfg(test)]
619#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
620mod tests {
621 use super::*;
622
623 mod load_state_tests {
628 use super::*;
629
630 #[test]
631 fn test_load_state_event_names() {
632 assert_eq!(LoadState::Load.event_name(), "load");
633 assert_eq!(LoadState::DomContentLoaded.event_name(), "DOMContentLoaded");
634 assert_eq!(LoadState::NetworkIdle.event_name(), "networkidle");
635 }
636
637 #[test]
638 fn test_load_state_default_timeouts() {
639 assert_eq!(LoadState::Load.default_timeout_ms(), 30_000);
640 assert_eq!(LoadState::DomContentLoaded.default_timeout_ms(), 30_000);
641 assert_eq!(LoadState::NetworkIdle.default_timeout_ms(), 60_000);
642 }
643
644 #[test]
645 fn test_load_state_default() {
646 assert_eq!(LoadState::default(), LoadState::Load);
647 }
648
649 #[test]
650 fn test_load_state_display() {
651 assert_eq!(format!("{}", LoadState::Load), "load");
652 assert_eq!(
653 format!("{}", LoadState::DomContentLoaded),
654 "DOMContentLoaded"
655 );
656 assert_eq!(format!("{}", LoadState::NetworkIdle), "networkidle");
657 }
658
659 #[test]
660 fn test_load_state_equality() {
661 assert_eq!(LoadState::Load, LoadState::Load);
662 assert_ne!(LoadState::Load, LoadState::DomContentLoaded);
663 }
664
665 #[test]
666 fn test_load_state_clone() {
667 let state = LoadState::NetworkIdle;
668 let cloned = state;
669 assert_eq!(state, cloned);
670 }
671 }
672
673 mod wait_options_tests {
678 use super::*;
679
680 #[test]
681 fn test_wait_options_default() {
682 let opts = WaitOptions::default();
683 assert_eq!(opts.timeout_ms, DEFAULT_WAIT_TIMEOUT_MS);
684 assert_eq!(opts.poll_interval_ms, DEFAULT_POLL_INTERVAL_MS);
685 assert_eq!(opts.wait_until, LoadState::Load);
686 }
687
688 #[test]
689 fn test_wait_options_new() {
690 let opts = WaitOptions::new();
691 assert_eq!(opts.timeout_ms, DEFAULT_WAIT_TIMEOUT_MS);
692 }
693
694 #[test]
695 fn test_wait_options_with_timeout() {
696 let opts = WaitOptions::new().with_timeout(5000);
697 assert_eq!(opts.timeout_ms, 5000);
698 }
699
700 #[test]
701 fn test_wait_options_with_poll_interval() {
702 let opts = WaitOptions::new().with_poll_interval(100);
703 assert_eq!(opts.poll_interval_ms, 100);
704 }
705
706 #[test]
707 fn test_wait_options_with_wait_until() {
708 let opts = WaitOptions::new().with_wait_until(LoadState::NetworkIdle);
709 assert_eq!(opts.wait_until, LoadState::NetworkIdle);
710 }
711
712 #[test]
713 fn test_wait_options_chained() {
714 let opts = WaitOptions::new()
715 .with_timeout(10_000)
716 .with_poll_interval(200)
717 .with_wait_until(LoadState::DomContentLoaded);
718 assert_eq!(opts.timeout_ms, 10_000);
719 assert_eq!(opts.poll_interval_ms, 200);
720 assert_eq!(opts.wait_until, LoadState::DomContentLoaded);
721 }
722
723 #[test]
724 fn test_wait_options_timeout_duration() {
725 let opts = WaitOptions::new().with_timeout(5000);
726 assert_eq!(opts.timeout(), Duration::from_millis(5000));
727 }
728
729 #[test]
730 fn test_wait_options_poll_interval_duration() {
731 let opts = WaitOptions::new().with_poll_interval(100);
732 assert_eq!(opts.poll_interval(), Duration::from_millis(100));
733 }
734 }
735
736 mod navigation_options_tests {
741 use super::*;
742
743 #[test]
744 fn test_navigation_options_default() {
745 let opts = NavigationOptions::default();
746 assert_eq!(opts.timeout_ms, DEFAULT_WAIT_TIMEOUT_MS);
747 assert_eq!(opts.wait_until, LoadState::Load);
748 assert!(opts.url_pattern.is_none());
749 }
750
751 #[test]
752 fn test_navigation_options_with_timeout() {
753 let opts = NavigationOptions::new().with_timeout(10_000);
754 assert_eq!(opts.timeout_ms, 10_000);
755 }
756
757 #[test]
758 fn test_navigation_options_with_wait_until() {
759 let opts = NavigationOptions::new().with_wait_until(LoadState::NetworkIdle);
760 assert_eq!(opts.wait_until, LoadState::NetworkIdle);
761 }
762
763 #[test]
764 fn test_navigation_options_with_url() {
765 let opts =
766 NavigationOptions::new().with_url(UrlPattern::Contains("example.com".into()));
767 assert!(opts.url_pattern.is_some());
768 }
769
770 #[test]
771 fn test_navigation_options_chained() {
772 let opts = NavigationOptions::new()
773 .with_timeout(5000)
774 .with_wait_until(LoadState::DomContentLoaded)
775 .with_url(UrlPattern::Exact("https://example.com".into()));
776 assert_eq!(opts.timeout_ms, 5000);
777 assert_eq!(opts.wait_until, LoadState::DomContentLoaded);
778 assert!(opts.url_pattern.is_some());
779 }
780 }
781
782 mod page_event_tests {
787 use super::*;
788
789 #[test]
790 fn test_page_event_as_str() {
791 assert_eq!(PageEvent::Load.as_str(), "load");
792 assert_eq!(PageEvent::DomContentLoaded.as_str(), "domcontentloaded");
793 assert_eq!(PageEvent::Close.as_str(), "close");
794 assert_eq!(PageEvent::Console.as_str(), "console");
795 assert_eq!(PageEvent::Dialog.as_str(), "dialog");
796 assert_eq!(PageEvent::Popup.as_str(), "popup");
797 assert_eq!(PageEvent::Request.as_str(), "request");
798 assert_eq!(PageEvent::Response.as_str(), "response");
799 }
800
801 #[test]
802 fn test_page_event_display() {
803 assert_eq!(format!("{}", PageEvent::Load), "load");
804 assert_eq!(format!("{}", PageEvent::Popup), "popup");
805 }
806
807 #[test]
808 fn test_page_event_equality() {
809 assert_eq!(PageEvent::Load, PageEvent::Load);
810 assert_ne!(PageEvent::Load, PageEvent::Close);
811 }
812
813 #[test]
814 fn test_all_page_events() {
815 let events = vec![
816 PageEvent::Close,
817 PageEvent::Console,
818 PageEvent::Crash,
819 PageEvent::Dialog,
820 PageEvent::Download,
821 PageEvent::FileChooser,
822 PageEvent::FrameAttached,
823 PageEvent::FrameDetached,
824 PageEvent::FrameNavigated,
825 PageEvent::Load,
826 PageEvent::DomContentLoaded,
827 PageEvent::PageError,
828 PageEvent::Popup,
829 PageEvent::Request,
830 PageEvent::RequestFailed,
831 PageEvent::RequestFinished,
832 PageEvent::Response,
833 PageEvent::WebSocket,
834 PageEvent::Worker,
835 ];
836 assert_eq!(events.len(), 19);
837 for event in events {
838 assert!(!event.as_str().is_empty());
839 }
840 }
841 }
842
843 mod wait_result_tests {
848 use super::*;
849
850 #[test]
851 fn test_wait_result_success() {
852 let result = WaitResult::success(Duration::from_millis(100), "test");
853 assert!(result.success);
854 assert_eq!(result.elapsed, Duration::from_millis(100));
855 assert_eq!(result.waited_for, "test");
856 }
857
858 #[test]
859 fn test_wait_result_timeout() {
860 let result = WaitResult::timeout(Duration::from_secs(30), "test condition");
861 assert!(!result.success);
862 assert_eq!(result.elapsed, Duration::from_secs(30));
863 assert_eq!(result.waited_for, "test condition");
864 }
865 }
866
867 mod waiter_tests {
872 use super::*;
873
874 #[test]
875 fn test_waiter_new() {
876 let waiter = Waiter::new();
877 assert!(waiter.current_url.is_none());
878 assert_eq!(waiter.load_state, LoadState::default());
879 }
880
881 #[test]
882 fn test_waiter_set_url() {
883 let mut waiter = Waiter::new();
884 waiter.set_url("https://example.com");
885 assert_eq!(waiter.current_url, Some("https://example.com".to_string()));
886 }
887
888 #[test]
889 fn test_waiter_set_load_state() {
890 let mut waiter = Waiter::new();
891 waiter.set_load_state(LoadState::NetworkIdle);
892 assert_eq!(waiter.load_state, LoadState::NetworkIdle);
893 }
894
895 #[test]
896 fn test_waiter_record_event() {
897 let mut waiter = Waiter::new();
898 waiter.record_event(PageEvent::Load);
899 waiter.record_event(PageEvent::DomContentLoaded);
900 assert_eq!(waiter.events.len(), 2);
901 assert!(waiter.events.contains(&PageEvent::Load));
902 }
903
904 #[test]
905 fn test_waiter_clear_events() {
906 let mut waiter = Waiter::new();
907 waiter.record_event(PageEvent::Load);
908 waiter.clear_events();
909 assert!(waiter.events.is_empty());
910 }
911
912 #[test]
913 fn test_waiter_wait_for_function_immediate_success() {
914 let waiter = Waiter::new();
915 let options = WaitOptions::new().with_timeout(100);
916 let result = waiter.wait_for_function(|| true, &options);
917 assert!(result.is_ok());
918 let result = result.unwrap();
919 assert!(result.success);
920 }
921
922 #[test]
923 fn test_waiter_wait_for_function_timeout() {
924 let waiter = Waiter::new();
925 let options = WaitOptions::new().with_timeout(100).with_poll_interval(10);
926 let result = waiter.wait_for_function(|| false, &options);
927 assert!(result.is_err());
928 match result {
929 Err(ProbarError::Timeout { ms }) => assert_eq!(ms, 100),
930 _ => panic!("Expected Timeout error"),
931 }
932 }
933
934 #[test]
935 fn test_waiter_wait_for_url_success() {
936 let mut waiter = Waiter::new();
937 waiter.set_url("https://example.com/test");
938 let options = WaitOptions::new().with_timeout(100);
939 let pattern = UrlPattern::Contains("example.com".into());
940 let result = waiter.wait_for_url(&pattern, &options);
941 assert!(result.is_ok());
942 }
943
944 #[test]
945 fn test_waiter_wait_for_url_timeout() {
946 let mut waiter = Waiter::new();
947 waiter.set_url("https://other.com");
948 let options = WaitOptions::new().with_timeout(100).with_poll_interval(10);
949 let pattern = UrlPattern::Contains("example.com".into());
950 let result = waiter.wait_for_url(&pattern, &options);
951 assert!(result.is_err());
952 }
953
954 #[test]
955 fn test_waiter_wait_for_load_state_load() {
956 let mut waiter = Waiter::new();
957 waiter.set_load_state(LoadState::Load);
958 let options = WaitOptions::new().with_timeout(100);
959 let result = waiter.wait_for_load_state(LoadState::Load, &options);
960 assert!(result.is_ok());
961 }
962
963 #[test]
964 fn test_waiter_wait_for_load_state_dom_content_loaded() {
965 let mut waiter = Waiter::new();
966 waiter.set_load_state(LoadState::DomContentLoaded);
967 let options = WaitOptions::new().with_timeout(100);
968 let result = waiter.wait_for_load_state(LoadState::DomContentLoaded, &options);
969 assert!(result.is_ok());
970 }
971
972 #[test]
973 fn test_waiter_wait_for_load_state_dom_satisfied_by_load() {
974 let mut waiter = Waiter::new();
975 waiter.set_load_state(LoadState::Load);
976 let options = WaitOptions::new().with_timeout(100);
977 let result = waiter.wait_for_load_state(LoadState::DomContentLoaded, &options);
979 assert!(result.is_ok());
980 }
981
982 #[test]
983 fn test_waiter_wait_for_event_success() {
984 let mut waiter = Waiter::new();
985 waiter.record_event(PageEvent::Popup);
986 let options = WaitOptions::new().with_timeout(100);
987 let result = waiter.wait_for_event(&PageEvent::Popup, &options);
988 assert!(result.is_ok());
989 }
990
991 #[test]
992 fn test_waiter_wait_for_event_timeout() {
993 let waiter = Waiter::new();
994 let options = WaitOptions::new().with_timeout(100).with_poll_interval(10);
995 let result = waiter.wait_for_event(&PageEvent::Popup, &options);
996 assert!(result.is_err());
997 }
998
999 #[test]
1000 fn test_waiter_wait_for_navigation() {
1001 let mut waiter = Waiter::new();
1002 waiter.set_url("https://example.com");
1003 waiter.set_load_state(LoadState::Load);
1004 let options = NavigationOptions::new()
1005 .with_timeout(100)
1006 .with_url(UrlPattern::Contains("example.com".into()));
1007 let result = waiter.wait_for_navigation(&options);
1008 assert!(result.is_ok());
1009 }
1010
1011 #[test]
1012 fn test_waiter_network_idle_no_requests() {
1013 let waiter = Waiter::new();
1014 assert!(waiter.is_network_idle());
1015 }
1016
1017 #[test]
1018 fn test_waiter_network_idle_with_pending() {
1019 let mut waiter = Waiter::new();
1020 waiter.set_pending_requests(1);
1021 assert!(!waiter.is_network_idle());
1022 }
1023
1024 #[test]
1025 fn test_waiter_with_options() {
1026 let options = WaitOptions::new().with_timeout(5000);
1027 let waiter = Waiter::with_options(options);
1028 assert_eq!(waiter.options.timeout_ms, 5000);
1029 }
1030 }
1031
1032 mod convenience_tests {
1037 use super::*;
1038
1039 #[test]
1040 fn test_wait_until_success() {
1041 let result = wait_until(|| true, 100);
1042 assert!(result.is_ok());
1043 }
1044
1045 #[test]
1046 fn test_wait_until_timeout() {
1047 let result = wait_until(|| false, 100);
1048 assert!(result.is_err());
1049 }
1050
1051 #[test]
1052 fn test_wait_timeout() {
1053 let start = Instant::now();
1054 wait_timeout(50);
1055 assert!(start.elapsed() >= Duration::from_millis(50));
1056 }
1057 }
1058
1059 mod wait_condition_tests {
1064 use super::*;
1065
1066 #[test]
1067 fn test_fn_condition_check_true() {
1068 let condition = FnCondition::new(|| true, "always true");
1069 assert!(condition.check());
1070 }
1071
1072 #[test]
1073 fn test_fn_condition_check_false() {
1074 let condition = FnCondition::new(|| false, "always false");
1075 assert!(!condition.check());
1076 }
1077
1078 #[test]
1079 fn test_fn_condition_description() {
1080 let condition = FnCondition::new(|| true, "my condition");
1081 assert_eq!(condition.description(), "my condition");
1082 }
1083
1084 #[test]
1085 fn test_waiter_wait_for_condition() {
1086 let waiter = Waiter::new();
1087 let options = WaitOptions::new().with_timeout(100);
1088 let condition = FnCondition::new(|| true, "test condition");
1089 let result = waiter.wait_for(&condition, &options);
1090 assert!(result.is_ok());
1091 }
1092 }
1093
1094 mod integration_tests {
1099 use super::*;
1100 use std::sync::atomic::{AtomicBool, Ordering};
1101 use std::sync::Arc;
1102
1103 #[test]
1104 fn test_wait_for_condition_becomes_true() {
1105 let flag = Arc::new(AtomicBool::new(false));
1106 let flag_clone = flag.clone();
1107
1108 std::thread::spawn(move || {
1110 std::thread::sleep(Duration::from_millis(50));
1111 flag_clone.store(true, Ordering::SeqCst);
1112 });
1113
1114 let waiter = Waiter::new();
1115 let options = WaitOptions::new().with_timeout(200).with_poll_interval(10);
1116 let result = waiter.wait_for_function(|| flag.load(Ordering::SeqCst), &options);
1117 assert!(result.is_ok());
1118 }
1119
1120 #[test]
1121 fn test_multiple_wait_operations() {
1122 let mut waiter = Waiter::new();
1123
1124 waiter.set_url("https://example.com");
1126 let options = WaitOptions::new().with_timeout(100);
1127 let result = waiter.wait_for_url(&UrlPattern::Contains("example".into()), &options);
1128 assert!(result.is_ok());
1129
1130 waiter.set_load_state(LoadState::Load);
1132 let result = waiter.wait_for_load_state(LoadState::Load, &options);
1133 assert!(result.is_ok());
1134
1135 waiter.record_event(PageEvent::Load);
1137 let result = waiter.wait_for_event(&PageEvent::Load, &options);
1138 assert!(result.is_ok());
1139 }
1140
1141 #[test]
1142 fn test_url_pattern_types() {
1143 let mut waiter = Waiter::new();
1144 waiter.set_url("https://example.com/path/to/page");
1145 let options = WaitOptions::new().with_timeout(100);
1146
1147 let result = waiter.wait_for_url(
1149 &UrlPattern::Exact("https://example.com/path/to/page".into()),
1150 &options,
1151 );
1152 assert!(result.is_ok());
1153
1154 let result = waiter.wait_for_url(&UrlPattern::Contains("/path/".into()), &options);
1156 assert!(result.is_ok());
1157
1158 let result =
1160 waiter.wait_for_url(&UrlPattern::Prefix("https://example".into()), &options);
1161 assert!(result.is_ok());
1162 }
1163 }
1164
1165 mod h0_constants_tests {
1170 use super::*;
1171
1172 #[test]
1173 fn h0_wait_01_default_timeout_ms() {
1174 assert_eq!(DEFAULT_WAIT_TIMEOUT_MS, 30_000);
1175 }
1176
1177 #[test]
1178 fn h0_wait_02_default_poll_interval_ms() {
1179 assert_eq!(DEFAULT_POLL_INTERVAL_MS, 50);
1180 }
1181
1182 #[test]
1183 fn h0_wait_03_network_idle_threshold_ms() {
1184 assert_eq!(NETWORK_IDLE_THRESHOLD_MS, 500);
1185 }
1186 }
1187
1188 mod h0_load_state_tests {
1189 use super::*;
1190
1191 #[test]
1192 fn h0_wait_04_load_state_load_event() {
1193 assert_eq!(LoadState::Load.event_name(), "load");
1194 }
1195
1196 #[test]
1197 fn h0_wait_05_load_state_dom_event() {
1198 assert_eq!(LoadState::DomContentLoaded.event_name(), "DOMContentLoaded");
1199 }
1200
1201 #[test]
1202 fn h0_wait_06_load_state_network_event() {
1203 assert_eq!(LoadState::NetworkIdle.event_name(), "networkidle");
1204 }
1205
1206 #[test]
1207 fn h0_wait_07_load_state_default() {
1208 assert_eq!(LoadState::default(), LoadState::Load);
1209 }
1210
1211 #[test]
1212 fn h0_wait_08_load_state_timeout_load() {
1213 assert_eq!(LoadState::Load.default_timeout_ms(), 30_000);
1214 }
1215
1216 #[test]
1217 fn h0_wait_09_load_state_timeout_network() {
1218 assert_eq!(LoadState::NetworkIdle.default_timeout_ms(), 60_000);
1219 }
1220
1221 #[test]
1222 fn h0_wait_10_load_state_display() {
1223 assert_eq!(format!("{}", LoadState::Load), "load");
1224 }
1225 }
1226
1227 mod h0_wait_options_tests {
1228 use super::*;
1229
1230 #[test]
1231 fn h0_wait_11_options_default_timeout() {
1232 let opts = WaitOptions::default();
1233 assert_eq!(opts.timeout_ms, 30_000);
1234 }
1235
1236 #[test]
1237 fn h0_wait_12_options_default_poll() {
1238 let opts = WaitOptions::default();
1239 assert_eq!(opts.poll_interval_ms, 50);
1240 }
1241
1242 #[test]
1243 fn h0_wait_13_options_default_state() {
1244 let opts = WaitOptions::default();
1245 assert_eq!(opts.wait_until, LoadState::Load);
1246 }
1247
1248 #[test]
1249 fn h0_wait_14_options_with_timeout() {
1250 let opts = WaitOptions::new().with_timeout(5000);
1251 assert_eq!(opts.timeout_ms, 5000);
1252 }
1253
1254 #[test]
1255 fn h0_wait_15_options_with_poll() {
1256 let opts = WaitOptions::new().with_poll_interval(100);
1257 assert_eq!(opts.poll_interval_ms, 100);
1258 }
1259
1260 #[test]
1261 fn h0_wait_16_options_with_state() {
1262 let opts = WaitOptions::new().with_wait_until(LoadState::NetworkIdle);
1263 assert_eq!(opts.wait_until, LoadState::NetworkIdle);
1264 }
1265
1266 #[test]
1267 fn h0_wait_17_options_timeout_duration() {
1268 let opts = WaitOptions::new().with_timeout(1000);
1269 assert_eq!(opts.timeout(), Duration::from_secs(1));
1270 }
1271
1272 #[test]
1273 fn h0_wait_18_options_poll_duration() {
1274 let opts = WaitOptions::new().with_poll_interval(100);
1275 assert_eq!(opts.poll_interval(), Duration::from_millis(100));
1276 }
1277
1278 #[test]
1279 fn h0_wait_19_options_clone() {
1280 let opts = WaitOptions::new().with_timeout(1000);
1281 let cloned = opts;
1282 assert_eq!(cloned.timeout_ms, 1000);
1283 }
1284
1285 #[test]
1286 fn h0_wait_20_options_debug() {
1287 let opts = WaitOptions::default();
1288 let debug = format!("{:?}", opts);
1289 assert!(debug.contains("WaitOptions"));
1290 }
1291 }
1292
1293 mod h0_navigation_options_tests {
1294 use super::*;
1295
1296 #[test]
1297 fn h0_wait_21_nav_default_timeout() {
1298 let opts = NavigationOptions::default();
1299 assert_eq!(opts.timeout_ms, 30_000);
1300 }
1301
1302 #[test]
1303 fn h0_wait_22_nav_default_state() {
1304 let opts = NavigationOptions::default();
1305 assert_eq!(opts.wait_until, LoadState::Load);
1306 }
1307
1308 #[test]
1309 fn h0_wait_23_nav_default_no_pattern() {
1310 let opts = NavigationOptions::default();
1311 assert!(opts.url_pattern.is_none());
1312 }
1313
1314 #[test]
1315 fn h0_wait_24_nav_with_timeout() {
1316 let opts = NavigationOptions::new().with_timeout(5000);
1317 assert_eq!(opts.timeout_ms, 5000);
1318 }
1319
1320 #[test]
1321 fn h0_wait_25_nav_with_state() {
1322 let opts = NavigationOptions::new().with_wait_until(LoadState::DomContentLoaded);
1323 assert_eq!(opts.wait_until, LoadState::DomContentLoaded);
1324 }
1325
1326 #[test]
1327 fn h0_wait_26_nav_with_url() {
1328 let opts = NavigationOptions::new().with_url(UrlPattern::Contains("test".into()));
1329 assert!(opts.url_pattern.is_some());
1330 }
1331
1332 #[test]
1333 fn h0_wait_27_nav_clone() {
1334 let opts = NavigationOptions::new().with_timeout(1000);
1335 let cloned = opts;
1336 assert_eq!(cloned.timeout_ms, 1000);
1337 }
1338
1339 #[test]
1340 fn h0_wait_28_nav_debug() {
1341 let opts = NavigationOptions::default();
1342 let debug = format!("{:?}", opts);
1343 assert!(debug.contains("NavigationOptions"));
1344 }
1345 }
1346
1347 mod h0_page_event_tests {
1348 use super::*;
1349
1350 #[test]
1351 fn h0_wait_29_event_close() {
1352 assert_eq!(PageEvent::Close.as_str(), "close");
1353 }
1354
1355 #[test]
1356 fn h0_wait_30_event_console() {
1357 assert_eq!(PageEvent::Console.as_str(), "console");
1358 }
1359
1360 #[test]
1361 fn h0_wait_31_event_crash() {
1362 assert_eq!(PageEvent::Crash.as_str(), "crash");
1363 }
1364
1365 #[test]
1366 fn h0_wait_32_event_dialog() {
1367 assert_eq!(PageEvent::Dialog.as_str(), "dialog");
1368 }
1369
1370 #[test]
1371 fn h0_wait_33_event_download() {
1372 assert_eq!(PageEvent::Download.as_str(), "download");
1373 }
1374
1375 #[test]
1376 fn h0_wait_34_event_display() {
1377 assert_eq!(format!("{}", PageEvent::Request), "request");
1378 }
1379
1380 #[test]
1381 fn h0_wait_35_event_equality() {
1382 assert_eq!(PageEvent::Load, PageEvent::Load);
1383 assert_ne!(PageEvent::Load, PageEvent::Close);
1384 }
1385
1386 #[test]
1387 fn h0_wait_36_event_clone() {
1388 let event = PageEvent::Response;
1389 let cloned = event;
1390 assert_eq!(cloned, PageEvent::Response);
1391 }
1392 }
1393
1394 mod h0_wait_result_tests {
1395 use super::*;
1396
1397 #[test]
1398 fn h0_wait_37_result_success_flag() {
1399 let result = WaitResult::success(Duration::from_millis(100), "test");
1400 assert!(result.success);
1401 }
1402
1403 #[test]
1404 fn h0_wait_38_result_success_elapsed() {
1405 let result = WaitResult::success(Duration::from_millis(500), "test");
1406 assert_eq!(result.elapsed, Duration::from_millis(500));
1407 }
1408
1409 #[test]
1410 fn h0_wait_39_result_timeout_flag() {
1411 let result = WaitResult::timeout(Duration::from_secs(30), "test");
1412 assert!(!result.success);
1413 }
1414
1415 #[test]
1416 fn h0_wait_40_result_waited_for() {
1417 let result = WaitResult::success(Duration::ZERO, "my condition");
1418 assert_eq!(result.waited_for, "my condition");
1419 }
1420
1421 #[test]
1422 fn h0_wait_41_result_clone() {
1423 let result = WaitResult::success(Duration::from_millis(100), "test");
1424 let cloned = result;
1425 assert!(cloned.success);
1426 }
1427
1428 #[test]
1429 fn h0_wait_42_result_debug() {
1430 let result = WaitResult::success(Duration::ZERO, "test");
1431 let debug = format!("{:?}", result);
1432 assert!(debug.contains("WaitResult"));
1433 }
1434 }
1435
1436 mod h0_waiter_tests {
1437 use super::*;
1438
1439 #[test]
1440 fn h0_wait_43_waiter_new() {
1441 let waiter = Waiter::new();
1442 assert!(waiter.current_url.is_none());
1443 }
1444
1445 #[test]
1446 fn h0_wait_44_waiter_set_url() {
1447 let mut waiter = Waiter::new();
1448 waiter.set_url("https://test.com");
1449 assert_eq!(waiter.current_url, Some("https://test.com".to_string()));
1450 }
1451
1452 #[test]
1453 fn h0_wait_45_waiter_set_load_state() {
1454 let mut waiter = Waiter::new();
1455 waiter.set_load_state(LoadState::NetworkIdle);
1456 assert_eq!(waiter.load_state, LoadState::NetworkIdle);
1457 }
1458
1459 #[test]
1460 fn h0_wait_46_waiter_record_event() {
1461 let mut waiter = Waiter::new();
1462 waiter.record_event(PageEvent::Load);
1463 assert_eq!(waiter.events.len(), 1);
1464 }
1465
1466 #[test]
1467 fn h0_wait_47_waiter_clear_events() {
1468 let mut waiter = Waiter::new();
1469 waiter.record_event(PageEvent::Load);
1470 waiter.clear_events();
1471 assert!(waiter.events.is_empty());
1472 }
1473
1474 #[test]
1475 fn h0_wait_48_waiter_pending_requests() {
1476 let mut waiter = Waiter::new();
1477 waiter.set_pending_requests(5);
1478 assert_eq!(waiter.pending_requests, 5);
1479 }
1480
1481 #[test]
1482 fn h0_wait_49_waiter_default() {
1483 let waiter = Waiter::default();
1484 assert_eq!(waiter.pending_requests, 0);
1485 }
1486
1487 #[test]
1488 fn h0_wait_50_waiter_with_options() {
1489 let options = WaitOptions::new().with_timeout(5000);
1490 let waiter = Waiter::with_options(options);
1491 assert_eq!(waiter.options.timeout_ms, 5000);
1492 }
1493 }
1494}