use std::sync::Arc;
use std::time::Duration;
use crate::action::{Action, ActionData};
use crate::element::{Element, ElementData};
use crate::error::{Error, Result};
use crate::event::ElementState;
use crate::provider::Provider;
use crate::selector::Selector;
#[derive(Clone)]
pub struct Locator {
provider: Arc<dyn Provider>,
root: Option<ElementData>,
selector: String,
nth: Option<usize>,
}
impl Locator {
pub fn new(provider: Arc<dyn Provider>, root: Option<ElementData>, selector: &str) -> Self {
Self {
provider,
root,
selector: selector.to_string(),
nth: None,
}
}
pub fn nth(mut self, n: usize) -> Self {
self.nth = Some(n);
self
}
pub fn first(self) -> Self {
self.nth(0)
}
pub fn child(mut self, child_selector: &str) -> Self {
self.selector = format!("{} > {}", self.selector, child_selector);
self.nth = None;
self
}
pub fn descendant(mut self, desc_selector: &str) -> Self {
self.selector = format!("{} {}", self.selector, desc_selector);
self.nth = None;
self
}
pub fn selector(&self) -> &str {
&self.selector
}
#[doc(hidden)]
pub fn provider(&self) -> &Arc<dyn Provider> {
&self.provider
}
#[doc(hidden)]
pub fn root(&self) -> Option<&ElementData> {
self.root.as_ref()
}
#[doc(hidden)]
pub fn nth_index(&self) -> Option<usize> {
self.nth
}
fn resolve_data(&self) -> Result<ElementData> {
let selector = Selector::parse(&self.selector)?;
let matches = self.provider.find_elements(
self.root.as_ref(),
&selector,
Some(self.nth.unwrap_or(0) + 1),
None,
)?;
let idx = self.nth.unwrap_or(0);
matches
.into_iter()
.nth(idx)
.ok_or_else(|| Error::SelectorNotMatched {
selector: self.selector.clone(),
})
}
pub fn exists(&self) -> Result<bool> {
match self.resolve_data() {
Ok(_) => Ok(true),
Err(Error::SelectorNotMatched { .. }) => Ok(false),
Err(e) => Err(e),
}
}
pub fn count(&self) -> Result<usize> {
let selector = Selector::parse(&self.selector)?;
let matches = self
.provider
.find_elements(self.root.as_ref(), &selector, None, None)?;
Ok(matches.len())
}
pub fn element(&self) -> Result<Element> {
let data = self.resolve_data()?;
Ok(Element::new(data, Arc::clone(&self.provider)))
}
pub fn elements(&self) -> Result<Vec<Element>> {
let selector = Selector::parse(&self.selector)?;
let matches = self
.provider
.find_elements(self.root.as_ref(), &selector, None, None)?;
Ok(matches
.into_iter()
.map(|d| Element::new(d, Arc::clone(&self.provider)))
.collect())
}
fn perform(&self, action: Action, data: Option<ActionData>) -> Result<()> {
if let Some(ref d) = data {
d.validate(action)?;
}
let element = self.resolve_data()?;
self.provider.perform_action(&element, action, data)
}
pub fn press(&self) -> Result<()> {
self.perform(Action::Press, None)
}
pub fn focus(&self) -> Result<()> {
self.perform(Action::Focus, None)
}
pub fn toggle(&self) -> Result<()> {
self.perform(Action::Toggle, None)
}
pub fn select(&self) -> Result<()> {
self.perform(Action::Select, None)
}
pub fn expand(&self) -> Result<()> {
self.perform(Action::Expand, None)
}
pub fn collapse(&self) -> Result<()> {
self.perform(Action::Collapse, None)
}
pub fn set_value(&self, value: &str) -> Result<()> {
self.perform(Action::SetValue, Some(ActionData::Value(value.to_string())))
}
pub fn set_numeric_value(&self, value: f64) -> Result<()> {
self.perform(Action::SetValue, Some(ActionData::NumericValue(value)))
}
pub fn increment(&self) -> Result<()> {
self.perform(Action::Increment, None)
}
pub fn decrement(&self) -> Result<()> {
self.perform(Action::Decrement, None)
}
pub fn show_menu(&self) -> Result<()> {
self.perform(Action::ShowMenu, None)
}
pub fn scroll_into_view(&self) -> Result<()> {
self.perform(Action::ScrollIntoView, None)
}
pub fn type_text(&self, text: &str) -> Result<()> {
self.perform(Action::TypeText, Some(ActionData::Value(text.to_string())))
}
pub fn select_text(&self, start: u32, end: u32) -> Result<()> {
self.perform(
Action::SetTextSelection,
Some(ActionData::TextSelection { start, end }),
)
}
pub fn blur(&self) -> Result<()> {
self.perform(Action::Blur, None)
}
pub fn scroll_up(&self, amount: f64) -> Result<()> {
self.perform(Action::ScrollDown, Some(ActionData::ScrollAmount(-amount)))
}
pub fn scroll_down(&self, amount: f64) -> Result<()> {
self.perform(Action::ScrollDown, Some(ActionData::ScrollAmount(amount)))
}
pub fn scroll_left(&self, amount: f64) -> Result<()> {
self.perform(Action::ScrollRight, Some(ActionData::ScrollAmount(-amount)))
}
pub fn scroll_right(&self, amount: f64) -> Result<()> {
self.perform(Action::ScrollRight, Some(ActionData::ScrollAmount(amount)))
}
pub fn wait_visible(&self, timeout: Duration) -> Result<Element> {
self.wait_for_state(ElementState::Visible, timeout)
.map(|opt| opt.expect("visible wait must return an element"))
}
pub fn wait_attached(&self, timeout: Duration) -> Result<Element> {
self.wait_for_state(ElementState::Attached, timeout)
.map(|opt| opt.expect("attached wait must return an element"))
}
pub fn wait_detached(&self, timeout: Duration) -> Result<()> {
self.wait_for_state(ElementState::Detached, timeout)
.map(|_| ())
}
pub fn wait_enabled(&self, timeout: Duration) -> Result<Element> {
self.wait_for_state(ElementState::Enabled, timeout)
.map(|opt| opt.expect("enabled wait must return an element"))
}
pub fn wait_disabled(&self, timeout: Duration) -> Result<Element> {
self.wait_for_state(ElementState::Disabled, timeout)
.map(|opt| opt.expect("disabled wait must return an element"))
}
pub fn wait_hidden(&self, timeout: Duration) -> Result<()> {
self.wait_for_state(ElementState::Hidden, timeout)
.map(|_| ())
}
pub fn wait_focused(&self, timeout: Duration) -> Result<Element> {
self.wait_for_state(ElementState::Focused, timeout)
.map(|opt| opt.expect("focused wait must return an element"))
}
pub fn wait_unfocused(&self, timeout: Duration) -> Result<Element> {
self.wait_for_state(ElementState::Unfocused, timeout)
.map(|opt| opt.expect("unfocused wait must return an element"))
}
pub fn wait_for_state(
&self,
state: ElementState,
timeout: Duration,
) -> Result<Option<Element>> {
self.poll_until(|element| state.is_met(element), timeout)
}
pub fn wait_until(
&self,
predicate: impl Fn(Option<&ElementData>) -> bool,
timeout: Duration,
) -> Result<Option<Element>> {
self.poll_until(&predicate, timeout)
}
fn poll_until(
&self,
predicate: impl Fn(Option<&ElementData>) -> bool,
timeout: Duration,
) -> Result<Option<Element>> {
let start = std::time::Instant::now();
let poll_interval = Duration::from_millis(100);
loop {
let elapsed = start.elapsed();
if elapsed >= timeout {
return Err(Error::Timeout { elapsed });
}
let matched = match self.resolve_data() {
Ok(data) => Some(data),
Err(Error::SelectorNotMatched { .. }) => None,
Err(e) => return Err(e),
};
if predicate(matched.as_ref()) {
return Ok(matched.map(|data| Element::new(data, Arc::clone(&self.provider))));
}
std::thread::sleep(poll_interval);
}
}
}