use std::time::{Duration, Instant};
use tracing::{debug, trace};
use crate::error::{Error, Result};
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(200);
#[derive(Debug, Clone)]
pub struct WaitOptions {
pub timeout: Duration,
pub poll_interval: Duration,
}
impl Default for WaitOptions {
fn default() -> Self {
Self {
timeout: DEFAULT_TIMEOUT,
poll_interval: DEFAULT_POLL_INTERVAL,
}
}
}
impl WaitOptions {
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn poll_interval(mut self, interval: Duration) -> Self {
self.poll_interval = interval;
self
}
}
pub async fn retry_until<F, Fut>(check_fn: F, opts: &WaitOptions, label: &str) -> Result<()>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<bool>>,
{
let start = Instant::now();
debug!("Waiting for: {label} (timeout: {:?})", opts.timeout);
loop {
match check_fn().await {
Ok(true) => {
debug!("Condition met: {label} ({:?} elapsed)", start.elapsed());
return Ok(());
}
Ok(false) | Err(_) => {
if start.elapsed() >= opts.timeout {
return Err(Error::Timeout(format!(
"Timed out waiting for: {label} ({:?})",
opts.timeout
)));
}
trace!("Not yet: {label}, polling in {:?}", opts.poll_interval);
tokio::time::sleep(opts.poll_interval).await;
}
}
}
}
pub async fn sleep(duration: Duration) {
tokio::time::sleep(duration).await;
}
pub async fn wait_sync<F>(predicate: F, opts: &WaitOptions, label: &str) -> Result<()>
where
F: Fn() -> bool,
{
let start = Instant::now();
loop {
if predicate() {
return Ok(());
}
if start.elapsed() >= opts.timeout {
return Err(Error::Timeout(format!("Timed out: {label}")));
}
tokio::time::sleep(opts.poll_interval).await;
}
}
pub async fn wait_element<F, Fut>(
find_fn: F,
locator: &str,
opts: &WaitOptions,
) -> Result<crate::element::Element>
where
F: Fn(&str) -> Fut,
Fut: std::future::Future<Output = Result<crate::element::Element>>,
{
let start = Instant::now();
loop {
match find_fn(locator).await {
Ok(el) => return Ok(el),
Err(_) => {
if start.elapsed() >= opts.timeout {
return Err(Error::Timeout(format!(
"Timed out waiting for element: {locator}"
)));
}
tokio::time::sleep(opts.poll_interval).await;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wait_options_default() {
let opts = WaitOptions::default();
assert_eq!(opts.timeout, DEFAULT_TIMEOUT);
assert_eq!(opts.poll_interval, DEFAULT_POLL_INTERVAL);
}
#[test]
fn test_wait_options_builder() {
let opts = WaitOptions::default()
.timeout(Duration::from_secs(30))
.poll_interval(Duration::from_millis(500));
assert_eq!(opts.timeout, Duration::from_secs(30));
assert_eq!(opts.poll_interval, Duration::from_millis(500));
}
#[tokio::test]
async fn test_wait_sync_immediate() {
let opts = WaitOptions::default();
let result = wait_sync(|| true, &opts, "immediate").await;
assert!(result.is_ok());
}
}