mirror of
https://github.com/appium/appium.git
synced 2026-11-11 04:23:55 +08:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e31d97af76 | ||
|
|
0e2334299e | ||
|
|
d67590c722 | ||
|
|
b590d9e9e8 | ||
|
|
b30fefb2c8 | ||
|
|
a5d7bd84dd | ||
|
|
379cf64994 | ||
|
|
2b1ec777c0 | ||
|
|
7219f1256b | ||
|
|
831d21143d | ||
|
|
892f91928c | ||
|
|
67e41af8d4 | ||
|
|
439a75ce38 | ||
|
|
625da250fa | ||
|
|
5dbac38d86 | ||
|
|
c813f39791 | ||
|
|
dda355f65c | ||
|
|
e0cc59dd94 | ||
|
|
a568ba58d2 | ||
|
|
cfbce46cb2 | ||
|
|
f9d1849e1a | ||
|
|
be6a8adc7c | ||
|
|
1d3b4debab | ||
|
|
2067d69a5d | ||
|
|
1f6bdaf570 | ||
|
|
23cc85ae24 | ||
|
|
57f8e05f03 | ||
|
|
cf47698327 | ||
|
|
91ab4a6b3b | ||
|
|
8e017aaa15 | ||
|
|
6d3c3c9a4f | ||
|
|
58d96221ba | ||
|
|
40d4728519 | ||
|
|
044246bb84 | ||
|
|
029b35e990 | ||
|
|
959b098816 | ||
|
|
3a10b18dfb | ||
|
|
83989943bc | ||
|
|
619447214b | ||
|
|
a4a4d9aeab | ||
|
|
4bf6c6e8fc | ||
|
|
b0f1e56ede | ||
|
|
b0857eefb1 | ||
|
|
8efff6895e | ||
|
|
69eed603c3 | ||
|
|
f91c6b50aa | ||
|
|
46395d7c46 | ||
|
|
5a2cf8d1c4 | ||
|
|
281b3cfdeb |
+5
-2
@@ -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
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
package-lock=false
|
||||
# Do not disable package-lock. Disabling package-lock also disables npm-shrinkwrap
|
||||
package-lock=true
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
@@ -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/) |
|
||||
|
||||
|
||||
@@ -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/) |
|
||||
|
||||
|
||||
@@ -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/) |
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ driver.getText("~SomeAccessibilityId");
|
||||
|
||||
// wd example
|
||||
let element = await driver.elementByAccessibilityId("SomeAccessibilityID");
|
||||
await element.submit();
|
||||
await element.text();
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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!");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Generated
+2814
File diff suppressed because it is too large
Load Diff
+20
-20
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user