use std::mem;
use std::sync::Arc;
use std::time::Duration;
use futures::Future;
use stringmatch::Needle;
use thirtyfour::error::{WebDriverError, WebDriverErrorInfo};
use thirtyfour::prelude::{WebDriver, WebDriverResult};
use thirtyfour::{By, WebDriverCommands, WebDriverSession, WebElement};
use crate::conditions::{handle_errors, negate};
use crate::{conditions, ElementPoller, ElementPollerTicker, ElementPredicate};
fn get_selector_summary(selectors: &[ElementSelector]) -> String {
let criteria: Vec<String> = selectors.iter().map(|s| s.by.to_string()).collect();
format!("[{}]", criteria.join(","))
}
fn no_such_element(selectors: &[ElementSelector], description: &str) -> WebDriverError {
let element_description = if description.is_empty() {
String::from("Element(s)")
} else {
format!("'{}' element(s)", description)
};
WebDriverError::NoSuchElement(WebDriverErrorInfo::new(&format!(
"{} not found using selectors: {}",
element_description,
&get_selector_summary(selectors)
)))
}
pub struct ElementSelector<'a> {
pub single: bool,
pub by: By<'a>,
pub filters: Vec<ElementPredicate>,
}
impl<'a> ElementSelector<'a> {
pub fn new(by: By<'a>) -> Self {
Self {
single: false,
by: by.clone(),
filters: Vec::new(),
}
}
pub fn set_single(&mut self) {
self.single = true;
}
pub fn add_filter(&mut self, f: ElementPredicate) {
self.filters.push(f);
}
pub async fn run_filters<'b>(
&self,
mut elements: Vec<WebElement<'b>>,
) -> WebDriverResult<Vec<WebElement<'b>>> {
for func in &self.filters {
let tmp_elements = mem::replace(&mut elements, Vec::new());
for element in tmp_elements {
if func(&element).await? {
elements.push(element);
}
}
if elements.is_empty() {
break;
}
}
Ok(elements)
}
}
pub enum ElementQuerySource<'a> {
Driver(&'a WebDriverSession),
Element(&'a WebElement<'a>),
}
pub struct ElementQuery<'a> {
source: Arc<ElementQuerySource<'a>>,
poller: ElementPoller,
selectors: Vec<ElementSelector<'a>>,
ignore_errors: bool,
description: String,
}
impl<'a> ElementQuery<'a> {
fn new(source: ElementQuerySource<'a>, poller: ElementPoller, by: By<'a>) -> Self {
let selector = ElementSelector::new(by.clone());
Self {
source: Arc::new(source),
poller,
selectors: vec![selector],
ignore_errors: true,
description: String::new(),
}
}
pub fn desc(mut self, description: &str) -> Self {
self.description = description.to_string();
self
}
pub fn ignore_errors(mut self, ignore: bool) -> Self {
self.ignore_errors = ignore;
self
}
pub fn with_poller(mut self, poller: ElementPoller) -> Self {
self.poller = poller;
self
}
pub fn wait(self, timeout: Duration, interval: Duration) -> Self {
self.with_poller(ElementPoller::TimeoutWithInterval(timeout, interval))
}
pub fn nowait(self) -> Self {
self.with_poller(ElementPoller::NoWait)
}
fn add_selector(mut self, selector: ElementSelector<'a>) -> Self {
self.selectors.push(selector);
self
}
pub fn or(self, by: By<'a>) -> Self {
self.add_selector(ElementSelector::new(by))
}
pub async fn exists(&self) -> WebDriverResult<bool> {
let elements = self.run_poller(false).await?;
Ok(!elements.is_empty())
}
pub async fn not_exists(&self) -> WebDriverResult<bool> {
let elements = self.run_poller(true).await?;
Ok(elements.is_empty())
}
pub async fn first(&self) -> WebDriverResult<WebElement<'a>> {
let mut elements = self.run_poller(false).await?;
if elements.is_empty() {
Err(no_such_element(&self.selectors, &self.description))
} else {
Ok(elements.remove(0))
}
}
pub async fn all(&self) -> WebDriverResult<Vec<WebElement<'a>>> {
self.run_poller(false).await
}
pub async fn all_required(&self) -> WebDriverResult<Vec<WebElement<'a>>> {
let elements = self.run_poller(false).await?;
if elements.is_empty() {
Err(no_such_element(&self.selectors, &self.description))
} else {
Ok(elements)
}
}
async fn run_poller(&self, inverted: bool) -> WebDriverResult<Vec<WebElement<'a>>> {
let no_such_element_error = no_such_element(&self.selectors, &self.description);
if self.selectors.is_empty() {
return Err(no_such_element_error);
}
let mut ticker = ElementPollerTicker::new(self.poller.clone());
let check = |value: bool| {
if inverted {
!value
} else {
value
}
};
loop {
for selector in &self.selectors {
let mut elements = match self.fetch_elements_from_source(selector).await {
Ok(x) => x,
Err(WebDriverError::NoSuchElement(_)) => Vec::new(),
Err(e) => return Err(e),
};
if !elements.is_empty() {
elements = selector.run_filters(elements).await?;
}
if check(!elements.is_empty()) {
return Ok(elements);
}
}
if !ticker.tick().await {
return Ok(Vec::new());
}
}
}
fn fetch_elements_from_source(
&self,
selector: &ElementSelector<'a>,
) -> impl Future<Output = WebDriverResult<Vec<WebElement<'a>>>> + Send {
let by = selector.by.clone();
let single = selector.single;
let source = self.source.clone();
async move {
match single {
true => match source.as_ref() {
ElementQuerySource::Driver(driver) => {
driver.find_element(by).await.map(|x| vec![x])
}
ElementQuerySource::Element(element) => {
element.find_element(by).await.map(|x| vec![x])
}
},
false => match source.as_ref() {
ElementQuerySource::Driver(driver) => driver.find_elements(by).await,
ElementQuerySource::Element(element) => element.find_elements(by).await,
},
}
}
}
pub fn with_filter(mut self, f: ElementPredicate) -> Self {
if let Some(selector) = self.selectors.last_mut() {
selector.add_filter(f);
}
self
}
pub fn with_single_selector(mut self) -> Self {
if let Some(selector) = self.selectors.last_mut() {
selector.set_single();
}
self
}
pub fn and_enabled(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_enabled(ignore_errors))
}
pub fn and_not_enabled(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_not_enabled(ignore_errors))
}
pub fn and_selected(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_selected(ignore_errors))
}
pub fn and_not_selected(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_not_selected(ignore_errors))
}
pub fn and_displayed(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_displayed(ignore_errors))
}
pub fn and_not_displayed(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_not_displayed(ignore_errors))
}
pub fn and_clickable(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_clickable(ignore_errors))
}
pub fn and_not_clickable(self) -> Self {
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_is_not_clickable(ignore_errors))
}
pub fn with_text<N>(self, text: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_text(text, ignore_errors))
}
pub fn without_text<N>(self, text: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_text(text, ignore_errors))
}
pub fn with_id<N>(self, id: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(Box::new(move |elem| {
let id = id.clone();
Box::pin(async move {
match elem.id().await {
Ok(Some(x)) => Ok(id.is_match(&x)),
Ok(None) => Ok(false),
Err(e) => handle_errors(Err(e), ignore_errors),
}
})
}))
}
pub fn without_id<N>(self, id: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(Box::new(move |elem| {
let id = id.clone();
Box::pin(async move {
match elem.id().await {
Ok(Some(x)) => Ok(!id.is_match(&x)),
Ok(None) => Ok(true),
Err(e) => handle_errors(Err(e), ignore_errors),
}
})
}))
}
pub fn with_class<N>(self, class_name: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_class(class_name, ignore_errors))
}
pub fn without_class<N>(self, class_name: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_class(class_name, ignore_errors))
}
pub fn with_tag<N>(self, tag_name: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(Box::new(move |elem| {
let tag_name = tag_name.clone();
Box::pin(async move {
handle_errors(elem.tag_name().await.map(|x| tag_name.is_match(&x)), ignore_errors)
})
}))
}
pub fn without_tag<N>(self, tag_name: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(Box::new(move |elem| {
let tag_name = tag_name.clone();
Box::pin(async move {
negate(elem.tag_name().await.map(|x| tag_name.is_match(&x)), ignore_errors)
})
}))
}
pub fn with_value<N>(self, value: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_value(value, ignore_errors))
}
pub fn without_value<N>(self, value: N) -> Self
where
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_value(value, ignore_errors))
}
pub fn with_attribute<S, N>(self, attribute_name: S, value: N) -> Self
where
S: Into<String>,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_attribute(attribute_name, value, ignore_errors))
}
pub fn without_attribute<S, N>(self, attribute_name: S, value: N) -> Self
where
S: Into<String>,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_attribute(attribute_name, value, ignore_errors))
}
pub fn with_attributes<S, N>(self, desired_attributes: &[(S, N)]) -> Self
where
S: Into<String> + Clone,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_attributes(desired_attributes, ignore_errors))
}
pub fn without_attributes<S, N>(self, desired_attributes: &[(S, N)]) -> Self
where
S: Into<String> + Clone,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_attributes(desired_attributes, ignore_errors))
}
pub fn with_property<S, N>(self, property_name: S, value: N) -> Self
where
S: Into<String>,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_property(property_name, value, ignore_errors))
}
pub fn without_property<S, N>(self, property_name: S, value: N) -> Self
where
S: Into<String>,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_property(property_name, value, ignore_errors))
}
pub fn with_properties<S, N>(self, desired_properties: &[(S, N)]) -> Self
where
S: Into<String> + Clone,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_properties(desired_properties, ignore_errors))
}
pub fn without_properties<S, N>(self, desired_properties: &[(S, N)]) -> Self
where
S: Into<String> + Clone,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_properties(desired_properties, ignore_errors))
}
pub fn with_css_property<S, N>(self, css_property_name: S, value: N) -> Self
where
S: Into<String>,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_css_property(
css_property_name,
value,
ignore_errors,
))
}
pub fn without_css_property<S, N>(self, css_property_name: S, value: N) -> Self
where
S: Into<String>,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_css_property(
css_property_name,
value,
ignore_errors,
))
}
pub fn with_css_properties<S, N>(self, desired_css_properties: &[(S, N)]) -> Self
where
S: Into<String> + Clone,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_has_css_properties(
desired_css_properties,
ignore_errors,
))
}
pub fn without_css_properties<S, N>(self, desired_css_properties: &[(S, N)]) -> Self
where
S: Into<String> + Clone,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.ignore_errors;
self.with_filter(conditions::element_lacks_css_properties(
desired_css_properties,
ignore_errors,
))
}
}
pub trait ElementQueryable {
fn query<'a>(&'a self, by: By<'a>) -> ElementQuery<'a>;
}
impl ElementQueryable for WebElement<'_> {
fn query<'a>(&'a self, by: By<'a>) -> ElementQuery<'a> {
let poller: ElementPoller =
self.session.config().get("ElementPoller").unwrap_or(ElementPoller::NoWait);
ElementQuery::new(ElementQuerySource::Element(&self), poller, by)
}
}
impl ElementQueryable for WebDriver {
fn query<'a>(&'a self, by: By<'a>) -> ElementQuery<'a> {
let poller: ElementPoller =
self.config().get("ElementPoller").unwrap_or(ElementPoller::NoWait);
ElementQuery::new(ElementQuerySource::Driver(&self.session), poller, by)
}
}
#[cfg(test)]
async fn _test_is_send() -> WebDriverResult<()> {
use thirtyfour::prelude::*;
fn is_send<T: Send>() {}
fn is_send_val<T: Send>(_val: &T) {}
let selector = ElementSelector::new(By::Css("div"));
is_send_val(&selector.run_filters(Vec::new()));
let caps = DesiredCapabilities::chrome();
let driver = WebDriver::new("http://localhost:4444", &caps).await?;
let query = driver.query(By::Css("div"));
is_send_val(&query.exists());
is_send_val(&query.not_exists());
is_send_val(&query.first());
is_send_val(&query.all());
is_send_val(&query.all_required());
Ok(())
}