1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::time::Duration;
use thirtyfour::extensions::query::{ElementPollerWithTimeout, IntoElementPoller};
use typed_builder::TypedBuilder;
/// Wait configuration used by thirtyfour element queries and element waits.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TypedBuilder)]
pub struct ElementQueryWaitConfig {
/// Maximum time an element query or element wait keeps polling before failing.
///
/// This controls `thirtyfour`'s explicit element-query polling, such as queries created via
/// `driver.query(...)` and waits that use the configured driver poller. It is not a
/// `WebDriver` protocol timeout, and it does not control page navigation, script execution, or
/// ordinary Rust futures in the test body.
///
/// In browser tests this is the timeout that usually determines how long the test waits for a
/// dynamic DOM condition, such as an element appearing after hydration, a button becoming
/// clickable, or content being inserted after an application request completes.
#[builder(setter(into))]
timeout: Duration,
/// Delay between element-query poll attempts during the timeout window.
///
/// Smaller intervals can make tests react faster once the expected element state appears, but
/// they also issue `WebDriver` commands more frequently. Larger intervals reduce browser-driver
/// traffic, but can make successful waits complete later than necessary.
///
/// Avoid `Duration::ZERO` for values from configuration, environment variables, or other
/// dynamic input. A zero interval is accepted by [`Self::new`] and the builder for trusted
/// construction, but can create an immediate retry loop in the underlying element poller. Use
/// [`Self::try_new`] when the interval is not a hard-coded trusted value.
#[builder(setter(into))]
interval: Duration,
}
/// Invalid element query wait configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ElementQueryWaitConfigError {
/// The poll interval must be non-zero.
#[error("Element query wait poll interval must be non-zero.")]
ZeroInterval,
}
impl ElementQueryWaitConfig {
/// Create a new element query wait configuration without validation.
///
/// Use [`Self::try_new`] for values from user input or environment configuration. A zero
/// interval is accepted here for const construction, but can cause immediate retry loops in the
/// underlying `WebDriver` element poller.
#[must_use]
pub const fn new(timeout: Duration, interval: Duration) -> Self {
Self { timeout, interval }
}
/// Create a new element query wait configuration.
///
/// # Errors
///
/// Returns [`ElementQueryWaitConfigError::ZeroInterval`] if `interval` is
/// [`Duration::ZERO`].
pub fn try_new(
timeout: Duration,
interval: Duration,
) -> Result<Self, ElementQueryWaitConfigError> {
if interval.is_zero() {
return Err(ElementQueryWaitConfigError::ZeroInterval);
}
Ok(Self { timeout, interval })
}
/// Maximum time an element query or element wait keeps polling before failing.
#[must_use]
pub const fn timeout(self) -> Duration {
self.timeout
}
/// Delay between element-query poll attempts during the timeout window.
#[must_use]
pub const fn interval(self) -> Duration {
self.interval
}
pub(crate) fn into_thirtyfour_poller(self) -> impl IntoElementPoller + Send + Sync {
ElementPollerWithTimeout::new(self.timeout, self.interval)
}
}
#[cfg(test)]
mod tests {
use super::*;
use assertr::prelude::*;
#[test]
fn builder_preserves_timeout_and_interval() {
let wait = ElementQueryWaitConfig::builder()
.timeout(Duration::from_secs(10))
.interval(Duration::from_millis(250))
.build();
assert_that!(wait.timeout()).is_equal_to(Duration::from_secs(10));
assert_that!(wait.interval()).is_equal_to(Duration::from_millis(250));
}
#[test]
fn builder_preserves_zero_interval_for_trusted_callers() {
let wait = ElementQueryWaitConfig::builder()
.timeout(Duration::from_secs(10))
.interval(Duration::ZERO)
.build();
assert_that!(wait.interval()).is_equal_to(Duration::ZERO);
}
#[test]
fn try_new_accepts_non_zero_interval() {
let wait =
ElementQueryWaitConfig::try_new(Duration::from_secs(10), Duration::from_millis(250))
.expect("non-zero interval should be accepted");
assert_that!(wait.timeout()).is_equal_to(Duration::from_secs(10));
assert_that!(wait.interval()).is_equal_to(Duration::from_millis(250));
}
#[test]
fn try_new_rejects_zero_interval() {
let err = ElementQueryWaitConfig::try_new(Duration::from_secs(10), Duration::ZERO)
.expect_err("zero interval should be rejected");
assert_that!(err).is_equal_to(ElementQueryWaitConfigError::ZeroInterval);
}
#[test]
fn new_preserves_zero_interval_for_const_trusted_callers() {
const WAIT: ElementQueryWaitConfig =
ElementQueryWaitConfig::new(Duration::from_secs(10), Duration::ZERO);
assert_that!(WAIT.interval()).is_equal_to(Duration::ZERO);
}
}