Skip to main content

jugar_probar/
wait.rs

1//! Wait Mechanisms (PMAT-005)
2//!
3//! Playwright-compatible wait mechanisms for synchronization.
4//!
5//! ## EXTREME TDD: Tests written FIRST per spec Section 6.1
6//!
7//! ## Toyota Way Application
8//!
9//! - **Jidoka**: Automatic detection of ready state
10//! - **Poka-Yoke**: Type-safe wait conditions prevent invalid waits
11//! - **Muda**: Efficient polling reduces wasted CPU cycles
12
13use crate::network::UrlPattern;
14use crate::result::{ProbarError, ProbarResult};
15use std::time::{Duration, Instant};
16
17// =============================================================================
18// CONSTANTS
19// =============================================================================
20
21/// Default timeout for wait operations (30 seconds)
22pub const DEFAULT_WAIT_TIMEOUT_MS: u64 = 30_000;
23
24/// Default polling interval (50ms)
25pub const DEFAULT_POLL_INTERVAL_MS: u64 = 50;
26
27/// Network idle threshold (500ms without requests)
28pub const NETWORK_IDLE_THRESHOLD_MS: u64 = 500;
29
30// =============================================================================
31// LOAD STATE
32// =============================================================================
33
34/// Page load states (Playwright parity)
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum LoadState {
37    /// Wait for the `load` event to fire
38    Load,
39    /// Wait for `DOMContentLoaded` event
40    DomContentLoaded,
41    /// Wait for network to be idle (no requests for 500ms)
42    NetworkIdle,
43}
44
45impl LoadState {
46    /// Get the JavaScript event name for this load state
47    #[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    /// Get default timeout for this load state
57    #[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, // Network idle can take longer
63        }
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// =============================================================================
80// WAIT OPTIONS
81// =============================================================================
82
83/// Options for wait operations
84#[derive(Debug, Clone)]
85pub struct WaitOptions {
86    /// Timeout in milliseconds
87    pub timeout_ms: u64,
88    /// Polling interval in milliseconds
89    pub poll_interval_ms: u64,
90    /// State to wait for (for navigation)
91    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    /// Create new wait options with defaults
106    #[must_use]
107    pub fn new() -> Self {
108        Self::default()
109    }
110
111    /// Set timeout in milliseconds
112    #[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    /// Set polling interval in milliseconds
119    #[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    /// Set load state to wait for
126    #[must_use]
127    pub const fn with_wait_until(mut self, state: LoadState) -> Self {
128        self.wait_until = state;
129        self
130    }
131
132    /// Get timeout as Duration
133    #[must_use]
134    pub const fn timeout(&self) -> Duration {
135        Duration::from_millis(self.timeout_ms)
136    }
137
138    /// Get poll interval as Duration
139    #[must_use]
140    pub const fn poll_interval(&self) -> Duration {
141        Duration::from_millis(self.poll_interval_ms)
142    }
143}
144
145// =============================================================================
146// NAVIGATION OPTIONS
147// =============================================================================
148
149/// Options for navigation wait
150#[derive(Debug, Clone)]
151pub struct NavigationOptions {
152    /// Timeout in milliseconds
153    pub timeout_ms: u64,
154    /// Load state to wait for
155    pub wait_until: LoadState,
156    /// URL pattern to match (optional)
157    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    /// Create new navigation options
172    #[must_use]
173    pub fn new() -> Self {
174        Self::default()
175    }
176
177    /// Set timeout
178    #[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    /// Set load state
185    #[must_use]
186    pub const fn with_wait_until(mut self, state: LoadState) -> Self {
187        self.wait_until = state;
188        self
189    }
190
191    /// Set URL pattern
192    #[must_use]
193    pub fn with_url(mut self, pattern: UrlPattern) -> Self {
194        self.url_pattern = Some(pattern);
195        self
196    }
197}
198
199// =============================================================================
200// PAGE EVENTS
201// =============================================================================
202
203/// Page event types (Playwright parity)
204#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub enum PageEvent {
206    /// Page closed
207    Close,
208    /// Console message
209    Console,
210    /// Page crashed
211    Crash,
212    /// Dialog opened (alert, confirm, prompt)
213    Dialog,
214    /// Download started
215    Download,
216    /// File chooser opened
217    FileChooser,
218    /// Frame attached
219    FrameAttached,
220    /// Frame detached
221    FrameDetached,
222    /// Frame navigated
223    FrameNavigated,
224    /// Page loaded
225    Load,
226    /// DOM content loaded
227    DomContentLoaded,
228    /// Page error
229    PageError,
230    /// Popup opened
231    Popup,
232    /// Request made
233    Request,
234    /// Request failed
235    RequestFailed,
236    /// Request finished
237    RequestFinished,
238    /// Response received
239    Response,
240    /// WebSocket created
241    WebSocket,
242    /// Worker created
243    Worker,
244}
245
246impl PageEvent {
247    /// Get the event name string
248    #[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
280// =============================================================================
281// WAIT CONDITION TRAIT
282// =============================================================================
283
284/// Trait for custom wait conditions
285pub trait WaitCondition: Send + Sync {
286    /// Check if the condition is satisfied
287    fn check(&self) -> bool;
288
289    /// Get description for error messages
290    fn description(&self) -> String;
291}
292
293/// A function-based wait condition
294pub 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    /// Create a new function condition
309    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// =============================================================================
328// WAIT RESULT
329// =============================================================================
330
331/// Result of a wait operation
332#[derive(Debug, Clone)]
333pub struct WaitResult {
334    /// Whether the wait was successful
335    pub success: bool,
336    /// Time spent waiting
337    pub elapsed: Duration,
338    /// Description of what was waited for
339    pub waited_for: String,
340}
341
342impl WaitResult {
343    /// Create a successful wait result
344    #[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    /// Create a timeout wait result
354    #[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// =============================================================================
365// WAITER IMPLEMENTATION
366// =============================================================================
367
368/// Waiter for synchronization operations
369#[derive(Debug, Clone, Default)]
370pub struct Waiter {
371    /// Default options (reserved for future use)
372    #[allow(dead_code)]
373    options: WaitOptions,
374    /// Current URL (for URL matching)
375    current_url: Option<String>,
376    /// Current load state
377    load_state: LoadState,
378    /// Pending network requests count
379    pending_requests: usize,
380    /// Time of last network activity
381    last_network_activity: Option<Instant>,
382    /// Events that have occurred
383    events: Vec<PageEvent>,
384}
385
386impl Waiter {
387    /// Create a new waiter with default options
388    #[must_use]
389    pub fn new() -> Self {
390        Self::default()
391    }
392
393    /// Create with custom options
394    #[must_use]
395    pub fn with_options(options: WaitOptions) -> Self {
396        Self {
397            options,
398            ..Default::default()
399        }
400    }
401
402    /// Set current URL (for testing)
403    pub fn set_url(&mut self, url: impl Into<String>) {
404        self.current_url = Some(url.into());
405    }
406
407    /// Set load state (for testing)
408    pub fn set_load_state(&mut self, state: LoadState) {
409        self.load_state = state;
410    }
411
412    /// Update pending request count
413    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    /// Record an event
421    pub fn record_event(&mut self, event: PageEvent) {
422        self.events.push(event);
423    }
424
425    /// Clear recorded events
426    pub fn clear_events(&mut self) {
427        self.events.clear();
428    }
429
430    /// Wait for a custom condition
431    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    /// Wait for URL to match pattern
456    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    /// Wait for load state
483    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    /// Check if network is idle
517    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, // No network activity = idle
525        }
526    }
527
528    /// Wait for navigation to complete
529    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 URL pattern specified, wait for URL first
535        if let Some(ref pattern) = options.url_pattern {
536            self.wait_for_url(pattern, &wait_options)?;
537        }
538
539        // Then wait for load state
540        self.wait_for_load_state(options.wait_until, &wait_options)
541    }
542
543    /// Wait for a page event to occur
544    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    /// Wait for function/predicate to return true
569    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
594// =============================================================================
595// CONVENIENCE FUNCTIONS
596// =============================================================================
597
598/// Wait for a condition with default options
599pub 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
609/// Wait for a fixed duration (discouraged - use wait conditions instead)
610pub fn wait_timeout(duration_ms: u64) {
611    std::thread::sleep(Duration::from_millis(duration_ms));
612}
613
614// =============================================================================
615// TESTS - EXTREME TDD: Tests written FIRST
616// =============================================================================
617
618#[cfg(test)]
619#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
620mod tests {
621    use super::*;
622
623    // =========================================================================
624    // LoadState Tests
625    // =========================================================================
626
627    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    // =========================================================================
674    // WaitOptions Tests
675    // =========================================================================
676
677    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    // =========================================================================
737    // NavigationOptions Tests
738    // =========================================================================
739
740    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    // =========================================================================
783    // PageEvent Tests
784    // =========================================================================
785
786    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    // =========================================================================
844    // WaitResult Tests
845    // =========================================================================
846
847    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    // =========================================================================
868    // Waiter Tests
869    // =========================================================================
870
871    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            // Load state satisfies DomContentLoaded
978            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    // =========================================================================
1033    // Convenience Function Tests
1034    // =========================================================================
1035
1036    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    // =========================================================================
1060    // WaitCondition Trait Tests
1061    // =========================================================================
1062
1063    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    // =========================================================================
1095    // Integration Tests
1096    // =========================================================================
1097
1098    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            // Set flag after 50ms
1109            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            // First wait - URL
1125            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            // Second wait - load state
1131            waiter.set_load_state(LoadState::Load);
1132            let result = waiter.wait_for_load_state(LoadState::Load, &options);
1133            assert!(result.is_ok());
1134
1135            // Third wait - event
1136            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            // Exact match
1148            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            // Contains match
1155            let result = waiter.wait_for_url(&UrlPattern::Contains("/path/".into()), &options);
1156            assert!(result.is_ok());
1157
1158            // Prefix match
1159            let result =
1160                waiter.wait_for_url(&UrlPattern::Prefix("https://example".into()), &options);
1161            assert!(result.is_ok());
1162        }
1163    }
1164
1165    // =========================================================================
1166    // Hâ‚€ EXTREME TDD: Wait Mechanism Tests (G.1 Auto-waiting P0)
1167    // =========================================================================
1168
1169    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}