use super::conditions::{collect_arg_slice, handle_errors, negate};
use super::{ElementPollerNoWait, ElementPollerWithTimeout, IntoElementPoller, conditions};
use crate::IntoArcStr;
use crate::error::{WebDriverError, WebDriverErrorInner};
use crate::prelude::WebDriverResult;
use crate::session::handle::SessionHandle;
use crate::{By, DynElementPredicate, ElementPredicate, WebElement};
use indexmap::IndexMap;
use std::borrow::Cow;
use std::fmt::{Debug, Display, Formatter, Write};
use std::sync::Arc;
use std::time::Duration;
use stringmatch::Needle;
fn get_selector_summary(selectors: &[ElementSelector]) -> String {
struct Criteria<'a>(&'a [ElementSelector]);
impl Display for Criteria<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for (i, by) in self.0.iter().map(|s| &s.by).enumerate() {
if i != 0 {
f.write_char(',')?
}
Display::fmt(by, f)?;
}
Ok(())
}
}
format!("[{}]", Criteria(selectors))
}
fn get_elements_description(len: Option<usize>, description: &str) -> Cow<'_, str> {
let suffix = match len {
Some(1) => "element",
Some(_) => "elements",
None => "element(s)",
};
match description.trim() {
"" => Cow::Borrowed(suffix),
_ => Cow::Owned(format!("'{}' {suffix}", description.escape_default())),
}
}
fn no_such_element(selectors: &[ElementSelector], description: &str) -> WebDriverError {
let element_description = get_elements_description(None, description);
crate::error::no_such_element(format!(
"no such element: {element_description} not found using selectors: {}",
get_selector_summary(selectors)
))
}
pub async fn filter_elements<I, P, Ref>(
mut elements: Vec<WebElement>,
filters: I,
) -> WebDriverResult<Vec<WebElement>>
where
I: IntoIterator<Item = Ref>,
Ref: AsRef<P>,
P: ElementPredicate + ?Sized,
{
for func in filters {
let tmp_elements = std::mem::take(&mut elements);
for element in tmp_elements {
if func.as_ref().call(element.clone()).await? {
elements.push(element);
}
}
if elements.is_empty() {
break;
}
}
Ok(elements)
}
pub struct ElementSelector {
pub by: By,
pub filters: Vec<Box<DynElementPredicate>>,
}
impl Debug for ElementSelector {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ElementSelector").field("by", &self.by).finish()
}
}
impl ElementSelector {
pub fn new(by: By) -> Self {
Self {
by,
filters: Vec::new(),
}
}
pub fn add_filter(&mut self, f: impl ElementPredicate + 'static) {
self.add_box_filter(DynElementPredicate::boxed(f));
}
pub fn add_box_filter(&mut self, f: Box<DynElementPredicate>) {
self.filters.push(f);
}
}
#[derive(Debug)]
pub enum ElementQuerySource {
Driver(Arc<SessionHandle>),
Element(WebElement),
}
#[derive(Debug, Clone, Default)]
pub enum ElementQueryWaitOptions {
#[default]
WaitDefault,
Wait {
timeout: Duration,
interval: Duration,
},
NoWait,
}
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct ElementQueryOptions {
ignore_errors: Option<bool>,
description: Option<Arc<str>>,
wait: Option<ElementQueryWaitOptions>,
}
impl ElementQueryOptions {
pub fn ignore_errors(mut self, ignore_errors: bool) -> Self {
self.ignore_errors = Some(ignore_errors);
self
}
pub fn set_ignore_errors(mut self, ignore_errors: Option<bool>) -> Self {
self.ignore_errors = ignore_errors;
self
}
pub fn description(mut self, description: impl IntoArcStr) -> Self {
self.description = Some(description.into());
self
}
pub fn set_description<T: Into<Arc<str>>>(mut self, description: Option<T>) -> Self {
self.description = description.map(|x| x.into());
self
}
pub fn wait(mut self, wait_option: ElementQueryWaitOptions) -> Self {
self.wait = Some(wait_option);
self
}
pub fn set_wait(mut self, wait_option: Option<ElementQueryWaitOptions>) -> Self {
self.wait = wait_option;
self
}
}
#[derive(Debug)]
pub struct ElementQuery {
source: ElementQuerySource,
poller: Arc<dyn IntoElementPoller + Send + Sync>,
selectors: Vec<ElementSelector>,
options: ElementQueryOptions,
}
macro_rules! disallow_empty {
($elements: expr, $self: expr) => {
if $elements.is_empty() {
let desc: &str = $self.options.description.as_deref().unwrap_or("");
Err(no_such_element(&$self.selectors, desc))
} else {
Ok($elements)
}
};
}
impl ElementQuery {
pub fn new(
source: ElementQuerySource,
by: By,
poller: Arc<dyn IntoElementPoller + Send + Sync>,
) -> Self {
let selector = ElementSelector::new(by);
Self {
source,
poller,
selectors: vec![selector],
options: ElementQueryOptions::default(),
}
}
pub fn options(mut self, options: ElementQueryOptions) -> Self {
self.options = options;
match self.options.wait {
None | Some(ElementQueryWaitOptions::WaitDefault) => self,
Some(ElementQueryWaitOptions::Wait {
timeout,
interval,
}) => self.wait(timeout, interval),
Some(ElementQueryWaitOptions::NoWait) => self.nowait(),
}
}
pub fn desc(mut self, description: &str) -> Self {
self.options = self.options.description(description);
self
}
pub fn ignore_errors(mut self, ignore: bool) -> Self {
self.options = self.options.ignore_errors(ignore);
self
}
pub fn with_poller(mut self, poller: Arc<dyn IntoElementPoller + Send + Sync>) -> Self {
self.poller = poller;
self
}
pub fn wait(self, timeout: Duration, interval: Duration) -> Self {
self.with_poller(Arc::new(ElementPollerWithTimeout::new(timeout, interval)))
}
pub fn nowait(self) -> Self {
self.with_poller(Arc::new(ElementPollerNoWait))
}
fn add_selector(mut self, selector: ElementSelector) -> Self {
self.selectors.push(selector);
self
}
pub fn or(self, by: By) -> Self {
self.add_selector(ElementSelector::new(by))
}
pub async fn exists(&self) -> WebDriverResult<bool> {
let elements = self.run_poller(true, false).await?;
Ok(!elements.is_empty())
}
pub async fn not_exists(&self) -> WebDriverResult<bool> {
let elements = self.run_poller(false, true).await?;
Ok(elements.is_empty())
}
pub async fn first_opt(&self) -> WebDriverResult<Option<WebElement>> {
let elements = self.run_poller(true, false).await?;
Ok(elements.into_iter().next())
}
pub async fn first(&self) -> WebDriverResult<WebElement> {
self.first_opt().await?.ok_or_else(|| {
let desc: &str = self.options.description.as_deref().unwrap_or("");
no_such_element(&self.selectors, desc)
})
}
pub async fn single(&self) -> WebDriverResult<WebElement> {
let mut elements = self.run_poller(false, false).await?;
if elements.len() == 1 {
Ok(elements.swap_remove(0))
} else if !elements.is_empty() {
let element_description = get_elements_description(
Some(elements.len()),
self.options.description.as_deref().unwrap_or(""),
);
Err(crate::error::no_such_element(format!(
"too many elements received; found {count} {element_description} using selectors: {selectors}",
count = elements.len(),
selectors = get_selector_summary(&self.selectors)
)))
} else {
let desc: &str = self.options.description.as_deref().unwrap_or("");
let err = no_such_element(&self.selectors, desc);
Err(err)
}
}
pub async fn any(&self) -> WebDriverResult<Vec<WebElement>> {
self.run_poller(false, false).await
}
pub async fn any_required(&self) -> WebDriverResult<Vec<WebElement>> {
let elements = self.run_poller(false, false).await?;
disallow_empty!(elements, self)
}
pub async fn all_from_selector(&self) -> WebDriverResult<Vec<WebElement>> {
self.run_poller(true, false).await
}
pub async fn all_from_selector_required(&self) -> WebDriverResult<Vec<WebElement>> {
let elements = self.run_poller(true, false).await?;
disallow_empty!(elements, self)
}
async fn run_poller(
&self,
short_circuit: bool,
stop_on_miss: bool,
) -> WebDriverResult<Vec<WebElement>> {
let desc: &str = self.options.description.as_deref().unwrap_or("");
let no_such_element_error = no_such_element(&self.selectors, desc);
if self.selectors.is_empty() {
return Err(no_such_element_error);
}
let mut poller = self.poller.start();
let mut elements = IndexMap::new();
loop {
for selector in &self.selectors {
let mut new_elements =
match self.fetch_elements_from_source(selector.by.clone()).await {
Ok(x) => x,
Err(e) if matches!(*e, WebDriverErrorInner::NoSuchElement(_)) => Vec::new(),
Err(e) => return Err(e),
};
if !new_elements.is_empty() {
new_elements = filter_elements(new_elements, &selector.filters).await?;
}
if short_circuit && (stop_on_miss == new_elements.is_empty()) {
return Ok(new_elements);
}
for element in new_elements {
elements.insert(element.element_id(), element);
}
}
if stop_on_miss == elements.is_empty() {
return Ok(elements.into_values().collect());
}
if !poller.tick().await {
return Ok(elements.into_values().collect());
}
}
}
async fn fetch_elements_from_source(&self, by: By) -> WebDriverResult<Vec<WebElement>> {
match &self.source {
ElementQuerySource::Driver(driver) => driver.find_all(by).await,
ElementQuerySource::Element(element) => element.find_all(by).await,
}
}
pub fn with_filter(mut self, f: impl ElementPredicate + 'static) -> Self {
if let Some(selector) = self.selectors.last_mut() {
selector.add_filter(f);
}
self
}
pub fn and_enabled(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_is_enabled(ignore_errors))
}
pub fn and_not_enabled(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_is_not_enabled(ignore_errors))
}
pub fn and_selected(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_is_selected(ignore_errors))
}
pub fn and_not_selected(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_is_not_selected(ignore_errors))
}
pub fn and_displayed(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_is_displayed(ignore_errors))
}
pub fn and_not_displayed(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_is_not_displayed(ignore_errors))
}
pub fn and_clickable(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_is_clickable(ignore_errors))
}
pub fn and_not_clickable(self) -> Self {
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
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.options.ignore_errors.unwrap_or_default();
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.options.ignore_errors.unwrap_or_default();
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.options.ignore_errors.unwrap_or_default();
self.with_filter(move |elem: WebElement| {
let id = id.clone();
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.options.ignore_errors.unwrap_or_default();
self.with_filter(move |elem: WebElement| {
let id = id.clone();
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.options.ignore_errors.unwrap_or_default();
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.options.ignore_errors.unwrap_or_default();
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.options.ignore_errors.unwrap_or_default();
self.with_filter(move |elem: WebElement| {
let tag_name = tag_name.clone();
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.options.ignore_errors.unwrap_or_default();
self.with_filter(move |elem: WebElement| {
let tag_name = tag_name.clone();
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.options.ignore_errors.unwrap_or_default();
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.options.ignore_errors.unwrap_or_default();
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: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_has_attribute(
attribute_name.into(),
value,
ignore_errors,
))
}
pub fn without_attribute<S, N>(self, attribute_name: S, value: N) -> Self
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_lacks_attribute(
attribute_name.into(),
value,
ignore_errors,
))
}
pub fn with_attributes<S, N>(self, desired_attributes: impl IntoIterator<Item = (S, N)>) -> Self
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_has_attributes(
collect_arg_slice(desired_attributes),
ignore_errors,
))
}
pub fn without_attributes<S, N>(
self,
desired_attributes: impl IntoIterator<Item = (S, N)>,
) -> Self
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_lacks_attributes(
collect_arg_slice(desired_attributes),
ignore_errors,
))
}
pub fn with_property<S, N>(self, property_name: S, value: N) -> Self
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_has_property(
property_name.into(),
value,
ignore_errors,
))
}
pub fn without_property<S, N>(self, property_name: S, value: N) -> Self
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_lacks_property(
property_name.into(),
value,
ignore_errors,
))
}
pub fn with_properties<S, N>(self, desired_properties: impl IntoIterator<Item = (S, N)>) -> Self
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_has_properties(
collect_arg_slice(desired_properties),
ignore_errors,
))
}
pub fn without_properties<S, N>(
self,
desired_properties: impl IntoIterator<Item = (S, N)>,
) -> Self
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_lacks_properties(
collect_arg_slice(desired_properties),
ignore_errors,
))
}
pub fn with_css_property<S, N>(self, css_property_name: S, value: N) -> Self
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_has_css_property(
css_property_name.into(),
value,
ignore_errors,
))
}
pub fn without_css_property<S, N>(self, css_property_name: S, value: N) -> Self
where
S: IntoArcStr,
N: Needle + Clone + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_lacks_css_property(
css_property_name.into(),
value,
ignore_errors,
))
}
pub fn with_css_properties<S, N>(
self,
desired_css_properties: impl IntoIterator<Item = (S, N)>,
) -> Self
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_has_css_properties(
collect_arg_slice(desired_css_properties),
ignore_errors,
))
}
pub fn without_css_properties<S, N>(
self,
desired_css_properties: impl IntoIterator<Item = (S, N)>,
) -> Self
where
S: IntoArcStr,
N: Needle + Send + Sync + 'static,
{
let ignore_errors = self.options.ignore_errors.unwrap_or_default();
self.with_filter(conditions::element_lacks_css_properties(
collect_arg_slice(desired_css_properties),
ignore_errors,
))
}
}
pub trait ElementQueryable {
fn query(&self, by: By) -> ElementQuery;
}
impl ElementQueryable for WebElement {
fn query(&self, by: By) -> ElementQuery {
ElementQuery::new(
ElementQuerySource::Element(self.clone()),
by,
self.handle.config().poller.clone(),
)
}
}
impl ElementQueryable for Arc<SessionHandle> {
fn query(&self, by: By) -> ElementQuery {
ElementQuery::new(
ElementQuerySource::Driver(self.clone()),
by,
self.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 selector = ElementSelector::new(By::Css("div"));
is_send_val(&filter_elements(Vec::new(), &selector.filters));
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_from_selector());
is_send_val(&query.all_from_selector_required());
Ok(())
}