use crate::error::{WebDriverErrorInner, WebDriverResult, no_such_element};
use crate::{By, WebElement};
use std::fmt::{Display, Formatter};
async fn set_selected(element: &WebElement, select: bool) -> WebDriverResult<()> {
if element.is_selected().await? != select {
element.click().await?;
}
Ok(())
}
struct Escaped<'a>(&'a str);
impl Display for Escaped<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for (i, substring) in self.0.split('\"').enumerate() {
if i != 0 {
f.write_str(", '\"', ")?
}
write!(f, "\"{}\"", substring)?;
}
if self.0.ends_with('\"') {
f.write_str(", '\"'")?;
}
Ok(())
}
}
pub fn escape_string(value: &str) -> String {
let contains_single = value.contains('\'');
let contains_double = value.contains('\"');
if contains_single && contains_double {
format!("concat({})", Escaped(value))
} else if contains_double {
format!("'{}'", value)
} else {
format!("\"{}\"", value)
}
}
fn get_longest_token(value: &str) -> &str {
value.split(' ').max_by_key(|x| x.len()).unwrap_or("")
}
#[derive(Debug)]
pub struct SelectElement {
element: WebElement,
multiple: bool,
}
impl SelectElement {
pub async fn new(element: &WebElement) -> WebDriverResult<SelectElement> {
let multiple = element.attr("multiple").await?.filter(|x| x != "false").is_some();
let element = element.clone();
Ok(SelectElement {
element,
multiple,
})
}
pub async fn options(&self) -> WebDriverResult<Vec<WebElement>> {
self.element.find_all(By::Tag("option")).await
}
pub async fn all_selected_options(&self) -> WebDriverResult<Vec<WebElement>> {
let mut selected = Vec::new();
for option in self.options().await? {
if option.is_selected().await? {
selected.push(option);
}
}
Ok(selected)
}
pub async fn first_selected_option(&self) -> WebDriverResult<WebElement> {
for option in self.options().await? {
if option.is_selected().await? {
return Ok(option);
}
}
Err(no_such_element("No options are selected".to_string()))
}
async fn set_selection_all(&self, select: bool) -> WebDriverResult<()> {
for option in self.options().await? {
set_selected(&option, select).await?;
}
Ok(())
}
async fn set_selection_by_value(&self, value: &str, select: bool) -> WebDriverResult<()> {
let selector = format!("option[value={}]", escape_string(value));
let options = self.element.find_all(By::Css(&*selector)).await?;
for option in options {
set_selected(&option, select).await?;
if !self.multiple {
break;
}
}
Ok(())
}
async fn set_selection_by_index(&self, index: u32, select: bool) -> WebDriverResult<()> {
let selector = format!("option:nth-of-type({})", index + 1);
let option = self.element.find(By::Css(&*selector)).await?;
set_selected(&option, select).await?;
Ok(())
}
async fn set_selection_by_visible_text(&self, text: &str, select: bool) -> WebDriverResult<()> {
let mut xpath = format!(".//option[normalize-space(.) = {}]", escape_string(text));
let options = match self.element.find_all(By::XPath(&*xpath)).await {
Ok(elems) => elems,
Err(e) if matches!(*e, WebDriverErrorInner::NoSuchElement(_)) => Vec::new(),
Err(e) => return Err(e),
};
let mut matched = false;
for option in &options {
set_selected(option, select).await?;
if !self.multiple {
return Ok(());
}
matched = true;
}
if options.is_empty() && text.contains(' ') {
let substring_without_space = get_longest_token(text);
let candidates = if substring_without_space.is_empty() {
self.options().await?
} else {
xpath =
format!(".//option[contains(.,{})]", escape_string(substring_without_space));
self.element.find_all(By::XPath(&*xpath)).await?
};
for candidate in candidates {
if text == candidate.text().await? {
set_selected(&candidate, select).await?;
if !self.multiple {
return Ok(());
}
matched = true;
}
}
}
if !matched {
Err(no_such_element(format!("Could not locate element with visible text: {}", text)))
} else {
Ok(())
}
}
async fn set_selection_by_xpath_condition(
&self,
condition: &str,
select: bool,
) -> WebDriverResult<()> {
let xpath = format!(".//option[{}]", condition);
let options = self.element.find_all(By::XPath(&*xpath)).await?;
if options.is_empty() {
return Err(no_such_element(format!(
"Could not locate element matching XPath condition: {:?}",
xpath
)));
}
for option in &options {
set_selected(option, select).await?;
if !self.multiple {
break;
}
}
Ok(())
}
async fn set_selection_by_exact_text(&self, text: &str, select: bool) -> WebDriverResult<()> {
let condition = format!("text() = {}", escape_string(text));
self.set_selection_by_xpath_condition(&condition, select).await
}
async fn set_selection_by_partial_text(&self, text: &str, select: bool) -> WebDriverResult<()> {
let condition = format!("contains(text(), {})", escape_string(text));
self.set_selection_by_xpath_condition(&condition, select).await
}
pub async fn select_all(&self) -> WebDriverResult<()> {
assert!(self.multiple, "You may only select all options of a multi-select");
self.set_selection_all(true).await
}
pub async fn select_by_value(&self, value: &str) -> WebDriverResult<()> {
self.set_selection_by_value(value, true).await
}
pub async fn select_by_index(&self, index: u32) -> WebDriverResult<()> {
self.set_selection_by_index(index, true).await
}
pub async fn select_by_visible_text(&self, text: &str) -> WebDriverResult<()> {
self.set_selection_by_visible_text(text, true).await
}
pub async fn select_by_xpath_condition(&self, condition: &str) -> WebDriverResult<()> {
self.set_selection_by_xpath_condition(condition, true).await
}
pub async fn select_by_exact_text(&self, text: &str) -> WebDriverResult<()> {
self.set_selection_by_exact_text(text, true).await
}
pub async fn select_by_partial_text(&self, text: &str) -> WebDriverResult<()> {
self.set_selection_by_partial_text(text, true).await
}
pub async fn deselect_all(&self) -> WebDriverResult<()> {
assert!(self.multiple, "You may only deselect all options of a multi-select");
self.set_selection_all(false).await
}
pub async fn deselect_by_value(&self, value: &str) -> WebDriverResult<()> {
assert!(self.multiple, "You may only deselect options of a multi-select");
self.set_selection_by_value(value, false).await
}
pub async fn deselect_by_index(&self, index: u32) -> WebDriverResult<()> {
assert!(self.multiple, "You may only deselect options of a multi-select");
self.set_selection_by_index(index, false).await
}
pub async fn deselect_by_visible_text(&self, text: &str) -> WebDriverResult<()> {
assert!(self.multiple, "You may only deselect options of a multi-select");
self.set_selection_by_visible_text(text, false).await
}
pub async fn deselect_by_xpath_condition(&self, condition: &str) -> WebDriverResult<()> {
self.set_selection_by_xpath_condition(condition, false).await
}
pub async fn deselect_by_exact_text(&self, text: &str) -> WebDriverResult<()> {
assert!(self.multiple, "You may only deselect options of a multi-select");
self.set_selection_by_exact_text(text, false).await
}
pub async fn deselect_by_partial_text(&self, text: &str) -> WebDriverResult<()> {
assert!(self.multiple, "You may only deselect options of a multi-select");
self.set_selection_by_partial_text(text, false).await
}
}