appium_client/
commands.rs

1//! Commands that you can issue to Appium server
2//!
3//! The commands in submodules are a facade to low-level `issue_cmd` ([fantoccini::Client::issue_cmd]).
4//! So in most cases, you need a specific function from one of those modules (e.g. [keyboard::HidesKeyboard::hide_keyboard]).
5//!
6//! ## Available commands
7//! **Please check all submodules if you want to learn what features are implemented in this lib.**
8//! See traits in below modules to learn what you can do with the client.
9//!
10//! Alternatively, you can check out [crate::IOSClient] and [crate::AndroidClient] to see all traits of those clients in the docs.
11//!
12//! ## How to use commands
13//! [AppiumCommand] is a struct used by low-level `issue_cmd` ([fantoccini::Client::issue_cmd]).
14//! So unless you're implementing missing features yourself, you don't need to wory about it.
15//!
16//! This lib exposes both APIs to be more flexible.
17//! So a rule of thumb is:
18//! * use a command from submodule if it's available (in other words - use by default),
19//! * use [AppiumCommand::Custom] in other cases
20//!
21//! ```no_run
22//!# use http::Method;
23//!# use serde_json::json;
24//!# use appium_client::capabilities::android::AndroidCapabilities;
25//!# use appium_client::capabilities::{AppCapable, UdidCapable, UiAutomator2AppCompatible};
26//!# use appium_client::ClientBuilder;
27//!# use appium_client::commands::AppiumCommand;
28//!# use appium_client::commands::keyboard::HidesKeyboard;
29//!# use appium_client::find::{AppiumFind, By};
30//!
31//!# #[tokio::main]
32//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
33//!# // create capabilities
34//! let mut capabilities = AndroidCapabilities::new_uiautomator();
35//!# capabilities.udid("emulator-5554");
36//!# capabilities.app("/apps/sample.apk");
37//!# capabilities.app_wait_activity("com.example.AppActivity");
38//!
39//! let client = ClientBuilder::native(capabilities)
40//!    .connect("http://localhost:4723/wd/hub/")
41//!    .await?;
42//!
43//! // this feature is implemented in keyboard submodule (recommended)
44//! client.hide_keyboard().await?;
45//!
46//! // this is a low-level implementation of the same command (not recommended, unless you have a specific use case for this)
47//! client.issue_cmd(AppiumCommand::Custom(
48//!     Method::POST,
49//!     "appium/device/hide_keyboard".to_string(),
50//!     Some(json!({})),
51//! )).await?;
52//!
53//!#     Ok(())
54//!# }
55//! ```
56//!
57
58pub mod rotation;
59pub mod keyboard;
60pub mod lock;
61pub mod contexts;
62pub mod location;
63pub mod time;
64pub mod files;
65pub mod apps;
66pub mod strings;
67pub mod network;
68pub mod android;
69pub mod settings;
70pub mod authentication;
71pub mod recording;
72pub mod clipboard;
73pub mod battery;
74pub mod ios;
75
76use fantoccini::wd::WebDriverCompatibleCommand;
77use http::Method;
78use serde_json::Value;
79use crate::find::By;
80
81/// Basic Appium commands
82///
83/// Use Custom if you want to implement anything non-standard.
84/// Those commands are to be used with `issue_cmd` ([fantoccini::Client::issue_cmd]).
85#[derive(Debug, PartialEq)]
86pub enum AppiumCommand {
87    FindElement(By),
88    FindElementWithContext(By, String),
89    FindElements(By),
90    FindElementsWithContext(By, String),
91    Custom(Method, String, Option<Value>),
92}
93
94impl WebDriverCompatibleCommand for AppiumCommand {
95    fn endpoint(
96        &self,
97        base_url: &url::Url,
98        session_id: Option<&str>,
99    ) -> Result<url::Url, url::ParseError> {
100        let base = { base_url.join(&format!("session/{}/", session_id.as_ref().unwrap()))? };
101        match self {
102            AppiumCommand::FindElement(..) =>
103                base.join("element"),
104            AppiumCommand::FindElements(..) =>
105                base.join("elements"),
106            AppiumCommand::FindElementWithContext(.., context) =>
107                base.join("element")
108                    .and_then(|url| url.join(context))
109                    .and_then(|url| url.join("element")),
110            AppiumCommand::FindElementsWithContext(.., context) =>
111                base.join("element")
112                    .and_then(|url| url.join(context))
113                    .and_then(|url| url.join("elements")),
114            AppiumCommand::Custom(_, command, ..) =>
115                base.join(command),
116        }
117    }
118
119    fn method_and_body(&self, _request_url: &url::Url) -> (Method, Option<String>) {
120        match self {
121            AppiumCommand::FindElement(by)
122            | AppiumCommand::FindElements(by)
123            | AppiumCommand::FindElementWithContext(by, ..)
124            | AppiumCommand::FindElementsWithContext(by, ..) => {
125                let method = Method::POST;
126                let body = Some(serde_json::to_string(&by).unwrap());
127
128                (method, body)
129            },
130
131            AppiumCommand::Custom(method, .., value) => {
132                let body = value.clone()
133                    .map(|v| v.to_string());
134
135                (method.clone(), body)
136            }
137        }
138    }
139
140    fn is_new_session(&self) -> bool {
141        false
142    }
143
144    fn is_legacy(&self) -> bool {
145        false
146    }
147}