Compare commits

...
Author SHA1 Message Date
Dan Graham e31d97af76 1.8.1 changelog (#10667) 2018-05-22 16:06:41 -07:00
dan@saucelabs.com 0e2334299e Update dependencies 2018-05-22 16:02:24 -07:00
Isaac A. Murchie d67590c722 1.8.1 2018-05-17 16:03:54 -07:00
dan@saucelabs.com b590d9e9e8 1.8.1-beta1 2018-05-08 11:38:43 -07:00
dan@saucelabs.com b30fefb2c8 v1.8.1-beta 2018-05-08 11:26:12 -07:00
Isaac A. Murchie a5d7bd84dd Update dependencies (#10666) 2018-05-08 10:04:14 -07:00
Dan Graham 379cf64994 Re-enable package-lock.json (#10660) 2018-05-08 10:00:31 -07:00
Isaac A. Murchie 2b1ec777c0 Prepare for v1.8.0 2018-04-20 10:53:59 -07:00
Dan Graham 7219f1256b Recommend coldboot in troubleshooting (#10516)
* I found that sometimes ADB can't find the device even after running `adb kill-server`
* The problem was that it was loaded using Fastboot and emulator doesn't always show up as online
* Fixed by closing emulator and then doing coldboot
2018-04-11 10:14:53 -05:00
Kazuaki Matsuo 831d21143d Add docs of clipboard (#10451)
* add clipboard with yml

* add generated docs for clipboard

* update descriptions and commands

* apply reviews
2018-04-11 10:14:42 -05:00
dan@saucelabs.com 892f91928c Prepare for v1.8.0-beta5 2018-04-10 16:08:11 -07:00
Dan Graham 67e41af8d4 Add otherApps to caps (#10460) 2018-04-10 15:06:11 -07:00
Isaac A. Murchie 439a75ce38 Add unnecessary stuff to npmignore (#10505) 2018-04-10 15:06:05 -07:00
Mykola Mokhnach 625da250fa Fix server property assignment in the umbrella driver (#10496) 2018-04-10 15:05:58 -07:00
Isaac A. Murchie 5dbac38d86 Add new chromedriver caps to docs (#10448) 2018-04-10 15:05:44 -07:00
Isaac Murchie c813f39791 Update docs 2018-04-10 15:05:37 -07:00
Andres Galindo dda355f65c Added support for hub protocol (#10434) 2018-04-10 15:05:27 -07:00
Isaac A. Murchie e0cc59dd94 Update Chromedriver docs (#10436)
* Update Chromedriver docs

Chromedriver 2.37 is out so add its minimum Chrome version to the table.

* Typo

* Add 2.5 and 2.6 mapping
2018-04-10 15:05:14 -07:00
dan@saucelabs.com a568ba58d2 Prepare for 1.8.0-beta4 2018-03-27 16:40:31 -07:00
Dan Graham cfbce46cb2 Pass protocol in execute command (#10362)
* Make it so executeCommand returns protocol and value/error
  * This is so that base driver can read the protocol and serve the response (base driver doesn't know what the protocol of the inner drivers are)
  * Added tests to confirm that it falls back to MJSONWP if an inner driver is not ready and handles concurrent MJSONWP and W3C sessions
  * Updated unit tests to match the new format
2018-03-27 16:40:31 -07:00
Jonathan Lipps f9d1849e1a Merge pull request #10406 from janpio/patch-1
[Docs] Add Apache Cordova to introduction
2018-03-27 16:40:31 -07:00
Mykola Mokhnach be6a8adc7c Attach server instance to each driver being created (#10400) 2018-03-27 16:40:31 -07:00
Jonathan Lipps 1d3b4debab Merge pull request #10398 from appium/isaac-source-docs
Add source docs to table of contents
2018-03-27 16:40:31 -07:00
Mykola Mokhnach 2067d69a5d Alter performance documentation (#10367) 2018-03-27 16:40:31 -07:00
Mykola Mokhnach 1f6bdaf570 Add documentation for performance tools available for iOS (#10348)
* Add documentation for performance tools available for iOS

* Fix grammar
2018-03-27 16:40:31 -07:00
Jonathan Lipps 23cc85ae24 Merge pull request #10335 from saikrishna321/master
added uiAutomator2 caps
2018-03-27 16:40:31 -07:00
Kazuaki Matsuo 57f8e05f03 add network related commands for Ruby (#10251)
* add network related commands for Ruby

* set client_support true
2018-02-26 15:50:31 -05:00
Kazuaki Matsuo cf47698327 add commands of power ac and power capacity (#10253) 2018-02-26 15:50:22 -05:00
Dan Graham 91ab4a6b3b Doc fix (#10234) 2018-02-26 15:50:14 -05:00
trixo 8e017aaa15 docs update (#10231) 2018-02-26 15:50:06 -05:00
Dan Graham 6d3c3c9a4f Go back to using nodeca/argparse (#10205) 2018-02-26 15:49:59 -05:00
Dan Graham 58d96221ba Fix customSSLCert documentation error (#10155)
Should be Sim only, not Sim/Emu
2018-02-15 08:42:45 -05:00
Isaac Murchie 40d4728519 Bump appium-ios-driver to 2.0 2018-02-15 08:40:30 -05:00
Kazuaki Matsuo 044246bb84 Update settings.md (#10196)
* Update settings.md

* fix review
2018-02-15 08:40:20 -05:00
Dan Graham 029b35e990 Use appium argparse (#10191) 2018-02-15 08:40:11 -05:00
Isaac A. Murchie 959b098816 Move to minimum node 6 (#10180) 2018-02-15 08:40:03 -05:00
Isaac A. Murchie 3a10b18dfb Update Contributing Docs (#10165)
* Update contributing docs

* Fix some links and examples

* Address comments
2018-02-15 08:39:52 -05:00
Isaac A. Murchie 83989943bc Update android driver version (#10163) 2018-02-15 08:39:45 -05:00
Dan Graham 619447214b Fix outdated test (#10162) 2018-02-15 08:39:37 -05:00
Nick Fox a4a4d9aeab Update migrating-to-xcuitest.md (#10112) 2018-02-15 08:39:28 -05:00
dan@saucelabs.com 4bf6c6e8fc Prepare for v1.8.0-beta3 2018-01-30 09:10:40 -08:00
dan@saucelabs.com b0f1e56ede Prepare for v1.8.0-beta2 2018-01-29 13:10:14 -08:00
Mykola Mokhnach b0857eefb1 Use uia2 backend by default if platform version is greater or equal to 6 (#10073)
* Use uia2 backend by default if platform version is greater or equal to 6

* Allow to force set the backend to UIA1

* Add unit tests

* Tune the log message

* Print a warning message instead of changing the driver type

* Remove redundant imports

* Tune the message

* Address PR comments
2018-01-29 11:31:37 -05:00
Dan Graham 8efff6895e Test content-type in errors (#10077) 2018-01-29 11:31:29 -05:00
Dan Graham 69eed603c3 Remove unnecessary 'finally' block (#10076)
* Shouldn't have had finally block because then it does two checks to fall back to MJSONWP
* Also fixed outdated tests
2018-01-29 11:31:20 -05:00
Dan Graham f91c6b50aa Fall back to MJSONWP if W3C is invalid (#10034)
* If processCapabilities throws error, then log a warning that it was invalid and log the message and fall back to using jsonwpCaps
2018-01-29 11:31:10 -05:00
Dan Graham 46395d7c46 Use MJSONWP if dcaps have extraneous params (#10019)
* Update `parseCapsForInnerDriver` so that now if it finds extraneous keys in `jsonwpCaps`, fall back to using MJSONWP instead of W3C
* After getting processed caps, check the protocol again because if `processedW3CCapabilities` was set to null, it should be JWP
* Removed tests that tested for capabilities being merged together (those are no longer needed)
* Added tests (unit test and e2e test) that check that creation of a new session will fall back to JSONWP when there are extraneous keys
2018-01-29 11:30:59 -05:00
Isaac A. Murchie 5a2cf8d1c4 Get rid of arrow functions in mocha aparatus (#10013) 2018-01-29 11:30:49 -05:00
Isaac A. Murchie 281b3cfdeb Add 2.34 and 2.35 to Chromedriver compatibility doc (#10012) 2018-01-29 11:30:39 -05:00
68 changed files with 4704 additions and 976 deletions
+5 -2
View File
@@ -1,6 +1,11 @@
assets/
submodules/
test/
coverage/
commands-yml/
docs/
sample-code/
_vimrc_local.vim
*.swp
*.swo
@@ -12,5 +17,3 @@ _vimrc_local.vim
*.trace
*.sublime-
*.prefs
./.npmignore
coverage
+2 -1
View File
@@ -1 +1,2 @@
package-lock=false
# Do not disable package-lock. Disabling package-lock also disables npm-shrinkwrap
package-lock=true
+82
View File
@@ -1,3 +1,83 @@
CHANGES IN VERSION 1.8.1 (from 1.8.0)
===================================
Appium 1.8.1 introduces multiple fixes and features. Most notably, it improves the performance of XCUITest
getPageSource.
#### General
* Fix shrinkwrap problem caused due to package-lock being set to false (#10660)
#### iOS
* Add keyboard presence verification endpoint (see http://appium.io/docs/en/commands/device/keys/is-keyboard-shown/)
* Add `mobile:startLogsBroadcast` feature (see http://appium.io/docs/en/commands/mobile-command/)
* Add cap called `realDeviceScreenshotter` to use idevicescreenshot for real device screenshots
* Add application platform verification
* Validates that `webdriverAgentUrl` capability is a valid URL
* Add an extension to retrieve battery info from a real device
* Fix Safari console log retrieval
#### Android
* Return the current connection state instead of undefined for setNetworkConnection
* Add a possibility to include stderr output into adb:shell call (see https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android/android-shell.md#supported-arguments)
* Add flags argument to pressKeyCode so it is possible to generate IME actions if needed (UIAutomator2 only)
* Add a cap called `userProfile` which is an integer to enforce user profile while launching applications
* Make it possible to retrieve a battery info from the device under test (mobile:batteryInfo)
* Add `deviceApiLevel` to returned session capabilities (UIAutomator2 only)
* Fix passing suppressKillServer option while creating ADB instance
* Improve performance of swipe unlock action
* Improve restore from background behaviour
CHANGES IN VERSION 1.8.0 (from 1.7.2)
===================================
Appium 1.8.0 introduces full support for the [W3C WebDriver specification](https://www.w3.org/TR/webdriver/)
([digested version of the spec](https://github.com/jlipps/simple-wd-spec)).
#### General
* Minimum [NodeJS](https://nodejs.org/en/) version moved to 6
* Add methods for
* getting and setting the clipboard contents
* get full screen screenshots
* Application management
* More efficient app downloading by caching URLs
* All `moveTo` operations in touch actions now take coordinates as absolute
* Support for registering selenium grid through HTTPS by setting the configuration
key `"hubProtocol"` to `"https"`
* Add `otherApps` desired capability, to specify array of ancillary apps to install
on session creation
#### iOS
* Support for iOS 11.3/Xcode 9.3
* Fix handling of process arguments
* Add capabilities
* `shutdownOtherSimulators` - shutdown other running simulators at session start up
* Add ability to record screen
* Add support for complex gestures
* Fix handling of custom SSL certificate on simulators
* Better handling of `xcodebuild` processes
* Maintain keychains while upgrading apps
* Better handle simulators in unexpected states
* Add performance measuring (see [documentation](https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/ios/ios-xctest-performance.md))
#### Android
* Support for Android P (API Level 28)
* Known issue in screenshot: https://issuetracker.google.com/issues/76114030
* Fix handling of webview contexts when resetting apps
* Fix handling of screen recording
* Make sure intent is broadcast when file is uploaded
* Add possibility to broadcast device logs through WebSocket
* Chromedriver
* Bundle version 2.37
* Automatically choose compatible Chromedriver for Chrome version
* `chromedriverExecutableDir` capability to specify where Chromedrivers are located
* `chromedriverChromeMappingFile` capability to specify what Chromedriver version corresponds to what Chrome version
* Fix Handling of idle state waiting
* Speed up screenshot logic in UiAutomator2
* Allow disabling of notification watching through settings
* Fix parsing and granting of permissions for app under test
* Support deep linking and Android Instant Apps
CHANGES IN VERSION 1.8.0-beta (from 1.7.2)
===================================
@@ -244,6 +324,8 @@ the automation process configurable.
* Fix licensing in all dependencies.
#### Android
* Added handlers for basic system alerts, so now it is possible to accept/decline/get text of the most of
them using the standard Selenium's switchTo().alert() interface (UIA2 only)
* Fix backgrounding of app.
* Add `androidInstallPath` capability to specify where on the device apps are installed.
* Speed up taking screenshots on Android 5.0 and higher devices.
+1 -1
View File
@@ -8,7 +8,7 @@ Note that no matter how you contribute, your participation is governed by our
### Make changes to the Appium code or docs
Fork the project, make a change, and send a pull request! Please have a look at
our [Style Guide](/docs/en/contributing-to-appium/style-guide-2.0.md) before
our [Style Guide](/docs/en/contributing-to-appium/style-guide.md) before
getting to work. Please make sure the unit and functional tests pass before
sending a pull request; for more information on how to run tests, keep reading!
@@ -0,0 +1,77 @@
---
name: Get Clipboard
short_description: Get the content of the system clipboard
description:
>
Get the content of the system clipboard
example_usage:
java:
|
driver.getClipboard(ClipboardContentType.PLAINTEXT); // get plaintext
driver.getClipboardText();
python:
|
self.driver.get_clipboard()
self.driver.get_clipboard_text()
javascript_wd:
|
await driver.getClipboard();
javascript_wdio:
|
await driver.getClipboard();
ruby:
|
@driver.get_clipboard
php:
|
// PHP Code here
csharp:
|
// CSharp Code here
client_docs:
java: "https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/WebElement.html#click--"
python: "https://github.com/appium/python-client"
javascript_wdio: "http://webdriver.io/index.html"
javascript_wd: "https://github.com/admc/wd/releases"
ruby: "https://github.com/appium/ruby_lib/releases/latest"
php: "https://github.com/appium/php-client/releases/latest-"
csharp: "https://github.com/appium/appium" # TODO
# Driver support by platform
driver_support:
ios:
xcuitest: true
uiautomation: false
android:
uiautomator2: true
uiautomator: false
mac:
mac: false
windows:
windows: false
client_support:
java: true
python: true
ruby: true
php: false
csharp: false
javascript_wd: true
javascript_wdio: true
endpoint:
url: /wd/hub/session/:session_id/appium/device/get_clipboard
method: 'POST'
url_parameters:
- name: session_id
description: ID of the session to route the command to
json_parameters:
- name: contentType
type: string
description: The type of the content to get. Plaintext, Image, URL. Android supports only plaintext.
response:
- type: string
description: Clipboard content as base64-encoded string or an empty string if the clipboard is empty
specifications: no
@@ -0,0 +1,84 @@
---
name: Set Clipboard
short_description: Set the content of the system clipboard
description:
>
Set the content of the system clipboard
example_usage:
java:
|
// base64Content is Base64-encoded content
driver.setClipboard("label", ClipboardContentType.PLAINTEXT, base64Content);
driver.setClipboardText("happy testing");
python:
|
self.driver.set_clipboard('happy testing')
self.driver.set_clipboard_text('happy testing')
javascript_wd:
|
await driver.setClipboard('happy testing', 'plaintext')
javascript_wdio:
|
await driver.setClipboard('happy testing', 'plaintext')
ruby:
|
@driver.set_clipboard content: 'happy testing'
php:
|
// PHP Code here
csharp:
|
// CSharp Code here
client_docs:
java: "https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/WebElement.html#click--"
python: "https://github.com/appium/python-client"
javascript_wdio: "http://webdriver.io/index.html"
javascript_wd: "https://github.com/admc/wd/releases"
ruby: "https://github.com/appium/ruby_lib/releases/latest"
php: "https://github.com/appium/php-client/releases/latest-"
csharp: "https://github.com/appium/appium" # TODO
# Driver support by platform
driver_support:
ios:
xcuitest: true
uiautomation: false
android:
uiautomator2: true
uiautomator: false
mac:
mac: false
windows:
windows: false
client_support:
java: true
python: true
ruby: true
php: false
csharp: false
javascript_wd: true
javascript_wdio: true
endpoint:
url: /wd/hub/session/:session_id/appium/device/set_clipboard
method: 'POST'
url_parameters:
- name: session_id
description: ID of the session to route the command to
json_parameters:
- name: content
type: String
description: The actual base64 encoded clipboard content.
- name: contentType
type: string
description: The type of the content to get. Plaintext, Image, URL. Android supports only plaintext.
- name: label
type: string
description: Clipboard data label for Android.
response:
- type: string
description: Response from Appium server
specifications: no
@@ -0,0 +1,58 @@
---
name: Emulate power state
short_description: Emulate power state change on the connected emulator.
description:
|
For Android emulator.
To set the state of the battery charger to connected or not.
example_usage:
java:
|
driver.setPowerAC(PowerACState.OFF);
ruby:
|
@driver.set_power_ac :on
client_docs:
java: "http://appium.github.io/java-client/" # TODO: Link
ruby: "https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#set_power_ac-instance_method"
# Driver support by platform
driver_support:
ios:
xcuitest: false
uiautomation: false
android:
uiautomator2: true
uiautomator: true
mac:
mac: false
windows:
windows: false
client_support:
java: true
python: false
ruby: true
php: false
csharp: false
javascript_wd: false
javascript_wdio: false
# Information about the HTTP endpoints
endpoint:
url: /session/:session_id/appium/device/power_ac
method: 'POST'
url_parameters:
- name: session_id
description: ID of the session to route the command to
json_parameters:
- name: state
type: string
description: set the state. on or off
# Links to specifications. Should link to at least one specification
specifications:
jsonwp: https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js#L381
@@ -0,0 +1,57 @@
---
name: Emulate power capacity
short_description: Emulate power capacity change on the connected emulator.
description:
|
For Android emulator.
To set the battery percentage.
example_usage:
java:
|
driver.setPowerCapacity(100);
ruby:
|
@driver.set_power_capacity 50
client_docs:
java: "http://appium.github.io/java-client/" # TODO: Link
ruby: "https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#set_power_capacity-instance_method"
# Driver support by platform
driver_support:
ios:
xcuitest: false
uiautomation: false
android:
uiautomator2: true
uiautomator: true
mac:
mac: false
windows:
windows: false
client_support:
java: true
python: false
ruby: true
php: false
csharp: false
javascript_wd: false
javascript_wdio: false
# Information about the HTTP endpoints
endpoint:
url: /session/:session_id/appium/device/power_capacity
method: 'POST'
url_parameters:
- name: session_id
description: ID of the session to route the command to
json_parameters:
- name: percent
type: integer
description: ercentage value in range [0, 100]
# Links to specifications. Should link to at least one specification
specifications:
jsonwp: https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js#L378
@@ -66,7 +66,10 @@ endpoint:
description: Key code pressed on the device. See [Key Event](http://developer.android.com/reference/android/view/KeyEvent.html).
- name: metastate
type: number
description: Metastate for the keypress
description: Metastate for the keypress. See [Key Event](http://developer.android.com/reference/android/view/KeyEvent.html).
- name: flags
type: number
description: Flags for the keypress. See [Key Event](http://developer.android.com/reference/android/view/KeyEvent.html).
# Links to specifications. Should link to at least one specification
specifications:
@@ -66,7 +66,10 @@ endpoint:
description: Key code pressed on the device. See [Key Event](http://developer.android.com/reference/android/view/KeyEvent.html).
- name: metastate
type: number
description: Metastate for the keypress
description: Metastate for the keypress. See [Key Event](http://developer.android.com/reference/android/view/KeyEvent.html).
- name: flags
type: number
description: Flags for the keypress. See [Key Event](http://developer.android.com/reference/android/view/KeyEvent.html).
# Links to specifications. Should link to at least one specification
specifications:
@@ -6,6 +6,9 @@ example_usage:
javascript_wd:
|
await driver.gsmCall('555-123-4567', 'Phone');
ruby:
|
@driver.gsm_call(phone_number: '555-123-4567', action: :call)
php:
|
// TODO
@@ -15,6 +18,7 @@ example_usage:
client_docs:
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L3183"
ruby: "http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#gsm_call-instance_method"
php: "https://github.com/appium/php-client/" # TODO
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO
@@ -33,7 +37,7 @@ driver_support:
client_support:
java: false
python: false
ruby: false
ruby: true
php: false
csharp: false
javascript_wd: true
@@ -6,6 +6,9 @@ example_usage:
javascript_wd:
|
await driver.gsmSignal(3);
ruby:
|
@driver.gsm_signal :good
php:
|
// TODO
@@ -15,6 +18,7 @@ example_usage:
client_docs:
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L3221"
ruby: "http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#gsm_signal-instance_method"
php: "https://github.com/appium/php-client/" # TODO
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO
@@ -33,7 +37,7 @@ driver_support:
client_support:
java: false
python: false
ruby: false
ruby: true
php: false
csharp: false
javascript_wd: true
@@ -6,6 +6,9 @@ example_usage:
javascript_wd:
|
await driver.gsmVoice('Voice state');
ruby:
|
@driver.gsm_voice :home
php:
|
// TODO
@@ -15,6 +18,7 @@ example_usage:
client_docs:
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L3241"
ruby: "https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#gsm_voice-instance_method"
php: "https://github.com/appium/php-client/" # TODO
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO
@@ -33,7 +37,7 @@ driver_support:
client_support:
java: false
python: false
ruby: false
ruby: true
php: false
csharp: false
javascript_wd: true
@@ -8,7 +8,7 @@ example_usage:
await driver.sendSms('555-123-4567', 'Hey lol');
ruby:
|
// TODO
@driver.send_sms(phone_number: '555-123-4567', message: 'Hey lol')
php:
|
// TODO
@@ -18,6 +18,7 @@ example_usage:
client_docs:
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L3183"
ruby: "http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#send_sms-instance_method"
php: "https://github.com/appium/php-client/" # TODO
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO
@@ -36,7 +37,7 @@ driver_support:
client_support:
java: false
python: false
ruby: false
ruby: true
php: false
csharp: false
javascript_wd: true
@@ -9,6 +9,9 @@ example_usage:
javascript_wdio:
|
driver.toggleData();
ruby:
|
@driver.toggle_data
php:
|
// TODO
@@ -19,6 +22,7 @@ example_usage:
client_docs:
javascript_wdio: "http://webdriver.io/api/mobile/toggleData.html"
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L2771"
ruby: "https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Core/Device#toggle_data-instance_method"
php: "https://github.com/appium/php-client/" # TODO
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO
@@ -15,6 +15,9 @@ example_usage:
javascript_wdio:
|
driver.toggleLocationServices();
ruby:
|
@driver.toggle_location_services
php:
|
// TODO
@@ -27,6 +30,7 @@ client_docs:
python: "https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py#L774"
javascript_wdio: "http://webdriver.io/api/mobile/toggleLocationServices.html"
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L2758"
ruby: "http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Core/Device#toggle_location_services-instance_method"
php: "https://github.com/appium/php-client/" # TODO
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO
@@ -45,7 +49,7 @@ driver_support:
client_support:
java: true
python: true
ruby: false
ruby: true
php: true
csharp: true
javascript_wd: true
@@ -9,6 +9,9 @@ example_usage:
javascript_wdio:
|
driver.toggleWiFi();
ruby:
|
@driver.toggle_wifi
php:
|
// TODO
@@ -19,6 +22,7 @@ example_usage:
client_docs:
javascript_wdio: "http://webdriver.io/api/mobile/toggleWiFi.html"
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L2738"
ruby: "http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Core/Device#toggle_wifi-instance_method"
php: "https://github.com/appium/php-client/" # TODO
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO
@@ -17,7 +17,7 @@ example_usage:
javascript_wd:
|
let element = await driver.elementByAccessibilityId("SomeAccessibilityID");
await element.submit();
await element.text();
javascript_wdio:
|
driver.getText("~SomeAccessibilityId");
+141
View File
@@ -0,0 +1,141 @@
---
name: Execute Mobile Command
short_description: Execute a native mobile command
description:
|
Execute a variety of native, mobile commands that aren't associated with a specific endpoint
Syntax is `execute("mobile: <commandName>", <JSON serializable argument>)`
List of available commands:
### IOS
| Command | Description | Argument | Argument Example |
| ------- | ----------- | -------- | ---------------- |
| mobile:viewportScreenshot | Like [screenshot](/commands/session/screenshot/) but only includes contents of viewport | <none> | <none> |
### IOS (XCUITest Only)
| Command | Description | Argument | Argument Example |
| ------- | ----------- | -------- | ---------------- |
| mobile:startPerfRecord | Starts performance profiling for the device under test | <none> | |
| mobile:stopPerfRecord | Stops performance profiling for the device under test | <none> | |
| mobile:startLogsBroadcast | Starts iOS system logs broadcast websocket on the same host and port where Appium server is running at `/ws/session/:sessionId:/appium/syslog` endpoint. | <none> | |
| mobile:stopLogsBroadcast | Stops the iOS system logs broadcasting websocket server started by `mobile:startLogsBroadcast` | <none> | |
| mobile:swipe | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-swipe) | | |
| mobile:scroll | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-scroll) | | |
| mobile:pinch | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-pinch) | | |
| mobile:doubleTap | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-doubleTap) | | |
| mobile:touchAndHold | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-touchAndHold) | | |
| mobile:twoFingerTap | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-twoFingerTap) | | |
| mobile:tap | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-tap) | | |
| mobile:dragFromToForDuration | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-dragFromToForDuration) | | |
| mobile:selectPickerWheelValue | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-selectPickerWheelValue) | | |
| mobile:alert | refer to [Automating Mobile Gestures For iOS With WebDriverAgent/XCTest Backend](/writing-running-appium/ios/ios-xctest-mobile-gestures/#mobile-alert) | | |
| mobile:installApp | refer to [XCUITest Mobile App Managemenet](/writing-running-appium/ios/ios-xctest-mobile-apps-management/#mobile-installapp) | | |
| mobile:removeApp | refer to [XCUITest Mobile App Managemenet](/writing-running-appium/ios/ios-xctest-mobile-apps-management/#mobile-removeapp) | | |
| mobile:isAppInstalled | refer to [XCUITest Mobile App Managemenet](/writing-running-appium/ios/ios-xctest-mobile-apps-management/#mobile-isappinstalled) | | |
| mobile:launchApp | refer to [XCUITest Mobile App Managemenet](/writing-running-appium/ios/ios-xctest-mobile-apps-management/#mobile-launchapp) | | |
| mobile:terminateApp | refer to [XCUITest Mobile App Managemenet](/writing-running-appium/ios/ios-xctest-mobile-apps-management/#mobile-terminateapp) | | |
| mobile:activateApp | refer to [XCUITest Mobile App Managemenet](/writing-running-appium/ios/ios-xctest-mobile-apps-management/#mobile-activateapp) | | |
| mobile:queryAppState | refer to [XCUITest Mobile App Managemenet](/writing-running-appium/ios/ios-xctest-mobile-apps-management/#mobile-queryappstate) | | |
| mobile:setPasteboard | refer to [IOS Pasteboard Guide](/writing-running-appium/ios/ios-xctest-pasteboard/#mobile-setpasteboard) | | |
| mobile:getPasteboard | refer to [IOS Pasteboard Guide](/writing-running-appium/ios/ios-xctest-pasteboard/#mobile-getpasteboard) | | |
| mobile:installCertificate | refer to [IOS Pasteboard Guide](/writing-running-appium/ios/ios-xctest-install-certificate.md) | | |
### Android
| Command | Description | Argument | Argument Example |
| ------- | ----------- | -------- | ---------------- |
| mobile:shell | Execute [ADB shell](https://developer.android.com/studio/command-line/adb#shellcommands) commands (requires [server flag](/writing-running-appium/server-args/#server-flags) `--relaxed-security` to be set) | ADB shell string | `am start -n com.example.demo/com.example.test.MainActivity` |
| mobile:startLogsBroadcast | Starts Android logcat broadcast websocket on the same host and port where Appium is running at `/ws/session/:sessionId:/appium/logcat` endpoint | <none> | <none> |
| mobile:stopLogsBroadcast | Stops the logcat broadcasting websocket server started by `mobile:startLogsBroadcast` | <none> | <none> |
| mobile:batteryInfo | Reads the battery information from the device under test | <none> | <none> |
| mobile:acceptAlert | Accepts an on-screen alert | Optional button label to click on | <none> |
| mobile:dismissAlert | Dismisses an on-screen alert | Optional button label to click on | <none> |
### Android (UiAutomator2 only)
| Command | Description | Argument | Argument Example |
| ------- | ----------- | -------- | ---------------- |
| mobile:scrollBackTo | Scroll from one element to another | `{elementId, elementToId}` | `{elementId: 2, elementToId: 1}` |
| mobile:viewportScreenshot | Like [screenshot](/commands/session/screenshot/) but only includes contents of viewport | <none> | <none> |
| mobile:deepLink | Opens a deep-link URL for testing [Instant Apps](https://support.google.com/googleplay/answer/7240211?hl=en) | `{url, package}` | `{url: "https://www.site.com/", package: "com.site.SomeAndroidPackage"}` |
example_usage:
java:
|
driver.executeScript("mobile: scroll", ImmutableMap.of("direction", "down"));;
python:
|
self.driver.execute_script("mobile: scroll", {'direction': 'down'})
javascript_wd:
|
await driver.execute('mobile: scroll', {direction: 'down'});
javascript_wdio:
|
var result = browser.execute('mobile: scroll', {direction: 'down'})
ruby:
|
@driver.execute_script("mobile: scroll", { "direction" => "down"})
php:
|
// TODO PHP sample
csharp:
|
// TODO C# sample
client_docs:
java: "https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/remote/RemoteWebDriver.html#executeScript-java.lang.String-java.lang.Object...-"
python: "http://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webdriver.WebDriver.execute_script"
javascript_wdio: "http://webdriver.io/api/protocol/execute.html"
javascript_wd: "https://github.com/admc/wd/blob/master/lib/commands.js#L102"
ruby: "http://www.rubydoc.info/gems/selenium-webdriver/Selenium/WebDriver/Driver:execute_script"
php: "https://github.com/appium/php-client/" # TODO PHP documentation link
csharp: "https://github.com/appium/appium-dotnet-driver/" # TODO Dotnet documentation link
# Driver support by platform
driver_support:
ios:
xcuitest: true
uiautomation: true
android:
uiautomator2: true
uiautomator: true
mac:
mac: true
windows:
windows: true
client_support:
java: true
python: true
ruby: true
php: true
csharp: true
javascript_wd: true
javascript_wdio: true
# Information about the HTTP endpoints
endpoint:
url: /wd/hub/session/:session_id/execute
method: GET
url_parameters:
- name: session_id
description: ID of the session to route the command to
json_parameters:
- name: script
type: string
description: The mobile command to execute
- name: args
type: array
description: The JSON serializable arguments
response:
- type: any
description: The command result
# Links to specifications. Should link to at least one specification
specifications:
w3c: https://www.w3.org/TR/webdriver/#dfn-execute-script
jsonwp: https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidexecute
+3 -4
View File
@@ -8,8 +8,8 @@ description:
This method is useful for inspecting your application hierarchy and using that to write [selectors](/docs/en/commands/element/find-element.md)
(NOTE: iOS and Android don't have standard ways of defining their application source, so on calls to 'Get Page Source' Appium traverses the app hierarchy
and creates an XML document. Thus, getting the source can oftentime be an expensive and time-consuming operation)
(NOTE: iOS and Android don't have standard ways of defining their application source, so on calls to 'Get Page Source' Appium traverses the app hierarchy
and creates an XML document. Thus, getting the source can often be an expensive and time-consuming operation)
example_usage:
java:
@@ -63,7 +63,6 @@ client_support:
csharp: true
javascript_wd: true
javascript_wdio: true
# Information about the HTTP endpoints
endpoint:
@@ -77,6 +76,6 @@ endpoint:
description: The sources of current context.
# Links to specifications. Should link to at least one specification
specifications:
specifications:
w3c: https://www.w3.org/TR/webdriver/#dfn-get-page-source
jsonwp: https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidsource
@@ -2,7 +2,7 @@
你想从源码运行Appium并帮助修复BUG和添加功能吗?
真棒!只需要fork工程,添加一个修改,然后发送pull请求即可!
在开始之前请阅读我们的代码风格指南([Style Guide](style-guide-2.0.md)。
在开始之前请阅读我们的代码风格指南([Style Guide](style-guide.md)。
在发送pull请求前请确保通过单元和功能测试;关于如何运行测试等更多信息,请继续阅读!
首先,确保你阅读README文件且按照设置说明走。
@@ -63,7 +63,7 @@ node.js的原生支持,Appium代码是_被移植_到ES5(JS更为广泛支持
### 排查和代码风格
对于所有Appium的JS而言,代码外观和使用感觉同样重要。这包括样式常规,编码模式以及我们解决各种问题时使用的
库。你应该熟悉我们新的[ES2015 风格指南](/docs/cn/contributing-to-appium/style-guide-2.0.md)。
库。你应该熟悉我们新的[ES2015 风格指南](/docs/cn/contributing-to-appium/style-guide.md)。
当转化时,Appium包将自动运行JSHint或其他lint工具,并在代码不符合我们规范的时候提供警告或错误反馈。
这些工具不一定能顾全我们关心的种种风格问题,所以我们在review代码的时候也应该注意代码规范问题。这不是
吹毛求疵,而是为了有一个整洁,一致并且可读的代码库。
@@ -102,7 +102,7 @@ _FORCE_LOGS=1 <command> # 显示测试运行期间的模块日志输出
任何非Appium主包的发布流程都是非常简洁明了的(请注意:如果你想要发布它,你需要成为一个NPM的所有者。
所有权由Appium提交者管理; 如果你对所有者有任何疑问,请联系@jlipps 或者 @imurchie)。
0. `rm -rf node_modules && npm install` 并运行测试以确保全新安装正常工作
0. `rm -rf node_modules && rm -rf package-lock.json && npm install` 并运行测试以确保全新安装正常工作
0. 根据[SemVer](http://semver.org/) 规则决定我们是否需要发布一个补丁(漏洞修复),微调(功能)或者是主要(迭代)(请参考 [how SemVer works with NPM](https://docs.npmjs.com/getting-started/semantic-versioning).
0. 通过任何适当的更改和提交来更新CHANGELOG以README文件。大多数子包没有CHANGELOG。
0. 通过适当的版本类型运行 `npm version <version-type>`
@@ -121,8 +121,8 @@ _FORCE_LOGS=1 <command> # 显示测试运行期间的模块日志输出
因此有必要在发布过程中手动管理它。 它需要与对`package.json`的更改一起提交到GitHub。
0. 如果 NPM shrinkwrap JSON 文件存在,请移除.
0. `rm -rf node_modules && npm install` 并运行测试以确保全新安装正常工作
0. `rm -rf node_modules && npm install --production` 以获取仅production部分.
0. `rm -rf node_modules && rm -rf package-lock.json && npm install` 并运行测试以确保全新安装正常工作
0. `rm -rf node_modules && rm -rf package-lock.json && npm install --production` 以获取仅production部分.
0. `npm shrinkwrap` 来编写新的 NPM shrinkwrap JSON 文件.
0. 根据SemVer来决定我们是否需要发布一个补丁(漏洞修复),微调(功能)或者是主要(迭代)
0. 用合适的新版本信息来更新`package.json`
+3
View File
@@ -22,6 +22,9 @@
<li>
<a href='/docs/en/commands/session/screenshot.md'>Screenshot</a>
</li>
<li>
<a href='/docs/en/commands/session/source.md'>Source</a>
</li>
<li>
Timeouts<ul> <li>
<a href='/docs/en/commands/session/timeouts/timeouts.md'>Timeouts</a>
+3 -4
View File
@@ -6,10 +6,9 @@ applications on iOS mobile, Android mobile, and Windows desktop platforms.
**Mobile web apps** are web apps accessed using a mobile browser (Appium
supports Safari on iOS and Chrome or the built-in 'Browser' app on Android).
**Hybrid apps** have a wrapper around a "webview" -- a native control that
enables interaction with web content. Projects like
[Phonegap](http://phonegap.com/), make it easy to build apps using web
technologies that are then bundled into a native wrapper, creating a hybrid
app.
enables interaction with web content. Projects like [Apache Cordova](https://cordova.apache.org)
or [Phonegap](http://phonegap.com/) make it easy to build apps using web
technologies that are then bundled into a native wrapper, creating a hybrid app.
Importantly, Appium is "cross-platform": it allows you to write tests against
multiple platforms (iOS, Android, Windows), using the same API. This enables
+1
View File
@@ -47,6 +47,7 @@ you will see your device on the grid console page:
"registerCycle": 5000,
"hubPort": <grid_port>,
"hubHost": "<Grid_host_name_or_grid_ip-address>"
"hubProtocol": "<Protocol_of_Grid_defaults_to_http>"
}
}
```
@@ -23,7 +23,7 @@ This locator strategy was specifically built on UIAutomation, so it is not inclu
### `xpath` locator strategy
1. Try not to use XPath locators unless there is absolutely no other alternatives. In general, xpath locators might be times slower, than other types of locators like accessibility id, class name and predicate (up to 100 times slower in some special cases). They are so slow, because xpath location is not natively supported by Apple's XCTest framework.
1. Try not to use XPath locators unless there is absolutely no other alternative. In general, xpath locators might be many times slower than other types of locators like accessibility id, class name and predicate (up to 100 times slower in some special cases). They are so slow, because xpath location is not natively supported by Apple's XCTest framework.
2. Use
```
+1
View File
@@ -43,6 +43,7 @@ Note that the actual commands you would use in your test script differ based on
|----|----|----|
|`ignoreUnimportantViews`|Boolean which sets whether Android devices should use `setCompressedLayoutHeirarchy()` which ignores all views which are marked IMPORTANT_FOR_ACCESSIBILITY_NO or IMPORTANT_FOR_ACCESSIBILITY_AUTO (and have been deemed not important by the system), in an attempt to make things less confusing or faster. `false` by default. | `false` or `true` |
|`allowInvisibleElements`|For UiAutomator2 Driver. Boolean which set whether Android device should show all elements, visible and invisible. `false` by default. | `false` or `true` |
|`enableNotificationListener`| For UiAutomator2 Driver. Boolean which sets whether the Android device should enable or disable the `NotificationListener`. `true` by default. | `false` or `true` |
#### Android UiAutomator Configurator
@@ -0,0 +1,104 @@
# Get Clipboard
Get the content of the system clipboard
## Example Usage
```java
// Java
driver.getClipboard(ClipboardContentType.PLAINTEXT); // To get plaintext
driver.getClipboardText();
```
```python
# Python
self.driver.get_clipboard()
self.driver.get_clipboard_text()
```
```javascript
// Javascript
// webdriver.io example
await driver.getClipboard();
// wd example
await driver.getClipboard();
```
```ruby
# Ruby
@driver.get_clipboard
```
```php
# PHP
// PHP Code here
```
```csharp
// C#
// CSharp Code here
```
## Description
Get the content of the system clipboard
## Support
### Appium Server
|Platform|Driver|Platform Versions|Appium Version|Driver Version|
|--------|----------------|------|--------------|--------------|
| iOS | [XCUITest](/docs/en/drivers/ios-xcuitest.md) | 9.3+ | 1.6.0+ | All |
| | [UIAutomation](/docs/en/drivers/ios-uiautomation.md) | None | None | None |
| Android | [UiAutomator2](/docs/en/drivers/android-uiautomator2.md) | ?+ | 1.6.0+ | All |
| | [UiAutomator](/docs/en/drivers/android-uiautomator.md) | None | None | None |
| Mac | [Mac](/docs/en/drivers/mac.md) | None | None | None |
| Windows | [Windows](/docs/en/drivers/windows.md) | None | None | None |
### Appium Clients
|Language|Support|Documentation|
|--------|-------|-------------|
|[Java](https://github.com/appium/java-client/releases/latest)| All | [seleniumhq.github.io](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/WebElement.html#click--) |
|[Python](https://github.com/appium/python-client/releases/latest)| All | [github.com](https://github.com/appium/python-client) |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | [webdriver.io](http://webdriver.io/index.html) |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/releases) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [github.com](https://github.com/appium/ruby_lib/releases/latest) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | [github.com](https://github.com/appium/php-client/releases/latest-) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | [github.com](https://github.com/appium/appium) |
## HTTP API Specifications
### Endpoint
`POST /wd/hub/session/:session_id/appium/device/get_clipboard`
### URL Parameters
|name|description|
|----|-----------|
|session_id|ID of the session to route the command to|
### JSON Parameters
|name|type|description|
|----|----|-----------|
| contentType | `string` | The type of the content to get. Plaintext, Image, URL. Android supports only plaintext. |
### Response
Clipboard content as base64-encoded string or an empty string if the clipboard is empty (`string`)
## See Also
@@ -0,0 +1,107 @@
# Set Clipboard
Set the content of the system clipboard
## Example Usage
```java
// Java
// base64Content is Base64-encoded content
driver.setClipboard("label", ClipboardContentType.PLAINTEXT, base64Content);
driver.setClipboardText("happy testing");
```
```python
# Python
self.driver.set_clipboard('happy testing')
self.driver.set_clipboard_text('happy testing')
```
```javascript
// Javascript
// webdriver.io example
await driver.setClipboard('happy testing', 'plaintext')
// wd example
await driver.setClipboard('happy testing', 'plaintext')
```
```ruby
# Ruby
@driver.set_clipboard content: 'happy testing'
```
```php
# PHP
// PHP Code here
```
```csharp
// C#
// CSharp Code here
```
## Description
Set the content of the system clipboard
## Support
### Appium Server
|Platform|Driver|Platform Versions|Appium Version|Driver Version|
|--------|----------------|------|--------------|--------------|
| iOS | [XCUITest](/docs/en/drivers/ios-xcuitest.md) | 9.3+ | 1.6.0+ | All |
| | [UIAutomation](/docs/en/drivers/ios-uiautomation.md) | None | None | None |
| Android | [UiAutomator2](/docs/en/drivers/android-uiautomator2.md) | ?+ | 1.6.0+ | All |
| | [UiAutomator](/docs/en/drivers/android-uiautomator.md) | None | None | None |
| Mac | [Mac](/docs/en/drivers/mac.md) | None | None | None |
| Windows | [Windows](/docs/en/drivers/windows.md) | None | None | None |
### Appium Clients
|Language|Support|Documentation|
|--------|-------|-------------|
|[Java](https://github.com/appium/java-client/releases/latest)| All | [seleniumhq.github.io](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/WebElement.html#click--) |
|[Python](https://github.com/appium/python-client/releases/latest)| All | [github.com](https://github.com/appium/python-client) |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | [webdriver.io](http://webdriver.io/index.html) |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/releases) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [github.com](https://github.com/appium/ruby_lib/releases/latest) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | [github.com](https://github.com/appium/php-client/releases/latest-) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | [github.com](https://github.com/appium/appium) |
## HTTP API Specifications
### Endpoint
`POST /wd/hub/session/:session_id/appium/device/set_clipboard`
### URL Parameters
|name|description|
|----|-----------|
|session_id|ID of the session to route the command to|
### JSON Parameters
|name|type|description|
|----|----|-----------|
| content | `String` | The actual base64 encoded clipboard content. |
| contentType | `string` | The type of the content to get. Plaintext, Image, URL. Android supports only plaintext. |
| label | `string` | Clipboard data label for Android. |
### Response
Response from Appium server (`string`)
## See Also
@@ -0,0 +1,93 @@
# Emulate power state
Emulate power state change on the connected emulator.
## Example Usage
```java
// Java
driver.setPowerAC(PowerACState.OFF);
```
```python
# Not supported
```
```javascript
// Not supported
// Not supported
```
```ruby
# Ruby
@driver.set_power_ac :on
```
```php
// Not supported
```
```csharp
// Not supported
```
## Description
For Android emulator.
To set the state of the battery charger to connected or not.
## Support
### Appium Server
|Platform|Driver|Platform Versions|Appium Version|Driver Version|
|--------|----------------|------|--------------|--------------|
| iOS | [XCUITest](/docs/en/drivers/ios-xcuitest.md) | None | None | None |
| | [UIAutomation](/docs/en/drivers/ios-uiautomation.md) | None | None | None |
| Android | [UiAutomator2](/docs/en/drivers/android-uiautomator2.md) | ?+ | 1.6.0+ | All |
| | [UiAutomator](/docs/en/drivers/android-uiautomator.md) | 4.2+ | All | All |
| Mac | [Mac](/docs/en/drivers/mac.md) | None | None | None |
| Windows | [Windows](/docs/en/drivers/windows.md) | None | None | None |
### Appium Clients
|Language|Support|Documentation|
|--------|-------|-------------|
|[Java](https://github.com/appium/java-client/releases/latest)| All | [appium.github.io](http://appium.github.io/java-client/) |
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| None | |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| None | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#set_power_ac-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | |
## HTTP API Specifications
### Endpoint
`POST /session/:session_id/appium/device/power_ac`
### URL Parameters
|name|description|
|----|-----------|
|session_id|ID of the session to route the command to|
### JSON Parameters
|name|type|description|
|----|----|-----------|
| state | `string` | set the state. on or off |
### Response
null
## See Also
* [JSONWP Specification](https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js#L381)
@@ -0,0 +1,93 @@
# Emulate power capacity
Emulate power capacity change on the connected emulator.
## Example Usage
```java
// Java
driver.setPowerCapacity(100);
```
```python
# Not supported
```
```javascript
// Not supported
// Not supported
```
```ruby
# Ruby
@driver.set_power_capacity 50
```
```php
// Not supported
```
```csharp
// Not supported
```
## Description
For Android emulator.
To set the battery percentage.
## Support
### Appium Server
|Platform|Driver|Platform Versions|Appium Version|Driver Version|
|--------|----------------|------|--------------|--------------|
| iOS | [XCUITest](/docs/en/drivers/ios-xcuitest.md) | None | None | None |
| | [UIAutomation](/docs/en/drivers/ios-uiautomation.md) | None | None | None |
| Android | [UiAutomator2](/docs/en/drivers/android-uiautomator2.md) | ?+ | 1.6.0+ | All |
| | [UiAutomator](/docs/en/drivers/android-uiautomator.md) | 4.2+ | All | All |
| Mac | [Mac](/docs/en/drivers/mac.md) | None | None | None |
| Windows | [Windows](/docs/en/drivers/windows.md) | None | None | None |
### Appium Clients
|Language|Support|Documentation|
|--------|-------|-------------|
|[Java](https://github.com/appium/java-client/releases/latest)| All | [appium.github.io](http://appium.github.io/java-client/) |
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| None | |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| None | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#set_power_capacity-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | |
## HTTP API Specifications
### Endpoint
`POST /session/:session_id/appium/device/power_capacity`
### URL Parameters
|name|description|
|----|-----------|
|session_id|ID of the session to route the command to|
### JSON Parameters
|name|type|description|
|----|----|-----------|
| percent | `integer` | ercentage value in range [0, 100] |
### Response
null
## See Also
* [JSONWP Specification](https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js#L378)
+4 -2
View File
@@ -21,7 +21,9 @@ await driver.gsmCall('555-123-4567', 'Phone');
```
```ruby
# Not supported
# Ruby
@driver.gsm_call(phone_number: '555-123-4567', action: :call)
```
```php
@@ -59,7 +61,7 @@ await driver.gsmCall('555-123-4567', 'Phone');
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/blob/master/lib/commands.js#L3183) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| None | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#gsm_call-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | [github.com](https://github.com/appium/php-client/) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | [github.com](https://github.com/appium/appium-dotnet-driver/) |
@@ -21,7 +21,9 @@ await driver.gsmSignal(3);
```
```ruby
# Not supported
# Ruby
@driver.gsm_signal :good
```
```php
@@ -59,7 +61,7 @@ await driver.gsmSignal(3);
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/blob/master/lib/commands.js#L3221) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| None | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#gsm_signal-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | [github.com](https://github.com/appium/php-client/) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | [github.com](https://github.com/appium/appium-dotnet-driver/) |
+4 -2
View File
@@ -21,7 +21,9 @@ await driver.gsmVoice('Voice state');
```
```ruby
# Not supported
# Ruby
@driver.gsm_voice :home
```
```php
@@ -59,7 +61,7 @@ await driver.gsmVoice('Voice state');
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/blob/master/lib/commands.js#L3241) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| None | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#gsm_voice-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | [github.com](https://github.com/appium/php-client/) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | [github.com](https://github.com/appium/appium-dotnet-driver/) |
+2 -2
View File
@@ -22,7 +22,7 @@ await driver.sendSms('555-123-4567', 'Hey lol');
```ruby
# Ruby
// TODO
@driver.send_sms(phone_number: '555-123-4567', message: 'Hey lol')
```
@@ -61,7 +61,7 @@ await driver.sendSms('555-123-4567', 'Hey lol');
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/blob/master/lib/commands.js#L3183) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| None | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Android/Device/Emulator#send_sms-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| None | [github.com](https://github.com/appium/php-client/) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| None | [github.com](https://github.com/appium/appium-dotnet-driver/) |
@@ -24,7 +24,9 @@ await driver.toggleData();
```
```ruby
# Not supported
# Ruby
@driver.toggle_data
```
```php
@@ -62,7 +64,7 @@ await driver.toggleData();
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | [webdriver.io](http://webdriver.io/api/mobile/toggleData.html) |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/blob/master/lib/commands.js#L2771) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](https://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Core/Device#toggle_data-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| All | [github.com](https://github.com/appium/php-client/) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| All | [github.com](https://github.com/appium/appium-dotnet-driver/) |
@@ -28,7 +28,9 @@ await driver.toggleLocationServices();
```
```ruby
# Not supported
# Ruby
@driver.toggle_location_services
```
```php
@@ -66,7 +68,7 @@ await driver.toggleLocationServices();
|[Python](https://github.com/appium/python-client/releases/latest)| All | [github.com](https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py#L774) |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | [webdriver.io](http://webdriver.io/api/mobile/toggleLocationServices.html) |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/blob/master/lib/commands.js#L2758) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| None | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Core/Device#toggle_location_services-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| All | [github.com](https://github.com/appium/php-client/) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| All | [github.com](https://github.com/appium/appium-dotnet-driver/) |
@@ -24,7 +24,9 @@ await driver.toggleWiFi();
```
```ruby
# Not supported
# Ruby
@driver.toggle_wifi
```
```php
@@ -62,7 +64,7 @@ await driver.toggleWiFi();
|[Python](https://github.com/appium/python-client/releases/latest)| None | |
|[Javascript (WebdriverIO)](http://webdriver.io/index.html)| All | [webdriver.io](http://webdriver.io/api/mobile/toggleWiFi.html) |
|[Javascript (WD)](https://github.com/admc/wd/releases/latest)| All | [github.com](https://github.com/admc/wd/blob/master/lib/commands.js#L2738) |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | |
|[Ruby](https://github.com/appium/ruby_lib/releases/latest)| All | [www.rubydoc.info](http://www.rubydoc.info/github/appium/ruby_lib_core/master/Appium/Core/Device#toggle_wifi-instance_method) |
|[PHP](https://github.com/appium/php-client/releases/latest)| All | [github.com](https://github.com/appium/php-client/) |
|[C#](https://github.com/appium/appium-dotnet-driver/releases/latest)| All | [github.com](https://github.com/appium/appium-dotnet-driver/) |
+1 -1
View File
@@ -26,7 +26,7 @@ driver.getText("~SomeAccessibilityId");
// wd example
let element = await driver.elementByAccessibilityId("SomeAccessibilityID");
await element.submit();
await element.text();
```
+2 -2
View File
@@ -52,8 +52,8 @@ In a web context, the source returns the source HTML of the current window. In a
This method is useful for inspecting your application hierarchy and using that to write [selectors](/docs/en/commands/element/find-element.md)
(NOTE: iOS and Android don't have standard ways of defining their application source, so on calls to 'Get Page Source' Appium traverses the app hierarchy
and creates an XML document. Thus, getting the source can oftentime be an expensive and time-consuming operation)
(NOTE: iOS and Android don't have standard ways of defining their application source, so on calls to 'Get Page Source' Appium traverses the app hierarchy
and creates an XML document. Thus, getting the source can often be an expensive and time-consuming operation)
## Support
@@ -2,11 +2,20 @@
So you want to run Appium from source and help fix bugs and add features?
Great! Just fork the project, make a change, and send a pull request! Please
have a look at our [Style Guide](style-guide-2.0.md) before getting to work.
have a look at our [Style Guide](style-guide.md) before getting to work.
Please make sure the unit and functional tests pass before sending a pull
request; for more information on how to run tests, keep reading!
Make sure you read and follow the setup instructions in the README first.
### Node.js
Appium is written in JavaScript, and run with the [Node.js](https://nodejs.org/) engine. Currently
version 6+ is supported. While Node.js can be installed globally on the system,
a version manager is _highly_ recommended.
* NVM - [https://github.com/creationix/nvm](https://github.com/creationix/nvm)
* N - [https://github.com/tj/n](https://github.com/tj/n)
Your Node.js installation will include the [NPM](https://www.npmjs.com/) package manager, which Appium
will need in order to manage dependencies. Appiums supports NPM version 3+.
### Setting up Appium from Source
@@ -17,57 +26,43 @@ instance of an Appium server, and then run your test.
The quick way to get started:
```center
```
git clone https://github.com/appium/appium.git
cd appium
npm install
gulp transpile # requires gulp, see below
npm install -g authorize-ios # for ios only
authorize-ios # for ios only
npm run build
node .
```
### Hacking on Appium
Make sure you have `ant`, `maven`, `adb` installed and added to system `PATH`, also you
would need the android-16 sdk (for Selendroid) and android-19 sdk installed.
From your local repo's command prompt, install the following packages using the
following commands (if you didn't install `node` using Homebrew, you might have
to run `npm` with sudo privileges):
```center
npm install -g mocha
npm install -g gulp
npm install -g gulp-cli
npm install -g appium-doctor && appium-doctor --dev
Install the [appium-doctor](https://github.com/appium/appium-doctor) tool, and run it to verify all of the
dependencies are set up correctly (since dependencies for building Appium
are different from those for simply running it):
```
npm install -g appium-doctor
appium-doctor --dev
```
Install the Node.js dependencies:
```
npm install
gulp transpile
```
The first two commands install test and build tools (`sudo` may not be
necessary if you installed node.js via Homebrew). The third command verifies
that all of the dependencies are set up correctly (since dependencies for
building Appium are different from those for simply running Appium) and fourth
command installs all app dependencies and builds supporting binaries and test
apps. The final command transpiles all the code so that `node` can run it.
When pulling new code from GitHub, if there are changes to `package.json` it
is necessary to remove the old dependencies and re-run `npm install`:
```center
rm -rf node_modules
npm install
gulp transpile
```
rm -rf node_modules && rm -rf package-lock.json && npm install
```
At this point, you will be able to start the Appium server:
```center
```
node .
```
See [the server documentation](/docs/en/writing-running-appium/server-args.md)
for a full list of arguments.
for a full list of command line arguments that can be used.
#### Hacking with Appium for iOS
@@ -80,36 +75,37 @@ have to modify your `/etc/authorization` file in one of two ways:
2. Run the following command which automatically modifies your
`/etc/authorization` file for you:
```center
```
npm install -g authorize-ios
sudo authorize-ios
```
At this point, run:
```center
rm -rf node-modules
npm install
gulp transpile
```
rm -rf node_modules && rm -rf package-lock.json && npm install
```
Now your Appium instance is ready to go. Run `node .` to kick up the Appium server.
#### Hacking with Appium for Android
To work on Android, make sure you have `ant`, `maven`, and `adb` installed
and added to system `PATH` environment variable. Also you would need the
android-16 sdk (for `Selendroid`) and android-19+ sdk installed.
From your local repo's command prompt, install/run the following:
Set up Appium by running:
```center
rm -rf node-modules
npm install
gulp transpile
```
rm -rf node_modules && rm -rf package-lock.json && npm install
```
Make sure you have one and only one Android emulator or device running, e.g.,
by running this command in another process (assuming the `emulator` command is
on your path):
```center
```
emulator -avd <MyAvdName>
```
@@ -123,11 +119,43 @@ update everything necessary. You will also need to do this when Appium bumps
its version up. Prior to running `npm install` it is recommended to remove
all the old dependencies in the `node_modules` directory:
```center
rm -rf node-modules
npm install
gulp transpile
```
rm -rf node_modules && rm -rf package-lock.json && npm install
```
### Different packages
Appium is made up of a number of different packages. While it is often possible
to work in a single package, it is also often the case that work, whether fixing
a bug or adding a new feature, requires working on multiple packages simultaneously.
Unfortunately the dependencies installed when running `npm install` are those that
have already been published, so some work is needed to link together local development
versions of the packages that are being worked on.
In the case where one package, `A`, depends on another package, `B`, the following steps
are necessary to link the two:
1. In one terminal, enter into package `B`
```
cd B
```
2. Use [NPM link](https://docs.npmjs.com/cli/link) to create symbolic link to this package
```
npm link
```
3. In another terminal, enter into package `A`
```
cd A
```
4. Use [NPM link](https://docs.npmjs.com/cli/link) to link the dependent package `B` to the development version
```
npm link B
```
Now the version of `B` that `A` uses will be your local version. Remember, however, that
changes made to the JavaScript will only be available when they have been transpiled, so
when you are going to test from package `A`, run `npm run build` in the directory for
package `B`.
### Running Tests
@@ -138,16 +166,23 @@ system is set up properly for the platforms you desire to test on.
Once your system is set up and your code is up to date, you can run unit tests
with:
```center
gulp once
```
npm run test
```
You can run functional tests for all supported platforms (after ensuring that
Appium is running in another window with `node .`) with:
```center
gulp e2e-test
```
npm run e2e-test
```
Before committing code, please run `gulp once` to execute some basic tests and
check your changes against code quality standards.
### Committing code
Each Appium package installs a pre-commit hook which will run the [linter](https://eslint.org/) and
the unit tests before the commit is made. Any error in either of these will stop
the commit from occurring.
Once code is committed and a [pull request](https://help.github.com/articles/about-pull-requests/)
is made to the correct Appium respository on [GitHub](https://github.com/), Appium build system
will run all of the functional tests.
@@ -0,0 +1,16 @@
## Appium development commands
Each Appium package has a number of NPM scripts that are used to automate
development tasks:
| Task | Description |
|------------------|--------------------------------------------------------|
| npm run build | Transpile code into the `build` directory |
| npm run lint | Runs ESLint |
| npm run test | Cleans, lints, transpiles, and runs unit tests |
| npm run e2e-test | Tranpiles and runs functional tests |
| npm run watch | Automatically runs `test` command when code is changed |
| npm run mocha | Gives access to `mocha` test runner |
In addition, the main Appium package has a task `npm generate-docs` which generates
the command documentation.
@@ -93,7 +93,7 @@ Most Appium packages have this as the default behavior when running `gulp`.
It's important for all of Appium's JS to look and feel the same. This includes
style conventions as well as coding patterns and which libraries we use to
solve various problems. You should get familiar with our new [ES2015 Style
Guide](/docs/en/contributing-to-appium/style-guide-2.0.md). When transpiling,
Guide](/docs/en/contributing-to-appium/style-guide.md). When transpiling,
Appium packages will automatically run ESLint or other lint tools and provide
warning or error feedback if the code doesn't conform to our style. These tools
are not necessarily exhaustive of the kinds of style issues we care about, so
@@ -147,12 +147,12 @@ package if you want to publish it. Ownership is managed by the Appium
committers; talk to @jlipps or @imurchie if you believe you should be an owner
and are not):
0. `rm -rf node_modules && npm install` and run tests to make sure a clean install works.
0. Determine whether we have a patch (bugfix), minor (feature), or major (breaking) release according to the principles of [SemVer](http://semver.org/) (see also this explanation of [how SemVer works with NPM](https://docs.npmjs.com/getting-started/semantic-versioning)).
0. Update the `CHANGELOG` and/or `README` with any appropriate changes and commit. Most subpackages don't have a `CHANGELOG`.
0. Run `npm version <version-type>` with the appropriate version type.
0. Push the appropriate branch to GitHub, and don't forget to include the `--tags` flag to include the tag just created by `npm version`.
0. Run `npm publish` (with `--tag beta` if this isn't an official release).
1. `rm -rf node_modules && rm -rf package-lock.json && npm install` and run tests to make sure a clean install works.
1. Determine whether we have a patch (bugfix), minor (feature), or major (breaking) release according to the principles of [SemVer](http://semver.org/) (see also this explanation of [how SemVer works with NPM](https://docs.npmjs.com/getting-started/semantic-versioning)).
1. Update the `CHANGELOG` and/or `README` with any appropriate changes and commit. Most subpackages don't have a `CHANGELOG`.
1. Run `npm version <version-type>` with the appropriate version type.
1. Push the appropriate branch to GitHub, and don't forget to include the `--tags` flag to include the tag just created by `npm version`.
1. Run `npm publish` (with `--tag beta` if this isn't an official release).
For the main Appium packages, all the above steps must be taken, but with
several changes. One reason is that for the main package we use NPM shrinkwrap
@@ -175,20 +175,20 @@ checked in to GitHub along with changes to `package.json`. With npm 5+ there is
also a `package-lock.json` file produced. During the shrinkwrap process this is
converted into the `npm-shrinkwrap.json` file.
0. Remove the NPM shrinkwrap and package-lock JSON files if they exists.
0. `rm -rf node_modules && npm install` and run tests to make sure a clean install works.
0. Determine whether we have a `patch` (bugfix), `minor` (feature), or `major` (breaking) release according to the principles of SemVer.
0. Update `package.json` with the appropriate new version.
0. Update the CHANGELOG/README with appropriate changes and submit for review as a PR, along with shrinkwrap and `package.json` changes. Wait for it to be merged, then pull it into the release branch.
0. `rm -rf node_modules && npm install --production` to get just the production dependencies.
0. `npm shrinkwrap` to write the new NPM shrinkwrap JSON file, and commit this file.
0. Create a tag of the form `v<version>` on the release branch (usually a minor branch like `1.5` or `1.4`), with: `git tag -a v<version>`, e.g., `git tag -a v1.5.0`. This is not necessary for beta versions.
0. Push the tag to upstream: `git push --tags <remote> <branch>`
0. Install dev dependencies (or at least `gulp` and `appium-gulp-plugins`), and undo the changes to the NPM shrinkwrap JSON file (e.g., `git checkout -- npm-shrinkwrap.json`).
0. Run `npm publish` (with `--tag beta` if this isn't an official release).
0. Remove the NPM shrinkwrap JSON file from Git and push the changes
0. Update the docs at appium.io. Check out the appium.io repo from github, check out the `gh-pages` branch and pull latest. Run `rake publish`.
0. Create a new release on GitHub: go to `https://github.com/appium/appium/releases/tag/v<VERSION>` and hit "Edit Tag". Make the release name `<VERSION>` (e.g., `2.0.5`), then paste in the changelog (but not the changelog header for this version). If it's a beta release, mark as pre-release.
0. Create a new post on discuss.appium.io announcing the release. Post it in the "News" category. Paste in the changelog and any choice comments. Pin it and unpin the previous release post.
0. Begin process of releasing `appium-desktop`.
0. Notify @jlipps to so he can tweet a link to the discuss post.
1. Remove the NPM shrinkwrap and package-lock JSON files if they exists.
1. `rm -rf node_modules && npm install` and run tests to make sure a clean install works.
1. Determine whether we have a `patch` (bugfix), `minor` (feature), or `major` (breaking) release according to the principles of SemVer.
1. Update `package.json` with the appropriate new version.
1. Update the CHANGELOG/README with appropriate changes and submit for review as a PR, along with shrinkwrap and `package.json` changes. Wait for it to be merged, then pull it into the release branch.
1. `rm -rf node_modules && npm install --production` to get just the production dependencies.
1. `npm shrinkwrap` to write the new NPM shrinkwrap JSON file, and commit this file.
1. Create a tag of the form `v<version>` on the release branch (usually a minor branch like `1.5` or `1.4`), with: `git tag -a v<version>`, e.g., `git tag -a v1.5.0`. This is not necessary for beta versions.
1. Push the tag to upstream: `git push --tags <remote> <branch>`
1. Install dev dependencies (or at least `gulp` and `appium-gulp-plugins`), and undo the changes to the NPM shrinkwrap JSON file (e.g., `git checkout -- npm-shrinkwrap.json`).
1. Run `npm publish` (with `--tag beta` if this isn't an official release).
1. Remove the NPM shrinkwrap JSON file from Git and push the changes
1. Update the docs at appium.io. Check out the appium.io repo from github, check out the `gh-pages` branch and pull latest. Run `rake publish`.
1. Create a new release on GitHub: go to `https://github.com/appium/appium/releases/tag/v<VERSION>` and hit "Edit Tag". Make the release name `<VERSION>` (e.g., `2.0.5`), then paste in the changelog (but not the changelog header for this version). If it's a beta release, mark as pre-release.
1. Create a new post on discuss.appium.io announcing the release. Post it in the "News" category. Paste in the changelog and any choice comments. Pin it and unpin the previous release post.
1. Begin process of releasing `appium-desktop`.
1. Notify @jlipps to so he can tweet a link to the discuss post.
-15
View File
@@ -1,15 +0,0 @@
## Appium gulp commands
[Gulp](http://gulpjs.com) is like `make` for Node.js! We use it to automate all
kinds of appium dev tasks. Here's what you can do:
|Task|Description|
|----|-----------|
|gulp once|Cleans, lints, transpiles and runs unit tests|
|gulp watch|Automatically runs `gulp once` when code changes|
|gulp lint|Runs JSLint|
|gulp jshint|Runs JSHint|
|gulp transpile|Transpiles our ES7/ES2015 code to ES5, generates `/build` directory + contents|
|gulp unit-test|Runs unit tests|
|gulp e2e-test|Runs e2e tests|
|gulp docs|Generates docs/en/writing-running-appium/server-args.md doc|
@@ -44,10 +44,10 @@ files in `docs/en/commands` and then they need to be committed and pushed.
### Adding Documents to Appium.io
Markdown files in `docs/` aren't automatically added to the site. To add a document to https://appium.io
Markdown files in `docs/` aren't automatically added to the site. To add a document to [appium.io](https://appium.io)
you need to add it as an entry in the appropriate location in the table of
contents, [toc.js](https://github.com/appium/appium/blob/master/docs/toc.js)
#### Publishing
To publish documentation on appium.io see [appium.io](https://github.com/appium/appium.io).
To publish documentation on [appium.io](https://appium.io) see [appium.io (Github)](https://github.com/appium/appium.io).
@@ -1,256 +0,0 @@
## Style guide for contributors
Thanks for your contribution to Appium! Here are the principles we use when
writing javascript. Please conform to these so we can merge your pull request
without going back and forth about style. The main principle is: *make your
code look like the surrounding code*.
### Rebasing
Commits in a pull request should consist of [logical changes](https://github.com/appium/appium/pull/920#issuecomment-21588553).
If there are multiple authors, make sure each author has their own commit.
It's not a good idea to modify author information. Merge commits should be
rebased out of pull requests.
### Linting
All code (except for code in `bootstrap.js` which uses proprietary Apple
methods) must pass JSLint. To check your code, you can simply run `grunt
lint` from the Appium repo dir. If you've created a new .js file,
please make sure it is covered by the wildcards in `grunt.js` or that it is
added specifically.
It's easy to have your code linted as you type, which makes the whole process
much smoother. We like [jshint](http://www.jshint.com),
which has integrations with a lot of source code editors. The file `
.jshintrc` is checked into the repo, so by adding jshint to your editor, you'll
take advantage of the linting.
Since jshint does not enforce code style anymore, we also use
[jscs](https://github.com/mdevils/node-jscs), for which it also exists some
source editor integrations.
These configuration files define the warnings you will see in your favorite
editor. See [this page for jshint](http://www.jshint.com/platforms/) and
[this page for jscs](https://github.com/mdevils/node-jscs#friendly-packages) to
get the list of editors and platforms supported and how setup your editor for
automatic linting.
### Style notes
We use a future version of JavaScript and take advantage of the Babel
transpiler to render it down to what is supported by current versions of
Node.js. We use ES2015 (link needed) (formerly called ES6) with some
not-yet-standard features, namely `async/await` (link needed). This style guide
must be followed diligently in all Appium contributions!
* Use two spaces for indentation, *no tabs*
* Use single spaces around operators
```javascript
var x = 1;
```
not
```javascript
var x=1;
```
* Spaces after commas and colons in lists, objects, function calls, etc...
```javascript
var x = myFunc("lol", {foo: bar, baz: boo});
```
not
```javascript
var x = myFunc("lol",{foo:bar,baz:boo});
```
* Always end statements with semicolons
* Comma-first
```javascript
var x = {
foo: 'bar'
, baz: 'boo'
, wuz: 'foz'
};
```
* Brackets for `function`, `if`, etc... go on same line, `else` gets sandwiched
```javascript
if (foo === bar) {
// do something
} else {
// do something else
}
```
* Space after `if`, `for`, and `function`:
```javascript
if (foo === bar) {
```
```javascript
for (var i = 0; i < 10; i ++) {
```
```javascript
var lol = function (foo) {
```
not
```javascript
if(foo === bar) {
```
```javascript
for(var i = 0; i < 10; i ++) {
```
```javascript
var lol = function(foo) {
```
* Avoid bracketless `if` for one-liners:
```javascript
if (foo === bar) {
foo++;
}
```
not
```javascript
if (foo === bar)
foo++;
```
except in the case of short-circuiting to a callback in the event of an error
```javascript
if (err) return cb(err);
```
* Use `===`, not `==`, and `!==`, not `!=` for no surprises
* Line length shouldn't be longer than 79 characters
* Break up long strings like this:
```javascript
myFunc("This is a really long string that's longer " +
"than 79 characters so I broke it up, woo");
```
* Comments should line up with code
```javascript
if (foo === 5) {
myFunc(foo);
// foo++;
}
```
not
```javascript
if (foo === 5) {
myFunc(foo);
//foo++;
}
```
* Subclassing by extending prototypes
```javascript
var _ = require('underscore');
var SuperClass = function () {
this.init();
};
SuperClass.prototype.init = function () {
// initialize
};
// Create a subclass
var SubClass = function () {
this.init();
};
_.extend(SubClass.prototype, SuperClass.prototype);
```
* Callbacks are always last in function definitions
```javascript
var foo = function (arg1, arg2, cb) {
...
};
```
* Define functions as variables
```javascript
var myFunc = function (a, b, c) {};
```
not
```javascript
function myFunc (a, b, c) {}
```
* Variable names should be camelCased:
```javascript
var myVariable = 42;
```
not
```javascript
var my_variable = 42;
```
* Check for undefined
```javascript
typeof myVariable === "undefined"
```
not
```javascript
myVariable === undefined
```
* Define a variable with a default value
```javascript
var x = y || z;
```
not
```javascript
var x = y ? y : z;
```
### Test Style:
Keep on the same line if it makes sense semantically and length is not an issue:
Examples:
```javascript
driver.elementByTagName('el1').should.become("123")
.nodeify(done);
driver
.elementsByTagName('el1').should.eventually.have.length(0)
.nodeify(done);
```
Alternatively use extra indents to improve readability:
```javascript
h.driver
.elementById('comments')
.clear()
.click()
.keys("hello world")
.getValue()
.should.become("hello world")
.elementById('comments')
.getValue().should.become("hello world")
.nodeify(done);
h.driver
.execute("'nan'--")
.should.be.rejectedWith("status: 13")
.nodeify(done);
```
+92 -162
View File
@@ -2,8 +2,17 @@
Thanks for your contribution to Appium! Here are the principles we use when
writing javascript. Please conform to these so we can merge your pull request
without going back and forth about style. The main principle is: *make your
code look like the surrounding code*.
without going back and forth about style. The main principle is: *make your
code look like the surrounding code*.
### JavaScript
With the exception of the code that runs on the devices themselves
([appium-uiautomator2-server](https://github.com/appium/appium-uiautomator2-server) for
Android, [WebDriverAgent](https://github.com/appium/WebDriverAgent) for iOS), Appium is written in [Node.js](https://nodejs.org/). If you are
not familiar with JavaScript, please familiarize yourself before attempting
to modify the code. There are plenty of good, free resources (see, for example,
[You Don't Know JavaScript](https://github.com/getify/You-Dont-Know-JS)).
### Rebasing
@@ -14,266 +23,187 @@ rebased out of pull requests.
### Linting
All code (except for code in `bootstrap.js` which uses proprietary Apple
methods) must pass JSLint. To check your code, you can simply run `grunt
lint` from the Appium repo dir. If you've created a new .js file,
please make sure it is covered by the wildcards in `grunt.js` or that it is
added specifically.
All code must pass [ESLint](https://eslint.org/). To check your code, you can simply run `npm run lint`
from the Appium repo dir. The configuration is specified in the
[eslint-config-appium](https://github.com/appium/eslint-config-appium) package.
It's easy to have your code linted as you type, which makes the whole process
much smoother. We like [jshint](http://www.jshint.com),
which has integrations with a lot of source code editors. The file `
.jshintrc` is checked into the repo, and its contents are:
```json
{
"laxcomma": true,
"strict": true,
"undef": true,
"unused": true,
"node": true,
"eqeqeq": true,
"trailing": true,
"indent": 2
}
```
Since jshint does not enforce code style anymore, we also use
[jscs](https://github.com/mdevils/node-jscs), for which it also exists some
source editor integrations. The configuration file is:
```json
{
"excludeFiles": ["submodules/**", "node_modules/**",
"./lib/server/static/**", "./lib/devices/firefoxos/atoms/*.js",
"./test/harmony/**/*.js", "./sample-code/examples/node/**/*-yiewd.js",
"./sample-code/apps/**", "./sample-code/examples/php/vendor/**"],
"requireCurlyBraces": ["for", "while", "do", "try", "catch"],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch",
"return", "try", "catch", "function"],
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
}
}
```
These configuration files define the warnings you will see in your favorite
editor. See [this page for jshint](http://www.jshint.com/platforms/) and
[this page for jscs](https://github.com/mdevils/node-jscs#friendly-packages) to
get the list of editors and platforms supported and how setup your editor for
automatic linting.
Most modern editors have integration with ESLint. See [here](https://eslint.org/docs/user-guide/integrations) for details.
### Style notes
We use a future version of JavaScript and take advantage of the [Babel](https://babeljs.io/)
transpiler to render it down to what is supported by current versions of
[Node.js](https://nodejs.org/). We use [ES2015](https://babeljs.io/learn-es2015/) (formerly called ES6) with some
not-yet-standard features, namely [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). This style guide
must be followed diligently in all Appium contributions! Luckily, linting
will enforce most of these rules!
* Use two spaces for indentation, *no tabs*
* Use single spaces around operators
```javascript
var x = 1;
```js
let x = 1;
```
not
```javascript
var x=1;
```js
let x=1;
```
* Spaces after commas and colons in lists, objects, function calls, etc...
```javascript
var x = myFunc("lol", {foo: bar, baz: boo});
```js
let x = myFunc('lol', {foo: bar, baz: boo});
```
not
```javascript
var x = myFunc("lol",{foo:bar,baz:boo});
```
* Always end statements with semicolons
* Comma-first
```javascript
var x = {
foo: 'bar'
, baz: 'boo'
, wuz: 'foz'
};
```js
let x = myFunc('lol',{foo:bar,baz:boo});
```
* Always end statements with semicolons (see, for example, [Kent Dodds](https://blog.kentcdodds.com/semicolons-in-javascript-a-preference-dd8fc8b80895))
* Brackets for `function`, `if`, etc... go on same line, `else` gets sandwiched
```javascript
```js
if (foo === bar) {
// do something
} else {
// do something else
}
```
not
```js
if (foo === bar)
{
// do something
}
else
{
// do something else
}
```
* Space after `if`, `for`, and `function`:
```javascript
```js
if (foo === bar) {
```
```javascript
for (var i = 0; i < 10; i ++) {
```js
for (let i = 0; i < 10; i ++) {
```
```javascript
var lol = function (foo) {
```js
let lol = function (foo) {
```
not
```javascript
```js
if(foo === bar) {
```
```javascript
for(var i = 0; i < 10; i ++) {
```js
for(let i = 0; i < 10; i ++) {
```
```javascript
var lol = function(foo) {
```js
let lol = function(foo) {
```
* Avoid bracketless `if` for one-liners:
```javascript
```js
if (foo === bar) {
foo++;
}
```
not
```javascript
```js
if (foo === bar)
foo++;
```
except in the case of short-circuiting to a callback in the event of an error
```javascript
if (err) return cb(err);
except in the case of short-circuiting to return/error
```js
if (err) return;
```
```js
if (err) throw new Error(err);
```
* Use `===`, not `==`, and `!==`, not `!=` for no surprises
* Use `===`, not `==`, and `!==`, not `!=` for [no surprises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness)
* Line length shouldn't be longer than 79 characters
* Break up long strings like this:
```javascript
myFunc("This is a really long string that's longer " +
"than 79 characters so I broke it up, woo");
myFunc('This is a really long string that's longer ' +
'than 79 characters so I broke it up, woo');
```
* Comments should line up with code
```javascript
```js
if (foo === 5) {
myFunc(foo);
// foo++;
}
```
not
```javascript
```js
if (foo === 5) {
myFunc(foo);
//foo++;
}
```
* Subclassing by extending prototypes
```javascript
var _ = require('underscore');
var SuperClass = function () {
this.init();
};
SuperClass.prototype.init = function () {
// initialize
};
// Create a subclass
var SubClass = function () {
this.init();
};
_.extend(SubClass.prototype, SuperClass.prototype);
```
* Callbacks are always last in function definitions
```javascript
var foo = function (arg1, arg2, cb) {
...
};
```
* Define functions as variables
```javascript
var myFunc = function (a, b, c) {};
```
not
```javascript
function myFunc (a, b, c) {}
```
* Variable names should be camelCased:
```javascript
var myVariable = 42;
```js
let myVariable = 42;
```
not
```javascript
var my_variable = 42;
```js
let my_variable = 42;
```
* Check for undefined
* Check for `undefined` using Appium's [appium-support](https://github.com/appium/appium-support) package
```javascript
typeof myVariable === "undefined"
```
not
```javascript
myVariable === undefined
```js
util.hasValue(myVariable)
```
* Define a variable with a default value
```javascript
var x = y || z;
```js
let x = y || z;
```
not
```javascript
var x = y ? y : z;
```js
let x = y ? y : z;
```
### Test Style:
Tests are written using [mocha](https://mochajs.org/) and [chai](http://chaijs.com/). The WebDriver
library used is [wd](https://github.com/admc/wd).
Keep on the same line if it makes sense semantically and length is not an issue:
Examples:
```javascript
driver.elementByTagName('el1').should.become("123")
.nodeify(done);
```js
driver.elementByTagName('el1').should.become('123');
driver
.elementsByTagName('el1').should.eventually.have.length(0)
.nodeify(done);
driver
.elementsByTagName('el1').should.eventually.have.length(0);
```
Alternatively use extra indents to improve readability:
```javascript
h.driver
```js
driver
.elementById('comments')
.clear()
.click()
.keys("hello world")
.keys('hello world')
.getValue()
.should.become("hello world")
.should.become('hello world')
.elementById('comments')
.getValue().should.become("hello world")
.nodeify(done);
.getValue().should.become('hello world');
h.driver
.execute("'nan'--")
.should.be.rejectedWith("status: 13")
.nodeify(done);
driver
.execute("'NaN'--")
.should.be.rejectedWith('status: 13');
```
@@ -28,27 +28,27 @@ The versioning and release model shape how we set Appium milestones. The next mi
For Appium, the basic flow looks like this:
1. All PRs go to `master` (aka `trunk`).
2. Whoever is leading a release acts as the "release engineer" (RE). When the release is ready to be shared (“Beta" status or better), the RE creates a new branch `v[Major].[Minor].[Patch]-branch`.
3. PRs continue to go to `master`.
4. If the release requires fixes committed to `master`, the RE cherrypicks those commits into the release branch.
5. The release branch can be amended with hotfixes for subsequent patch releases. This allows the team to carefully scope small changesets for quick release. Fixes can also be pulled into previous release branches branches when needed.
6. Rinse, repeat.
1. Whoever is leading a release acts as the "release engineer" (RE). When the release is ready to be shared (“Beta" status or better), the RE creates a new branch `v[Major].[Minor].[Patch]-branch`.
1. PRs continue to go to `master`.
1. If the release requires fixes committed to `master`, the RE cherrypicks those commits into the release branch.
1. The release branch can be amended with hotfixes for subsequent patch releases. This allows the team to carefully scope small changesets for quick release. Fixes can also be pulled into previous release branches branches when needed.
1. Rinse, repeat.
Developers can maintain working branches however they like. These are strictly for personal use. All “official” branches should conform to the model defined above.
### Example
1. It’s 1 June. The Appium team plans on releasing 20.1-beta on 15 July and the full 20.1 release on 1 August.
2. For the next six weeks, the team commits their work to `master`.
3. On 15 July, the acting RE creates `20.1-branch`. The first node is tagged “20.1.0 Beta”.
4. One team member begins fixing bugs in the beta. The fixes are committed to `master`.
5. Other contributors begin committing changes that are planned for 20.2. These also go to `master`.
6. The RE cherrypicks the fixes into `20.1-branch`, leaving the other changes on `master`.
7. The team celebrates that all the beta bugs are fixed for the 1 August release.
8. The RE tags HEAD of `20.1-branch` as `20.1.0` and publishes the release.
9. A few weeks later, a crash is discovered in `20.1.0` and users need a fix NOW.
10. The acting RE pulls the crash fix from master into `20.1-branch`, tags HEAD as `20.1.1`, and publishes the hotfix.
11. The cycle repeats once the `20.2` release is ready.
1. For the next six weeks, the team commits their work to `master`.
1. On 15 July, the acting RE creates `20.1-branch`. The first node is tagged “20.1.0 Beta”.
1. One team member begins fixing bugs in the beta. The fixes are committed to `master`.
1. Other contributors begin committing changes that are planned for 20.2. These also go to `master`.
1. The RE cherrypicks the fixes into `20.1-branch`, leaving the other changes on `master`.
1. The team celebrates that all the beta bugs are fixed for the 1 August release.
1. The RE tags HEAD of `20.1-branch` as `20.1.0` and publishes the release.
1. A few weeks later, a crash is discovered in `20.1.0` and users need a fix NOW.
1. The acting RE pulls the crash fix from master into `20.1-branch`, tags HEAD as `20.1.1`, and publishes the hotfix.
1. The cycle repeats once the `20.2` release is ready.
+6 -1
View File
@@ -85,6 +85,8 @@ These Capabilities are available only on Android-based drivers (like
|`keyAlias`| Alias for key |e.g., `androiddebugkey`|
|`keyPassword`| Password for key |e.g., `foo`|
|`chromedriverExecutable`| The absolute local path to webdriver executable (if Chromium embedder provides its own webdriver, it should be used instead of original chromedriver bundled with Appium) |`/abs/path/to/webdriver`|
|`chromedriverExecutableDir`| The absolute path to a directory to look for Chromedriver executables in, for automatic discovery of compatible Chromedrivers |`/abs/path/to/chromedriver/directory`|
|`chromedriverChromeMappingFile`| The absolute path to a file which maps Chromedriver versions to the minimum Chrome that it supports |`/abs/path/to/mapping.json`|
|`autoWebviewTimeout`| Amount of time to wait for Webview context to become active, in ms. Defaults to `2000`| e.g. `4`|
|`intentAction`| Intent action which will be used to start activity (default `android.intent.action.MAIN`)| e.g.`android.intent.action.MAIN`, `android.intent.action.VIEW`|
|`intentCategory`| Intent category which will be used to start activity (default `android.intent.category.LAUNCHER`)| e.g. `android.intent.category.LAUNCHER`, `android.intent.category.APP_CONTACTS`
@@ -104,6 +106,9 @@ These Capabilities are available only on Android-based drivers (like
|`networkSpeed`|Set the network speed emulation. Specify the maximum network upload and download speeds. Defaults to `full`| `['full','gsm', 'edge', 'hscsd', 'gprs', 'umts', 'hsdpa', 'lte', 'evdo']` Check [-netspeed option](https://developer.android.com/studio/run/emulator-commandline.html) more info about speed emulation for avds|
|`gpsEnabled`|Toggle gps location provider for emulators before starting the session. By default the emulator will have this option enabled or not according to how it has been provisioned.|`true` or `false`|
|`isHeadless`|Set this capability to `true` to run the Emulator headless when device display is not needed to be visible. `false` is the default value. _isHeadless_ is also support for iOS, check XCUITest-specific capabilities. |e.g., `true`|
|`uiautomator2ServerLaunchTimeout`|Timeout in milliseconds used to wait for an uiAutomator2 server to launch. Defaults to `20000` |e.g., `20000`|
|`uiautomator2ServerInstallTimeout`|Timeout in milliseconds used to wait for an uiAutomator2 server to be installed. Defaults to `20000` |e.g., `20000`|
|`otherApps`|App or list of apps (as a JSON array) to install prior to running tests|e.g., `"/path/to/app.apk"`, `https://www.example.com/url/to/app.apk`, `["/path/to/app-a.apk", "/path/to/app-b.apk"]`|
### iOS Only
@@ -137,7 +142,7 @@ Driver](/docs/en/drivers/ios-uiautomation.md).
|`waitForAppScript`| The ios automation script used to determined if the app has been launched, by default the system wait for the page source not to be empty. The result must be a boolean |e.g. `true;`, `target.elements().length > 0;`, `$.delay(5000); true;` |
|`webviewConnectRetries`| Number of times to send connection message to remote debugger, to get webview. Default: `8` |e.g., `12`|
|`appName`| The display name of the application under test. Used to automate backgrounding the app in iOS 9+.|e.g., `UICatalog`|
|`customSSLCert`|(Sim/Emu-only) Add an SSL certificate to simulator. | e.g. <br/>`-----BEGIN CERTIFICATE-----MIIFWjCCBEKg...`<br/>`-----END CERTIFICATE-----`|
|`customSSLCert`|(Sim only) Add an SSL certificate to IOS Simulator. | e.g. <br/>`-----BEGIN CERTIFICATE-----MIIFWjCCBEKg...`<br/>`-----END CERTIFICATE-----`|
|`webkitResponseTimeout`|(Real device only) Set the time, in ms, to wait for a response from WebKit in a Safari session. Defaults to `5000`|e.g., `10000`|
### iOS Only, using XCUITest
@@ -0,0 +1,53 @@
## Automating Performance Metrics Measurement
Apple provides the `instruments` tool with quite a rich set of features for desktop and mobile applications performance measurement. The collected data can be then visualized with `Instruments.app`, which is a part of Xcode DevTools. By default Xcode provides several measurement templates, like `Activity Monitor` or `Time Profiler`, but one can also create their own profiles and select a custom set of metrics to record and visualize. Read https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide to get a more detailed overwiew of available tool features.
### mobile: startPerfRecord
This command starts the performance recorder for the given profile name (template) on the device under test. In case this command is called two or more times in a row then the previous recorder will be forcefully stopped and the new one will start. All the previous data will be lost.
Important: It is expected, that `--relaxed-security` command line parameter is set for the Appium server command line in order to meassure Simulator performance, since the `instruments` tool records the data from all running processes on the host machine.
#### Supported arguments
* _timeout_: For how long the recorder should be running in milliseconds. 5 minutes by default. Try not to set too high a value to this argument, since .trace files generated by `instruments` are pretty big in size.
* _profileName_: The name of an existing performance template. `Activity Monitor` by default.
* _pid_: The ID of the process to meassure the performance for. Set it to `current` in order to meassure only the performance of the process, which belongs to the currently active application (this might not work for some profiles). All processes running on the device are meassured if process ID is unset (the default setting). Setting process ID while device under test is Simulator might require `instruments` to be launched with sudo privileges, which is not supported and will throw a timeout exception.
#### Usage examples
```java
// Java
Map<String, Object> args = new HashMap<>();
args.put("timeout", 60 * 1000);
args.put("profileName", "Time Profiler");
driver.executeScript("mobile: startPerfRecord", args);
```
### mobile: stopPerfRecord
This command stops performance recorder for the given profile name (template) on the device under test and returns the collected data in zipped format or uploads it to a remote server. An exception will be thrown if `startPerfRecord` command has not been invoked before.
Important: It is expected, that `--relaxed-security` command line parameter is set for the Appium server command line in order to meassure Simulator performance, since the `instruments` tool records the data from all running processes on the host machine.
#### Supported arguments
* _profileName_: The name of an existing performance template, for which the monitoring has been running before. `Activity Monitor` by default.
* _remotePath_: The path to a remote location, where the resulting zipped .trace file should be uploaded. The following protocols are supported: http/https, ftp. Null or empty string value (the default setting) means the content of resulting file should be zipped, encoded as Base64 and passed as the return value. An exception will be thrown if the generated file is too big to fit into the available server process memory.
* _user_: The name of the user for the remote authentication. Only works if `remotePath` is provided.
* _pass_: The password for the remote authentication. Only works if `remotePath` is provided.
* _method_: The http multipart upload method name. Only works if `remotePath` is provided. Equals to `PUT` by default.
#### Usage examples
```python
# Python
driver.execute_script('mobile: stopPerfRecord', {
'profileName': 'Time Profiler',
'remotePath': 'ftp://myserver/upload/',
'user': 'serveruser',
'pass': 'secretpass'
})
```
@@ -34,6 +34,7 @@ own items in the SDK manager. Make sure you install the build-tools and platform
* Make sure the Android emulator is up and running.
* It's sometimes useful to run `adb kill-server && adb devices`. This can
reset the connection to the Android device.
* If you're running the Android emulator using Fastboot, try doing a cold boot (Android Studio > Tools > AVD Manager > Actions > Cold Boot Now)
* Make sure you set ANDROID_HOME pointing to the Android SDK directory
### Windows
@@ -42,6 +42,10 @@ Chrome version that is automatable:
| Version | Minimum Chrome Version | Link to Chromedriver |
|---------|------------------------|-----------------------------------------------------------------------------------|
| 2.37 | 64.0.3282.0 | [v2.37 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.37/) |
| 2.36 | 63.0.3239.0 | [v2.36 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.36/) |
| 2.35 | 62.0.3202.0 | [v2.35 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.35/) |
| 2.34 | 61.0.3163.0 | [v2.34 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.34/) |
| 2.33 | 60.0.3112.0 | [v2.33 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.33/) |
| 2.32 | 59.0.3071.0 | [v2.32 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.32/) |
| 2.31 | 58.0.3029.0 | [v2.31 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.31/) |
@@ -69,8 +73,8 @@ Chrome version that is automatable:
| 2.9 | 31.0.1650.59 | [v2.9 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.9/) |
| 2.8 | 30.0.1573.2 | [v2.8 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.8/) |
| 2.7 | 30.0.1573.2 | [v2.7 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.7/) |
| 2.6 | | [v2.6 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.6/) |
| 2.5 | | [v2.5 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.5/) |
| 2.6 | 29.0.1545.0 | [v2.6 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.6/) |
| 2.5 | 29.0.1545.0 | [v2.5 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.5/) |
| 2.4 | 29.0.1545.0 | [v2.4 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.4/) |
| 2.3 | 28.0.1500.0 | [v2.3 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.3/) |
| 2.2 | 27.0.1453.0 | [v2.2 (link)](https://chromedriver.storage.googleapis.com/index.html?path=2.2/) |
@@ -84,6 +88,29 @@ check out the release commit, and check the variable `kMinimumSupportedChromeVer
in the file `src/chrome/test/chromedriver/chrome/version.cc`. (To find the
release commits, you can use `git log --pretty=format:'%h | %s%d' | grep -i "Release Chromedriver version"`.)
### Automatic discovery of compatible Chromedriver
Beginning with Appium 1.8.0, Appium is able to pick the correct Chromedriver for the
version of Chrome under test. While Appium only comes bundled with the Chromedriver
most recently released at the time of the Appium version's release, more Chromedriver
versions can be downloaded and either placed inside the Appium installation (_not
recommended_ since upgrading Appium will remove them) or in a custom location,
which can be indicated to Appium with the `chromedriverExecutableDir` desired
capability. This capability is the absolute path to the directory in which you have
placed one or more Chromedriver executables.
As well, since new versions of Chromedriver may be available that were not when
an Appium version was released, a custom mapping of Chromedrivers to the minimum
Chrome version they support can be given to Appium through the `chromedriverChromeMappingFile`
desired capability. This should be the absolute path to a file with the mapping
in it. The contents of the file need to be parsable as a JSON object, like:
```JSON
{
"2.42": "63.0.3239",
"2.41": "62.0.3202"
}
```
### Installing an network issues
When Appium is installed it needs to download Chromedriver, so there is the possibility
+3 -2
View File
@@ -28,6 +28,7 @@ module.exports = {
["Get Session Capabilities", "get.md"],
["Go Back", "back.md"],
["Screenshot", "screenshot.md"],
["Source", "source.md"],
["Timeouts", ["timeouts",
["Timeouts", "timeouts.md"],
["Implicit Wait", "implicit-wait.md"],
@@ -239,8 +240,8 @@ module.exports = {
["Contributing", ["contributing-to-appium",
["Running Appium from Source", "appium-from-source.md"],
["Developer Overview", "developers-overview.md"],
["Standard Gulp Commands", "gulp.md"],
["Appium Style Guide", "style-guide-2.0.md"],
["Standard Dev Commands", "dev-tools.md"],
["Appium Style Guide", "style-guide.md"],
["How to Write Docs", "how-to-write-docs.md"],
["Appium Package Structure", "appium-packages.md"],
["Credits", "credits.md"]]]
+125 -80
View File
@@ -1,8 +1,7 @@
import _ from 'lodash';
import log from './logger';
import { getAppiumConfig } from './config';
import { BaseDriver, routeConfiguringFunction, errors,
isSessionCommand } from 'appium-base-driver';
import { BaseDriver, errors, isSessionCommand } from 'appium-base-driver';
import { FakeDriver } from 'appium-fake-driver';
import { AndroidDriver } from 'appium-android-driver';
import { IosDriver } from 'appium-ios-driver';
@@ -16,6 +15,7 @@ import { EspressoDriver } from 'appium-espresso-driver';
import B from 'bluebird';
import AsyncLock from 'async-lock';
import { inspectObject, parseCapsForInnerDriver } from './utils';
import semver from 'semver';
const sessionsListGuard = new AsyncLock();
const pendingDriversGuard = new AsyncLock();
@@ -88,18 +88,25 @@ class AppiumDriver extends BaseDriver {
}
if (caps.platformName.toLowerCase() === 'android') {
const platformVersion = semver.valid(semver.coerce(caps.platformVersion));
if (platformVersion && semver.satisfies(platformVersion, '>=6.0.0')) {
log.warn("Consider setting 'automationName' capability to " +
`'${this.validAutomations.UIAUTOMATOR2.toLowerCase()}' ` +
"on Android >= 6, since UIAutomator framework " +
"is not maintained anymore by the OS vendor.");
}
return AndroidDriver;
}
if (caps.platformName.toLowerCase() === 'ios') {
if (caps.platformVersion) {
let majorVer = caps.platformVersion.toString().split(".")[0];
if (parseInt(majorVer, 10) >= 10) {
log.info("Requested iOS support with version >= 10, using XCUITest " +
"driver instead of UIAutomation-based driver, since the " +
"latter is unsupported on iOS 10 and up.");
return XCUITestDriver;
}
const platformVersion = semver.valid(semver.coerce(caps.platformVersion));
if (platformVersion && semver.satisfies(platformVersion, '>=10.0.0')) {
log.info("Requested iOS support with version >= 10, " +
`using '${this.validAutomations.XCUITEST.toLowerCase()}' ` +
"driver instead of UIAutomation-based driver, since the " +
"latter is unsupported on iOS 10 and up.");
return XCUITestDriver;
}
return IosDriver;
@@ -180,73 +187,98 @@ class AppiumDriver extends BaseDriver {
*/
async createSession (jsonwpCaps, reqCaps, w3cCapabilities) {
let {defaultCapabilities} = this.args;
// Parse the caps into a format that the InnerDriver will accept
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(
jsonwpCaps,
w3cCapabilities,
this.desiredCapConstraints, defaultCapabilities
);
let InnerDriver = this.getDriverForCaps(desiredCaps);
this.printNewSessionAnnouncement(InnerDriver, desiredCaps);
if (this.args.sessionOverride) {
const sessionIdsToDelete = await sessionsListGuard.acquire(AppiumDriver.name, () => _.keys(this.sessions));
if (sessionIdsToDelete.length) {
log.info(`Session override is on. Deleting other ${sessionIdsToDelete.length} active session${sessionIdsToDelete.length ? '' : 's'}.`);
try {
await B.map(sessionIdsToDelete, (id) => this.deleteSession(id));
} catch (ign) {}
}
}
let runningDriversData, otherPendingDriversData;
let d = new InnerDriver(this.args);
if (this.args.relaxedSecurityEnabled) {
log.info(`Applying relaxed security to ${InnerDriver.name} as per server command line argument`);
d.relaxedSecurityEnabled = true;
}
try {
runningDriversData = await this.curSessionDataForDriver(InnerDriver);
} catch (e) {
throw new errors.SessionNotCreatedError(e.message);
}
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
this.pendingDrivers[InnerDriver.name].push(d);
});
let protocol;
let innerSessionId, dCaps;
try {
[innerSessionId, dCaps] = await d.createSession(
processedJsonwpCaps,
reqCaps,
processedW3CCapabilities,
[...runningDriversData, ...otherPendingDriversData]
// Parse the caps into a format that the InnerDriver will accept
const parsedCaps = parseCapsForInnerDriver(
jsonwpCaps,
w3cCapabilities,
this.desiredCapConstraints,
defaultCapabilities
);
await sessionsListGuard.acquire(AppiumDriver.name, () => {
this.sessions[innerSessionId] = d;
});
} finally {
let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, error} = parsedCaps;
protocol = parsedCaps.protocol;
// If the parsing of the caps produced an error, throw it in here
if (error) {
throw error;
}
let InnerDriver = this.getDriverForCaps(desiredCaps);
this.printNewSessionAnnouncement(InnerDriver, desiredCaps);
if (this.args.sessionOverride) {
const sessionIdsToDelete = await sessionsListGuard.acquire(AppiumDriver.name, () => _.keys(this.sessions));
if (sessionIdsToDelete.length) {
log.info(`Session override is on. Deleting other ${sessionIdsToDelete.length} active session${sessionIdsToDelete.length ? '' : 's'}.`);
try {
await B.map(sessionIdsToDelete, (id) => this.deleteSession(id));
} catch (ign) {}
}
}
let runningDriversData, otherPendingDriversData;
let d = new InnerDriver(this.args);
if (this.args.relaxedSecurityEnabled) {
log.info(`Applying relaxed security to ${InnerDriver.name} as per server command line argument`);
d.relaxedSecurityEnabled = true;
}
// This assignment is required for correct web sockets functionality inside the driver
d.server = this.server;
try {
runningDriversData = await this.curSessionDataForDriver(InnerDriver);
} catch (e) {
throw new errors.SessionNotCreatedError(e.message);
}
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
_.pull(this.pendingDrivers[InnerDriver.name], d);
this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
this.pendingDrivers[InnerDriver.name].push(d);
});
try {
[innerSessionId, dCaps] = await d.createSession(
processedJsonwpCapabilities,
reqCaps,
processedW3CCapabilities,
[...runningDriversData, ...otherPendingDriversData]
);
protocol = d.protocol;
await sessionsListGuard.acquire(AppiumDriver.name, () => {
this.sessions[innerSessionId] = d;
});
} finally {
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
_.pull(this.pendingDrivers[InnerDriver.name], d);
});
}
// this is an async function but we don't await it because it handles
// an out-of-band promise which is fulfilled if the inner driver
// unexpectedly shuts down
this.attachUnexpectedShutdownHandler(d, innerSessionId);
log.info(`New ${InnerDriver.name} session created successfully, session ` +
`${innerSessionId} added to master session list`);
// set the New Command Timeout for the inner driver
d.startNewCommandTimeout();
} catch (error) {
return {
protocol,
error,
};
}
// this is an async function but we don't await it because it handles
// an out-of-band promise which is fulfilled if the inner driver
// unexpectedly shuts down
this.attachUnexpectedShutdownHandler(d, innerSessionId);
log.info(`New ${InnerDriver.name} session created successfully, session ` +
`${innerSessionId} added to master session list`);
// set the New Command Timeout for the inner driver
d.startNewCommandTimeout();
return [innerSessionId, dCaps];
return {
protocol,
value: [innerSessionId, dCaps, protocol]
};
}
async attachUnexpectedShutdownHandler (driver, innerSessionId) {
@@ -287,6 +319,7 @@ class AppiumDriver extends BaseDriver {
}
async deleteSession (sessionId) {
let protocol;
try {
let otherSessionsData = null;
let dstSession = null;
@@ -299,16 +332,23 @@ class AppiumDriver extends BaseDriver {
.filter(([key, value]) => value.constructor.name === curConstructorName && key !== sessionId)
.map(([, value]) => value.driverData);
dstSession = this.sessions[sessionId];
protocol = dstSession.protocol;
log.info(`Removing session ${sessionId} from our master session list`);
// regardless of whether the deleteSession completes successfully or not
// make the session unavailable, because who knows what state it might
// be in otherwise
delete this.sessions[sessionId];
});
await dstSession.deleteSession(sessionId, otherSessionsData);
return {
protocol,
value: await dstSession.deleteSession(sessionId, otherSessionsData),
};
} catch (e) {
log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
throw e;
return {
protocol,
error: e,
};
}
}
@@ -318,6 +358,7 @@ class AppiumDriver extends BaseDriver {
if (cmd === 'getStatus') {
return await this.getStatus();
}
if (isAppiumDriverCommand(cmd)) {
return await super.executeCommand(cmd, ...args);
}
@@ -327,7 +368,17 @@ class AppiumDriver extends BaseDriver {
if (!dstSession) {
throw new Error(`The session with id '${sessionId}' does not exist`);
}
return await dstSession.executeCommand(cmd, ...args);
let res = {
protocol: dstSession.protocol
};
try {
res.value = await dstSession.executeCommand(cmd, ...args);
} catch (e) {
res.error = e;
}
return res;
}
proxyActive (sessionId) {
@@ -352,10 +403,4 @@ function isAppiumDriverCommand (cmd) {
return !isSessionCommand(cmd) || cmd === "deleteSession";
}
function getAppiumRouter (args) {
let appium = new AppiumDriver(args);
return routeConfiguringFunction(appium);
}
export { AppiumDriver, getAppiumRouter };
export default getAppiumRouter;
export { AppiumDriver };
+4 -4
View File
@@ -38,16 +38,16 @@ async function getAppiumConfig () {
function checkNodeOk () {
let [major, minor] = getNodeVersion();
if (major < 5) {
let msg = `Node version must be >= 5. Currently ${major}.${minor}`;
if (major < 6) {
let msg = `Node version must be >= 6. Currently ${major}.${minor}`;
logger.errorAndThrow(msg);
}
}
function warnNodeDeprecations () {
let [major] = getNodeVersion();
if (major < 4) {
logger.warn("Appium support for versions of node < 4 has been " +
if (major < 8) {
logger.warn("Appium support for versions of node < 8 has been " +
"deprecated and will be removed in a future version. Please " +
"upgrade!");
}
+7 -3
View File
@@ -2,6 +2,10 @@ import request from 'request-promise';
import { fs } from 'appium-support';
import logger from './logger';
const hubUri = (config) => {
const protocol = config.hubProtocol || 'http';
return `${protocol}://${config.hubHost}:${config.hubPort}`;
};
async function registerNode (configFile, addr, port) {
let data;
@@ -26,7 +30,7 @@ async function registerToGrid (options_post, jsonObject) {
if (response === undefined || response.statusCode !== 200) {
throw new Error('Request failed');
}
let logMessage = `Appium successfully registered with the grid on ${jsonObject.configuration.hubHost}:${jsonObject.configuration.hubPort}`;
let logMessage = `Appium successfully registered with the grid on ${hubUri(jsonObject.configuration)}`;
logger.debug(logMessage);
} catch (err) {
logger.error(`Request to register with grid was unsuccessful: ${err.message}`);
@@ -79,7 +83,7 @@ async function postRequest (data, addr, port) {
};
// the post options
let post_options = {
url: `http://${jsonObject.configuration.hubHost}:${jsonObject.configuration.hubPort}/grid/register`,
url: `${hubUri(jsonObject.configuration)}/grid/register`,
method: 'POST',
body: data,
headers: post_headers,
@@ -116,7 +120,7 @@ async function isAlreadyRegistered (jsonObject) {
let id = jsonObject.configuration.id;
try {
let response = await request({
uri: `http://${jsonObject.configuration.hubHost}:${jsonObject.configuration.hubPort}/grid/api/proxy?id=${id}`,
uri: `${hubUri(jsonObject.configuration)}/grid/api/proxy?id=${id}`,
method : 'GET',
timeout : 10000,
resolveWithFullResponse: true // return the full response, not just the body
+5 -3
View File
@@ -4,13 +4,13 @@
import { init as logsinkInit } from './logsink';
import logger from './logger'; // logger needs to remain first of imports
import _ from 'lodash';
import { server as baseServer } from 'appium-base-driver';
import { server as baseServer, routeConfiguringFunction } from 'appium-base-driver';
import { asyncify } from 'asyncbox';
import { default as getParser, getDefaultArgs } from './parser';
import { showConfig, checkNodeOk, validateServerArgs,
warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
getDeprecatedArgs, getGitRev, APPIUM_VER } from './config';
import getAppiumRouter from './appium';
import { AppiumDriver } from './appium';
import registerNode from './grid-register';
import { inspectObject } from './utils';
@@ -120,8 +120,10 @@ async function main (args = null) {
await logsinkInit(args);
await preflightChecks(parser, args, throwInsteadOfExit);
await logStartupInfo(parser, args);
let router = getAppiumRouter(args);
let appiumDriver = new AppiumDriver(args);
let router = routeConfiguringFunction(appiumDriver);
let server = await baseServer(router, args.port, args.address);
appiumDriver.server = server;
try {
// TODO prelaunch if args.launch is set
// TODO: startAlertSocket(server, appiumServer);
+36 -33
View File
@@ -1,6 +1,6 @@
import _ from 'lodash';
import logger from './logger';
import { processCapabilities } from 'appium-base-driver';
import { processCapabilities, BaseDriver } from 'appium-base-driver';
function inspectObject (args) {
@@ -44,6 +44,10 @@ function parseCapsForInnerDriver (jsonwpCaps, w3cCapabilities, constraints={}, d
const hasW3CCaps = _.isPlainObject(w3cCapabilities);
const hasJSONWPCaps = _.isPlainObject(jsonwpCaps);
let protocol;
const {W3C, MJSONWP} = BaseDriver.DRIVER_PROTOCOL;
// Make copies of the capabilities that include the default capabilities
if (hasW3CCaps) {
w3cCapabilities = {
@@ -64,55 +68,54 @@ function parseCapsForInnerDriver (jsonwpCaps, w3cCapabilities, constraints={}, d
// Get MJSONWP caps
let desiredCaps = {};
let processedJsonwpCapabilities = null;
if (hasJSONWPCaps) {
protocol = MJSONWP;
desiredCaps = jsonwpCaps;
processedJsonwpCapabilities = {...desiredCaps};
}
// Get W3C caps
let processedW3CCapabilities = null;
let error;
if (hasW3CCaps) {
protocol = W3C;
// Call the process capabilities algorithm to find matching caps on the W3C
// (see: https://github.com/jlipps/simple-wd-spec#processing-capabilities)
let matchingW3CCaps = processCapabilities(w3cCapabilities, constraints, true);
if (hasJSONWPCaps) {
let jsonwpKeys = _.keys(desiredCaps);
let matchingW3CCapsKeys = _.keys(matchingW3CCaps);
// If JSONWP has keys that W3C doesn't have, log a warning message
let differingKeys = _.differenceWith(jsonwpKeys, matchingW3CCapsKeys);
if (!_.isEmpty(differingKeys)) {
logger.warn(`The following capabilities were provided in the JSONWP desired capabilities but
not the W3C capabilities: ${JSON.stringify(differingKeys)}`);
let matchingW3CCaps;
try {
matchingW3CCaps = processCapabilities(w3cCapabilities, constraints, true);
} catch (err) {
if (jsonwpCaps) {
logger.warn(`Could not parse W3C capabilities: ${err.message}. Falling back to JSONWP protocol.`);
protocol = MJSONWP;
} else {
error = err;
}
}
// If JSONWP caps and W3C caps were provided and there are discrepancies between them
// we'll be forgiving for now and just merge the two. But in the future, the clients
// should not be sending discrepant caps and Appium should ignore the extraneous MJSONWP caps
desiredCaps = {
...desiredCaps, // TODO: This line should be removed once we decide to not allow discrepent caps
...matchingW3CCaps,
};
}
desiredCaps = matchingW3CCaps;
let processedJsonwpCaps = null;
let processedW3CCapabilities = null;
// Create new JSONWP desired capabilities and W3C 'capabilities' objects that have the reconciled attributes
if (hasJSONWPCaps) {
processedJsonwpCaps = {...desiredCaps};
}
// Translate the matched W3C caps back into W3C capabilities object so that it
// can be proxied to the inner driver
if (hasW3CCaps) {
// Create a new w3c capabilities payload that contains only the matching caps in `alwaysMatch`
processedW3CCapabilities = {
alwaysMatch: {...insertAppiumPrefixes(desiredCaps)}, // Insert 'appium:' prefix to non-prefixed caps so that the Inner Driver doesn't complain about bad W3C prefixes
alwaysMatch: {...insertAppiumPrefixes(desiredCaps)},
firstMatch: [{}],
};
// If we found extraneuous keys in JSONWP caps, fall back to JSONWP
if (hasJSONWPCaps) {
let differingKeys = _.difference(_.keys(jsonwpCaps), _.keys(matchingW3CCaps));
if (!_.isEmpty(differingKeys)) {
logger.warn(`The following capabilities were provided in the JSONWP desired capabilities that are missing ` +
`in W3C capabilities: ${JSON.stringify(differingKeys)}. Falling back to JSONWP protocol.`);
protocol = MJSONWP;
desiredCaps = jsonwpCaps;
return {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities: null, protocol};
}
}
}
return {desiredCaps, processedJsonwpCaps, processedW3CCapabilities};
return {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol, error};
}
+2814
View File
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -11,7 +11,7 @@
"firefoxos",
"testing"
],
"version": "1.8.0-beta",
"version": "1.8.1",
"author": "https://github.com/appium",
"license": "Apache-2.0",
"repository": {
@@ -22,7 +22,7 @@
"url": "https://github.com/appium/appium/issues"
},
"engines": {
"node": ">=5",
"node": ">=6",
"npm": ">=3"
},
"main": "./build/lib/main.js",
@@ -34,30 +34,30 @@
"doc": "./docs"
},
"dependencies": {
"appium-android-driver": "1.x",
"appium-base-driver": "^2.20.0",
"appium-android-driver": "2.x",
"appium-base-driver": "^2.20.2",
"appium-espresso-driver": "^1.0.0-beta.3",
"appium-fake-driver": "^0.3.0",
"appium-ios-driver": "1.x",
"appium-fake-driver": "^0.x",
"appium-ios-driver": "2.x",
"appium-mac-driver": "1.x",
"appium-selendroid-driver": "1.x",
"appium-support": "2.x",
"appium-uiautomator2-driver": "1.x",
"appium-windows-driver": "1.x",
"appium-xcuitest-driver": "2.x",
"appium-youiengine-driver": "1.x",
"argparse": "1.x",
"appium-youiengine-driver": "^1.0.17",
"argparse": "^1.0.10",
"async-lock": "^1.0.0",
"asyncbox": "2.x",
"babel-runtime": "=5.8.24",
"bluebird": "2.x",
"bluebird": "3.x",
"continuation-local-storage": "3.x",
"dateformat": "^2.0.0",
"fkill": "^5.2.0",
"dateformat": "^3.0.3",
"lodash": "4.x",
"npmlog": "2.x",
"npmlog": "4.x",
"request": "^2.81.0",
"request-promise": "1.x",
"request-promise": "4.x",
"semver": "^5.5.0",
"source-map-support": "0.x",
"teen_process": "1.x",
"winston": "2.x"
@@ -73,7 +73,8 @@
"precommit-test": "REPORTER=dot gulp once",
"lint": "gulp eslint",
"coverage": "gulp coveralls",
"generate-docs": "babel-node ./commands-yml/parse.js"
"generate-docs": "babel-node ./commands-yml/parse.js",
"clean": "rm -rf node_modules && rm -rf package-lock.json && npm install"
},
"pre-commit": [
"precommit-msg",
@@ -84,21 +85,20 @@
"babel-cli": "^6.26.0",
"babel-eslint": "7.x",
"babel-preset-env": "^1.6.0",
"chai": "3.x",
"chai-as-promised": "5.x",
"chai": "4.x",
"chai-as-promised": "7.x",
"eslint": "3.x",
"eslint-config-appium": "2.x",
"eslint-plugin-babel": "3.x",
"eslint-plugin-import": "2.x",
"eslint-plugin-mocha": "4.x",
"eslint-plugin-promise": "3.x",
"gulp": "3.x",
"handlebars": "^4.0.10",
"mocha": "2.x",
"mocha": "5.x",
"pre-commit": "1.x",
"replace-ext": "^1.0.0",
"sinon": "1.x",
"validate.js": "^0.11.1",
"sinon": "5.x",
"validate.js": "^0.12.0",
"wd": "1.x",
"yaml-js": "^0.2.0"
},
+82 -90
View File
@@ -15,9 +15,9 @@ let should = chai.should();
chai.use(chaiAsPromised);
describe('Config', () => {
describe('getGitRev', () => {
it('should get a reasonable git revision', async () => {
describe('Config', function () {
describe('getGitRev', function () {
it('should get a reasonable git revision', async function () {
let rev = await getGitRev();
rev.should.be.a('string');
rev.length.should.be.equal(40);
@@ -25,9 +25,9 @@ describe('Config', () => {
});
});
describe('Appium config', () => {
describe('getAppiumConfig', () => {
it('should get a configuration object', async () => {
describe('Appium config', function () {
describe('getAppiumConfig', function () {
it('should get a configuration object', async function () {
let config = await getAppiumConfig();
config.should.be.an('object');
should.exist(config['git-sha']);
@@ -35,11 +35,11 @@ describe('Config', () => {
should.exist(config.version);
});
});
describe('showConfig', () => {
before(() => {
describe('showConfig', function () {
before(function () {
sinon.spy(console, "log");
});
it('should log the config to console', async () => {
it('should log the config to console', async function () {
let config = await getAppiumConfig();
await showConfig();
console.log.calledOnce.should.be.true; // eslint-disable-line no-console
@@ -48,9 +48,9 @@ describe('Config', () => {
});
});
describe('node.js config', () => {
describe('node.js config', function () {
let _process = process;
before(() => {
before(function () {
// need to be able to write to process.version
// but also to have access to process methods
// so copy them over to a writable object
@@ -60,11 +60,11 @@ describe('Config', () => {
}
process = tempProcess;
});
after(() => {
after(function () {
process = _process;
});
describe('checkNodeOk', () => {
it('should fail if node is below 4', () => {
describe('checkNodeOk', function () {
it('should fail if node is below 6', function () {
process.version = 'v4.4.7';
checkNodeOk.should.throw();
process.version = 'v0.9.12';
@@ -75,77 +75,69 @@ describe('Config', () => {
checkNodeOk.should.throw();
process.version = 'v0.12.14';
checkNodeOk.should.throw();
});
it('should succeed if node is 5+', () => {
process.version = 'v5.7.0';
checkNodeOk.should.not.throw();
checkNodeOk.should.throw();
});
it('should succeed if node is 6+', () => {
it('should succeed if node is 6+', function () {
process.version = 'v6.3.1';
checkNodeOk.should.not.throw();
});
it('should succeed if node is 7+', () => {
it('should succeed if node is 7+', function () {
process.version = 'v7.1.1';
checkNodeOk.should.not.throw();
});
it('should succeed if node is 8+', () => {
it('should succeed if node is 8+', function () {
process.version = 'v8.1.2';
checkNodeOk.should.not.throw();
});
it('should succeed if node is 9+', function () {
process.version = 'v9.1.2';
checkNodeOk.should.not.throw();
});
});
describe('warnNodeDeprecations', () => {
describe('warnNodeDeprecations', function () {
let spy;
before(() => {
before(function () {
spy = sinon.spy(logger, "warn");
});
beforeEach(() => {
spy.reset();
beforeEach(function () {
spy.resetHistory();
});
it('should log a warning if node is below 4', () => {
process.version = 'v0.9.12';
it('should log a warning if node is below 8', function () {
process.version = 'v7.10.1';
warnNodeDeprecations();
logger.warn.callCount.should.equal(1);
});
it('should log a warning if node is 0.12', () => {
process.version = 'v0.12.0';
warnNodeDeprecations();
logger.warn.callCount.should.equal(1);
});
it('should not log a warning if node is 4+', () => {
process.version = 'v4.4.7';
it('should not log a warning if node is 8+', function () {
process.version = 'v8.0.0';
warnNodeDeprecations();
logger.warn.callCount.should.equal(0);
});
it('should not log a warning if node is 5+', () => {
process.version = 'v5.7.0';
warnNodeDeprecations();
logger.warn.callCount.should.equal(0);
});
it('should not log a warning if node is 6+', () => {
process.version = 'v6.3.1';
it('should not log a warning if node is 9+', function () {
process.version = 'v9.0.0';
warnNodeDeprecations();
logger.warn.callCount.should.equal(0);
});
});
});
describe('server arguments', () => {
describe('server arguments', function () {
let parser = getParser();
parser.debug = true; // throw instead of exit on error; pass as option instead?
let args = {};
beforeEach(() => {
beforeEach(function () {
// give all the defaults
for (let rawArg of parser.rawArgs) {
args[rawArg[1].dest] = rawArg[1].defaultValue;
}
});
describe('getNonDefaultArgs', () => {
it('should show none if we have all the defaults', () => {
describe('getNonDefaultArgs', function () {
it('should show none if we have all the defaults', function () {
let nonDefaultArgs = getNonDefaultArgs(parser, args);
_.keys(nonDefaultArgs).length.should.equal(0);
});
it('should catch a non-default argument', () => {
it('should catch a non-default argument', function () {
args.isolateSimDevice = true;
let nonDefaultArgs = getNonDefaultArgs(parser, args);
_.keys(nonDefaultArgs).length.should.equal(1);
@@ -153,18 +145,18 @@ describe('Config', () => {
});
});
describe('getDeprecatedArgs', () => {
it('should show none if we have no deprecated arguments', () => {
describe('getDeprecatedArgs', function () {
it('should show none if we have no deprecated arguments', function () {
let deprecatedArgs = getDeprecatedArgs(parser, args);
_.keys(deprecatedArgs).length.should.equal(0);
});
it('should catch a deprecated argument', () => {
it('should catch a deprecated argument', function () {
args.showIOSLog = true;
let deprecatedArgs = getDeprecatedArgs(parser, args);
_.keys(deprecatedArgs).length.should.equal(1);
should.exist(deprecatedArgs['--show-ios-log']);
});
it('should catch a non-boolean deprecated argument', () => {
it('should catch a non-boolean deprecated argument', function () {
args.calendarFormat = 'orwellian';
let deprecatedArgs = getDeprecatedArgs(parser, args);
_.keys(deprecatedArgs).length.should.equal(1);
@@ -173,58 +165,58 @@ describe('Config', () => {
});
});
describe('checkValidPort', () => {
it('should be false for port too high', () => {
describe('checkValidPort', function () {
it('should be false for port too high', function () {
checkValidPort(65536).should.be.false;
});
it('should be false for port too low', () => {
it('should be false for port too low', function () {
checkValidPort(0).should.be.false;
});
it('should be true for port 1', () => {
it('should be true for port 1', function () {
checkValidPort(1).should.be.true;
});
it('should be true for port 65535', () => {
it('should be true for port 65535', function () {
checkValidPort(65535).should.be.true;
});
});
describe('validateTmpDir', () => {
it('should fail to use a tmp dir with incorrect permissions', async () => {
describe('validateTmpDir', function () {
it('should fail to use a tmp dir with incorrect permissions', async function () {
validateTmpDir('/private/if_you_run_with_sudo_this_wont_fail').should.be.rejectedWith(/could not ensure/);
});
it('should fail to use an undefined tmp dir', async () => {
it('should fail to use an undefined tmp dir', async function () {
validateTmpDir().should.be.rejectedWith(/could not ensure/);
});
it('should be able to use a tmp dir with correct permissions', async () => {
it('should be able to use a tmp dir with correct permissions', async function () {
validateTmpDir('/tmp/test_tmp_dir/with/any/number/of/levels').should.not.be.rejected;
});
});
describe('parsing args with empty argv[1]', () => {
describe('parsing args with empty argv[1]', function () {
let argv1;
before(() => {
before(function () {
argv1 = process.argv[1];
});
after(() => {
after(function () {
process.argv[1] = argv1;
});
it('should not fail if process.argv[1] is undefined', () => {
it('should not fail if process.argv[1] is undefined', function () {
process.argv[1] = undefined;
let args = getParser();
args.prog.should.be.equal('Appium');
});
it('should set "prog" to process.argv[1]', () => {
it('should set "prog" to process.argv[1]', function () {
process.argv[1] = 'Hello World';
let args = getParser();
args.prog.should.be.equal('Hello World');
});
});
describe('validateServerArgs', () => {
describe('validateServerArgs', function () {
let parser = getParser();
parser.debug = true; // throw instead of exit on error; pass as option instead?
const defaultArgs = {};
@@ -233,98 +225,98 @@ describe('Config', () => {
defaultArgs[rawArg[1].dest] = rawArg[1].defaultValue;
}
let args = {};
beforeEach(() => {
beforeEach(function () {
args = _.clone(defaultArgs);
});
describe('mutually exclusive server arguments', () => {
describe('noReset and fullReset', () => {
it('should not allow both', () => {
describe('mutually exclusive server arguments', function () {
describe('noReset and fullReset', function () {
it('should not allow both', function () {
(() => {
args.noReset = args.fullReset = true;
validateServerArgs(parser, args);
}).should.throw();
});
it('should allow noReset', () => {
it('should allow noReset', function () {
(() => {
args.noReset = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
it('should allow fullReset', () => {
it('should allow fullReset', function () {
(() => {
args.fullReset = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
});
describe('ipa and safari', () => {
it('should not allow both', () => {
describe('ipa and safari', function () {
it('should not allow both', function () {
(() => {
args.ipa = args.safari = true;
validateServerArgs(parser, args);
}).should.throw();
});
it('should allow ipa', () => {
it('should allow ipa', function () {
(() => {
args.ipa = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
it('should allow safari', () => {
it('should allow safari', function () {
(() => {
args.safari = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
});
describe('app and safari', () => {
it('should not allow both', () => {
describe('app and safari', function () {
it('should not allow both', function () {
(() => {
args.app = args.safari = true;
validateServerArgs(parser, args);
}).should.throw();
});
it('should allow app', () => {
it('should allow app', function () {
(() => {
args.app = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
});
describe('forceIphone and forceIpad', () => {
it('should not allow both', () => {
describe('forceIphone and forceIpad', function () {
it('should not allow both', function () {
(() => {
args.forceIphone = args.forceIpad = true;
validateServerArgs(parser, args);
}).should.throw();
});
it('should allow forceIphone', () => {
it('should allow forceIphone', function () {
(() => {
args.forceIphone = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
it('should allow forceIpad', () => {
it('should allow forceIpad', function () {
(() => {
args.forceIpad = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
});
describe('deviceName and defaultDevice', () => {
it('should not allow both', () => {
describe('deviceName and defaultDevice', function () {
it('should not allow both', function () {
(() => {
args.deviceName = args.defaultDevice = true;
validateServerArgs(parser, args);
}).should.throw();
});
it('should allow deviceName', () => {
it('should allow deviceName', function () {
(() => {
args.deviceName = true;
validateServerArgs(parser, args);
}).should.not.throw();
});
it('should allow defaultDevice', () => {
it('should allow defaultDevice', function () {
(() => {
args.defaultDevice = true;
validateServerArgs(parser, args);
@@ -332,19 +324,19 @@ describe('Config', () => {
});
});
});
describe('validated arguments', () => {
describe('validated arguments', function () {
// checking ports is already done.
// the only argument left is `backendRetries`
describe('backendRetries', () => {
it('should fail with value less than 0', () => {
describe('backendRetries', function () {
it('should fail with value less than 0', function () {
args.backendRetries = -1;
(() => {validateServerArgs(parser, args);}).should.throw();
});
it('should succeed with value of 0', () => {
it('should succeed with value of 0', function () {
args.backendRetries = 0;
(() => {validateServerArgs(parser, args);}).should.not.throw();
});
it('should succeed with value above 0', () => {
it('should succeed with value above 0', function () {
args.backendRetries = 100;
(() => {validateServerArgs(parser, args);}).should.not.throw();
});
+157 -27
View File
@@ -8,6 +8,9 @@ import wd from 'wd';
import request from 'request-promise';
import { main as appiumServer } from '../lib/main';
import { TEST_FAKE_APP, TEST_HOST, TEST_PORT } from './helpers';
import { BaseDriver } from 'appium-base-driver';
import { FakeDriver } from 'appium-fake-driver';
import sinon from 'sinon';
chai.use(chaiAsPromised);
@@ -15,23 +18,23 @@ const should = chai.should();
const shouldStartServer = process.env.USE_RUNNING_SERVER !== "0";
const caps = {platformName: "Fake", deviceName: "Fake", app: TEST_FAKE_APP};
describe('FakeDriver - via HTTP', () => {
describe('FakeDriver - via HTTP', function () {
let server = null;
const baseUrl = `http://${TEST_HOST}:${TEST_PORT}/wd/hub/session`;
before(async () => {
before(async function () {
if (shouldStartServer) {
let args = {port: TEST_PORT, host: TEST_HOST};
server = await appiumServer(args);
}
});
after(async () => {
after(async function () {
if (server) {
await server.close();
}
});
describe('session handling', () => {
it('should start and stop a session', async () => {
describe('session handling', function () {
it('should start and stop a session', async function () {
let driver = wd.promiseChainRemote(TEST_HOST, TEST_PORT);
let [sessionId] = await driver.init(caps);
should.exist(sessionId);
@@ -40,7 +43,7 @@ describe('FakeDriver - via HTTP', () => {
await driver.title().should.eventually.be.rejectedWith(/terminated/);
});
it('should be able to run two FakeDriver sessions simultaneously', async () => {
it('should be able to run two FakeDriver sessions simultaneously', async function () {
let driver1 = wd.promiseChainRemote(TEST_HOST, TEST_PORT);
let [sessionId1] = await driver1.init(caps);
should.exist(sessionId1);
@@ -54,7 +57,7 @@ describe('FakeDriver - via HTTP', () => {
await driver2.quit();
});
it('should not be able to run two FakeDriver sessions simultaneously when one is unique', async () => {
it('should not be able to run two FakeDriver sessions simultaneously when one is unique', async function () {
let uniqueCaps = _.clone(caps);
uniqueCaps.uniqueApp = true;
let driver1 = wd.promiseChainRemote(TEST_HOST, TEST_PORT);
@@ -66,7 +69,7 @@ describe('FakeDriver - via HTTP', () => {
await driver1.quit();
});
it('should use the newCommandTimeout of the inner Driver on session creation', async () => {
it('should use the newCommandTimeout of the inner Driver on session creation', async function () {
let driver = wd.promiseChainRemote(TEST_HOST, TEST_PORT);
caps.newCommandTimeout = 0.25;
@@ -78,7 +81,7 @@ describe('FakeDriver - via HTTP', () => {
await driver.source().should.eventually.be.rejectedWith(/terminated/);
});
it('should accept valid W3C capabilities and start a W3C session', async () => {
it('should accept valid W3C capabilities and start a W3C session', async function () {
// Try with valid capabilities and check that it returns a session ID
const w3cCaps = {
capabilities: {
@@ -105,15 +108,18 @@ describe('FakeDriver - via HTTP', () => {
screenshotValue.should.equal('hahahanotreallyascreenshot');
// Now use that sessionID to call an arbitrary W3C-only endpoint that isn't implemented to see if it responds with correct error
const {statusCode, message} = await request.post({url: `${baseUrl}/${value.sessionId}/execute/async`, json: {script: '', args: ['a']}}).should.eventually.be.rejected;
const {statusCode, error} = await request.post({url: `${baseUrl}/${value.sessionId}/execute/async`, json: {script: '', args: ['a']}}).should.eventually.be.rejected;
statusCode.should.equal(404);
const {error:errorMessage, message, stacktrace} = error.value;
errorMessage.should.match(/unknown method/);
message.should.match(/Method has not yet been implemented/);
stacktrace.should.match(/FakeDriver.executeCommand/);
// End session
await request.delete({url: `${baseUrl}/${value.sessionId}`}).should.eventually.be.resolved;
await request.delete({url: `${baseUrl}/${value.sessionId}`});
});
it('should reject invalid W3C capabilities and respond with a 400 Bad Parameters error', async () => {
it('should reject invalid W3C capabilities and respond with a 400 Bad Parameters error', async function () {
const badW3Ccaps = {
capabilities: {
alwaysMatch: {},
@@ -121,16 +127,15 @@ describe('FakeDriver - via HTTP', () => {
}
};
const {statusCode, message} = await request.post({url: baseUrl, json: badW3Ccaps}).should.eventually.be.rejected;
const {statusCode, error} = await request.post({url: baseUrl, json: badW3Ccaps}).should.eventually.be.rejected;
statusCode.should.equal(400);
message.should.match(/can't be blank/);
error.value.message.should.match(/can't be blank/);
});
it('should accept a combo of W3C and JSONWP capabilities but default to W3C', async () => {
it('should accept a combo of W3C and JSONWP capabilities but default to W3C', async function () {
const combinedCaps = {
"desiredCapabilities": {
...caps,
mjsonwpParam: 'mjsonwpParam',
},
"capabilities": {
"alwaysMatch": {...caps},
@@ -147,11 +152,42 @@ describe('FakeDriver - via HTTP', () => {
value.capabilities.should.deep.equal({
...caps,
w3cParam: 'w3cParam',
mjsonwpParam: 'mjsonwpParam', // Note: For now we're accepting a mix of the two to alleviate client issues. This ought to not be supported in the future though.
});
// End session
await request.delete({ url: `${baseUrl}/${value.sessionId}` });
});
it('should reject bad W3C capabilities with a BadParametersError (400)', async () => {
it('should accept a combo of W3C and JSONWP but use JSONWP if desiredCapabilities contains extraneous keys', async function () {
const combinedCaps = {
"desiredCapabilities": {
...caps,
automationName: 'Fake',
anotherParam: 'Hello',
},
"capabilities": {
"alwaysMatch": {...caps},
"firstMatch": [{
w3cParam: 'w3cParam',
}],
}
};
const {sessionId, status, value} = await request.post({url: baseUrl, json: combinedCaps});
status.should.exist;
sessionId.should.exist;
should.not.exist(value.sessionId);
value.should.deep.equal({
...caps,
automationName: 'Fake',
anotherParam: 'Hello',
});
// End session
await request.delete({ url: `${baseUrl}/${sessionId}` });
});
it('should reject bad W3C capabilities with a BadParametersError (400)', async function () {
const w3cCaps = {
"capabilities": {
"alwaysMatch": {
@@ -160,12 +196,14 @@ describe('FakeDriver - via HTTP', () => {
}
},
};
const {message, statusCode} = await request.post({url: baseUrl, json: w3cCaps}).should.eventually.be.rejected;
const {error, statusCode, response} = await request.post({url: baseUrl, json: w3cCaps}).should.eventually.be.rejected;
response.headers['content-type'].should.match(/application\/json/);
const {message} = error.value;
message.should.match(/BadAutomationName not part of/);
statusCode.should.equal(400);
});
it('should accept capabilities that are provided in the firstMatch array', async () => {
it('should accept capabilities that are provided in the firstMatch array', async function () {
const w3cCaps = {
"capabilities": {
"alwaysMatch": {},
@@ -178,30 +216,122 @@ describe('FakeDriver - via HTTP', () => {
should.not.exist(status);
should.not.exist(sessionId);
value.capabilities.should.deep.equal(caps);
// End session
await request.delete({ url: `${baseUrl}/${value.sessionId}` });
});
it('should fall back to MJSONWP if w3c caps are invalid', async function () {
const combinedCaps = {
"desiredCapabilities": {
...caps,
},
"capabilities": {
"alwaysMatch": {},
"firstMatch": [{}, {
...caps,
deviceName: null,
}],
},
};
const {value, sessionId, status} = await request.post({url: baseUrl, json: combinedCaps});
status.should.exist;
sessionId.should.exist;
value.should.deep.equal(caps);
// End session
await request.delete({ url: `${baseUrl}/${sessionId}` });
});
it('should fall back to MJSONWP if Inner Driver is not ready for W3C', async function () {
const combinedCaps = {
"desiredCapabilities": {
...caps,
},
"capabilities": {
"alwaysMatch": {
...caps,
deviceName: null,
},
},
};
const createSessionStub = sinon.stub(FakeDriver.prototype, 'createSession').callsFake(async function (jsonwpCaps) {
const res = await BaseDriver.prototype.createSession.call(this, jsonwpCaps);
this.protocol.should.equal('MJSONWP');
return res;
});
const {value, sessionId, status} = await request.post({url: baseUrl, json: combinedCaps});
status.should.exist;
sessionId.should.exist;
value.should.deep.equal(caps);
createSessionStub.restore();
});
it('should handle concurrent MJSONWP and W3C sessions', async function () {
const combinedCaps = {
"desiredCapabilities": {
...caps,
},
"capabilities": {
"alwaysMatch": {
...caps,
},
},
};
// Have an MJSONWP and W3C session running concurrently
const {sessionId:mjsonwpSessId, value:mjsonwpValue, status} = await request.post({url: baseUrl, json: _.omit(combinedCaps, 'capabilities')});
const {value} = await request.post({url: baseUrl, json: _.omit(combinedCaps, 'desiredCapabilities')});
const w3cSessId = value.sessionId;
status.should.exist;
mjsonwpValue.should.eql(caps);
mjsonwpSessId.should.exist;
value.sessionId.should.exist;
value.capabilities.should.eql(caps);
// Test that both return the proper payload based on their protocol
const mjsonwpPayload = await request(`${baseUrl}/${mjsonwpSessId}`, {json: true});
const w3cPayload = await request(`${baseUrl}/${w3cSessId}`, {json: true});
// Test that the payloads are MJSONWP and W3C
mjsonwpPayload.sessionId.should.exist;
mjsonwpPayload.status.should.exist;
mjsonwpPayload.value.should.eql(caps);
should.not.exist(w3cPayload.sessionId);
should.not.exist(w3cPayload.status);
w3cPayload.value.should.eql(caps);
// End session
await request.delete({url: `${baseUrl}/${mjsonwpSessId}`});
await request.delete({url: `${baseUrl}/${w3cSessId}`});
});
});
});
describe('Logsink', () => {
describe('Logsink', function () {
let server = null;
let logs = [];
let logHandler = (level, message) => {
let logHandler = function (level, message) {
logs.push([level, message]);
};
let args = {port: TEST_PORT, host: TEST_HOST, logHandler};
before(async () => {
before(async function () {
server = await appiumServer(args);
});
after(async () => {
after(async function () {
await server.close();
});
it('should send logs to a logHandler passed in by a parent package', async () => {
it('should send logs to a logHandler passed in by a parent package', async function () {
logs.length.should.be.above(1);
logs[0].length.should.equal(2);
logs[0][1].should.include("Welcome to Appium");
let welcomeIndex = logs[0][1].includes('versions of node') ? 1 : 0;
logs[welcomeIndex].length.should.equal(2);
logs[welcomeIndex][1].should.include("Welcome to Appium");
});
});
+66 -51
View File
@@ -1,6 +1,6 @@
// transpile:mocha
import { AppiumDriver, getAppiumRouter } from '../lib/appium';
import { AppiumDriver } from '../lib/appium';
import { FakeDriver } from 'appium-fake-driver';
import { BASE_CAPS, W3C_CAPS } from './helpers';
import _ from 'lodash';
@@ -17,15 +17,8 @@ chai.use(chaiAsPromised);
const SESSION_ID = 1;
describe('AppiumDriver', () => {
describe('getAppiumRouter', () => {
it('should return a route configuring function', async () => {
let routeConfiguringFunction = getAppiumRouter({});
routeConfiguringFunction.should.be.a.function;
});
});
describe('AppiumDriver', () => {
describe('AppiumDriver', function () {
describe('AppiumDriver', function () {
function getDriverAndFakeDriver () {
let appium = new AppiumDriver({});
let fakeDriver = new FakeDriver();
@@ -37,25 +30,25 @@ describe('AppiumDriver', () => {
};
return [appium, mockFakeDriver];
}
describe('createSession', () => {
describe('createSession', function () {
let appium;
let mockFakeDriver;
beforeEach(() => {
beforeEach(function () {
[appium, mockFakeDriver] = getDriverAndFakeDriver();
});
afterEach(async () => {
afterEach(async function () {
mockFakeDriver.restore();
await appium.deleteSession(SESSION_ID);
});
it('should call inner driver\'s createSession with desired capabilities', async () => {
it('should call inner driver\'s createSession with desired capabilities', async function () {
mockFakeDriver.expects("createSession")
.once().withExactArgs(BASE_CAPS, undefined, null, [])
.returns([SESSION_ID, BASE_CAPS]);
await appium.createSession(BASE_CAPS);
mockFakeDriver.verify();
});
it('should call inner driver\'s createSession with desired and default capabilities', async () => {
it('should call inner driver\'s createSession with desired and default capabilities', async function () {
let defaultCaps = {deviceName: 'Emulator'}
, allCaps = _.extend(_.clone(defaultCaps), BASE_CAPS);
appium.args.defaultCapabilities = defaultCaps;
@@ -65,7 +58,7 @@ describe('AppiumDriver', () => {
await appium.createSession(BASE_CAPS);
mockFakeDriver.verify();
});
it('should call inner driver\'s createSession with desired and default capabilities without overriding caps', async () => {
it('should call inner driver\'s createSession with desired and default capabilities without overriding caps', async function () {
// a default capability with the same key as a desired capability
// should do nothing
let defaultCaps = {platformName: 'Ersatz'};
@@ -76,7 +69,7 @@ describe('AppiumDriver', () => {
await appium.createSession(BASE_CAPS);
mockFakeDriver.verify();
});
it('should kill all other sessions if sessionOverride is on', async () => {
it('should kill all other sessions if sessionOverride is on', async function () {
appium.args.sessionOverride = true;
// mock three sessions that should be removed when the new one is created
@@ -139,26 +132,48 @@ describe('AppiumDriver', () => {
await appium.createSession(undefined, undefined, w3cCaps);
mockFakeDriver.verify();
});
it('should call "createSession" with JSONWP capabilities if W3C has incomplete capabilities', async function () {
let w3cCaps = {
...W3C_CAPS,
alwaysMatch: {
...W3C_CAPS.alwaysMatch,
'appium:someOtherParm': 'someOtherParm',
},
};
let jsonwpCaps = {
...BASE_CAPS,
automationName: 'Fake',
someOtherParam: 'someOtherParam',
};
mockFakeDriver.expects("createSession")
.once().withArgs(jsonwpCaps, undefined, null)
.returns([SESSION_ID, jsonwpCaps]);
await appium.createSession(jsonwpCaps, undefined, w3cCaps);
mockFakeDriver.verify();
});
});
describe('deleteSession', () => {
describe('deleteSession', function () {
let appium;
let mockFakeDriver;
beforeEach(() => {
beforeEach(function () {
[appium, mockFakeDriver] = getDriverAndFakeDriver();
});
afterEach(() => {
afterEach(function () {
mockFakeDriver.restore();
});
it('should remove the session if it is found', async () => {
let [sessionId] = await appium.createSession(BASE_CAPS);
it('should remove the session if it is found', async function () {
let [sessionId] = (await appium.createSession(BASE_CAPS)).value;
let sessions = await appium.getSessions();
sessions.should.have.length(1);
await appium.deleteSession(sessionId);
sessions = await appium.getSessions();
sessions.should.have.length(0);
});
it('should call inner driver\'s deleteSession method', async () => {
const [sessionId] = await appium.createSession(BASE_CAPS);
it('should call inner driver\'s deleteSession method', async function () {
const [sessionId] = (await appium.createSession(BASE_CAPS)).value;
mockFakeDriver.expects("deleteSession")
.once().withExactArgs(sessionId, [])
.returns();
@@ -169,28 +184,28 @@ describe('AppiumDriver', () => {
await mockFakeDriver.object.deleteSession();
});
});
describe('getSessions', () => {
describe('getSessions', function () {
let appium;
let sessions;
before(() => {
before(function () {
appium = new AppiumDriver({});
});
afterEach(async () => {
afterEach(async function () {
for (let session of sessions) {
await appium.deleteSession(session.id);
}
});
it('should return an empty array of sessions', async () => {
it('should return an empty array of sessions', async function () {
sessions = await appium.getSessions();
sessions.should.be.an.array;
sessions.should.be.an('array');
sessions.should.be.empty;
});
it('should return sessions created', async () => {
let session1 = await appium.createSession(_.extend(_.clone(BASE_CAPS), {cap: 'value'}));
let session2 = await appium.createSession(_.extend(_.clone(BASE_CAPS), {cap: 'other value'}));
it('should return sessions created', async function () {
let session1 = (await appium.createSession(_.extend(_.clone(BASE_CAPS), {cap: 'value'}))).value;
let session2 = (await appium.createSession(_.extend(_.clone(BASE_CAPS), {cap: 'other value'}))).value;
sessions = await appium.getSessions();
sessions.should.be.an.array;
sessions.should.be.an('array');
sessions.should.have.length(2);
sessions[0].id.should.equal(session1[0]);
sessions[0].capabilities.should.eql(session1[1]);
@@ -198,49 +213,49 @@ describe('AppiumDriver', () => {
sessions[1].capabilities.should.eql(session2[1]);
});
});
describe('getStatus', () => {
describe('getStatus', function () {
let appium;
before(() => {
before(function () {
appium = new AppiumDriver({});
});
it('should return a status', async () => {
it('should return a status', async function () {
let status = await appium.getStatus();
status.build.should.exist;
status.build.version.should.exist;
});
});
describe('sessionExists', () => {
describe('sessionExists', function () {
});
describe('attachUnexpectedShutdownHandler', () => {
describe('attachUnexpectedShutdownHandler', function () {
let appium
, mockFakeDriver;
beforeEach(() => {
beforeEach(function () {
[appium, mockFakeDriver] = getDriverAndFakeDriver();
});
afterEach(async () => {
afterEach(async function () {
await mockFakeDriver.object.deleteSession();
mockFakeDriver.restore();
appium.args.defaultCapabilities = {};
});
it('should remove session if inner driver unexpectedly exits with an error', async () => {
let [sessionId,] = await appium.createSession(_.clone(BASE_CAPS)); // eslint-disable-line comma-spacing
it('should remove session if inner driver unexpectedly exits with an error', async function () {
let [sessionId,] = (await appium.createSession(_.clone(BASE_CAPS))).value; // eslint-disable-line comma-spacing
_.keys(appium.sessions).should.contain(sessionId);
appium.sessions[sessionId].unexpectedShutdownDeferred.reject(new Error("Oops"));
// let event loop spin so rejection is handled
await sleep(1);
_.keys(appium.sessions).should.not.contain(sessionId);
});
it('should remove session if inner driver unexpectedly exits with no error', async () => {
let [sessionId,] = await appium.createSession(_.clone(BASE_CAPS)); // eslint-disable-line comma-spacing
it('should remove session if inner driver unexpectedly exits with no error', async function () {
let [sessionId,] = (await appium.createSession(_.clone(BASE_CAPS))).value; // eslint-disable-line comma-spacing
_.keys(appium.sessions).should.contain(sessionId);
appium.sessions[sessionId].unexpectedShutdownDeferred.resolve();
// let event loop spin so rejection is handled
await sleep(1);
_.keys(appium.sessions).should.not.contain(sessionId);
});
it('should not remove session if inner driver cancels unexpected exit', async () => {
let [sessionId,] = await appium.createSession(_.clone(BASE_CAPS)); // eslint-disable-line comma-spacing
it('should not remove session if inner driver cancels unexpected exit', async function () {
let [sessionId,] = (await appium.createSession(_.clone(BASE_CAPS))).value; // eslint-disable-line comma-spacing
_.keys(appium.sessions).should.contain(sessionId);
appium.sessions[sessionId].onUnexpectedShutdown.cancel();
// let event loop spin so rejection is handled
@@ -248,12 +263,12 @@ describe('AppiumDriver', () => {
_.keys(appium.sessions).should.contain(sessionId);
});
});
describe('getDriverForCaps', () => {
it('should not blow up if user does not provide platformName', () => {
describe('getDriverForCaps', function () {
it('should not blow up if user does not provide platformName', function () {
let appium = new AppiumDriver({});
(() => { appium.getDriverForCaps({}); }).should.throw(/platformName/);
});
it('should get XCUITestDriver driver for automationName of XCUITest', () => {
it('should get XCUITestDriver driver for automationName of XCUITest', function () {
let appium = new AppiumDriver({});
let driver = appium.getDriverForCaps({
platformName: 'iOS',
@@ -262,7 +277,7 @@ describe('AppiumDriver', () => {
driver.should.be.an.instanceof(Function);
driver.should.equal(XCUITestDriver);
});
it('should get iosdriver for ios < 10', () => {
it('should get iosdriver for ios < 10', function () {
let appium = new AppiumDriver({});
let caps = {
platformName: 'iOS',
@@ -292,7 +307,7 @@ describe('AppiumDriver', () => {
driver = appium.getDriverForCaps(caps);
driver.should.equal(IosDriver);
});
it('should get xcuitestdriver for ios >= 10', () => {
it('should get xcuitestdriver for ios >= 10', function () {
let appium = new AppiumDriver({});
let caps = {
platformName: 'iOS',
+2 -2
View File
@@ -12,12 +12,12 @@ const TEST_FAKE_APP = path.resolve(__dirname, "..", "..", "node_modules",
function initSession (caps) {
let resolve = () => {};
let driver;
before(async () => {
before(async function () {
driver = wd.promiseChainRemote({host: TEST_HOST, port: TEST_PORT});
resolve(driver);
await driver.init(caps);
});
after(async () => {
after(async function () {
await driver.quit();
});
return new B((_resolve) => {
+7 -7
View File
@@ -10,19 +10,19 @@ let forceLogs = process.env._FORCE_LOGS;
process.env._FORCE_LOGS = 1;
let log = logger.getLogger('Appium');
describe('logging', () => {
describe('logging', function () {
let stderrSpy;
let stdoutSpy;
beforeEach(() => {
beforeEach(function () {
stderrSpy = sinon.spy(process.stderr, 'write');
stdoutSpy = sinon.spy(process.stdout, 'write');
logsinkClear();
});
afterEach(() => {
afterEach(function () {
stderrSpy.restore();
stdoutSpy.restore();
});
after(() => {
after(function () {
process.env._FORCE_LOGS = forceLogs;
});
@@ -36,7 +36,7 @@ describe('logging', () => {
log.debug(debugMsg);
}
it('should send error, info and debug when loglevel is debug', async () => {
it('should send error, info and debug when loglevel is debug', async function () {
await logsinkInit({loglevel: 'debug'});
doLogging();
@@ -48,7 +48,7 @@ describe('logging', () => {
stdoutSpy.args[0][0].should.include(warnMsg);
stdoutSpy.args[1][0].should.include(debugMsg);
});
it('should send error and info when loglevel is info', async () => {
it('should send error and info when loglevel is info', async function () {
await logsinkInit({loglevel: 'info'});
doLogging();
@@ -59,7 +59,7 @@ describe('logging', () => {
stdoutSpy.callCount.should.equal(1);
stdoutSpy.args[0][0].should.include(warnMsg);
});
it('should send error when loglevel is error', async () => {
it('should send error when loglevel is error', async function () {
await logsinkInit({loglevel: 'error'});
doLogging();
+9 -9
View File
@@ -5,43 +5,43 @@ import chai from 'chai';
const should = chai.should();
describe('Parser', () => {
describe('Parser', function () {
let p = getParser();
p.debug = true; // throw instead of exit on error; pass as option instead?
it('should return an arg parser', () => {
it('should return an arg parser', function () {
should.exist(p.parseArgs);
p.parseArgs([]).should.have.property('port');
});
it('should keep the raw server flags array', () => {
it('should keep the raw server flags array', function () {
should.exist(p.rawArgs);
});
it('should have help for every arg', () => {
it('should have help for every arg', function () {
for (let arg of p.rawArgs) {
arg[1].should.have.property('help');
}
});
it('should throw an error with unknown argument', () => {
it('should throw an error with unknown argument', function () {
(() => {p.parseArgs(['--apple']);}).should.throw();
});
it('should parse default capabilities correctly from a string', () => {
it('should parse default capabilities correctly from a string', function () {
let defaultCapabilities = {a: 'b'};
let args = p.parseArgs(['--default-capabilities',
JSON.stringify(defaultCapabilities)]);
args.defaultCapabilities.should.eql(defaultCapabilities);
});
it('should parse default capabilities correctly from a file', () => {
it('should parse default capabilities correctly from a file', function () {
let defaultCapabilities = {a: 'b'};
let args = p.parseArgs(['--default-capabilities',
'test/fixtures/caps.json']);
args.defaultCapabilities.should.eql(defaultCapabilities);
});
it('should throw an error with invalid arg to default capabilities', () => {
it('should throw an error with invalid arg to default capabilities', function () {
(() => {p.parseArgs(['-dc', '42']);}).should.throw();
(() => {p.parseArgs(['-dc', 'false']);}).should.throw();
(() => {p.parseArgs(['-dc', 'null']);}).should.throw();
(() => {p.parseArgs(['-dc', 'does/not/exist.json']);}).should.throw();
});
it('should parse args that are caps into default capabilities', () => {
it('should parse args that are caps into default capabilities', function () {
let defaultCapabilities = {localizableStringsDir: '/my/dir'};
let args = p.parseArgs(['--localizable-strings-dir', '/my/dir']);
args.defaultCapabilities.should.eql(defaultCapabilities);
+3 -3
View File
@@ -8,12 +8,12 @@ import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
const expect = chai.expect;
describe.skip('shrinkwrap checks', () => {
it('shrinkwrap file should exist', async () => {
describe.skip('shrinkwrap checks', function () {
it('shrinkwrap file should exist', async function () {
require('../../npm-shrinkwrap.json');
});
it('shrinkwrap should not include fsevents', async () => {
it('shrinkwrap should not include fsevents', async function () {
// fsevents is an optional dep that only works on Mac.
// if it's in shrinkwrap, non-Mac hosts won't be able to install appium
let shrinkwrap = require('../../npm-shrinkwrap.json');
+50 -70
View File
@@ -2,97 +2,46 @@ import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { parseCapsForInnerDriver, insertAppiumPrefixes } from '../lib/utils';
import { BASE_CAPS, W3C_CAPS } from './helpers';
import _ from 'lodash';
const should = chai.should();
chai.use(chaiAsPromised);
describe('utils', function () {
describe('parseCapsForInnerDriver()', () => {
describe('parseCapsForInnerDriver()', function () {
it('should return JSONWP caps unchanged if only JSONWP caps provided', function () {
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(BASE_CAPS);
let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver(BASE_CAPS);
desiredCaps.should.deep.equal(BASE_CAPS);
processedJsonwpCaps.should.deep.equal(BASE_CAPS);
processedJsonwpCapabilities.should.deep.equal(BASE_CAPS);
should.not.exist(processedW3CCapabilities);
protocol.should.equal('MJSONWP');
});
it('should return W3C caps unchanged if only W3C caps were provided', function () {
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(undefined, W3C_CAPS);
let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver(undefined, W3C_CAPS);
desiredCaps.should.deep.equal(BASE_CAPS);
should.not.exist(processedJsonwpCaps);
should.not.exist(processedJsonwpCapabilities);
processedW3CCapabilities.should.deep.equal(W3C_CAPS);
protocol.should.equal('W3C');
});
it('should return JSONWP and W3C caps if both were provided', function () {
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS);
let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS);
desiredCaps.should.deep.equal(BASE_CAPS);
processedJsonwpCaps.should.deep.equal(BASE_CAPS);
processedJsonwpCapabilities.should.deep.equal(BASE_CAPS);
processedW3CCapabilities.should.deep.equal(W3C_CAPS);
});
it('should merge the capabilities together if some are different', function () {
let jsonwpCaps = {
...BASE_CAPS,
foo: 'bar',
};
let w3cCaps = {
...W3C_CAPS,
alwaysMatch: {
...W3C_CAPS.alwaysMatch,
'appium:hello': 'world',
}
};
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(jsonwpCaps, w3cCaps);
let expectedCaps = {
...jsonwpCaps,
'hello': 'world'
};
desiredCaps.should.deep.equal(expectedCaps);
processedJsonwpCaps.should.deep.equal(expectedCaps);
processedW3CCapabilities.should.deep.equal({
alwaysMatch: {...insertAppiumPrefixes(expectedCaps)},
firstMatch: [{}],
});
});
it('should merge the capabilities together and give preference to W3C if some are matching', function () {
// Add caps to JSONWP caps
let jsonwpCaps = {
foo: 'bar',
...BASE_CAPS,
};
// Add caps to W3C caps
let w3cCaps = {
alwaysMatch: {
hello: 'world',
foo: 'BAR',
...BASE_CAPS,
},
firstMatch: [{}],
};
// Expected result is that w3c caps override jsonwp caps
let expectedDesiredCaps = {
...jsonwpCaps,
...w3cCaps.alwaysMatch,
};
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(jsonwpCaps, w3cCaps);
desiredCaps.foo.should.equal('BAR');
desiredCaps.should.deep.equal(expectedDesiredCaps);
processedJsonwpCaps.should.deep.equal(expectedDesiredCaps);
processedW3CCapabilities.should.deep.equal({
alwaysMatch: insertAppiumPrefixes(expectedDesiredCaps),
firstMatch: [{}],
});
protocol.should.equal('W3C');
});
it('should include default capabilities in results', function () {
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS, {}, {foo: 'bar'});
let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} = parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS, {}, {foo: 'bar'});
desiredCaps.should.deep.equal({foo: 'bar', ...BASE_CAPS});
processedJsonwpCaps.should.deep.equal({foo: 'bar', ...BASE_CAPS});
processedJsonwpCapabilities.should.deep.equal({foo: 'bar', ...BASE_CAPS});
processedW3CCapabilities.alwaysMatch.should.deep.equal({'appium:foo': 'bar', ...insertAppiumPrefixes(BASE_CAPS)});
});
it('should reject if W3C caps are not passing constraints', function () {
(() => parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS, {hello: {presence: true}})).should.throw(/'hello' can't be blank/);
const err = parseCapsForInnerDriver(undefined, W3C_CAPS, {hello: {presence: true}}).error;
err.message.should.match(/'hello' can't be blank/);
_.isError(err).should.be.true;
});
it('should only accept W3C caps that have passing constraints', function () {
let w3cCaps = {
@@ -102,11 +51,12 @@ describe('utils', function () {
{hello: 'world'},
],
};
let {desiredCaps, processedJsonwpCaps, processedW3CCapabilities} = parseCapsForInnerDriver(BASE_CAPS, w3cCaps, {hello: {presence: true}});
let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver(BASE_CAPS, w3cCaps, {hello: {presence: true}});
const expectedResult = {hello: 'world', ...BASE_CAPS};
desiredCaps.should.deep.equal(expectedResult);
processedJsonwpCaps.should.deep.equal(expectedResult);
processedJsonwpCapabilities.should.deep.equal({...BASE_CAPS});
processedW3CCapabilities.alwaysMatch.should.deep.equal(insertAppiumPrefixes(expectedResult));
protocol.should.equal('W3C');
});
it('should add appium prefixes to W3C caps that are not standard in W3C', function () {
parseCapsForInnerDriver(undefined, {
@@ -119,6 +69,36 @@ describe('utils', function () {
firstMatch: [{}],
});
});
it('should fall back to MJSONWP caps if MJSONWP contains extraneous caps that aren not in W3C', function () {
let jsonwpCaps = {
...BASE_CAPS,
automationName: 'Fake',
};
const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver(jsonwpCaps, {
alwaysMatch: {platformName: 'Fake', propertyName: 'PROP_NAME'},
});
should.not.exist(processedW3CCapabilities);
desiredCaps.should.eql(jsonwpCaps);
processedJsonwpCapabilities.should.eql(jsonwpCaps);
protocol.should.equal('MJSONWP');
});
it('should fall back to MJSONWP caps if W3C capabilities are invalid', function () {
let w3cCapabilities = {
alwaysMatch: {platformName: 'Fake', propertyName: 'PROP_NAME'},
};
let constraints = {
deviceName: {
presence: true,
}
};
const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver({...BASE_CAPS}, w3cCapabilities, constraints);
should.not.exist(processedW3CCapabilities);
desiredCaps.should.eql(BASE_CAPS);
processedJsonwpCapabilities.should.eql(BASE_CAPS);
protocol.should.equal('MJSONWP');
});
});
describe('insertAppiumPrefixes()', function () {