use std::sync::Arc;
use std::time::Duration;
use crate::error::{WebDriverError, WebDriverErrorInfo};
use crate::prelude::{WebDriver, WebDriverResult};
use crate::{By, WebDriverCommands, WebDriverSession, WebElement};
use stringmatch::Needle;
use crate::query::conditions::{handle_errors, negate};
use crate::query::{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 fn run_filters<'b>(
&self,
mut elements: Vec<WebElement<'b>>,
) -> WebDriverResult<Vec<WebElement<'b>>> {
for func in &self.filters {
let tmp_elements = std::mem::take(&mut elements);
for element in tmp_elements {
if func(&element)? {
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 fn exists(&self) -> WebDriverResult<bool> {
let elements = self.run_poller(false)?;
Ok(!elements.is_empty())
}
pub fn not_exists(&self) -> WebDriverResult<bool> {
let elements = self.run_poller(true)?;
Ok(elements.is_empty())
}
pub fn first_opt(&self) -> WebDriverResult<Option<WebElement<'a>>> {
let elements = self.run_poller(false)?;
Ok(elements.into_iter().next())
}
pub fn first(&self) -> WebDriverResult<WebElement<'a>> {
let mut elements = self.run_poller(false)?;
if elements.is_empty() {
Err(no_such_element(&self.selectors, &self.description))
} else {
Ok(elements.remove(0))
}
}
pub fn all(&self) -> WebDriverResult<Vec<WebElement<'a>>> {
self.run_poller(false)
}
pub fn all_required(&self) -> WebDriverResult<Vec<WebElement<'a>>> {
let elements = self.run_poller(false)?;
if elements.is_empty() {
Err(no_such_element(&self.selectors, &self.description))
} else {
Ok(elements)
}
}
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) {
Ok(x) => x,
Err(WebDriverError::NoSuchElement(_)) => Vec::new(),
Err(e) => return Err(e),
};
if !elements.is_empty() {
elements = selector.run_filters(elements)?;
}
if check(!elements.is_empty()) {
return Ok(elements);
}
}
if !ticker.tick() {
return Ok(Vec::new());
}
}
}
fn fetch_elements_from_source(
&self,
selector: &ElementSelector<'a>,
) -> WebDriverResult<Vec<WebElement<'a>>> {
let by = selector.by.clone();
let single = selector.single;
let source = self.source.clone();
match single {
true => match source.as_ref() {
ElementQuerySource::Driver(driver) => driver.find_element(by).map(|x| vec![x]),
ElementQuerySource::Element(element) => element.find_element(by).map(|x| vec![x]),
},
false => match source.as_ref() {
ElementQuerySource::Driver(driver) => driver.find_elements(by),
ElementQuerySource::Element(element) => element.find_elements(by),
},
}
}
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();
match elem.id() {
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();
match elem.id() {
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();
handle_errors(elem.tag_name().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();
negate(elem.tag_name().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 {
let poller: ElementPoller = self.session.config().query_poller.clone();
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.session.config().query_poller.clone();
ElementQuery::new(ElementQuerySource::Driver(&self.session), poller, by)
}
}
#[cfg(test)]
fn _test_is_send() -> WebDriverResult<()> {
use crate::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)?;
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(())
}