1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
//! Wait API for waiting for elements to appear on screen
//!
//! It's basically like [crate::find], but does not return results immediately.
//! If the element is not on screen, then it tries to locate it again in intervals until it appears.
//!
//! Wait has two parameters: interval and timeout.
//!
//! **Interval** is how often to check if it appeared on screen.
//! **Timeout** is how much time do you want to wait (max).
//!
//! If Appium finds the element before the timeout is exceeded, then it is returned immediately.
//! If not, then the client will query Appium again after a given interval.
//!
//! ## Basic usage
//! ```no_run
//! use appium_client::capabilities::android::AndroidCapabilities;
//!# use appium_client::capabilities::{AppCapable, UdidCapable, UiAutomator2AppCompatible};
//! use appium_client::ClientBuilder;
//! use appium_client::find::{AppiumFind, By};
//!
//!# #[tokio::main]
//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!# // create capabilities
//!# use appium_client::wait::AppiumWait;
//! let mut capabilities = AndroidCapabilities::new_uiautomator();
//!# capabilities.udid("emulator-5554");
//!# capabilities.app("/apps/sample.apk");
//!# capabilities.app_wait_activity("com.example.AppActivity");
//!#
//! // create the client
//! let client = ClientBuilder::native(capabilities)
//! .connect("http://localhost:4723/wd/hub/")
//! .await?;
//!
//! // wait until element appears
//! let element = client
//! .appium_wait()
//! .for_element(By::accessibility_id("Wait for me"))
//! .await?;
//!
//! # Ok(())
//! # }
//! ```
//!
//! ## Tweaking the interval and timeout
//! To change those parameters, use [Wait::check_every] (for changing the interval) and [Wait::at_most] (the timeout).
//! See the documentation of those functions for more info.
//!
//! ```no_run
//! use appium_client::capabilities::android::AndroidCapabilities;
//!# use appium_client::capabilities::{AppCapable, UdidCapable, UiAutomator2AppCompatible};
//! use appium_client::ClientBuilder;
//! use appium_client::find::{AppiumFind, By};
//!
//!# #[tokio::main]
//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!# use std::time::Duration;
//! // create capabilities
//!# use appium_client::wait::AppiumWait;
//! let mut capabilities = AndroidCapabilities::new_uiautomator();
//!# capabilities.udid("emulator-5554");
//!# capabilities.app("/apps/sample.apk");
//!# capabilities.app_wait_activity("com.example.AppActivity");
//!#
//! // create the client
//! let client = ClientBuilder::native(capabilities)
//! .connect("http://localhost:4723/wd/hub/")
//! .await?;
//!
//! // wait 1 s until element appears (and check every 250ms if it's appeared)
//! let element = client
//! .appium_wait()
//! .check_every(Duration::from_millis(250))
//! .at_most(Duration::from_secs(1))
//! .for_element(By::accessibility_id("Wait for me"))
//! .await?;
//!
//! # Ok(())
//! # }
//! ```
//!
//! Waiting for a list of elements
//! Instead of [Wait::for_element], there is [Wait::for_elements] (notice the **s** at the end).
//! The second method returns a Vec of elements instead of one elements.
//!
//! When you use the second function, the wait works the same.
//! It still waits in the same way, and waits for the element to appear.
//!
//! The only difference is that after AT LEAST ONE matching elements has appeared, the wait is over.
//! You get the result, and the result is a Vec of all matching elements.
//!
//! That means - if all elements appeared simultaneously, then you will get all elements.
//! This method will not wait for all possible elements to appear.
//! So if some elements appear with a delay - then they might not be there.
//! This method returns immediately after at least one match.
//!
use std::time::Duration;
use fantoccini::Client;
use fantoccini::elements::Element;
use fantoccini::error::CmdError;
use tokio::time::{Instant, interval};
use crate::find::{AppiumFind, By};
use async_trait::async_trait;
pub trait AppiumWait {
fn appium_wait(&self) -> Wait;
}
impl AppiumWait for Client {
fn appium_wait(&self) -> Wait {
Wait {
client: self,
timeout: Duration::from_secs(30),
check_delay: Duration::from_millis(250),
}
}
}
/// Wait parameters
#[derive(Debug)]
pub struct Wait<'c> {
client: &'c Client,
timeout: Duration,
check_delay: Duration,
}
impl Wait<'_> {
/// Set the timeout for maximum wait.
///
/// Checks are performed in a loop, with an interval.
/// To prevent infinite wait, the loop will exit after this timeout and the wait will result in an error indicating timeout.
///
/// It is not guaranteed that the loop exits at the exact duration, as the check interval may hold it off.
/// It works like this:
/// 1. is the timeout exceeded?
/// 2. try to locate
/// 3. wait for interval
/// 4. repeat
pub fn at_most(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
/// Sets the period to delay checks.
///
/// Checks are performed in a loop, with an interval defined by this method.
/// For example, if you set it to 250 ms,
/// then the loop will check if element is present, wait 250 ms and repeat.
pub fn check_every(mut self, interval: Duration) -> Self {
self.check_delay = interval;
self
}
/// Waits for element using Appium locator.
///
/// Tries to locate element in loop, with interval defined by "check delay".
/// If the timeout is exceeded, then it returns an error.
pub async fn for_element(self, search: By) -> Result<Element, CmdError> {
WaitOnSingle(WaitSelector::new(self, search))
.wait()
.await
}
/// Waits for a list of elements using Appium locator.
///
/// Tries to locate list of elements in loop, with interval defined by "check delay".
/// If the timeout is exceeded, then it returns an error.
pub async fn for_elements(self, search: By) -> Result<Vec<Element>, CmdError> {
WaitOnMultiple(WaitSelector::new(self, search))
.wait()
.await
}
}
#[async_trait]
trait AppiumWaitOnSelector<T> where Self: Sized {
/// Checks if target can be located, then returns the result.
/// If not found, waits for given delay and retries.
/// Loops until a timeout is exceeded.
async fn wait(self) -> Result<T, CmdError> {
let wait = self.get_wait();
let mut interval = interval(wait.check_delay);
let timeout = wait.timeout;
let start = Instant::now();
loop {
if start.elapsed() > timeout {
return Err(CmdError::WaitTimeout);
}
{
let find_element = self.locate();
if let Some(result) = find_element.await? {
return Ok(result);
}
}
interval.tick().await;
}
}
/// Returns wait parameters
fn get_wait(&self) -> &Wait;
/// Logic for locating the target.
async fn locate(&self) -> Result<Option<T>, CmdError>;
}
struct WaitSelector<'a> {
wait: Wait<'a>,
selector: By,
}
impl<'a> WaitSelector<'a> {
pub fn new(wait: Wait, selector: By) -> WaitSelector {
WaitSelector {
wait,
selector,
}
}
}
struct WaitOnSingle<'a>(WaitSelector<'a>);
struct WaitOnMultiple<'a>(WaitSelector<'a>);
#[async_trait]
impl<'a> AppiumWaitOnSelector<Element> for WaitOnSingle<'a> {
fn get_wait(&self) -> &Wait {
&self.0.wait
}
async fn locate(&self) -> Result<Option<Element>, CmdError> {
find_element(&self.0.wait, self.0.selector.clone()).await
}
}
#[async_trait]
impl<'a> AppiumWaitOnSelector<Vec<Element>> for WaitOnMultiple<'a> {
fn get_wait(&self) -> &Wait {
&self.0.wait
}
async fn locate(&self) -> Result<Option<Vec<Element>>, CmdError> {
find_all_elements(&self.0.wait, self.0.selector.clone()).await
}
}
async fn find_element(wait: &Wait<'_>, selector: By) -> Result<Option<Element>, CmdError> {
match wait.client.find_by(selector).await {
Ok(element) => Ok(Some(element)),
Err(CmdError::NoSuchElement(_)) => Ok(None),
Err(err) => Err(err),
}
}
async fn find_all_elements(wait: &Wait<'_>, selector: By) -> Result<Option<Vec<Element>>, CmdError> {
match wait.client.find_all_by(selector).await {
Ok(result) => Ok(Some(result)),
Err(CmdError::NoSuchElement(_)) => Ok(None),
Err(err) => Err(err),
}
}