mirror of
https://github.com/appium/appium.git
synced 2026-11-14 14:48:47 +08:00
Compare commits
85
Commits
v1.5.1
...
v1.6.0-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47e5a86e10 | ||
|
|
5e07e5b3ee | ||
|
|
b6c62e259d | ||
|
|
a3792a0185 | ||
|
|
52f990a2f4 | ||
|
|
8afc543316 | ||
|
|
b7dc1cdc4f | ||
|
|
99b0dfddb6 | ||
|
|
25ac4d3e3a | ||
|
|
3002117c0e | ||
|
|
fb4a9c0be0 | ||
|
|
99c8e182d2 | ||
|
|
1482423198 | ||
|
|
77ea7e9866 | ||
|
|
9341bbde35 | ||
|
|
32d5b7a132 | ||
|
|
72a97f2910 | ||
|
|
0c14015659 | ||
|
|
a1425e5870 | ||
|
|
7403a13c1a | ||
|
|
d3ee825371 | ||
|
|
50725af059 | ||
|
|
f418cb3486 | ||
|
|
4ae1b28ff5 | ||
|
|
068c4e5d0a | ||
|
|
02b9fcb30c | ||
|
|
6413d55b4f | ||
|
|
c22c00fb82 | ||
|
|
56bea2e511 | ||
|
|
e026a14141 | ||
|
|
fa98737c4f | ||
|
|
486827af71 | ||
|
|
1f8ce36b7d | ||
|
|
4843d41679 | ||
|
|
135e43a523 | ||
|
|
340a83ab93 | ||
|
|
97b5015f9d | ||
|
|
154cb49102 | ||
|
|
0b5df721d4 | ||
|
|
e593208f9e | ||
|
|
2276be5333 | ||
|
|
5a67708df0 | ||
|
|
c92d23d4be | ||
|
|
0f99de7715 | ||
|
|
72fde58638 | ||
|
|
312c0e924c | ||
|
|
1b86d0cba9 | ||
|
|
101bad0aa6 | ||
|
|
9c663cf18e | ||
|
|
e494df980f | ||
|
|
1662884395 | ||
|
|
3babf0ce3d | ||
|
|
f6c8faff9c | ||
|
|
169ca60023 | ||
|
|
3b56d2d1d8 | ||
|
|
a09e3e00f6 | ||
|
|
ad054100d6 | ||
|
|
e22414b4ce | ||
|
|
6e5f3a308a | ||
|
|
2ffc5b00bf | ||
|
|
3c2c86f0aa | ||
|
|
350c452901 | ||
|
|
9c1b85ff88 | ||
|
|
ebcf3da165 | ||
|
|
7966de6703 | ||
|
|
734a96be5a | ||
|
|
00041de3a3 | ||
|
|
b3c1e9c622 | ||
|
|
1ddeb74939 | ||
|
|
609ba21b13 | ||
|
|
1672203f42 | ||
|
|
406a06a7de | ||
|
|
216264ff16 | ||
|
|
1eb84376b3 | ||
|
|
fa6d8b7442 | ||
|
|
afd50a2637 | ||
|
|
f6ed186748 | ||
|
|
9ab17fdd1b | ||
|
|
d4c7e5500c | ||
|
|
6eb48cf2e3 | ||
|
|
7aee45c1ab | ||
|
|
caa1323bf0 | ||
|
|
566147c20d | ||
|
|
0f65f2b657 | ||
|
|
141948b035 |
@@ -0,0 +1 @@
|
||||
sample-code
|
||||
+3
-1
@@ -3,6 +3,8 @@ sudo: false
|
||||
node_js:
|
||||
- "stable"
|
||||
- "4"
|
||||
- "0.12"
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test
|
||||
after_success:
|
||||
- gulp coveralls
|
||||
|
||||
+111
@@ -1,3 +1,114 @@
|
||||
CHANGES IN VERSION 1.6.0 beta 1 (from 1.5.3)
|
||||
===================================
|
||||
|
||||
This release of Appium marks the beginning of support for two brand new platforms:
|
||||
* [Windows](https://www.microsoft.com/en-us/windows) desktop applications (see the [usage documentation](https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/windows-app-testing.md))
|
||||
* [You.i TV](http://www.youi.tv/) (see the [driver documentation](https://github.com/YOU-i-Labs/appium-youiengine-driver#appium-youi-engine-driver)).
|
||||
|
||||
Further, since Apple removed the Instruments automation functionality in Xcode 8,
|
||||
this release of Appium has preliminary support for XCUITest, allowing for the automation
|
||||
of applications in iOS 9.3 and 10.
|
||||
|
||||
#### General
|
||||
- Require Node 4 or above (**possible breaking change**)
|
||||
- Add `automationName` entries for `XCUITest`, and `youiengine`
|
||||
- Add `platformName` entry for `Windows`
|
||||
|
||||
#### iOS
|
||||
- Add preliminary support for Xcode 8 and iOS 10 (using `automationName` of `XCUITest`). For information on using this driver, see the [driver documentation](https://github.com/appium/appium-xcuitest-driver#external-dependencies)
|
||||
- Make sure device name gets properly translated into actual device name
|
||||
- Fix case where orientation would get lost
|
||||
- Fix Safari page change logic to actually catch when a page changes
|
||||
- Try harder to kill Instruments if the normal way does not work, to avoid hanging processes
|
||||
|
||||
#### Android
|
||||
- Add `androidInstallTimeout` desired capability, to customize the timeout when installing an app
|
||||
- Add `androidScreenshotPath` desired capability, to set the path in which screenshot files are saved on the device
|
||||
- Add `appWaitDuration` desired capability, to customize how long to wait for an application
|
||||
- Fix optional intent arguments to allow for hyphens
|
||||
- Wait for apps to launch before proceeding
|
||||
- Switch to clearing text fields using adb, to improve reliability and speed
|
||||
- Add ability to detect screen orientation
|
||||
- Make sure Selendroid mode doesn't lose connection through adb when network changes
|
||||
- Make sure the release action in a touch action chain doesn't happen in the wrong place
|
||||
- Make application install more reliable
|
||||
- Fix screenshot on Windows
|
||||
- Make Chromedriver connect on a random port if none specified
|
||||
|
||||
|
||||
|
||||
CHANGES IN VERSION 1.5.3 (from 1.5.2)
|
||||
===================================
|
||||
|
||||
*NOTE*: Appium 1.5.3 is primarily a bug-fix release.
|
||||
|
||||
It also adds functionality to support for iOS [WKWebViews](https://developer.apple.com/library/ios/documentation/WebKit/Reference/WKWebView_Ref/) and for automating the iOS Calendar app, as well as adding a capability to use `abd` for screenshots in Android web/hybrid sessions, selecting the correct version of Android emulator, and providing a list of Android activities to wait for on session startup.
|
||||
|
||||
Further, Android session reporting is increased, so that automation clients can get the device UDID for the automation session, as well as the currently running `appPackage` and `appActivity`.
|
||||
|
||||
#### General
|
||||
- Allow `--default-capabilities` server argument to specify a file containing the default capabilities.
|
||||
- Fix handling of statuses from proxied drivers (Chrome, Selendroid, etc.).
|
||||
- Fix handling of `browserName` capability for Selenium Grid usage.
|
||||
- Fix intermittent bug where creating a directory might crash the server.
|
||||
- Fix handling of `--session-override` server argument.
|
||||
|
||||
#### iOS
|
||||
- Add support for `WKWebView`-based webviews.
|
||||
- Add support for automating the built-in Calendar app.
|
||||
- Fix `ios-debug-proxy-launcher`.
|
||||
- Fix bug where scrolling in Safari would crash.
|
||||
- Fix handling of Safari in "new window" state.
|
||||
- Fix handling of arguments for `keys` method so that clients that send an array do not fail.
|
||||
- Clean up handling of `ipa` files for real device tests.
|
||||
- Make sure `processArguments` parsing works.
|
||||
- Fix iwd script to allow re-running.
|
||||
|
||||
#### Android
|
||||
- Add `nativeWebScreenshot` capability, to use `adb` screenshots instead of ChromeDriver if necessary.
|
||||
- Add support to auto-select device based on `platformVersion` rather than using first available device.
|
||||
- Allow `appWaitActivity` to be a list of activities
|
||||
- Fix setting wifi and data state
|
||||
- Implement `initAutoWebView` capability.
|
||||
- Report `appPackage`, `appActivity`, and `deviceUDID` when requesting session details.
|
||||
- Make sure unlock app is closed at the end of a session so that the device works again.
|
||||
- Fix handling of unlock to make less flakey.
|
||||
- Make sure unicode encoding works correctly when strings are long.
|
||||
- Fix handling of `disableAndroidWatchers` capability.
|
||||
- Fix killing ChromeDriver on Ubuntu 16.04.
|
||||
- Fix bug where early ChromeDriver failure crashed Appium.
|
||||
|
||||
|
||||
|
||||
CHANGES IN VERSION 1.5.2 (from 1.5.1)
|
||||
===================================
|
||||
|
||||
*NOTE*: Appium 1.5.2 is a bug-fix release.
|
||||
|
||||
#### General
|
||||
- deprecated `--command-timeout`. Use `newCommandTimeout` desired capability instead
|
||||
- ensure implicit wait can be set through `timeout` method
|
||||
- add better logging for `EPIPE` errors
|
||||
|
||||
#### iOS
|
||||
- make sure `ipa` files are handled correctly for installing on real devices
|
||||
- ensure that existing SafariLauncher on device is used instead of rebuilding and reinstalling
|
||||
- fix issues with getting webview contexts on real devices
|
||||
- add full timeout support through `timeout` method
|
||||
- make sure Xpath searches respect implicit wait timeout
|
||||
- make sure bare Instruments process arguments are accepted
|
||||
|
||||
#### Android
|
||||
- fix failure when `apk` file is too large
|
||||
- re-implement setting geolocation so it does not use Telnet.
|
||||
- add support for Chromium browser
|
||||
- fix issues with `flick`
|
||||
- fix bug where touch action `release` would throw an error
|
||||
- fix bug in later Android SDK version where noticing a newly started avd would fail
|
||||
- implement `autoWebviewTimeout`
|
||||
|
||||
|
||||
|
||||
CHANGES IN VERSION 1.5.1 (from 1.5)
|
||||
===================================
|
||||
|
||||
|
||||
+1
-8
@@ -60,11 +60,4 @@ are any questions that you can answer.
|
||||
### Submit bug reports or feature requests
|
||||
|
||||
Just use the GitHub issue tracker to submit your bug reports and feature
|
||||
requests. If you are submitting a bug report, please remember to include:
|
||||
|
||||
* Your complete Appium log (as a [gist](https://gist.github.com)---do *NOT* paste the whole thing into the issue description). This will tell us the exact version of Appium and the exact capabilities you are using, so you don't have to.
|
||||
* Your relevant client code, if you are able to share it (again a gist is most useful).
|
||||
* Any other information helpful in trying to reproduce your issue.
|
||||
|
||||
In general, the less work we have to do to reproduce a bug, the faster we will
|
||||
fix it. So please include as much information as possible up front!
|
||||
requests. If you are submitting a bug report, please follow the [issue template](https://github.com/appium/appium/issues/new).
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
|
||||
[](https://team-appium.ci.cloudbees.com/job/Appium/)
|
||||
|
||||
Appium is an open source, cross-platform test automation tool for native, hybrid and mobile web apps, tested on simulators (iOS, FirefoxOS), emulators (Android), and real devices (iOS, Android, FirefoxOS).
|
||||
Appium is an open source, cross-platform test automation tool for native, hybrid and mobile web apps, tested on simulators (iOS, FirefoxOS), emulators (Android), and real devices (iOS, Android, Windows, FirefoxOS).
|
||||
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
* iOS
|
||||
* Android
|
||||
* Windows
|
||||
* FirefoxOS
|
||||
|
||||
See the [platform support doc](/docs/en/appium-setup/platform-support.md) for more detailed information.
|
||||
@@ -49,7 +50,7 @@ Your environment needs to be setup for the particular mobile platforms that you
|
||||
want to run tests on. See below for particular platform requirements.
|
||||
|
||||
If you want to run Appium via an `npm install`, hack with or contribute to Appium, you will need
|
||||
[node.js and npm](http://nodejs.org) 0.12 or greater (use [n](https://github.com/visionmedia/n) or
|
||||
[node.js and npm](http://nodejs.org) 4 or greater (use [n](https://github.com/visionmedia/n) or
|
||||
`brew install node` to install Node.js. Make sure you have not installed Node or Appium with `sudo`,
|
||||
otherwise you'll run into problems). We recommend the latest stable version.
|
||||
|
||||
@@ -57,7 +58,7 @@ To verify that all of Appium's dependencies are met you can use
|
||||
`appium-doctor`. Install it with `npm install -g appium-doctor` (or run it
|
||||
from [source](https://github.com/appium/appium-doctor)), then run
|
||||
`appium-doctor` and supply the `--ios` or `--android` flags to verify that all
|
||||
of the dependencies are set up correctly.
|
||||
of the dependencies are set up correctly.
|
||||
|
||||
You also need to download the Appium client for your language so you can write tests. The Appium clients are simple extensions to the WebDriver clients. You can see the list of clients and links to download instructions at the [Appium clients list](/docs/en/about-appium/appium-clients.md).
|
||||
|
||||
@@ -76,6 +77,11 @@ You also need to download the Appium client for your language so you can write t
|
||||
* [linux](/docs/en/appium-setup/running-on-linux.md)
|
||||
* [osx](/docs/en/appium-setup/running-on-osx.md)
|
||||
* [windows](/docs/en/appium-setup/running-on-windows.md)
|
||||
|
||||
#### Windows Requirements
|
||||
|
||||
* Windows 10
|
||||
* [Documentation](/docs/en/appium-setup/running-on-windows.md)
|
||||
|
||||
#### FirefoxOS Requirements
|
||||
|
||||
@@ -124,6 +130,8 @@ Appium drives Apple's UIAutomation library for iOS support, which is based on
|
||||
Android support uses the UiAutomator framework for newer platforms and
|
||||
[Selendroid](http://github.com/DominikDary/selendroid) for older Android platforms.
|
||||
|
||||
Windows support uses Microsoft's [WinAppDriver](https://github.com/Microsoft/WinAppDriver)
|
||||
|
||||
FirefoxOS support leverages [Marionette](https://developer.mozilla.org/en-US/docs/Marionette),
|
||||
an automation driver that is compatible with WebDriver and is used to automate
|
||||
Gecko-based platforms.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint no-console:0 */
|
||||
|
||||
/*
|
||||
* Small tool, launching and monitoring ios-web-kit-proxy, and relauching
|
||||
* on predefined errors.
|
||||
@@ -19,7 +21,7 @@
|
||||
"use strict";
|
||||
|
||||
var spawn = require('child_process').spawn,
|
||||
_ = require('underscore');
|
||||
_ = require('lodash');
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
@@ -48,7 +50,7 @@ var startProxy = function () {
|
||||
proxy.stderr.on('data', function (data) {
|
||||
console.log('stderr: ' + data);
|
||||
var restartMessage = _(RESTART_ON_MESSAGES).find(function (message) {
|
||||
return ('' + data).indexOf(message) >= 0;
|
||||
return ('' + data).indexOf(message) >= 0;
|
||||
});
|
||||
if (restartMessage) {
|
||||
console.log('Detected error message:', restartMessage);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Appium is an open-source tool for automating native,
|
||||
mobile web, and hybrid applications on iOS and Android platforms.
|
||||
**Native apps** are those written using the iOS or Android SDKs. **Mobile web apps** are web apps accessed using a mobile browser (Appium supports
|
||||
**Native apps** are those written using the iOS, Android, or Windows SDKs. **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/),
|
||||
@@ -10,7 +10,7 @@ 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), using the same API. This enables code reuse between iOS and Android testsuites.
|
||||
multiple platforms (iOS, Android, Windows), using the same API. This enables code reuse between iOS, Android, and Windows testsuites.
|
||||
|
||||
For specific information about what it means for Appium to "support" its
|
||||
platforms, and automation modalities, please see the [platform support doc](/docs/en/appium-setup/platform-support.md).
|
||||
@@ -34,6 +34,7 @@ third-party code or frameworks to your app. This means **you're testing the same
|
||||
* iOS: Apple's [UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/)
|
||||
* Android 4.2+: Google's [UiAutomator](http://developer.android.com/tools/help/uiautomator/index.html)
|
||||
* Android 2.3+: Google's [Instrumentation](http://developer.android.com/reference/android/app/Instrumentation.html). (Instrumentation support is provided by bundling a separate project, [Selendroid](http://selendroid.io))
|
||||
* Windows: Microsoft's [WinAppDriver](http://github.com/microsoft/winappdriver)
|
||||
|
||||
We meet requirement #2 by wrapping the vendor-provided frameworks in one API,
|
||||
the [WebDriver](http://docs.seleniumhq.org/projects/webdriver/) API.
|
||||
@@ -83,13 +84,13 @@ a map or hash) sent to the Appium server to tell the server what kind of
|
||||
automation session we're interested in starting up. There are also various
|
||||
capabilities which can modify the behavior of the server during automation.
|
||||
For example, we might set the `platformName` capability to `iOS` to tell
|
||||
Appium that we want an iOS session, rather than an Android one. Or we might
|
||||
Appium that we want an iOS session, rather than an Android or Windows one. Or we might
|
||||
set the `safariAllowPopups` capability to `true` in order to ensure that,
|
||||
during a Safari automation session, we're allowed to use JavaScript to open
|
||||
up new windows. See the [capabilities doc](/docs/en/writing-running-appium/caps.md) for the complete list of capabilities available for Appium.
|
||||
|
||||
**Appium Server**<br/>
|
||||
Appium is a server written in Node.js. It can be built and installed [from source](github.com/appium/appium) or installed directly from NPM:
|
||||
Appium is a server written in Node.js. It can be built and installed [from source](https://github.com/appium/appium/blob/master/docs/en/contributing-to-appium/appium-from-source.md) or installed directly from NPM:
|
||||
```
|
||||
$ npm install -g appium
|
||||
$ appium
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
## Cross-domain iFrame Automation
|
||||
|
||||
[Same-origin policy](https://en.wikipedia.org/wiki/Same-origin_policy) prevents Appium from automating iFrames that have a different domain to the parent.
|
||||
|
||||
### Subdomain workaround
|
||||
If the parent and the iFrame share the same domain (e.g. `site.com` and `shop.site.com`), you can
|
||||
set `document.domain` on both the parent and each iFrame to a common domain. This solves the same-origin policy issue and allows automation. For example:
|
||||
|
||||
Parent:
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
document.domain = 'site.com';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://shop.site.com" width="200" height="200"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Child iFrame:
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
document.domain = 'site.com';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>This is an iFrame!</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -38,3 +38,17 @@ Settings are implemented via the following API endpoints:
|
||||
### Supported Settings
|
||||
|
||||
**"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.
|
||||
|
||||
#### Android UiAutomator Configurator
|
||||
|
||||
sets [UiAutomator Configurator](https://developer.android.com/reference/android/support/test/uiautomator/Configurator.html) timeouts and delays in Android devices. only works in Android API 18 and above.
|
||||
|
||||
**"actionAcknowledgmentTimeout"** - Int which is the same as [setActionAcknowledgmentTimeout](https://developer.android.com/reference/android/support/test/uiautomator/Configurator.html#setActionAcknowledgmentTimeout(long)). If a negative value is given, it would set to default(3 * 1000 milliseconds)
|
||||
|
||||
**"keyInjectionDelay"** - Int which is the same as [setKeyInjectionDelay](https://developer.android.com/reference/android/support/test/uiautomator/Configurator.html#setKeyInjectionDelay(long)). If a negative value is given, it would set to default(0 milliseconds)
|
||||
|
||||
**"scrollAcknowledgmentTimeout"** - Int which is the same as [setScrollAcknowledgmentTimeout](https://developer.android.com/reference/android/support/test/uiautomator/Configurator.html#setScrollAcknowledgmentTimeout(long)). If a negative value is given, it would set to default(200 milliseconds)
|
||||
|
||||
**"waitForIdleTimeout"** - Int which is the same as [setWaitForIdleTimeout](https://developer.android.com/reference/android/support/test/uiautomator/Configurator.html#setWaitForIdleTimeout(long)). If a negative value is given, it would set to default(10 * 1000 milliseconds)
|
||||
|
||||
**"waitForSelectorTimeout"** - Int which is the same as [setWaitForSelectorTimeout](https://developer.android.com/reference/android/support/test/uiautomator/Configurator.html#setWaitForSelectorTimeout(long)). If a negative value is given, it would set to default(10 * 1000 milliseconds)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Android Setup
|
||||
|
||||
To get started, you'll need to install node.js (v0.12 or greater). Just
|
||||
To get started, you'll need to install node.js (v4 or greater). Just
|
||||
follow the [instructions for your flavor of linux](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager).
|
||||
|
||||
Once you've got node.js installed, install the [Android SDK](http://developer.android.com/sdk/index.html).
|
||||
|
||||
@@ -38,7 +38,7 @@ instructions.
|
||||
* Versions: 2.3 and up
|
||||
* Versions 2.3 through 4.2 are supported via Appium's bundled version of
|
||||
[Selendroid](http://selendroid.io), which utilizes [Instrumentation](http://developer.android.com/reference/android/app/Instrumentation.html). Selendroid has a different set of commands than the default Appium (though this is rapidly being minimized) and a different support profile. To access this automation backend, use the `automationName` capability with the value `Selendroid`.
|
||||
* Versions 4.2 and up are supported via Appium's own [UiAutomator](http://developer.android.com/tools/help/uiautomator/index.html)
|
||||
* Versions 4.2 and up are supported via Appium's own [UiAutomator](http://developer.android.com/tools/testing-support-library/index.html#UIAutomator)
|
||||
libraries. This is the default automation backend.
|
||||
* Devices: Android emulators and real Android devices
|
||||
* Native app support: Yes
|
||||
@@ -61,3 +61,9 @@ instructions.
|
||||
* Support for automating vendor-provided or third-party apps: Yes (but not
|
||||
when using the Selendroid backend)
|
||||
* Support for automating custom, non-standard UI controls: No
|
||||
|
||||
### Windows Support
|
||||
|
||||
See [running on Windows](running-on-windows.md) for details
|
||||
|
||||
* [Windows App Testing](windows-app-testing.md)
|
||||
|
||||
@@ -21,6 +21,22 @@ can find information about this in the [Apple documentation](https://developer.a
|
||||
Appium will attempt to install your app using Fruitstrap, but it is often easier
|
||||
to pre-install your app using Xcode to ensure there are no problems (see the [iOS deploy](ios-deploy.md) doc for more information).
|
||||
|
||||
### Testing using Xcode 8 (including iOS 10) with XCUITest
|
||||
|
||||
This functionality currently depends on logging based on `idevicesyslog`, and
|
||||
port forwarding based on `iProxy`, both of which are part of `libimobiledevice`.
|
||||
Install it with [Homebrew](http://brew.sh/),
|
||||
|
||||
```
|
||||
brew install libimobiledevice
|
||||
```
|
||||
|
||||
Alternatively, logging can be done using `deviceconsole`, which is available
|
||||
[here](https://github.com/rpetrich/deviceconsole). To choose between them, use
|
||||
the desired capability `realDeviceLogger`, passing in the path to the logging
|
||||
program.
|
||||
|
||||
|
||||
### Running your tests with Appium
|
||||
|
||||
Once your device and app are configured, you can run tests on that device by
|
||||
|
||||
@@ -61,7 +61,85 @@ If you're using multiple Xcode versions, you can switch between them using:
|
||||
|
||||
sudo xcode-select --switch <path to required xcode>
|
||||
|
||||
### Testing using Xcode 8 (including iOS 10) with XCUITest
|
||||
|
||||
In order to automate iOS devices with Xcode 8 (which includes all testing of iOS 10+),
|
||||
you need to install the [Carthage](https://github.com/Carthage/Carthage) dependency
|
||||
manager:
|
||||
|
||||
```
|
||||
brew install carthage
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### System setup (Android)
|
||||
|
||||
Instructions for setting up Android and running tests on Mac OS X are the same as
|
||||
those on Linux. See the [Android setup docs](/docs/en/appium-setup/android-setup.md).
|
||||
|
||||
### Running iOS tests on OS X using Jenkins
|
||||
|
||||
First download the jenkins-cli.jar and verify the Mac successfully connects to Jenkins master. Ensure you've run the `authorize_ios` command mentioned above.
|
||||
|
||||
`wget https://jenkins.ci.cloudbees.com/jnlpJars/jenkins-cli.jar`
|
||||
|
||||
```
|
||||
java -jar jenkins-cli.jar \
|
||||
-s https://team-appium.ci.cloudbees.com \
|
||||
-i ~/.ssh/id_rsa \
|
||||
on-premise-executor \
|
||||
-fsroot ~/jenkins \
|
||||
-labels osx \
|
||||
-name mac_appium
|
||||
```
|
||||
|
||||
Next define a LaunchAgent for Jenkins to launch automatically on login. A LaunchDaemon will not work because daemons don't have GUI access. Make sure the plist doesn't contain the `SessionCreate` or `User` key as that may prevent tests from running. You'll see a `Failed to authorize rights` error if misconfigured.
|
||||
|
||||
```
|
||||
$ sudo nano /Library/LaunchAgents/com.jenkins.ci.plist
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.jenkins.ci</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>java</string>
|
||||
<string>-Djava.awt.headless=true</string>
|
||||
<string>-jar</string>
|
||||
<string>/Users/appium/jenkins/jenkins-cli.jar</string>
|
||||
<string>-s</string>
|
||||
<string>https://instructure.ci.cloudbees.com</string>
|
||||
<string>on-premise-executor</string>
|
||||
<string>-fsroot</string>
|
||||
<string>/Users/appium/jenkins</string>
|
||||
<string>-executors</string>
|
||||
<string>1</string>
|
||||
<string>-labels</string>
|
||||
<string>mac</string>
|
||||
<string>-name</string>
|
||||
<string>mac_appium</string>
|
||||
<string>-persistent</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/Users/appium/jenkins/stdout.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/Users/appium/jenkins/error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
Finally set the owner, permissions, and then start the agent.
|
||||
|
||||
```
|
||||
sudo chown root:wheel /Library/LaunchAgents/com.jenkins.ci.plist
|
||||
sudo chmod 644 /Library/LaunchAgents/com.jenkins.ci.plist
|
||||
|
||||
launchctl load /Library/LaunchAgents/com.jenkins.ci.plist
|
||||
launchctl start com.jenkins.ci
|
||||
```
|
||||
|
||||
@@ -1,61 +1,48 @@
|
||||
#Windows Setup
|
||||
|
||||
Appium on Windows supports both Windows and Android app automation!
|
||||
|
||||
See [Windows App Testing](/docs/en/writing-running-appium/windows-app-testing.md) for more details.
|
||||
|
||||
## Running Appium on Windows
|
||||
|
||||
### Limitations
|
||||
|
||||
If you are running Appium on Windows, you can use the
|
||||
[Appium.exe](https://github.com/appium/appium-dot-exe) client, which will allow
|
||||
you to quickly launch an Appium server and use the Inspector. You will not be
|
||||
able to test iOS apps on a locally hosted server, because Appium relies on OS
|
||||
X-only libraries to support iOS testing. You can however use the `Remote Server`
|
||||
option to connect to an Appium server running on a Mac.
|
||||
|
||||
### Setup
|
||||
## Setup
|
||||
|
||||
To get started:
|
||||
|
||||
1. Install [node.js](http://nodejs.org/download/) (v.0.12 or greater). Use the
|
||||
installer from nodejs.org.
|
||||
1. Install the [Android SDK](http://developer.android.com/sdk/index.html).
|
||||
You will need to run the 'android' tool (included in the SDK, in the `tools` folder) and make sure
|
||||
you have an API Level 17 or greater API installed. Set `ANDROID_HOME` to be
|
||||
1. Download latest [node and npm tools](https://nodejs.org/download/release/v6.3.0/node-v6.3.0-x64.msi) MSI (version >= 6.0). The `npm` and `nodejs` paths should be in your PATH environment variable.
|
||||
2. Download latest Java JDK [here](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) (accept the license agreement first). Set 'JAVA_HOME' to be your JDK path. The `bin` in that directory should be added to your PATH variable.
|
||||
3. Install the [Android SDK](http://developer.android.com/sdk/index.html). Set `ANDROID_HOME` to be
|
||||
your Android SDK path and add the `tools` and `platform-tools` folders to your
|
||||
PATH variable.
|
||||
1. Install the Java JDK and set `JAVA_HOME` to your JDK folder.
|
||||
1. Install [Apache Ant](http://ant.apache.org/bindownload.cgi) or use the one
|
||||
that comes with the Android Windows SDK in the eclipse\plugins folder. Be
|
||||
sure to add the folder containing Ant to your PATH variable.
|
||||
1. Install [Apache Maven](http://maven.apache.org/download.cgi) and set the
|
||||
M2HOME and M2 environment variables. Set `M2HOME` to the directory maven is
|
||||
installed in, and set `M2` to `%M2HOME\bin`. Add the path you used for `M2` to
|
||||
your PATH.
|
||||
1. Install [Git](http://git-scm.com/download/win) Be sure to install Git for
|
||||
windows to run in the regular command prompt.
|
||||
1. Install [cURL](http://curl.haxx.se/download.html).
|
||||
4. Install [Apache Ant](http://ant.apache.org/bindownload.cgi) or use the one that comes with the Android Windows SDK in the eclipse\plugins folder. Be sure to add the folder containing Ant to your PATH variable.
|
||||
5. Install [Apache Maven](http://maven.apache.org/download.cgi) and set the M2HOME and M2 environment variables. Set `M2_HOME` to the directory maven is installed in, and set `M2` to the `bin` in that directory. Add the path you used for `M2` to your PATH.
|
||||
6. Open admin cmd
|
||||
7. run command: npm install -g appium
|
||||
8. run command: appium
|
||||
9. Run a test from any Appium client (including Visual Studio)
|
||||
|
||||
Now that you've downloaded everything, if you're running from source, run the
|
||||
following in the folder where you cloned appium:
|
||||
## Additional Setup for Android App Testing
|
||||
|
||||
```center
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
```
|
||||
1. To run tests on Windows, you will need to have the Android Emulator booted or an Android Device connected that is running an AVD with API Level 17 orgreater. Then run Appium on the command line (via the `appium` command)
|
||||
2. Within test script, change value of 'platformVersion' to correspond with emulator version (i.e. '4.4.4')
|
||||
3. Within test script, change value of 'app' to PATH('path of .apk') and ensure the entire path (including close parenthesis) is on one line
|
||||
|
||||
### Running Appium
|
||||
## Additional Setup for Windows App Testing
|
||||
|
||||
To run tests on Windows, you will need to have the Android Emulator booted or
|
||||
an Android Device connected that is running an AVD with API Level 17 or
|
||||
greater. Then run Appium on the command line (via the `appium` command), or if
|
||||
you're running from source, inside the folder where you installed appium, using
|
||||
node.js:
|
||||
1. To test a Windows app, make sure you have turned [developer mode](https://msdn.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) on.
|
||||
|
||||
```center
|
||||
node .
|
||||
```
|
||||
## Running Appium
|
||||
|
||||
See the [server documentation](/docs/en/writing-running-appium/server-args.md) for all the command line
|
||||
arguments.
|
||||
|
||||
### Notes
|
||||
## Notes
|
||||
|
||||
- URL endpoint has to append "/wd/hub"
|
||||
- DesiredCapabilities:
|
||||
- platformName = "Windows"
|
||||
- deviceName = "WindowsPC"
|
||||
|
||||
* On windows run appium.app as administrator or when running from source you need to run cmd as administrator.
|
||||
* You must supply the `--no-reset` or `--full-reset` flags for
|
||||
@@ -64,3 +51,6 @@ arguments.
|
||||
limitations. For more information you can check out this
|
||||
[page](/docs/en/appium-setup/android-hax-emulator.md).
|
||||
* Make sure that `hw.battery=yes` in your AVD's `config.ini`.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,12 @@ own items in the SDK manager. Make sure you install the build-tools and platform
|
||||
reset the connection to the Android device.
|
||||
* Make sure you set ANDROID_HOME pointing to the Android SDK directory
|
||||
|
||||
### Windows
|
||||
|
||||
* Make sure developer mode is on
|
||||
* Make sure command prompt is Admin
|
||||
* Check that the URL Appium server is listening to matches the one specified in test script
|
||||
|
||||
### IOS
|
||||
|
||||
* Make sure Instruments.app is not open
|
||||
@@ -51,6 +57,7 @@ own items in the SDK manager. Make sure you install the build-tools and platform
|
||||
/tmp/instruments_sock` and try again as not-sudo.
|
||||
* If this is the first time you've run Appium, make sure to authorize the use
|
||||
of Instruments. See [running on OSX documentation](./running-on-osx.md#authorizing-ios-on-the-computer).
|
||||
* If Instruments is crashing when running against a physical device ("exited with code 253"), ensure Xcode has downloaded device symbols. Go to Window -> Devices, and it should start automatically. This is needed after iOS version upgrades.
|
||||
* If you see `iOS Simulator failed to install the application.` and the
|
||||
paths are correct, try restarting the computer.
|
||||
* If you have custom elements in your app, they will not be automatable by
|
||||
|
||||
@@ -22,7 +22,7 @@ git clone https://github.com/appium/appium.git
|
||||
cd appium
|
||||
npm install
|
||||
gulp transpile # requires gulp, see below
|
||||
sudo node_modules/.bin/authorize-ios.js # for ios only
|
||||
sudo node_modules/.bin/authorize-ios # for ios only
|
||||
# alternatively
|
||||
sudo $(npm bin)/authorize-ios
|
||||
node .
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## appium
|
||||
- runs basic checks
|
||||
- node version (>= 0.12)
|
||||
- node version (>= 4)
|
||||
- CLI arguments checks
|
||||
- contains all available and supported CLI arguments
|
||||
- check for deprecation and mutual exclusion
|
||||
@@ -16,14 +16,14 @@
|
||||
- starts baseServer (appium-express)
|
||||
- passes routes given by driver
|
||||
|
||||
## appium-express
|
||||
## appium-express (part of appium-base-driver)
|
||||
- starts express server (allows x-domain-origin)
|
||||
- initialises routes from AppiumDriver
|
||||
- timeout handling
|
||||
- serves a static page for test purposes
|
||||
- connects req/res events to logger
|
||||
|
||||
## mobile-json-wire-protocol
|
||||
## mobile-json-wire-protocol (part of appium-base-driver)
|
||||
- provides list of Appium commands
|
||||
- subclassed by drivers that will use the protocol
|
||||
- kind of middleware between client and driver
|
||||
@@ -146,7 +146,7 @@
|
||||
- launches, restarts and stops (or kills all) chrome instances
|
||||
- uses appium-jsonwp-proxy to send json wire protocol commands to the driver
|
||||
|
||||
## jsonwp-proxy
|
||||
## jsonwp-proxy (part of appium-base-driver)
|
||||
- allows to send json wire protocol commands to a server that understands it (browser drivers)
|
||||
- parses response into json
|
||||
- allows to proxy requests to a proxied server
|
||||
|
||||
@@ -13,3 +13,4 @@
|
||||
* [ios-webkit-debug-proxy](https://github.com/google/ios-webkit-debug-proxy)
|
||||
* [instruments-without-delay](https://github.com/facebook/instruments-without-delay)
|
||||
* [deviceconsole](https://github.com/rpetrich/deviceconsole)
|
||||
* [WinAppDriver](http://github.com/Microsoft/WinAppDriver)
|
||||
|
||||
@@ -151,6 +151,7 @@ checked in to GitHub along with changes to `package.json`.
|
||||
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. 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 1.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`).
|
||||
0. Run `npm publish` (with `--tag beta` if this isn't an official release).
|
||||
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,0 +1,245 @@
|
||||
# Apple XCUITest
|
||||
|
||||
Xcode 7 introduces [new UI testing features](https://developer.apple.com/videos/play/wwdc2015/406/). Moving forward, [Appium iOS Driver](https://github.com/appium/appium-ios-driver) will leverage this framework, replacing UI Automation. While there are no official docs, @joemasilotti has created [some handy unofficial docs](http://masilotti.com/xctest-documentation/index.html) by pulling out comments from the header files.
|
||||
|
||||
## Launching the App (XCUIApplication)
|
||||
- ##### – launch
|
||||
|
||||
Launches the application. This call is synchronous and when it returns the application is launched
|
||||
and ready to handle user events. Any failure in the launch sequence is reported as a test failure
|
||||
and halts the test at this point. If the application is already running, this call will first
|
||||
terminate the existing instance to ensure clean state of the launched instance.
|
||||
|
||||
- ##### – terminate
|
||||
|
||||
Terminates any running instance of the application. If the application has an existing debug session
|
||||
via Xcode, the termination is implemented as a halt via that debug connection. Otherwise, a SIGKILL
|
||||
is sent to the process.
|
||||
|
||||
- ##### launchArguments
|
||||
|
||||
The arguments that will be passed to the application on launch. If not modified, these are the
|
||||
arguments that Xcode will pass on launch. Those arguments can be changed, added to, or removed.
|
||||
Unlike NSTask, it is legal to modify these arguments after the application has been launched. These
|
||||
changes will not affect the current launch session, but will take effect the next time the application
|
||||
is launched.
|
||||
|
||||
- ##### launchEnvironment
|
||||
|
||||
The environment that will be passed to the application on launch. If not modified, this is the
|
||||
environment that Xcode will pass on launch. Those variables can be changed, added to, or removed.
|
||||
Unlike NSTask, it is legal to modify the environment after the application has been launched. These
|
||||
changes will not affect the current launch session, but will take effect the next time the application
|
||||
is launched.
|
||||
|
||||
## Accessing the Device (XCUIDevice)
|
||||
- ##### + sharedDevice
|
||||
|
||||
The current device.
|
||||
|
||||
- ##### orientation
|
||||
|
||||
The orientation of the device.
|
||||
|
||||
- ##### – pressButton:
|
||||
|
||||
Simulates the user pressing a physical button.
|
||||
|
||||
## Querying for Elements (XCUIElementQuery)
|
||||
- ##### element
|
||||
|
||||
Returns an element that will use the query for resolution.
|
||||
|
||||
- ##### count
|
||||
|
||||
Evaluates the query at the time it is called and returns the number of matches found.
|
||||
|
||||
- ##### – elementAtIndex:
|
||||
|
||||
Returns an element that will resolve to the index into the query’s result set.
|
||||
|
||||
- ##### – elementBoundByIndex:
|
||||
|
||||
Returns an element that will use the index into the query’s results to determine which underlying accessibility element it is matched with.
|
||||
|
||||
- ##### – elementMatchingPredicate:
|
||||
|
||||
Returns an element that matches the predicate. The predicate will be evaluated against objects of type idXCUIElementAttributes.
|
||||
|
||||
- ##### – elementMatchingType:identifier:
|
||||
|
||||
Returns an element that matches the type and identifier.
|
||||
|
||||
- ##### – objectForKeyedSubscript:
|
||||
|
||||
Keyed subscripting is implemented as a shortcut for matching an identifier only. For example, app.descendants[“Foo”] -> XCUIElement.
|
||||
|
||||
- ##### allElementsBoundByAccessibilityElement
|
||||
|
||||
Immediately evaluates the query and returns an array of elements bound to the resulting accessibility elements.
|
||||
|
||||
- ##### allElementsBoundByIndex
|
||||
|
||||
Immediately evaluates the query and returns an array of elements bound by the index of each result.
|
||||
|
||||
- ##### – descendantsMatchingType:
|
||||
|
||||
Returns a new query that finds the descendants of all the elements found by the receiver.
|
||||
|
||||
- ##### – childrenMatchingType:
|
||||
|
||||
Returns a new query that finds the direct children of all the elements found by the receiver.
|
||||
|
||||
- ##### – matchingPredicate:
|
||||
|
||||
Returns a new query that applies the specified attributes or predicate to the receiver. The predicate will be evaluated against objects of type idXCUIElementAttributes.
|
||||
|
||||
- ##### – matchingType:identifier:
|
||||
|
||||
- ##### – matchingIdentifier:
|
||||
|
||||
- ##### – containingPredicate:
|
||||
|
||||
Returns a new query for finding elements that contain a descendant matching the specification. The predicate will be evaluated against objects of type idXCUIElementAttributes.
|
||||
|
||||
- ##### – containingType:identifier:
|
||||
|
||||
- ##### debugDescription
|
||||
|
||||
@discussion
|
||||
Provides debugging information about the query. The data in the string will vary based on the time
|
||||
at which it is captured, but it may include any of the following as well as additional data:
|
||||
• A description of each step of the query.
|
||||
• Information about the inputs and matched outputs of each step of the query.
|
||||
This data should be used for debugging only - depending on any of the data as part of a test is unsupported.
|
||||
|
||||
## Interacting with Elements (XCUIElement)
|
||||
### XCUIElementEventSynthesis Methods
|
||||
- ##### – typeText:
|
||||
|
||||
Types a string into the element. The element or a descendant must have keyboard focus; otherwise an
|
||||
error is raised.
|
||||
|
||||
- ##### – tap
|
||||
|
||||
Sends a tap event to a hittable point computed for the element.
|
||||
|
||||
- ##### – doubleTap
|
||||
|
||||
Sends a double tap event to a hittable point computed for the element.
|
||||
|
||||
- ##### – twoFingerTap
|
||||
|
||||
Sends a two finger tap event to a hittable point computed for the element.
|
||||
|
||||
- ##### – tapWithNumberOfTaps:numberOfTouches:
|
||||
|
||||
Sends one or more taps with one of more touch points.
|
||||
|
||||
- ##### – pressForDuration:
|
||||
|
||||
Sends a long press gesture to a hittable point computed for the element, holding for the specified duration.
|
||||
|
||||
- ##### – pressForDuration:thenDragToElement:
|
||||
|
||||
Initiates a press-and-hold gesture that then drags to another element, suitable for table cell reordering and similar operations.
|
||||
@param duration
|
||||
Duration of the initial press-and-hold.
|
||||
@param otherElement
|
||||
The element to finish the drag gesture over. In the example of table cell reordering, this would be the reorder element of the destination row.
|
||||
|
||||
- ##### – swipeUp
|
||||
|
||||
Sends a swipe-up gesture.
|
||||
|
||||
- ##### – swipeDown
|
||||
|
||||
Sends a swipe-down gesture.
|
||||
|
||||
- ##### – swipeLeft
|
||||
|
||||
Sends a swipe-left gesture.
|
||||
|
||||
- ##### – swipeRight
|
||||
|
||||
Sends a swipe-right gesture.
|
||||
|
||||
- ##### – pinchWithScale:velocity:
|
||||
|
||||
Sends a pinching gesture with two touches.
|
||||
|
||||
- ##### – rotate:withVelocity:
|
||||
|
||||
Sends a rotation gesture with two touches.
|
||||
|
||||
### XCUIElementTypeSlider Methods
|
||||
- ##### – adjustToNormalizedSliderPosition:
|
||||
|
||||
Manipulates the UI to change the displayed value of the slider to one based on a normalized position. 0 corresponds to the minimum value of the slider, 1 corresponds to its maximum value. The adjustment is a “best effort” to move the indicator to the desired position; absolute fidelity is not guaranteed.
|
||||
|
||||
- ##### normalizedSliderPosition
|
||||
|
||||
Returns the position of the slider’s indicator as a normalized value where 0 corresponds to the minimum value of the slider and 1 corresponds to its maximum value.
|
||||
|
||||
### XCUIElementTypePickerWheel Methods
|
||||
- ##### – adjustToPickerWheelValue:
|
||||
|
||||
Changes the displayed value for the picker wheel. Will generate a test failure if the specified value is not available.
|
||||
|
||||
### Other Methods
|
||||
- ##### exists
|
||||
|
||||
Test to determine if the element exists.
|
||||
|
||||
- ##### hittable
|
||||
|
||||
Whether or not a hit point can be computed for the element for the purpose of synthesizing events.
|
||||
|
||||
- ##### – descendantsMatchingType:
|
||||
|
||||
Returns a query for all descendants of the element matching the specified type.
|
||||
|
||||
- ##### – childrenMatchingType:
|
||||
|
||||
Returns a query for direct children of the element matching the specified type.
|
||||
|
||||
- ##### – coordinateWithNormalizedOffset:
|
||||
|
||||
Creates and returns a new coordinate that will compute its screen point by adding the offset multiplied by the size of the element’s frame to the origin of the element’s frame.
|
||||
|
||||
- ##### debugDescription
|
||||
|
||||
@discussion
|
||||
Provides debugging information about the element. The data in the string will vary based on the
|
||||
time at which it is captured, but it may include any of the following as well as additional data:
|
||||
• Values for the elements attributes.
|
||||
• The entire tree of descendants rooted at the element.
|
||||
• The element’s query.
|
||||
This data should be used for debugging only - depending on any of the data as part of a test is unsupported.
|
||||
|
||||
## Using Coordinates (XCUICoordinate)
|
||||
- ##### – init
|
||||
|
||||
Coordinates are never instantiated directly. Instead, they are created by elements or by other coordinates.
|
||||
|
||||
- ##### referencedElement
|
||||
|
||||
The element that the coordinate is based on, either directly or via the coordinate from which it was derived.
|
||||
|
||||
- ##### screenPoint
|
||||
|
||||
The dynamically computed value of the coordinate’s location on screen. Note that this value is dependent on the current frame of the referenced element; if the element’s frame changes, so will the value returned by this property. If the referenced element does exist when this is called, it will fail the test; check the referenced element’s exists property if the element may not be present.
|
||||
|
||||
- ##### – coordinateWithOffset:
|
||||
|
||||
Creates a new coordinate with an absolute offset in points from the original coordinate.
|
||||
|
||||
- ##### – tap
|
||||
|
||||
- ##### – doubleTap
|
||||
|
||||
- ##### – pressForDuration:
|
||||
|
||||
- ##### – pressForDuration:thenDragToCoordinate:
|
||||
|
||||
- ##### – hover
|
||||
@@ -33,6 +33,7 @@
|
||||
|`androidCoverage`| Fully qualified instrumentation class. Passed to -w in adb shell am instrument -e coverage true -w | `com.my.Pkg/com.my.Pkg.instrumentation.MyInstrumentation`|
|
||||
|`enablePerformanceLogging`| (Chrome and webview only) Enable Chromedriver's performance logging (default `false`)| `true`, `false`|
|
||||
|`androidDeviceReadyTimeout`|Timeout in seconds used to wait for a device to become ready after booting|e.g., `30`|
|
||||
|`androidInstallTimeout`|Timeout in milliseconds used to wait for an apk to install to the device. Defaults to `90000` |e.g., `90000`|
|
||||
|`adbPort`|Port used to connect to the ADB server (default `5037`)|`5037`|
|
||||
|`androidDeviceSocket`|Devtools socket name. Needed only when tested app is a Chromium embedding browser. The socket is open by the browser and Chromedriver connects to it as a devtools client.|e.g., `chrome_devtools_remote`|
|
||||
|`avd`| Name of avd to launch|e.g., `api19`|
|
||||
@@ -49,7 +50,7 @@
|
||||
|`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`
|
||||
|`intentFlags`| Flags that will be used to start activity (default `0x10200000`)| e.g. `0x10200000`
|
||||
|`optionalIntentArguments`| Additional intent arguments that will be used to start activity. See [Intent arguments](http://developer.android.com/tools/help/adb.html#IntentSpec) | e.g. `--esn <EXTRA_KEY>`, `--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE>`, etc.
|
||||
|`optionalIntentArguments`| Additional intent arguments that will be used to start activity. See [Intent arguments](http://developer.android.com/reference/android/content/Intent.html) | e.g. `--esn <EXTRA_KEY>`, `--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE>`, etc.
|
||||
|`dontStopAppOnReset`| Doesn't stop the process of the app under test, before starting the app using adb. If the app under test is created by another anchor app, setting this false, allows the process of the anchor app to be still alive, during the start of the test app using adb. In other words, with `dontStopAppOnReset` set to `true`, we will not include the `-S` flag in the `adb shell am start` call. With this capability omitted or set to `false`, we include the `-S` flag. Default `false`| `true` or `false`|
|
||||
|`unicodeKeyboard`| Enable Unicode input, default `false`| `true` or `false`|
|
||||
|`resetKeyboard`| Reset keyboard to its original state, after running Unicode tests with `unicodeKeyboard` capability. Ignored if used alone. Default `false`| `true` or `false`|
|
||||
@@ -58,6 +59,8 @@
|
||||
|`disableAndroidWatchers`| Disables android watchers that watch for application not responding and application crash, this will reduce cpu usage on android device/emulator. This capability will work only with UiAutomator and not with selendroid, default `false`| `true` or `false`|
|
||||
|`chromeOptions`| Allows passing chromeOptions capability for ChromeDriver. For more information see [chromeOptions](https://sites.google.com/a/chromium.org/chromedriver/capabilities) | `chromeOptions: {args: ['--disable-popup-blocking']}` |
|
||||
|`recreateChromeDriverSessions`| Kill ChromeDriver session when moving to a non-ChromeDriver webview. Defaults to `false` | `true` or `false`|
|
||||
|`nativeWebScreenshot`| In a web context, use native (adb) method for taking a screenshot, rather than proxying to ChromeDriver. Defaults to `false` | `true` or `false`|
|
||||
|`androidScreenshotPath`| The name of the directory on the device in which the screenshot will be put. Defaults to `/data/local/tmp` |e.g. `/sdcard/screenshots/`|
|
||||
|
||||
### iOS Only
|
||||
|
||||
@@ -89,3 +92,22 @@
|
||||
|`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`|
|
||||
|
||||
### You.i Engine Only
|
||||
|
||||
<expand_table>
|
||||
|
||||
|Capability|Description|Values|
|
||||
|----|-----------|-------|
|
||||
|`youiEngineAppAddress`| The IP address of the device on which the app is running. Use `localhost` for simulator.
|
||||
Use device’s IP address for a real device. |e.g. `localhost` or `192.168.1.203`|
|
||||
|
||||
### WinAppDriver Only
|
||||
|
||||
<expand_table>
|
||||
|
||||
|Capability|Description|Values|
|
||||
|----|-----------|-------|
|
||||
|`platformName`| Which platform the test is being done on |e.g. `Windows`|
|
||||
|`deviceName`| The name of the device being tested on | e.g. `WindowsPC`|
|
||||
|`app`| appID of the windows app for testing or the path to the .exe file. See [this page](/docs/en/writing-running-appium/windows-app-testing.md) for details on how to find the appID | e.g. `c24c8163-548e-4b84-a466-530178fc0580_scyf5npe3hv32!App`|
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
Appium 1.5 does away with most CLI flags; the remainder can be converted into JSON and made part of the `--default-capabilities` flag. For example:
|
||||
|
||||
```
|
||||
# raw JSON as an argument
|
||||
--default-capabilities '{"app": "myapp.app", "deviceName": "iPhone Simulator"}'
|
||||
# or the name of a JSON file
|
||||
--default-capabilities /path/to/file.json
|
||||
```
|
||||
|
||||
**Windows users** will need to escape the quotes: `--default-capabilities "{\"app\": \"myapp.app\"}"`
|
||||
**Windows users** will need to escape the quotes in JSON passed on the command line: `--default-capabilities "{\"app\": \"myapp.app\"}"`
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ It is worth looking at *'-ios uiautomation'* search strategy with **Predicates**
|
||||
```
|
||||
|
||||
Native JS search strategy (powered by Apple) provides much more flexibility and is like Xpath.
|
||||
**[Predicates](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Predicates/predicates.html)** can be used to restrict an elements set to select only those ones for which some condition is true.
|
||||
**[Predicates](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html)** can be used to restrict an elements set to select only those ones for which some condition is true.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -20,7 +20,7 @@ appiumDriver.findElementsByIosUIAutomation("collectionViews()[0].cells().withPre
|
||||
|
||||
\- will select only those ```UIACollectionCell``` elements that have visible ```UIAStaticText``` child elements, and themselves are childs of 1st ```UIACollectionView``` element that should be located under the main app window. Here ```staticTexts()``` and ```isVisible()``` are methods available in ```UIAElementArray``` and ```UIAElement``` classes respectively. **Note that ```UIAElementArray``` numbering begins with ```0``` unlike Xpath where indexes counting starts from ```1```**
|
||||
|
||||
Here's a list of available Predicates (mostly taken from [Predicates Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Predicates/predicates.html))
|
||||
Here's a list of available Predicates (mostly taken from [Predicates Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html))
|
||||
|
||||
### Basic Comparisons
|
||||
|
||||
|
||||
@@ -20,6 +20,11 @@ connection API for iOS.
|
||||
Choose the setting you would like to use, and then send the correct bitmask from
|
||||
the table above.
|
||||
|
||||
### Windows
|
||||
|
||||
Unfortunately, at the moment Appium does not support the Selenium network
|
||||
connection API for Windows.
|
||||
|
||||
```javascript
|
||||
// javascript
|
||||
// set airplane mode
|
||||
|
||||
@@ -19,6 +19,10 @@ unpack it for you. Nice if you're not using Appium locally.
|
||||
Nothing in particular needs to be done to run your .apk using Appium. If you
|
||||
want to zip it up, you can.
|
||||
|
||||
### Preparing your app for test (Windows)
|
||||
|
||||
Nothing in particular needs to be done to run your test.
|
||||
|
||||
### Running your test app with Appium (iOS)
|
||||
|
||||
The best way to see what to do currently is to look at the example tests:
|
||||
@@ -91,9 +95,9 @@ First, make sure you have one and only one Android emulator or device
|
||||
connected. If you run `adb devices`, for example, you should see one device
|
||||
connected. This is the device Appium will use for tests. Of course, to have
|
||||
a device connected, you'll need to have made an Android AVD (see system
|
||||
setup ([Windows](running-on-windows.md),
|
||||
[Mac](running-on-osx.md),
|
||||
or [Linux](running-on-linux.md))
|
||||
setup ([Windows](https://github.com/appium/appium/blob/master/docs/en/appium-setup/running-on-windows.md),
|
||||
[Mac](https://github.com/appium/appium/blob/master/docs/en/appium-setup/running-on-osx.md),
|
||||
or [Linux](https://github.com/appium/appium/blob/master/docs/en/appium-setup/running-on-linux.md))
|
||||
for more information). If the Android SDK tools are on your path, you can
|
||||
simply run:
|
||||
|
||||
@@ -196,7 +200,7 @@ all set now!
|
||||
### Running your test app with Appium (Android devices < 4.2, and hybrid tests)
|
||||
|
||||
Android devices before version 4.2 (API Level 17) do not have Google's
|
||||
[UiAutomator framework](http://developer.android.com/tools/help/uiautomator/index.html)
|
||||
[UiAutomator framework](http://developer.android.com/tools/testing-support-library/index.html#UIAutomator)
|
||||
installed. This is what Appium uses to perform the automation behaviors on
|
||||
the device. For earlier devices or tests of hybrid (webview-based) apps,
|
||||
Appium comes bundled with another automation backend called [Selendroid]
|
||||
@@ -268,3 +272,11 @@ session. One of the downsides to using Selendroid is that its API differs
|
||||
sometimes significantly with Appium's. Therefore we recommend you thoroughly
|
||||
read [Selendroid's documentation](http://selendroid.io/native.html) before
|
||||
writing your scripts for older devices or hybrid apps.
|
||||
|
||||
### Running your test app with Appium (Windows)
|
||||
|
||||
Ensure that Appium is listening
|
||||
|
||||
Deploy your test from your test runner of choice
|
||||
|
||||
See our [samples](https://github.com/Microsoft/WinAppDriver/tree/master/Samples) for details
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## Appium server arguments
|
||||
# Appium server arguments
|
||||
|
||||
Many Appium 1.5 server arguments have been deprecated in favor of the [--default-capabilities flag](/docs/en/writing-running-appium/default-capabilities-arg.md).
|
||||
|
||||
Usage: `node . [flags]`
|
||||
|
||||
### Server flags
|
||||
## Server flags
|
||||
All flags are optional, but some are required in conjunction with certain others.
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ All flags are optional, but some are required in conjunction with certain others
|
||||
|`--chromedriver-executable`|null|ChromeDriver executable full path||
|
||||
|`--show-config`|false|Show info about the appium server configuration and exit||
|
||||
|`--no-perms-check`|false|Bypass Appium's checks to ensure we can read/write necessary files||
|
||||
|`--command-timeout`|60|The default command timeout for the server to use for all sessions (in seconds and should be less than 2147483). Will still be overridden by newCommandTimeout cap||
|
||||
|`--strict-caps`|false|Cause sessions to fail if desired caps are sent in that Appium does not recognize as valid for the selected device||
|
||||
|`--isolate-sim-device`|false|Xcode 6 has a bug on some platforms where a certain simulator can only be launched without error if all other simulator devices are first deleted. This option causes Appium to delete all devices other than the one being used by Appium. Note that this is a permanent deletion, and you are responsible for using simctl or xcode to manage the categories of devices used with Appium.||
|
||||
|`--tmp`|null|Absolute path to directory Appium can use to manage temporary files, like built-in iOS apps it needs to move around. On *nix/Mac defaults to /tmp, on Windows defaults to C:\Windows\Temp||
|
||||
@@ -52,7 +51,8 @@ All flags are optional, but some are required in conjunction with certain others
|
||||
|`--suppress-adb-kill-server`|false|(Android-only) If set, prevents Appium from killing the adb server instance||
|
||||
|`--async-trace`|false|Add long stack traces to log entries. Recommended for debugging only.||
|
||||
|`--webkit-debug-proxy-port`|27753|(IOS-only) Local port used for communication with ios-webkit-debug-proxy|`--webkit-debug-proxy-port 27753`|
|
||||
|`--default-capabilities`|{}|Set the default desired capabilities, which will be set on each session unless overridden by received capabilities. [More infomation](/docs/en/writing-running-appium/default-capabilities-arg.md). |`--default-capabilities '{"app": "myapp.app", "deviceName": "iPhone Simulator"}'`|
|
||||
|`-dc`, `--default-capabilities`|{}|Set the default desired capabilities, which will be set on each session unless overridden by received capabilities.|`--default-capabilities [ '{"app": "myapp.app", "deviceName": "iPhone Simulator"}' | /path/to/caps.json ]`|
|
||||
|`--command-timeout`|60|[DEPRECATED] No effect. This used to be the default command timeout for the server to use for all sessions (in seconds and should be less than 2147483). Use newCommandTimeout cap instead||
|
||||
|`-k`, `--keep-artifacts`|false|[DEPRECATED] - no effect, trace is now in tmp dir by default and is cleared before each run. Please also refer to the --trace-dir flag.||
|
||||
|`--platform-name`|null|[DEPRECATED] - Name of the mobile platform: iOS, Android, or FirefoxOS|`--platform-name iOS`|
|
||||
|`--platform-version`|null|[DEPRECATED] - Version of the mobile platform|`--platform-version 7.1`|
|
||||
|
||||
@@ -47,7 +47,7 @@ the timing of the gesture.
|
||||
`moveTo` coordinates are *relative* to the current position. For example, dragging from
|
||||
100,100 to 200,200 can be achieved by:
|
||||
```
|
||||
.press(100,100) // Start at 100,00
|
||||
.press(100,100) // Start at 100,100
|
||||
.moveTo(100,100) // Increase X & Y by 100 each, ending up at 200,200
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# UIAutomator 2
|
||||
|
||||
While the API remains almost the same, the internal implementation has changed and we see the introduction of UIObject2
|
||||
|
||||
### UIObject2
|
||||
|
||||
Unlike UiObject, the UIElement is bound to a particular view instance and can become stale if the underlying view object is destroyed. As a result, it may be necessary to call findObject(BySelector) to obtain a new UiObject2 instance if the UI changes significantly.
|
||||
|
||||
### The Build System
|
||||
|
||||
UIAutomator 2 utilizes capabilities of Gradle as a build system. Previous, UIAutomator used Maven/Ant.
|
||||
|
||||
### Test Assets
|
||||
|
||||
Test packages produced are now APKs. Previously, UIAutomator produced .jar or .zip files. This makes UIAutomator 2 completely Android instrumentation capable.
|
||||
|
||||
### ADB
|
||||
|
||||
ADB deals with UIAutomator 2 slightly differently.
|
||||
|
||||
The original version of UiAutomator ran as a shell program:
|
||||
|
||||
```adb shell uiautomator runtest ...```
|
||||
|
||||
UiAutomator 2 is based on Android Instrumentation. Tests are compiled to APKs, and run in an application process:
|
||||
|
||||
```adb shell am instrument ...```
|
||||
@@ -1,7 +1,7 @@
|
||||
## uiautomator UiSelector
|
||||
|
||||
Appium enables searching using [UiSelectors](http://developer.android.com/tools/help/uiautomator/UiSelector.html).
|
||||
[UiScrollable](http://developer.android.com/tools/help/uiautomator/UiScrollable.html)
|
||||
Appium enables searching using [UiSelectors](http://developer.android.com/reference/android/support/test/uiautomator/UiSelector.html).
|
||||
[UiScrollable](http://developer.android.com/reference/android/support/test/uiautomator/UiScrollable.html)
|
||||
is also supported.
|
||||
|
||||
Note that the index selector is unreliable so prefer instance instead. The
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#Windows Application UI Testing
|
||||
Windows specific UI testing on Appium is powered by [WinAppDriver] (https://github.com/Microsoft/WinAppDriver), which is downloaded as part of the Appium install.
|
||||
|
||||
## Java Samples
|
||||
1. Open the sample folder as an existing project in a Java IDE such as IntelliJ. For example: [CalculatorTest](https://github.com/Microsoft/WinAppDriver/tree/master/Samples/Java/CalculatorTest)
|
||||
2. In the Java IDE build and run the test
|
||||
|
||||
## C# Samples
|
||||
1. Pull and open `CalculatorTest.sln` under [CalculatorTest](https://github.com/Microsoft/WinAppDriver/tree/master/Samples/C%23/CalculatorTest)
|
||||
2. In Visual Studio 2015 with the test solution open build the test and select **Test > Run > All Tests**
|
||||
|
||||
## Features
|
||||
Windows Application Driver supports testing **Universal Windows Platform (UWP)** and **Classic Windows (Win32)** apps on **Windows 10 PC**
|
||||
|
||||
## Creating Your Own Test Script
|
||||
You can choose any programming language or tools supported by Appium/Selenium to write your test scripts. In the example below, we will author the test script in C# using Microsoft Visual Studio 2015.
|
||||
### Create Test Project
|
||||
1. Open **Microsoft Visual Studio 2015**
|
||||
2. Create the test project and solution. I.e. select **New Project > Templates > Visual C# > Test > Unit Test Project**
|
||||
3. Once created, select **Project > Manage NuGet Packages... > Browse** and search for **Appium.WebDriver**
|
||||
4. Install the **Appium.WebDriver** NuGet packages for the test project
|
||||
5. Start writing your test (see sample code under [samples])
|
||||
### Universal Windows Platform App Testing
|
||||
To test a UWP app, you can use any Selenium supported language and simply specify the **Application Id** for the app under test in the **app** capabilities entry. Below is an example of creating a test session for Windows **Alarms & Clock** app written in C#:
|
||||
```c#
|
||||
// Launch the AlarmClock app
|
||||
DesiredCapabilities appCapabilities = new DesiredCapabilities();
|
||||
appCapabilities.SetCapability("app", "Microsoft.WindowsAlarms_8wekyb3d8bbwe!App");
|
||||
AlarmClockSession = new IOSDriver<IOSElement>(new Uri("http://127.0.0.1:4723"), appCapabilities);
|
||||
// Control the AlarmClock app
|
||||
AlarmClockSession.FindElementByAccessibilityId("AddAlarmButton").Click();
|
||||
AlarmClockSession.FindElementByAccessibilityId("AlarmNameTextBox").Clear();
|
||||
```
|
||||
When testing the application you authored yourself, you can find the **Application Id** in the generetated `AppX\vs.appxrecipe` file under `RegisteredUserNmodeAppID` node. E.g. ```c24c8163-548e-4b84-a466-530178fc0580_scyf5npe3hv32!App```
|
||||
### Classic Windows App Testing
|
||||
To test a classic Windows app, you can also use any Selenium supported language and specify the **full executable path** for the app under test in the **app** capabilities entry. Below is an example of creating a test session for Windows **Notepad** app:
|
||||
```c#
|
||||
// Launch Notepad
|
||||
DesiredCapabilities appCapabilities = new DesiredCapabilities();
|
||||
appCapabilities.SetCapability("app", @"C:\Windows\System32\notepad.exe");
|
||||
NotepadSession = new IOSDriver<IOSElement>(new Uri("http://127.0.0.1:4723"), appCapabilities);
|
||||
// Control the AlarmClock app
|
||||
NotepadSession.FindElementByClassName("Edit").SendKeys("This is some text");
|
||||
```
|
||||
### Inspecting UI Elements
|
||||
Microsoft Visual Studio 2015 by default includes Windows SDK that provides great tool to inspect the application you are testing. This tool allows you to see every UI element/node that you can query using Windows Application Driver. This **inspect.exe** tool can be found under the Windows SDK folder such as `C:\Program Files (x86)\Windows Kits\10\bin\x86`
|
||||
|
||||
| Locator Strategy | Matched Attribute |
|
||||
|------------------|-------------------|
|
||||
| accessibility id | AutomationId |
|
||||
| class name | ClassName |
|
||||
| name | Name |
|
||||
+8
-3
@@ -1,5 +1,10 @@
|
||||
/* eslint no-console:0 */
|
||||
"use strict";
|
||||
|
||||
// turn all logging on since we have tests that rely on npmlog logs actually
|
||||
// getting sent to the handler
|
||||
process.env._FORCE_LOGS="1";
|
||||
|
||||
var gulp = require('gulp'),
|
||||
boilerplate = require('appium-gulp-plugins').boilerplate.use(gulp),
|
||||
path = require('path'),
|
||||
@@ -39,8 +44,8 @@ gulp.task('docs', ['transpile'], function () {
|
||||
var appiumArguments = parser.getParser().rawArgs;
|
||||
var docFile = path.resolve(__dirname, "docs/en/writing-running-appium/server-args.md");
|
||||
var md = "# Appium server arguments\n\n";
|
||||
md += "Many Appium 1.5 server arguments have been deprecated in favor of the "
|
||||
md += "[--default-capabilities flag](/docs/en/writing-running-appium/default-capabilities-arg.md)."
|
||||
md += "Many Appium 1.5 server arguments have been deprecated in favor of the ";
|
||||
md += "[--default-capabilities flag](/docs/en/writing-running-appium/default-capabilities-arg.md).";
|
||||
md += "\n\nUsage: `node . [flags]`\n\n";
|
||||
md += "## Server flags\n";
|
||||
md += "All flags are optional, but some are required in conjunction with " +
|
||||
@@ -57,7 +62,7 @@ gulp.task('docs', ['transpile'], function () {
|
||||
// let's replace it with <user>/...
|
||||
if (arg[0][0] === '--keystore-path') {
|
||||
var userPath = process.env.HOME || process.env.USERPROFILE;
|
||||
argOpts.defaultValue = argOpts.defaultValue.replace(userPath, '<user>')
|
||||
argOpts.defaultValue = argOpts.defaultValue.replace(userPath, '<user>');
|
||||
}
|
||||
|
||||
// handle empty objects
|
||||
|
||||
+51
-10
@@ -1,13 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
import log from './logger';
|
||||
import { getAppiumConfig } from './config';
|
||||
import { BaseDriver } from 'appium-base-driver';
|
||||
import { BaseDriver, routeConfiguringFunction, errors,
|
||||
isSessionCommand } from 'appium-base-driver';
|
||||
import { FakeDriver } from 'appium-fake-driver';
|
||||
import { AndroidDriver } from 'appium-android-driver';
|
||||
import { IosDriver } from 'appium-ios-driver';
|
||||
import { SelendroidDriver } from 'appium-selendroid-driver';
|
||||
import { routeConfiguringFunction, errors,
|
||||
isSessionCommand } from 'mobile-json-wire-protocol';
|
||||
import { XCUITestDriver } from 'appium-xcuitest-driver';
|
||||
import { YouiEngineDriver } from 'appium-youiengine-driver';
|
||||
import { WindowsDriver } from 'appium-windows-driver';
|
||||
import B from 'bluebird';
|
||||
import util from 'util';
|
||||
|
||||
@@ -41,9 +43,17 @@ class AppiumDriver extends BaseDriver {
|
||||
}
|
||||
|
||||
// we don't necessarily have an `automationName` capability,
|
||||
// but if we do and it is 'Selendroid', act on it
|
||||
if ((caps.automationName || '').toLowerCase() === 'selendroid') {
|
||||
return SelendroidDriver;
|
||||
if (caps.automationName) {
|
||||
if (caps.automationName.toLowerCase() === 'selendroid') {
|
||||
// but if we do and it is 'Selendroid', act on it
|
||||
return SelendroidDriver;
|
||||
} else if (caps.automationName.toLowerCase() === 'xcuitest') {
|
||||
// but if we do and it is 'XCUITest', act on it
|
||||
return XCUITestDriver;
|
||||
} else if (caps.automationName.toLowerCase() === 'youiengine') {
|
||||
// but if we do and it is 'YouiEngine', act on it
|
||||
return YouiEngineDriver;
|
||||
}
|
||||
}
|
||||
|
||||
if (caps.platformName.toLowerCase() === "fake") {
|
||||
@@ -55,9 +65,23 @@ class AppiumDriver extends BaseDriver {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return IosDriver;
|
||||
}
|
||||
|
||||
if (caps.platformName.toLowerCase() === 'windows') {
|
||||
return WindowsDriver;
|
||||
}
|
||||
|
||||
let msg;
|
||||
if (caps.automationName) {
|
||||
msg = `Could not find a driver for automationName '${caps.automationName}' and platformName ` +
|
||||
@@ -81,7 +105,7 @@ class AppiumDriver extends BaseDriver {
|
||||
async getSessions () {
|
||||
let sessions = [];
|
||||
for (let [id, driver] of _.toPairs(this.sessions)) {
|
||||
sessions.push({id: id, capabilities: driver.caps});
|
||||
sessions.push({id, capabilities: driver.caps});
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
@@ -96,11 +120,28 @@ class AppiumDriver extends BaseDriver {
|
||||
for (let [cap, value] of _.toPairs(caps)) {
|
||||
log.info(` ${cap}: ${util.inspect(value)}`);
|
||||
}
|
||||
|
||||
// sessionOverride server flag check
|
||||
// this will need to be re-thought when we go to multiple session support
|
||||
if (this.args.sessionOverride && !!this.sessions && _.keys(this.sessions).length > 0) {
|
||||
log.info('Session override is on. Deleting other sessions.');
|
||||
for (let id of _.keys(this.sessions)) {
|
||||
log.info(` Deleting session '${id}'`);
|
||||
try {
|
||||
await this.deleteSession(id);
|
||||
} catch (ign) {
|
||||
// the error has already been logged in AppiumDriver.deleteSession
|
||||
// continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
curSessions = this.curSessionDataForDriver(InnerDriver);
|
||||
} catch (e) {
|
||||
throw new errors.SessionNotCreatedError(e.message);
|
||||
}
|
||||
|
||||
let d = new InnerDriver(this.args);
|
||||
let [innerSessionId, dCaps] = await d.createSession(caps, reqCaps, curSessions);
|
||||
this.sessions[innerSessionId] = d;
|
||||
@@ -129,8 +170,8 @@ class AppiumDriver extends BaseDriver {
|
||||
|
||||
curSessionDataForDriver (InnerDriver) {
|
||||
let data = _.values(this.sessions)
|
||||
.filter(s => s.constructor.name === InnerDriver.name)
|
||||
.map(s => s.driverData);
|
||||
.filter((s) => s.constructor.name === InnerDriver.name)
|
||||
.map((s) => s.driverData);
|
||||
for (let datum of data) {
|
||||
if (!datum) {
|
||||
throw new Error(`Problem getting session data for driver type ` +
|
||||
@@ -139,7 +180,7 @@ class AppiumDriver extends BaseDriver {
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSession (sessionId) {
|
||||
try {
|
||||
|
||||
+11
-11
@@ -3,7 +3,7 @@ import path from 'path';
|
||||
import { mkdirp, fs } from 'appium-support';
|
||||
import { exec } from 'teen_process';
|
||||
import logger from './logger';
|
||||
import pkgObj from '../../package.json';
|
||||
import pkgObj from '../../package.json'; // eslint-disable-line import/no-unresolved
|
||||
|
||||
|
||||
const APPIUM_VER = pkgObj.version;
|
||||
@@ -30,24 +30,24 @@ async function getAppiumConfig () {
|
||||
let built = stat.mtime.getTime();
|
||||
let config = {
|
||||
'git-sha': await getGitRev(),
|
||||
'built': built,
|
||||
'version': APPIUM_VER,
|
||||
built,
|
||||
version: APPIUM_VER,
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
function checkNodeOk () {
|
||||
let [major, minor] = getNodeVersion();
|
||||
if (major === 0 && minor < 12) {
|
||||
let msg = `Node version must be >= 0.12. Currently ${major}.${minor}`;
|
||||
if (major < 4) {
|
||||
let msg = `Node version must be >= 4. Currently ${major}.${minor}`;
|
||||
logger.errorAndThrow(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function warnNodeDeprecations () {
|
||||
let [major, minor] = getNodeVersion();
|
||||
if (major === 0 && minor < 12) {
|
||||
logger.warn("Appium support for versions of node < 0.12 has been " +
|
||||
let [major] = getNodeVersion();
|
||||
if (major < 4) {
|
||||
logger.warn("Appium support for versions of node < 4 has been " +
|
||||
"deprecated and will be removed in a future version. Please " +
|
||||
"upgrade!");
|
||||
}
|
||||
@@ -55,14 +55,14 @@ function warnNodeDeprecations () {
|
||||
|
||||
async function showConfig () {
|
||||
let config = await getAppiumConfig();
|
||||
console.log(JSON.stringify(config));
|
||||
console.log(JSON.stringify(config)); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
function getNonDefaultArgs (parser, args) {
|
||||
let nonDefaults = {};
|
||||
for (let rawArg of parser.rawArgs) {
|
||||
let arg = rawArg[1].dest;
|
||||
if (args[arg] !== rawArg[1].defaultValue) {
|
||||
if (args[arg] && args[arg] !== rawArg[1].defaultValue) {
|
||||
nonDefaults[arg] = args[arg];
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ function getDeprecatedArgs (parser, args) {
|
||||
let arg = rawArg[1].dest;
|
||||
let defaultValue = rawArg[1].defaultValue;
|
||||
let isDeprecated = !!rawArg[1].deprecatedFor;
|
||||
if (args[arg] !== defaultValue && isDeprecated) {
|
||||
if (args[arg] && args[arg] !== defaultValue && isDeprecated) {
|
||||
deprecated[rawArg[0]] = rawArg[1].deprecatedFor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,14 @@ async function postRequest (data, addr, port) {
|
||||
jsonObject.configuration.url = `http://${addr}:${port}/wd/hub`;
|
||||
jsonObject.configuration.host = addr;
|
||||
jsonObject.configuration.port = port;
|
||||
|
||||
// re-serialize the configuration with the auto populated data
|
||||
data = JSON.stringify(jsonObject);
|
||||
}
|
||||
// if the node config does not have id automatically add it
|
||||
if (!jsonObject.configuration.id) {
|
||||
jsonObject.configuration.id = `http://${jsonObject.configuration.host}:${jsonObject.configuration.port}`;
|
||||
}
|
||||
|
||||
// re-serialize the configuration with the auto populated data
|
||||
data = JSON.stringify(jsonObject);
|
||||
|
||||
// prepare the header
|
||||
let post_headers = {
|
||||
@@ -97,7 +101,7 @@ async function postRequest (data, addr, port) {
|
||||
|
||||
async function isAlreadyRegistered (jsonObject) {
|
||||
//check if node is already registered
|
||||
let id = `http://${jsonObject.configuration.host}:${jsonObject.configuration.port}`;
|
||||
let id = jsonObject.configuration.id;
|
||||
try {
|
||||
let response = await request({
|
||||
uri: `http://${jsonObject.configuration.hubHost}:${jsonObject.configuration.hubPort}/grid/api/proxy?id=${id}`,
|
||||
|
||||
+21
-11
@@ -69,7 +69,7 @@ function _createConsoleTransport (args, logLvl) {
|
||||
exitOnError: false,
|
||||
json: false,
|
||||
level: logLvl,
|
||||
formatter: function (options) {
|
||||
formatter (options) {
|
||||
let meta = options.meta && Object.keys(options.meta).length ? `\n\t${JSON.stringify(options.meta)}` : '';
|
||||
let timestampPrefix = '';
|
||||
if (options.timestamp) {
|
||||
@@ -81,21 +81,21 @@ function _createConsoleTransport (args, logLvl) {
|
||||
if (args.logNoColors) {
|
||||
applyStripColorPatch(transport);
|
||||
}
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
function _createFileTransport (args, logLvl) {
|
||||
let transport = new (winston.transports.File)({
|
||||
name: "file",
|
||||
timestamp: timestamp,
|
||||
filename: args.log,
|
||||
maxFiles: 1,
|
||||
handleExceptions: true,
|
||||
exitOnError: false,
|
||||
json: false,
|
||||
level: logLvl,
|
||||
}
|
||||
);
|
||||
name: "file",
|
||||
timestamp,
|
||||
filename: args.log,
|
||||
maxFiles: 1,
|
||||
handleExceptions: true,
|
||||
exitOnError: false,
|
||||
json: false,
|
||||
level: logLvl,
|
||||
});
|
||||
applyStripColorPatch(transport);
|
||||
return transport;
|
||||
}
|
||||
@@ -151,6 +151,7 @@ async function _createTransports (args) {
|
||||
|
||||
transports.push(_createFileTransport(args, fileLogLevel));
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Tried to attach logging to file ${args.log} but an error ` +
|
||||
`occurred: ${e.message}`);
|
||||
}
|
||||
@@ -160,6 +161,7 @@ async function _createTransports (args) {
|
||||
try {
|
||||
transports.push(_createHttpTransport(args, fileLogLevel));
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Tried to attach logging to Http at ${args.webhook} but ` +
|
||||
`an error occurred: ${e.message}`);
|
||||
}
|
||||
@@ -178,6 +180,10 @@ async function init (args) {
|
||||
winston.addColors(colors);
|
||||
}
|
||||
|
||||
// clean up in case we have initted before since npmlog is a global
|
||||
// object
|
||||
clear();
|
||||
|
||||
logger = new (winston.Logger)({
|
||||
transports: await _createTransports(args)
|
||||
});
|
||||
@@ -191,6 +197,10 @@ async function init (args) {
|
||||
msg = `${prefix.magenta} ${msg}`;
|
||||
}
|
||||
logger[winstonLevel](msg);
|
||||
if (args.logHandler && typeof args.logHandler === "function") {
|
||||
args.logHandler(logObj.level, msg);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
+25
-6
@@ -4,9 +4,9 @@
|
||||
import { init as logsinkInit } from './logsink';
|
||||
import logger from './logger'; // logger needs to remain first of imports
|
||||
import _ from 'lodash';
|
||||
import { default as baseServer } from 'appium-express';
|
||||
import { server as baseServer } from 'appium-base-driver';
|
||||
import { asyncify } from 'asyncbox';
|
||||
import getParser from './parser';
|
||||
import { default as getParser, getDefaultArgs } from './parser';
|
||||
import { showConfig, checkNodeOk, validateServerArgs,
|
||||
warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
|
||||
getDeprecatedArgs, getGitRev, APPIUM_VER } from './config';
|
||||
@@ -15,7 +15,7 @@ import registerNode from './grid-register';
|
||||
import util from 'util';
|
||||
|
||||
|
||||
async function preflightChecks (parser, args) {
|
||||
async function preflightChecks (parser, args, throwInsteadOfExit = false) {
|
||||
try {
|
||||
checkNodeOk();
|
||||
if (args.asyncTrace) {
|
||||
@@ -32,6 +32,10 @@ async function preflightChecks (parser, args) {
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err.message.red);
|
||||
if (throwInsteadOfExit) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -93,11 +97,26 @@ function logServerPort (address, port) {
|
||||
|
||||
async function main (args = null) {
|
||||
let parser = getParser();
|
||||
if (!args) {
|
||||
let throwInsteadOfExit = false;
|
||||
if (args) {
|
||||
// a containing package passed in their own args, let's fill them out
|
||||
// with defaults
|
||||
args = Object.assign({}, getDefaultArgs(), args);
|
||||
|
||||
// if we have a containing package instead of running as a CLI process,
|
||||
// that package might not appreciate us calling 'process.exit' willy-
|
||||
// nilly, so give it the option to have us throw instead of exit
|
||||
if (args.throwInsteadOfExit) {
|
||||
throwInsteadOfExit = true;
|
||||
// but remove it since it's not a real server arg per se
|
||||
delete args.throwInsteadOfExit;
|
||||
}
|
||||
} else {
|
||||
// otherwise parse from CLI
|
||||
args = parser.parseArgs();
|
||||
}
|
||||
await logsinkInit(args);
|
||||
await preflightChecks(parser, args);
|
||||
await preflightChecks(parser, args, throwInsteadOfExit);
|
||||
await logStartupInfo(parser, args);
|
||||
let router = getAppiumRouter(args);
|
||||
let server = await baseServer(router, args.port, args.address);
|
||||
@@ -110,7 +129,7 @@ async function main (args = null) {
|
||||
await registerNode(args.nodeconfig, args.address, args.port);
|
||||
}
|
||||
} catch (err) {
|
||||
server.close();
|
||||
await server.close();
|
||||
throw err;
|
||||
}
|
||||
logServerPort(args.address, args.port);
|
||||
|
||||
+46
-17
@@ -1,7 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import { ArgumentParser } from 'argparse';
|
||||
import pkgObj from '../../package.json';
|
||||
import pkgObj from '../../package.json'; // eslint-disable-line import/no-unresolved
|
||||
|
||||
|
||||
const args = [
|
||||
@@ -10,6 +11,7 @@ const args = [
|
||||
defaultValue: null,
|
||||
help: 'Enter REPL mode',
|
||||
nargs: 0,
|
||||
dest: 'shell',
|
||||
}],
|
||||
|
||||
[['--ipa'], {
|
||||
@@ -17,6 +19,7 @@ const args = [
|
||||
defaultValue: null,
|
||||
help: '(IOS-only) abs path to compiled .ipa file',
|
||||
example: '/abs/path/to/my.ipa',
|
||||
dest: 'ipa',
|
||||
}],
|
||||
|
||||
[['-a', '--address'], {
|
||||
@@ -24,6 +27,7 @@ const args = [
|
||||
required: false,
|
||||
example: '0.0.0.0',
|
||||
help: 'IP Address to listen on',
|
||||
dest: 'address',
|
||||
}],
|
||||
|
||||
[['-p', '--port'], {
|
||||
@@ -32,6 +36,7 @@ const args = [
|
||||
type: 'int',
|
||||
example: '4723',
|
||||
help: 'port to listen on',
|
||||
dest: 'port',
|
||||
}],
|
||||
|
||||
[['-ca', '--callback-address'], {
|
||||
@@ -140,12 +145,14 @@ const args = [
|
||||
defaultValue: null,
|
||||
required: false,
|
||||
example: 'localhost:9876',
|
||||
dest: 'webhook',
|
||||
help: 'Also send log output to this HTTP listener',
|
||||
}],
|
||||
|
||||
[['--safari'], {
|
||||
defaultValue: false,
|
||||
action: 'storeTrue',
|
||||
dest: 'safari',
|
||||
required: false,
|
||||
help: '(IOS-Only) Use the safari app',
|
||||
nargs: 0,
|
||||
@@ -197,6 +204,7 @@ const args = [
|
||||
[['--nodeconfig'], {
|
||||
required: false,
|
||||
defaultValue: null,
|
||||
dest: 'nodeconfig',
|
||||
help: 'Configuration JSON file to register appium with selenium grid',
|
||||
example: '/abs/path/to/nodeconfig.json',
|
||||
}],
|
||||
@@ -228,12 +236,12 @@ const args = [
|
||||
}],
|
||||
|
||||
[['--chromedriver-port'], {
|
||||
defaultValue: 9515,
|
||||
defaultValue: null,
|
||||
dest: 'chromeDriverPort',
|
||||
required: false,
|
||||
type: 'int',
|
||||
example: '9515',
|
||||
help: 'Port upon which ChromeDriver will run',
|
||||
help: 'Port upon which ChromeDriver will run. If not given, Android driver will pick a random available port.',
|
||||
}],
|
||||
|
||||
[['--chromedriver-executable'], {
|
||||
@@ -259,16 +267,6 @@ const args = [
|
||||
help: 'Bypass Appium\'s checks to ensure we can read/write necessary files',
|
||||
}],
|
||||
|
||||
[['--command-timeout'], {
|
||||
defaultValue: 60,
|
||||
dest: 'defaultCommandTimeout',
|
||||
type: 'int',
|
||||
required: false,
|
||||
help: 'The default command timeout for the server to use for all ' +
|
||||
'sessions (in seconds and should be less than 2147483). ' +
|
||||
'Will still be overridden by newCommandTimeout cap',
|
||||
}],
|
||||
|
||||
[['--strict-caps'], {
|
||||
defaultValue: false,
|
||||
dest: 'enforceStrictCaps',
|
||||
@@ -344,18 +342,29 @@ const args = [
|
||||
help: '(IOS-only) Local port used for communication with ios-webkit-debug-proxy'
|
||||
}],
|
||||
|
||||
[['--default-capabilities'], {
|
||||
[['-dc', '--default-capabilities'], {
|
||||
dest: 'defaultCapabilities',
|
||||
defaultValue: {},
|
||||
type: JSON.parse,
|
||||
type: parseDefaultCaps,
|
||||
required: false,
|
||||
example: '{"app": "myapp.app", "deviceName": "iPhone Simulator"}',
|
||||
example: '[ \'{"app": "myapp.app", "deviceName": "iPhone Simulator"}\' ' +
|
||||
'| /path/to/caps.json ]',
|
||||
help: 'Set the default desired capabilities, which will be set on each ' +
|
||||
'session unless overridden by received capabilities.'
|
||||
}],
|
||||
];
|
||||
|
||||
const deprecatedArgs = [
|
||||
[['--command-timeout'], {
|
||||
defaultValue: 60,
|
||||
dest: 'defaultCommandTimeout',
|
||||
type: 'int',
|
||||
required: false,
|
||||
help: '[DEPRECATED] No effect. This used to be the default command ' +
|
||||
'timeout for the server to use for all sessions (in seconds and ' +
|
||||
'should be less than 2147483). Use newCommandTimeout cap instead'
|
||||
}],
|
||||
|
||||
[['-k', '--keep-artifacts'], {
|
||||
defaultValue: false,
|
||||
dest: 'keepArtifacts',
|
||||
@@ -731,6 +740,26 @@ function updateParseArgsForDefaultCapabilities (parser) {
|
||||
};
|
||||
}
|
||||
|
||||
function parseDefaultCaps (caps) {
|
||||
try {
|
||||
// use synchronous file access, as `argparse` provides no way of either
|
||||
// awaiting or using callbacks. This step happens in startup, in what is
|
||||
// effectively command-line code, so nothing is blocked in terms of
|
||||
// sessions, so holding up the event loop does not incur the usual
|
||||
// drawbacks.
|
||||
if (fs.statSync(caps).isFile()) {
|
||||
caps = fs.readFileSync(caps, 'utf8');
|
||||
}
|
||||
} catch (err) {
|
||||
// not a file, or not readable
|
||||
}
|
||||
caps = JSON.parse(caps);
|
||||
if (!_.isPlainObject(caps)) {
|
||||
throw 'Invalid format for default capabilities';
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
function getParser () {
|
||||
let parser = new ArgumentParser({
|
||||
version: pkgObj.version,
|
||||
@@ -749,7 +778,7 @@ function getParser () {
|
||||
|
||||
function getDefaultArgs () {
|
||||
let defaults = {};
|
||||
for (let [,arg] of args) {
|
||||
for (let [, arg] of args) {
|
||||
defaults[arg.dest] = arg.defaultValue;
|
||||
}
|
||||
return defaults;
|
||||
|
||||
+37
-22
@@ -11,7 +11,7 @@
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0-beta1",
|
||||
"author": "https://github.com/appium",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -22,8 +22,8 @@
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12",
|
||||
"iojs": ">=1.0.0"
|
||||
"node": ">=4",
|
||||
"npm": ">=3"
|
||||
},
|
||||
"main": "./build/lib/main.js",
|
||||
"bin": {
|
||||
@@ -35,44 +35,59 @@
|
||||
"doc": "./docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"appium-android-driver": "^1.8.0",
|
||||
"appium-base-driver": "^1.4.1",
|
||||
"appium-express": "^1.2.2",
|
||||
"appium-fake-driver": "^0.1.9",
|
||||
"appium-ios-driver": "^1.9.0",
|
||||
"appium-android-driver": "^1.10.19",
|
||||
"appium-base-driver": "^2.0.8",
|
||||
"appium-fake-driver": "^0.1.10",
|
||||
"appium-ios-driver": "^1.12.19",
|
||||
"appium-logger": "^2.1.0",
|
||||
"appium-selendroid-driver": "^1.3.0",
|
||||
"appium-support": "^2.0.10",
|
||||
"appium-selendroid-driver": "^1.3.4",
|
||||
"appium-support": "^2.3.0",
|
||||
"appium-xcuitest-driver": "^2.0.0",
|
||||
"appium-youiengine-driver": "^1.0.3",
|
||||
"appium-windows-driver": "^0.1.2",
|
||||
"argparse": "^1.0.7",
|
||||
"asyncbox": "^2.3.1",
|
||||
"authorize-ios": "^1.0.4",
|
||||
"babel-runtime": "=5.8.24",
|
||||
"bluebird": "^2.10.2",
|
||||
"continuation-local-storage": "^3.1.6",
|
||||
"date-utils": "^1.2.19",
|
||||
"lodash": "^4.6.1",
|
||||
"mobile-json-wire-protocol": "^1.3.3",
|
||||
"npmlog": "^2.0.3",
|
||||
"continuation-local-storage": "^3.1.7",
|
||||
"date-utils": "^1.2.21",
|
||||
"lodash": "^4.13.1",
|
||||
"npmlog": "^2.0.4",
|
||||
"request-promise": "^1.0.2",
|
||||
"source-map-support": "^0.4.0",
|
||||
"teen_process": "^1.5.2",
|
||||
"teen_process": "^1.7.1",
|
||||
"winston": "^2.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "gulp prepublish",
|
||||
"test": "gulp once",
|
||||
"watch": "gulp"
|
||||
"watch": "gulp",
|
||||
"precommit-msg": "echo 'Pre-commit checks...' && exit 0",
|
||||
"lint": "gulp eslint"
|
||||
},
|
||||
"pre-commit": [
|
||||
"precommit-msg",
|
||||
"lint",
|
||||
"test"
|
||||
],
|
||||
"devDependencies": {
|
||||
"appium-gulp-plugins": "^1.4.3",
|
||||
"appium-gulp-plugins": "^1.4.5",
|
||||
"babel-eslint": "^6.1.0",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^5.2.0",
|
||||
"chai-as-promised": "^5.3.0",
|
||||
"eslint": "^2.13.1",
|
||||
"eslint-config-appium": "0.0.6",
|
||||
"eslint-plugin-babel": "^3.3.0",
|
||||
"eslint-plugin-import": "^1.9.2",
|
||||
"eslint-plugin-mocha": "^3.0.0",
|
||||
"gulp": "^3.9.1",
|
||||
"mocha": "^2.4.5",
|
||||
"sinon": "^1.17.3",
|
||||
"mocha": "^2.5.3",
|
||||
"pre-commit": "^1.1.3",
|
||||
"sinon": "^1.17.4",
|
||||
"wd": "~0.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "^1.0.8"
|
||||
"fsevents": "^1.0.12"
|
||||
}
|
||||
}
|
||||
|
||||
+93
-45
@@ -42,8 +42,8 @@ describe('Config', () => {
|
||||
it('should log the config to console', async () => {
|
||||
let config = await getAppiumConfig();
|
||||
await showConfig();
|
||||
console.log.calledOnce.should.be.true;
|
||||
console.log.getCall(0).args[0].should.contain(JSON.stringify(config));
|
||||
console.log.calledOnce.should.be.true; // eslint-disable-line no-console
|
||||
console.log.getCall(0).args[0].should.contain(JSON.stringify(config)); // eslint-disable-line no-console
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -64,17 +64,27 @@ describe('Config', () => {
|
||||
process = _process;
|
||||
});
|
||||
describe('checkNodeOk', () => {
|
||||
it('should fail if node is below 0.10', () => {
|
||||
it('should fail if node is below 4', () => {
|
||||
process.version = 'v0.9.12';
|
||||
checkNodeOk.should.throw;
|
||||
checkNodeOk.should.throw();
|
||||
process.version = 'v0.1';
|
||||
checkNodeOk.should.throw();
|
||||
process.version = 'v0.10.36';
|
||||
checkNodeOk.should.throw();
|
||||
process.version = 'v0.12.14';
|
||||
checkNodeOk.should.throw();
|
||||
});
|
||||
it('should succeed if node is 0.10+', () => {
|
||||
process.version = 'v0.10.0';
|
||||
checkNodeOk.should.not.throw;
|
||||
it('should succeed if node is 4+', () => {
|
||||
process.version = 'v4.4.7';
|
||||
checkNodeOk.should.not.throw();
|
||||
});
|
||||
it('should succeed if node is 1.x', () => {
|
||||
process.version = 'v1.0.0';
|
||||
checkNodeOk.should.not.throw;
|
||||
it('should succeed if node is 5+', () => {
|
||||
process.version = 'v5.7.0';
|
||||
checkNodeOk.should.not.throw();
|
||||
});
|
||||
it('should succeed if node is 6+', () => {
|
||||
process.version = 'v6.3.1';
|
||||
checkNodeOk.should.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,18 +96,28 @@ describe('Config', () => {
|
||||
beforeEach(() => {
|
||||
spy.reset();
|
||||
});
|
||||
it('should log a warning if node is below 0.12', () => {
|
||||
it('should log a warning if node is below 4', () => {
|
||||
process.version = 'v0.9.12';
|
||||
warnNodeDeprecations();
|
||||
logger.warn.callCount.should.equal(1);
|
||||
});
|
||||
it('should not log a warning if node is 0.12+', () => {
|
||||
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';
|
||||
warnNodeDeprecations();
|
||||
logger.warn.callCount.should.equal(0);
|
||||
});
|
||||
it('should not log a warning if node is 1.x', () => {
|
||||
process.version = 'v1.0.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';
|
||||
warnNodeDeprecations();
|
||||
logger.warn.callCount.should.equal(0);
|
||||
});
|
||||
@@ -106,6 +126,7 @@ describe('Config', () => {
|
||||
|
||||
describe('server arguments', () => {
|
||||
let parser = getParser();
|
||||
parser.debug = true; // throw instead of exit on error; pass as option instead?
|
||||
let args = {};
|
||||
beforeEach(() => {
|
||||
// give all the defaults
|
||||
@@ -175,85 +196,109 @@ describe('Config', () => {
|
||||
|
||||
describe('validateServerArgs', () => {
|
||||
let parser = getParser();
|
||||
parser.debug = true; // throw instead of exit on error; pass as option instead?
|
||||
const defaultArgs = {};
|
||||
// give all the defaults
|
||||
for (let rawArg of parser.rawArgs) {
|
||||
defaultArgs[rawArg[1].dest] = rawArg[1].defaultValue;
|
||||
}
|
||||
let args = {};
|
||||
beforeEach(() => {
|
||||
args = _.clone(defaultArgs);
|
||||
});
|
||||
describe('mutually exclusive server arguments', () => {
|
||||
describe('noReset and fullReset', () => {
|
||||
it('should not allow both', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {noReset: true, fullReset: true});
|
||||
}).should.throw;
|
||||
args.noReset = args.fullReset = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.throw();
|
||||
});
|
||||
it('should allow noReset', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {noReset: true});
|
||||
}).should.not.throw;
|
||||
args.noReset = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
it('should allow fullReset', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {fullReset: true});
|
||||
}).should.not.throw;
|
||||
args.fullReset = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
});
|
||||
describe('ipa and safari', () => {
|
||||
it('should not allow both', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {ipa: true, safari: true});
|
||||
}).should.throw;
|
||||
args.ipa = args.safari = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.throw();
|
||||
});
|
||||
it('should allow ipa', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {ipa: true});
|
||||
}).should.not.throw;
|
||||
args.ipa = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
it('should allow safari', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {safari: true});
|
||||
}).should.not.throw;
|
||||
args.safari = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
});
|
||||
describe('app and safari', () => {
|
||||
it('should not allow both', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {app: true, safari: true});
|
||||
}).should.throw;
|
||||
args.app = args.safari = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.throw();
|
||||
});
|
||||
it('should allow app', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {app: true});
|
||||
}).should.not.throw;
|
||||
args.app = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
});
|
||||
describe('forceIphone and forceIpad', () => {
|
||||
it('should not allow both', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {forceIphone: true, forceIpad: true});
|
||||
}).should.throw;
|
||||
args.forceIphone = args.forceIpad = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.throw();
|
||||
});
|
||||
it('should allow forceIphone', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {forceIphone: true});
|
||||
}).should.not.throw;
|
||||
args.forceIphone = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
it('should allow forceIpad', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {forceIpad: true});
|
||||
}).should.not.throw;
|
||||
args.forceIpad = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
});
|
||||
describe('deviceName and defaultDevice', () => {
|
||||
it('should not allow both', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {deviceName: true, defaultDevice: true});
|
||||
}).should.throw;
|
||||
args.deviceName = args.defaultDevice = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.throw();
|
||||
});
|
||||
it('should allow deviceName', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {deviceName: true});
|
||||
}).should.not.throw;
|
||||
args.deviceName = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
it('should allow defaultDevice', () => {
|
||||
(() => {
|
||||
validateServerArgs(parser, {defaultDevice: true});
|
||||
}).should.not.throw;
|
||||
args.defaultDevice = true;
|
||||
validateServerArgs(parser, args);
|
||||
}).should.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -262,13 +307,16 @@ describe('Config', () => {
|
||||
// the only argument left is `backendRetries`
|
||||
describe('backendRetries', () => {
|
||||
it('should fail with value less than 0', () => {
|
||||
(() => {validateServerArgs(parser, {backendRetries: -1});}).should.throw;
|
||||
args.backendRetries = -1;
|
||||
(() => {validateServerArgs(parser, args);}).should.throw();
|
||||
});
|
||||
it('should succeed with value of 0', () => {
|
||||
(() => {validateServerArgs(parser, {backendRetries: 0});}).should.not.throw;
|
||||
args.backendRetries = 0;
|
||||
(() => {validateServerArgs(parser, args);}).should.not.throw();
|
||||
});
|
||||
it('should succeed with value above 0', () => {
|
||||
(() => {validateServerArgs(parser, {backendRetries: 100});}).should.not.throw;
|
||||
args.backendRetries = 100;
|
||||
(() => {validateServerArgs(parser, args);}).should.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import wd from 'wd';
|
||||
import { main as appiumServer } from '../lib/main';
|
||||
import { getDefaultArgs } from '../lib/parser';
|
||||
import { TEST_FAKE_APP, TEST_HOST, TEST_PORT } from './helpers';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
@@ -19,15 +18,13 @@ describe('FakeDriver - via HTTP', () => {
|
||||
let server = null;
|
||||
before(async () => {
|
||||
if (shouldStartServer) {
|
||||
let args = getDefaultArgs();
|
||||
args.port = TEST_PORT;
|
||||
args.host = TEST_HOST;
|
||||
let args = {port: TEST_PORT, host: TEST_HOST};
|
||||
server = await appiumServer(args);
|
||||
}
|
||||
});
|
||||
after(async () => {
|
||||
if (server) {
|
||||
await B.promisify(server.close.bind(server))();
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -80,3 +77,27 @@ describe('FakeDriver - via HTTP', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Logsink', () => {
|
||||
let server = null;
|
||||
let logs = [];
|
||||
let logHandler = (level, message) => {
|
||||
logs.push([level, message]);
|
||||
};
|
||||
let args = {port: TEST_PORT, host: TEST_HOST, logHandler};
|
||||
|
||||
before(async () => {
|
||||
server = await appiumServer(args);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('should send logs to a logHandler passed in by a parent package', async () => {
|
||||
logs.length.should.be.above(1);
|
||||
logs[0].length.should.equal(2);
|
||||
logs[0][1].should.include("Welcome to Appium");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+100
-1
@@ -7,6 +7,9 @@ import _ from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { XCUITestDriver } from 'appium-xcuitest-driver';
|
||||
import { IosDriver } from 'appium-ios-driver';
|
||||
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
@@ -71,6 +74,41 @@ describe('AppiumDriver', () => {
|
||||
await appium.createSession(BASE_CAPS);
|
||||
mockFakeDriver.verify();
|
||||
});
|
||||
it('should kill all other sessions if sessionOverride is on', async () => {
|
||||
appium.args.sessionOverride = true;
|
||||
|
||||
// mock three sessions that should be removed when the new one is created
|
||||
let fakeDrivers = [new FakeDriver(),
|
||||
new FakeDriver(),
|
||||
new FakeDriver()];
|
||||
let mockFakeDrivers = _.map(fakeDrivers, (fd) => {return sinon.mock(fd);});
|
||||
mockFakeDrivers[0].expects('deleteSession')
|
||||
.once();
|
||||
mockFakeDrivers[1].expects('deleteSession')
|
||||
.once()
|
||||
.throws('Cannot shut down Android driver; it has already shut down');
|
||||
mockFakeDrivers[2].expects('deleteSession')
|
||||
.once();
|
||||
appium.sessions['abc-123-xyz'] = fakeDrivers[0];
|
||||
appium.sessions['xyz-321-abc'] = fakeDrivers[1];
|
||||
appium.sessions['123-abc-xyz'] = fakeDrivers[2];
|
||||
|
||||
let sessions = await appium.getSessions();
|
||||
sessions.should.have.length(3);
|
||||
|
||||
mockFakeDriver.expects("createSession")
|
||||
.once().withExactArgs(BASE_CAPS, undefined, [])
|
||||
.returns([1, BASE_CAPS]);
|
||||
await appium.createSession(BASE_CAPS);
|
||||
|
||||
sessions = await appium.getSessions();
|
||||
sessions.should.have.length(1);
|
||||
|
||||
for (let mfd of mockFakeDrivers) {
|
||||
mfd.verify();
|
||||
}
|
||||
mockFakeDriver.verify();
|
||||
});
|
||||
});
|
||||
describe('deleteSession', () => {
|
||||
let appium
|
||||
@@ -135,10 +173,71 @@ describe('AppiumDriver', () => {
|
||||
describe('sessionExists', () => {
|
||||
});
|
||||
describe('getDriverForCaps', () => {
|
||||
it('should not blow up if user doesnt provide platformName', () => {
|
||||
it('should not blow up if user does not provide platformName', () => {
|
||||
let appium = new AppiumDriver({});
|
||||
(() => { appium.getDriverForCaps({}); }).should.throw(/platformName/);
|
||||
});
|
||||
it('should get XCUITestDriver driver for automationName of XCUITest', () => {
|
||||
let appium = new AppiumDriver({});
|
||||
let driver = appium.getDriverForCaps({
|
||||
platformName: 'iOS',
|
||||
automationName: 'XCUITest'
|
||||
});
|
||||
driver.should.be.an.instanceof(Function);
|
||||
driver.should.equal(XCUITestDriver);
|
||||
});
|
||||
it('should get iosdriver for ios < 10', () => {
|
||||
let appium = new AppiumDriver({});
|
||||
let caps = {
|
||||
platformName: 'iOS',
|
||||
platformVersion: '8.0',
|
||||
};
|
||||
let driver = appium.getDriverForCaps(caps);
|
||||
driver.should.be.an.instanceof(Function);
|
||||
driver.should.equal(IosDriver);
|
||||
|
||||
caps.platformVersion = '8.1';
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(IosDriver);
|
||||
|
||||
caps.platformVersion = '9.4';
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(IosDriver);
|
||||
|
||||
caps.platformVersion = '';
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(IosDriver);
|
||||
|
||||
caps.platformVersion = 'foo';
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(IosDriver);
|
||||
|
||||
delete caps.platformVersion;
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(IosDriver);
|
||||
});
|
||||
it('should get xcuitestdriver for ios >= 10', () => {
|
||||
let appium = new AppiumDriver({});
|
||||
let caps = {
|
||||
platformName: 'iOS',
|
||||
platformVersion: '10',
|
||||
};
|
||||
let driver = appium.getDriverForCaps(caps);
|
||||
driver.should.be.an.instanceof(Function);
|
||||
driver.should.equal(XCUITestDriver);
|
||||
|
||||
caps.platformVersion = '10.0';
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(XCUITestDriver);
|
||||
|
||||
caps.platformVersion = '10.1';
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(XCUITestDriver);
|
||||
|
||||
caps.platformVersion = '12.14';
|
||||
driver = appium.getDriverForCaps(caps);
|
||||
driver.should.equal(XCUITestDriver);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"a": "b"
|
||||
}
|
||||
+15
-2
@@ -7,6 +7,7 @@ const should = chai.should();
|
||||
|
||||
describe('Parser', () => {
|
||||
let p = getParser();
|
||||
p.debug = true; // throw instead of exit on error; pass as option instead?
|
||||
it('should return an arg parser', () => {
|
||||
should.exist(p.parseArgs);
|
||||
p.parseArgs([]).should.have.property('port');
|
||||
@@ -20,14 +21,26 @@ describe('Parser', () => {
|
||||
}
|
||||
});
|
||||
it('should throw an error with unknown argument', () => {
|
||||
(() => {p.parseArgs(['--apple']);}).should.throw;
|
||||
(() => {p.parseArgs(['--apple']);}).should.throw();
|
||||
});
|
||||
it('should parse default capabilities correctly', () => {
|
||||
it('should parse default capabilities correctly from a string', () => {
|
||||
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', () => {
|
||||
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', () => {
|
||||
(() => {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', () => {
|
||||
let defaultCapabilities = {localizableStringsDir: '/my/dir'};
|
||||
let args = p.parseArgs(['--localizable-strings-dir', '/my/dir']);
|
||||
|
||||
Reference in New Issue
Block a user