use super::conditions::{collect_arg_slice, handle_errors};
use super::{ElementPollerWithTimeout, IntoElementPoller, conditions};
use crate::IntoArcStr;
use crate::error::WebDriverError;
use crate::prelude::WebDriverResult;
use crate::{DynElementPredicate, ElementPredicate, WebElement};
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use stringmatch::Needle;
#[derive(Debug)]
pub struct ElementWaiter {
element: WebElement,
poller: Arc<dyn IntoElementPoller + Send + Sync>,
message: String,
ignore_errors: bool,
}
impl ElementWaiter {
pub fn new(element: WebElement, poller: Arc<dyn IntoElementPoller + Send + Sync>) -> Self {
Self {
element,
poller,
message: String::new(),
ignore_errors: true,
}
}
pub fn with_poller(mut self, poller: Arc<dyn IntoElementPoller + Send + Sync>) -> Self {
self.poller = poller;
self
}
pub fn error(mut self, message: &str) -> Self {
self.message = message.to_string();
self
}
pub fn ignore_errors(mut self, ignore: bool) -> Self {
self.ignore_errors = ignore;
self
}
pub fn wait(self, timeout: Duration, interval: Duration) -> Self {
self.with_poller(Arc::new(ElementPollerWithTimeout::new(timeout, interval)))
}
async fn run_poller<'a, F, I, P>(&self, conditions: F) -> WebDriverResult<bool>
where
F: Fn() -> I,
I: IntoIterator<Item = &'a P>,
P: ElementPredicate + ?Sized + 'a,
{
let mut poller = self.poller.start();
loop {
let mut conditions_met = true;
for f in conditions() {
if !f.call(self.element.clone()).await? {
conditions_met = false;
break;
}
}
if conditions_met {
return Ok(true);
}
if !poller.tick().await {
return Ok(false);
}
}
}
fn timeout(self) -> WebDriverResult<()> {
Err(WebDriverError::Timeout(format!("element condition timed out: {}", self.message)))
}
pub async fn condition(self, f: impl ElementPredicate) -> WebDriverResult<()> {
match self.run_poller(|| [&f].into_iter()).await? {
true => Ok(()),
false => self.timeout(),
}
}
pub async fn conditions(
self,
conditions: Vec<Box<DynElementPredicate>>,
) -> WebDriverResult<()> {
match self.run_poller(|| conditions.iter().map(Box::deref)).await? {
true => Ok(()),
false => self.timeout(),
}
}
pub async fn stale(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(move |elem: WebElement| async move {
handle_errors(elem.is_present().await.map(|x| !x), ignore_errors)
})
.await
}
pub async fn displayed(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_displayed(ignore_errors)).await
}
pub async fn not_displayed(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_not_displayed(ignore_errors)).await
}
pub async fn selected(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_selected(ignore_errors)).await
}
pub async fn not_selected(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_not_selected(ignore_errors)).await
}
pub async fn enabled(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_enabled(ignore_errors)).await
}
pub async fn not_enabled(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_not_enabled(ignore_errors)).await
}
pub async fn clickable(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_clickable(ignore_errors)).await
}
pub async fn not_clickable(self) -> WebDriverResult<()> {
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_is_not_clickable(ignore_errors)).await
}
pub async fn has_class<N>(self, class_name: N) -> WebDriverResult<()>
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_class(class_name, ignore_errors)).await
}
pub async fn lacks_class<N>(self, class_name: N) -> WebDriverResult<()>
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_class(class_name, ignore_errors)).await
}
pub async fn has_text<N>(self, text: N) -> WebDriverResult<()>
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_text(text, ignore_errors)).await
}
pub async fn lacks_text<N>(self, text: N) -> WebDriverResult<()>
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_text(text, ignore_errors)).await
}
pub async fn has_value<N>(self, value: N) -> WebDriverResult<()>
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_value(value, ignore_errors)).await
}
pub async fn lacks_value<N>(self, value: N) -> WebDriverResult<()>
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_value(value, ignore_errors)).await
}
pub async fn has_attribute<S, N>(self, attribute_name: S, value: N) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_attribute(
attribute_name.into(),
value,
ignore_errors,
))
.await
}
pub async fn lacks_attribute<S, N>(self, attribute_name: S, value: N) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_attribute(
attribute_name.into(),
value,
ignore_errors,
))
.await
}
pub async fn has_attributes<S, N>(
self,
desired_attributes: impl IntoIterator<Item = (S, N)>,
) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_attributes(
collect_arg_slice(desired_attributes),
ignore_errors,
))
.await
}
pub async fn lacks_attributes<S, N>(
self,
desired_attributes: impl IntoIterator<Item = (S, N)>,
) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_attributes(
collect_arg_slice(desired_attributes),
ignore_errors,
))
.await
}
pub async fn has_property<S, N>(self, property_name: S, value: N) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_property(property_name.into(), value, ignore_errors))
.await
}
pub async fn lacks_property<S, N>(self, property_name: S, value: N) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_property(
property_name.into(),
value,
ignore_errors,
))
.await
}
pub async fn has_properties<S, N>(
self,
desired_properties: impl IntoIterator<Item = (S, N)>,
) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let desired_properties: Arc<[(Arc<str>, N)]> =
desired_properties.into_iter().map(|(a, b)| (a.into(), b)).collect();
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_properties(desired_properties, ignore_errors)).await
}
pub async fn lacks_properties<S, N>(
self,
desired_properties: impl IntoIterator<Item = (S, N)>,
) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_properties(
collect_arg_slice(desired_properties),
ignore_errors,
))
.await
}
pub async fn has_css_property<S, N>(self, css_property_name: S, value: N) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_css_property(
css_property_name.into(),
value,
ignore_errors,
))
.await
}
pub async fn lacks_css_property<S, N>(
self,
css_property_name: S,
value: N,
) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_css_property(
css_property_name.into(),
value,
ignore_errors,
))
.await
}
pub async fn has_css_properties<S, N>(
self,
desired_css_properties: impl IntoIterator<Item = (S, N)>,
) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_has_css_properties(
collect_arg_slice(desired_css_properties),
ignore_errors,
))
.await
}
pub async fn lacks_css_properties<S, N>(
self,
desired_css_properties: impl IntoIterator<Item = (S, N)>,
) -> WebDriverResult<()>
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.condition(conditions::element_lacks_css_properties(
collect_arg_slice(desired_css_properties),
ignore_errors,
))
.await
}
}
pub trait ElementWaitable {
fn wait_until(&self) -> ElementWaiter;
}
impl ElementWaitable for WebElement {
fn wait_until(&self) -> ElementWaiter {
ElementWaiter::new(self.clone(), self.handle.config().poller.clone())
}
}
#[cfg(test)]
async fn _test_is_send() -> WebDriverResult<()> {
use crate::prelude::*;
fn is_send<T: Send>() {}
fn is_send_val<T: Send>(_val: &T) {}
let caps = DesiredCapabilities::chrome();
let driver = WebDriver::new("http://localhost:4444", caps).await?;
let elem = driver.find(By::Css(r#"div"#)).await?;
is_send_val(&elem.wait_until().stale());
is_send_val(&elem.wait_until().displayed());
is_send_val(&elem.wait_until().selected());
is_send_val(&elem.wait_until().enabled());
is_send_val(
&elem.wait_until().condition(move |elem: WebElement| async move {
elem.is_enabled().await.or(Ok(false))
}),
);
Ok(())
}