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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
//! A set of parameters used to start an Appium session.
//!
//! ## What are capabilities?
//! The information in the set is used to describe what sort of "capabilities" you want your session to have,
//! for example, a certain mobile operating system or a certain version of a device.
//!
//! When you start your Appium session, your Appium client will include the set of capabilities
//! you've defined as an object in the JSON-formatted body of the request.
//!
//! Capabilities are represented as key-value pairs, with values allowed to be any valid JSON type, including other objects.
//! Appium will then examine the capabilities and make sure that it can satisfy them before proceeding to start the session and
//! return an ID representing the session to your client library.
//!
//! See also <https://appium.io/docs/en/2.1/guides/caps/>.
//!
//! ## Platform-specific capabilities
//! You can create capabilities to pass to Appium sercer by using either [android::AndroidCapabilities] or [ios::IOSCapabilities].
//!
//! For example, if you wish to use UiAutomator2 as a driver for Android tests, you can write:
//! ```rust
//! use appium_client::capabilities::android::AndroidCapabilities;
//! use appium_client::capabilities::{AppCapable, AppiumCapability, UdidCapable, UiAutomator2AppCompatible};
//!
//! let mut capabilities = AndroidCapabilities::new_uiautomator();
//! capabilities.udid("emulator-5554");  // This capability selects the device you wish to run test on
//! capabilities.app("/apps/sample.apk"); // This is a path to apk on your computer
//! capabilities.app_wait_activity("com.example.AppActivity"); // This is an activity of a home screen of the app.
//!
//! // If you wish to specify appium capabilities manually (without predefined methods),
//! // then use methods like set_bool, set_str, set_number
//! capabilities.set_bool("appium:noReset", true);
//! ```
//!
//! ## Blank capabilities
//! To use blank capabilities (without any predefined methods for ease of configuration), use [empty::EmptyCapabilities].
//! [empty::EmptyCapabilities] lets you configure Appium for any driver that was not implemented out of the box.
//!
//! Note that you will loose many built in features for Android and iOS, basically [empty::EmptyCapabilities] requires
//! that you setup everything by yourself (including some Appium commands).

pub mod ios;
pub mod automation;
pub mod android;
pub mod empty;

use std::ops::{Deref, DerefMut};
use std::time::Duration;
use fantoccini::wd::Capabilities;
use serde_json::{Number, Value};

/// Extensions to easily define capabilities for Appium driver. See <https://appium.io/docs/en/2.1/guides/caps/>.
pub trait AppiumCapability
    where Self: Deref<Target=Capabilities>,
          Self: DerefMut<Target=Capabilities> {

    /// Set the automation driver to use (the engine for tests, eg. XCuiTest for iOS).
    /// 
    /// Appium usually autoselects the driver based on platform, but you choose the preferred driver.
    /// See [automation] for possible values.
    fn automation_name(&mut self, automation_name: &str) {
        self.set_str("appium:automationName", automation_name);
    }

    /// The version of a platform, e.g., for iOS, "16.0"
    fn platform_version(&mut self, version: &str) {
        self.set_str("appium:platformVersion", version);
    }

    /// The name of a particular device to automate.
    ///
    /// For example "iPhone 14".
    /// Currently only actually useful for specifying iOS simulators,
    /// since in other situations it's typically recommended to use a specific device
    /// id via the `appium:udid` capability.
    fn device_name(&mut self, device_name: &str) {
        self.set_str("appium:deviceName", device_name);
    }

    /// Sets a string capability.
    ///
    /// For example `set_str("appium:deviceName", "iPhone 14")`.
    fn set_str(&mut self, name: &str, value: &str) {
        self.insert(name.to_string(), Value::String(value.to_string()));
    }

    /// Sets a number capability.
    ///
    /// For example `set_number("appium:newCommandTimeout", Number::from(120u64))`.
    fn set_number(&mut self, name: &str, value: Number) {
        self.insert(name.to_string(), Value::Number(value));
    }

    /// Sets a boolean capability.
    ///
    /// For example `set_bool("appium:noReset", true)`
    fn set_bool(&mut self, name: &str, value: bool) {
        self.insert(name.to_string(), Value::Bool(value));
    }
}

/// Capabilities for drivers that are used to run test on a device.
pub trait UdidCapable: AppiumCapability {
    /// Device id.
    ///
    /// Android id can be retrieved by using ADB (`adb devices`).
    /// For iOS, it's the phone's serial number.
    fn udid(&mut self, udid: &str) {
        self.set_str("appium:udid", udid);
    }
}

/// Capabilities for drivers that are used to run an app.
pub trait AppCapable: AppiumCapability {
    /// The path to an installable application.
    fn app(&mut self, app_path: &str) {
        self.set_str("appium:app", app_path);
    }

    /// App or list of apps (as a JSON array) to install prior to running tests.
    ///
    /// Note that it will not work with `automationName` of `Espresso` and iOS real devices
    fn other_apps(&mut self, paths: &[&str]) {
        let paths = paths.iter()
            .map(|p| Value::String(p.to_string()))
            .collect();

        self.insert("appium:otherApps".to_string(), Value::Array(paths));
    }

    /// Don't reset app state before this session.
    ///
    /// "Reset" means to delete app data (like a fresh install).
    /// If true, instruct an Appium driver to avoid its usual reset logic during session start and cleanup (default false).
    fn no_reset(&mut self, no_reset: bool) {
        self.set_bool("appium:noReset", no_reset);
    }

    /// Perform a complete reset.
    ///
    /// "Complete reset" usually means a reinstall.
    /// If true, instruct an Appium driver to augment its usual reset logic with additional steps to ensure maximum environmental reproducibility (default false)
    fn full_reset(&mut self, full_reset: bool) {
        self.set_bool("appium:fullReset", full_reset);
    }

    /// When a find operation fails, print the current page source. Defaults to false.
    ///
    /// When the element you're looking for is not found on screen, then this setting will print DOM
    /// of the visible app screen.
    /// This DOM can be further inspected to check if the locator is correct, or if the correct page is displayed.
    fn print_page_source_on_find_failure(&mut self, value: bool) {
        self.set_bool("appium:printPageSourceOnFindFailure", value);
    }
}

/// Capabilities for UiAutomator2 (Android).
pub trait UiAutomator2AppCompatible: AppiumCapability {
    /// Activity name for the Android activity you want to launch from your package.
    ///
    /// This often needs to be preceded by a `.` (a dot, e.g., `.MainActivity` instead of `MainActivity`).
    /// By default this capability is received from the package manifest.
    fn app_activity(&mut self, activity: &str) {
        self.set_str("appium:appActivity", activity);
    }

    /// Java package of the Android app you want to run.
    ///
    /// By default this capability is received from the package manifest.
    fn app_package(&mut self, activity: &str) {
        self.set_str("appium:appPackage", activity);
    }

    /// Activity name/names, comma separated, for the Android activity you want to wait for.
    ///
    /// By default the value of this capability is the same as for appActivity.
    /// You must set it to the very first focused application activity name in case it is different
    /// from the one which is set as appActivity if your capability has `appActivity` and `appPackage`.
    /// You can also use wildcards (*).
    fn app_wait_activity(&mut self, activity: &str) {
        self.set_str("appium:appWaitActivity", activity);
    }

    /// Java package of the Android app you want to wait for.
    ///
    /// By default the value of this capability is the same as for appActivity.
    fn app_wait_package(&mut self, activity: &str) {
        self.set_str("appium:appWaitPackage", activity);
    }

    /// Timeout in milliseconds used to wait for the appWaitActivity to launch (default 20000)
    fn app_wait_duration(&mut self, duration: Duration) {
        self.set_number("appium:appWaitDuration", Number::from(duration.as_millis() as u64));
    }

    /// Timeout in milliseconds used to wait for an apk to install to the device. Defaults to 90000
    fn android_install_timeout(&mut self, duration: Duration) {
        self.set_number("appium:androidInstallTimeout", Number::from(duration.as_millis() as u64));
    }

    /// Block until app starts.
    ///
    /// Whether to block until the app under test returns the control to the caller after its activity
    /// has been started by Activity Manager (true, the default value) or to continue the test without waiting for that (false)
    fn app_wait_for_launch(&mut self, value: bool) {
        self.set_bool("appium:appWaitForLaunch", value);
    }

    /// Always start app forcefully when testing starts.
    ///
    /// Set it to true if you want the application under test to be always forcefully
    /// restarted on session startup even if appium:noReset is true,
    /// and the app was already running. If noReset is falsy, then the app under test is going
    /// to be restarted if either this capability set to true or appium:dontStopAppOnReset is falsy
    /// (the default behavior). false by default. Available since driver version 2.12
    fn force_app_launch(&mut self, value: bool) {
        self.set_bool("appium:forceAppLaunch", value)
    }

    /// Whether to launch the application under test automatically after a test starts.
    ///
    /// Default: true
    fn auto_launch(&mut self, value: bool) {
        self.set_bool("appium:autoLaunch", value)
    }

    /// Set an optional intent category to be applied when starting the given appActivity by Activity Manager.
    ///
    /// Defaults to `android.intent.category.LAUNCHER`.
    /// Please use `mobile:startActivity` in case you don't set an explicit value.
    fn intent_category(&mut self, value: &str) {
        self.set_str("appium:intentCategory", value);
    }

    /// Set an optional intent action to be applied when starting the given appActivity by Activity Manager.
    ///
    /// Defaults to `android.intent.action.MAIN`. Please use `mobile:startActivity` in case you don't set an explicit value.
    fn intent_action(&mut self, value: &str) {
        self.set_str("appium:intentAction", value);
    }

    /// Set an optional intent flags to be applied when starting the given appActivity by Activity Manager.
    ///
    /// Defaults to 0x10200000 (FLAG_ACTIVITY_NEW_TASK)
    fn intent_flags(&mut self, value: &str) {
        self.set_str("appium:intentFlags", value);
    }

    /// Set an optional intent arguments to be applied when starting the given appActivity by Activity Manager
    fn optional_intent_arguments(&mut self, value: &str) {
        self.set_str("appium:optionalIntentArguments", value);
    }

    /// Set it to true if you don't want the application to be restarted if it was already running.
    ///
    /// If appium:noReset is falsy, then the app under test is going to be restarted if either
    /// this capability is falsy (the default behavior) or appium:forceAppLaunch is set to true.
    ///
    /// `false` by default
    fn dont_stop_app_on_reset(&mut self, value: bool) {
        self.set_bool("appium:dontStopAppOnReset", value);
    }

    /// Allows to set one or more comma-separated package identifiers to be uninstalled from the device before a test starts.
    fn uninstall_other_packages(&mut self, value: &str) {
        self.set_str("appium:uninstallOtherPackages", value);
    }

    /// Sets the maximum amount of application packages to be cached on the device under test.
    ///
    /// This is needed for devices that don't support streamed installs (Android 7 and below),
    /// because ADB must push app packages to the device first in order to install them, which takes some time.
    ///
    /// Setting this capability to zero disables apps caching. 10 by default.
    fn remote_apps_cache_limit(&mut self, value: u64) {
        self.set_number("appium:remoteAppsCacheLimit", Number::from(value));
    }

    /// Use packages built with test flag.
    ///
    /// If set to true then it would be possible to use packages built with the test flag
    /// for the automated testing (literally adds -t flag to the adb install command).
    ///
    /// false by default
    fn allow_test_packages(&mut self, value: bool) {
        self.set_bool("appium:allowTestPackages", value);
    }

    /// Reinstall app (even if it's a downgrade).
    ///
    /// If set to true then the application under test is always reinstalled even if a newer version
    /// of it already exists on the device under test.
    /// This capability has no effect if `appium:noReset` is set to true.
    ///
    /// `false` by default
    fn enforce_app_install(&mut self, value: bool) {
        self.set_bool("appium:enforceAppInstall", value);
    }
}

/// Capabilities for Settings API (<https://appium.io/docs/en/2.1/guides/settings/>).
pub trait AppiumSettingsCapable: AppiumCapability {
    fn set_setting(&mut self, name: &str, value: Value) {
        self.insert(format!("appium:settings[{name}]"), value);
    }
}

/// Capabilities for XCUITest (iOS).
pub trait XCUITestAppCompatible: AppiumCapability {
    /// Bundle id of app. Looks like app package (`com.my.app`).
    fn bundle_id(&mut self, id: &str) {
        self.set_str("appium:bundleId", id);
    }

    /// Where to look for localizable strings. Default en.lproj
    fn localizable_strings_dir(&mut self, dir: &str) {
        self.set_str("appium:localizableStringsDir", dir);
    }

    /// Language to set for the simulator / emulator.
    ///
    /// You need to set this manually on physical devices.
    fn language(&mut self, language: &str) {
        self.set_str("appium:language", language);
    }

    /// Locale to set for the simulator / emulator.
    ///
    /// You need to set this manually on physical devices.
    fn locale(&mut self, locale: &str) {
        self.set_str("appium:locale", locale);
    }

    /// Calendar format to set for the iOS Simulator (eg. `gregorian`).
    fn calendar_format(&mut self, value: &str) {
        self.set_str("appium:calendarFormat", value);
    }

    /// Timeout for application upload in millisecond, on real devices
    fn app_push_timeout(&mut self, duration: Duration) {
        self.set_number("appium:appPushTimeout", Number::from(duration.as_millis() as u64));
    }

    /// Select application installation strategy for real devices.
    ///
    /// The following strategies are supported:
    /// * `serial` (default) - pushes app files to the device in a sequential order; this is the least performant strategy, although the most reliable;
    /// * `parallel` - pushes app files simultaneously; this is usually the the most performant strategy, but sometimes could not be very stable;
    /// * `ios-deploy` - tells the driver to use a third-party tool ios-deploy to install the app; obviously the tool must be installed separately first and must be present in PATH before it could be used.
    fn app_install_strategy(&mut self, value: &str) {
        self.set_str("appium:appInstallStrategy", value);
    }

    /// Accept all iOS alerts automatically if they pop up.
    ///
    /// This includes privacy access permission alerts (e.g., location, contacts, photos). Default is false.
    fn auto_accept_alerts(&mut self, value: bool) {
        self.set_bool("appium:autoAcceptAlerts", value);
    }
}