use crate::error::{WebDriverError, WebDriverResult};
use crate::{By, WebElement};
async fn set_selected(element: &WebElement, select: bool) -> WebDriverResult<()> {
if element.is_selected().await? != select {
element.click().await?;
}
Ok(())
}
pub fn escape_string(value: &str) -> String {
let contains_single = value.contains('\'');
let contains_double = value.contains('\"');
if contains_single && contains_double {
let mut result = vec![String::from("concat(")];
for substring in value.split('\"') {
result.push(format!("\"{}\"", substring));
result.push(String::from(", '\"', "));
}
result.pop();
if value.ends_with('\"') {
result.push(String::from(", '\"'"));
}
return result.join("") + ")";
}
if contains_double {
format!("'{}'", value)
} else {
format!("\"{}\"", value)
}
}
fn get_longest_token(value: &str) -> &str {
let mut longest = "";
for item in value.split(' ') {
if item.len() > longest.len() {
longest = item;
}
}
longest
}
pub struct SelectElement {
element: WebElement,
multiple: bool,
}
impl SelectElement {
pub async fn new(element: &WebElement) -> WebDriverResult<SelectElement> {
let multiple = element.get_attribute("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_elements(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(WebDriverError::NoSuchElement("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_elements(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 str_index: String = index.to_string();
for option in self.options().await? {
if option.get_attribute("index").await?.filter(|i| i == &str_index).is_some() {
set_selected(&option, select).await?;
return Ok(());
}
}
Err(WebDriverError::NoSuchElement(format!("Could not locate element with index {}", index)))
}
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_elements(By::XPath(&xpath)).await {
Ok(elems) => elems,
Err(WebDriverError::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_elements(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(WebDriverError::NoSuchElement(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_elements(By::XPath(&xpath)).await?;
if options.is_empty() {
return Err(WebDriverError::NoSuchElement(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
}
}