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
//! Commands that you can issue to Appium server
//!
//! The commands in submodules are a facade to low-level `issue_cmd` ([fantoccini::Client::issue_cmd]).
//! So in most cases, you need a specific function from one of those modules (e.g. [keyboard::HidesKeyboard::hide_keyboard]).
//!
//! ## Available commands
//! **Please check all submodules if you want to learn what features are implemented in this lib.**
//! See traits in below modules to learn what you can do with the client.
//!
//! Alternatively, you can check out [crate::IOSClient] and [crate::AndroidClient] to see all traits of those clients in the docs.
//!
//! ## How to use commands
//! [AppiumCommand] is a struct used by low-level `issue_cmd` ([fantoccini::Client::issue_cmd]).
//! So unless you're implementing missing features yourself, you don't need to wory about it.
//!
//! This lib exposes both APIs to be more flexible.
//! So a rule of thumb is:
//! * use a command from submodule if it's available (in other words - use by default),
//! * use [AppiumCommand::Custom] in other cases
//!
//! ```no_run
//!# use http::Method;
//!# use serde_json::json;
//!# use appium_client::capabilities::android::AndroidCapabilities;
//!# use appium_client::capabilities::{AppCapable, UdidCapable, UiAutomator2AppCompatible};
//!# use appium_client::ClientBuilder;
//!# use appium_client::commands::AppiumCommand;
//!# use appium_client::commands::keyboard::HidesKeyboard;
//!# use appium_client::find::{AppiumFind, By};
//!
//!# #[tokio::main]
//!# async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!# // create capabilities
//! let mut capabilities = AndroidCapabilities::new_uiautomator();
//!# capabilities.udid("emulator-5554");
//!# capabilities.app("/apps/sample.apk");
//!# capabilities.app_wait_activity("com.example.AppActivity");
//!
//! let client = ClientBuilder::native(capabilities)
//!    .connect("http://localhost:4723/wd/hub/")
//!    .await?;
//!
//! // this feature is implemented in keyboard submodule (recommended)
//! client.hide_keyboard().await?;
//!
//! // this is a low-level implementation of the same command (not recommended, unless you have a specific use case for this)
//! client.issue_cmd(AppiumCommand::Custom(
//!     Method::POST,
//!     "appium/device/hide_keyboard".to_string(),
//!     Some(json!({})),
//! )).await?;
//!
//!#     Ok(())
//!# }
//! ```
//!

pub mod rotation;
pub mod keyboard;
pub mod lock;
pub mod contexts;
pub mod location;
pub mod time;
pub mod files;
pub mod apps;
pub mod strings;
pub mod network;
pub mod android;
pub mod settings;
pub mod authentication;
pub mod recording;
pub mod clipboard;
pub mod battery;
pub mod ios;

use fantoccini::wd::WebDriverCompatibleCommand;
use http::Method;
use serde_json::Value;
use crate::find::By;

/// Basic Appium commands
///
/// Use Custom if you want to implement anything non-standard.
/// Those commands are to be used with `issue_cmd` ([fantoccini::Client::issue_cmd]).
#[derive(Debug, PartialEq)]
pub enum AppiumCommand {
    FindElement(By),
    FindElementWithContext(By, String),
    FindElements(By),
    FindElementsWithContext(By, String),
    Custom(Method, String, Option<Value>),
}

impl WebDriverCompatibleCommand for AppiumCommand {
    fn endpoint(
        &self,
        base_url: &url::Url,
        session_id: Option<&str>,
    ) -> Result<url::Url, url::ParseError> {
        let base = { base_url.join(&format!("session/{}/", session_id.as_ref().unwrap()))? };
        match self {
            AppiumCommand::FindElement(..) =>
                base.join("element"),
            AppiumCommand::FindElements(..) =>
                base.join("elements"),
            AppiumCommand::FindElementWithContext(.., context) =>
                base.join("element")
                    .and_then(|url| url.join(context))
                    .and_then(|url| url.join("element")),
            AppiumCommand::FindElementsWithContext(.., context) =>
                base.join("element")
                    .and_then(|url| url.join(context))
                    .and_then(|url| url.join("elements")),
            AppiumCommand::Custom(_, command, ..) =>
                base.join(command),
        }
    }

    fn method_and_body(&self, _request_url: &url::Url) -> (Method, Option<String>) {
        match self {
            AppiumCommand::FindElement(by)
            | AppiumCommand::FindElements(by)
            | AppiumCommand::FindElementWithContext(by, ..)
            | AppiumCommand::FindElementsWithContext(by, ..) => {
                let method = Method::POST;
                let body = Some(serde_json::to_string(&by).unwrap());

                (method, body)
            },

            AppiumCommand::Custom(method, .., value) => {
                let body = value.clone()
                    .map(|v| v.to_string());

                (method.clone(), body)
            }
        }
    }

    fn is_new_session(&self) -> bool {
        false
    }

    fn is_legacy(&self) -> bool {
        false
    }
}