mirror of
https://github.com/appium/appium.git
synced 2026-11-12 21:43:19 +08:00
Compare commits
111
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c9e49c00 | ||
|
|
308bff0d06 | ||
|
|
6d940db8de | ||
|
|
9e911fab58 | ||
|
|
aa5ffd9900 | ||
|
|
b69d6c517b | ||
|
|
5c4cdcaa26 | ||
|
|
f27fdea5f1 | ||
|
|
9ed17802f3 | ||
|
|
d173e74525 | ||
|
|
0eaade74f0 | ||
|
|
236a946bd8 | ||
|
|
0313cc3e61 | ||
|
|
d20ca92880 | ||
|
|
6e45e59fb4 | ||
|
|
3e6db9eafb | ||
|
|
13ae50286b | ||
|
|
b2f654f494 | ||
|
|
ecbd04f41d | ||
|
|
daf65b074d | ||
|
|
9a7ea11024 | ||
|
|
5c0453f2ba | ||
|
|
8e01053e2f | ||
|
|
b40aa0ad3a | ||
|
|
44914494de | ||
|
|
bc74b082b1 | ||
|
|
3f23576a12 | ||
|
|
7d190b6e53 | ||
|
|
a58f4554ea | ||
|
|
a9d3dfe0b4 | ||
|
|
0daa242615 | ||
|
|
927ffe65cd | ||
|
|
0eb4d5cf06 | ||
|
|
141c40368a | ||
|
|
21d0492772 | ||
|
|
3c8be3188e | ||
|
|
7ec74b133f | ||
|
|
8244b605e1 | ||
|
|
a39841036a | ||
|
|
f1ac64fa7b | ||
|
|
034914e20f | ||
|
|
3349fc657c | ||
|
|
b755e0d9f2 | ||
|
|
6fc0fab3e4 | ||
|
|
ea17470eb8 | ||
|
|
0a6061636c | ||
|
|
d26353c56d | ||
|
|
65d34ac240 | ||
|
|
984bc48c34 | ||
|
|
293dd8f63e | ||
|
|
49ca79235d | ||
|
|
94cfa7651d | ||
|
|
6e8ddda2b2 | ||
|
|
3133552000 | ||
|
|
e9c89ac6e4 | ||
|
|
3678582c90 | ||
|
|
08310e6d8c | ||
|
|
46b84681b5 | ||
|
|
0822de6d27 | ||
|
|
9949023889 | ||
|
|
b0145dcf53 | ||
|
|
3564e523b5 | ||
|
|
c7b582418b | ||
|
|
1f198881dc | ||
|
|
6c42970851 | ||
|
|
fb5df00a6a | ||
|
|
2da504e68f | ||
|
|
6d0143439c | ||
|
|
0923f50830 | ||
|
|
aa64ac7383 | ||
|
|
ce62208290 | ||
|
|
e7cad78f2d | ||
|
|
8f63e2f91e | ||
|
|
a89066c6c4 | ||
|
|
7c3437fb46 | ||
|
|
4caad14ba7 | ||
|
|
393c37eba7 | ||
|
|
20bed5e41d | ||
|
|
d62cf6de1f | ||
|
|
dc6a500287 | ||
|
|
7e804db96c | ||
|
|
36fc3fec0e | ||
|
|
94fe13693b | ||
|
|
f8890302a8 | ||
|
|
26471c5386 | ||
|
|
7257b1f8d7 | ||
|
|
dea97f6844 | ||
|
|
a7becef815 | ||
|
|
15c6906cf4 | ||
|
|
6f4f3bcdeb | ||
|
|
6729fdd0eb | ||
|
|
1c871e054d | ||
|
|
44faa8db0e | ||
|
|
fdbe71473b | ||
|
|
80a285627e | ||
|
|
3efec0b073 | ||
|
|
effe14b6ab | ||
|
|
437b7bed17 | ||
|
|
045ec45ab9 | ||
|
|
0b30bd1cb4 | ||
|
|
4d4c8a2ba0 | ||
|
|
60ce2c302e | ||
|
|
aa2e40fe40 | ||
|
|
a9e1b78f00 | ||
|
|
8f4eaa7745 | ||
|
|
5bc053be9a | ||
|
|
13420f8093 | ||
|
|
a1def6633b | ||
|
|
df21a5390c | ||
|
|
2ba44979a3 | ||
|
|
f19e0fdbee |
@@ -27,7 +27,5 @@ org.eclipse.ltk.core.refactoring.prefs
|
||||
.appiumconfig*
|
||||
build/
|
||||
.idea
|
||||
lib/devices/ios/uiauto/lib/status.js
|
||||
lib/devices/ios/uiauto/appium/xpath.js
|
||||
*DerivedData
|
||||
__pycache__
|
||||
|
||||
+3
-6
@@ -16,9 +16,6 @@
|
||||
[submodule "submodules/unlock_apk"]
|
||||
path = submodules/unlock_apk
|
||||
url = https://github.com/appium/unlock_apk.git
|
||||
[submodule "submodules/SafariLauncher"]
|
||||
path = submodules/SafariLauncher
|
||||
url = https://github.com/snevesbarros/SafariLauncher.git
|
||||
[submodule "submodules/libimobiledevice-macosx"]
|
||||
path = submodules/libimobiledevice-macosx
|
||||
url = https://github.com/benvium/libimobiledevice-macosx.git
|
||||
@@ -52,7 +49,7 @@
|
||||
[submodule "submodules/deviceconsole"]
|
||||
path = submodules/deviceconsole
|
||||
url = https://github.com/rpetrich/deviceconsole.git
|
||||
[submodule "submodules/appium-ci-gulper"]
|
||||
path = submodules/appium-ci-gulper
|
||||
url = https://github.com/appium/appium-ci-gulper.git
|
||||
[submodule "submodules/appium-ci"]
|
||||
path = submodules/appium-ci
|
||||
url = https://github.com/appium/appium-ci.git
|
||||
branch = master
|
||||
|
||||
+44
-3
@@ -1,4 +1,33 @@
|
||||
CHANGES IN VERSION 1.4
|
||||
CHANGES IN VERSION 1.5 (from 1.4.?)
|
||||
======================================
|
||||
|
||||
*IN DEVELOPMENT*
|
||||
|
||||
- add Tapster support for some more touch methods
|
||||
|
||||
CHANGES IN VERSION 1.4.2 (from 1.4.1)
|
||||
===================================
|
||||
|
||||
#### General
|
||||
- update ES6-based dependencies which got bit by a breaking Regenerator
|
||||
change
|
||||
|
||||
#### iOS
|
||||
- keep track of the correct app ID for Safari webviews (fixes problems in
|
||||
finding the webview for iOS)
|
||||
|
||||
#### Android
|
||||
- fix sendKeys and clear for Samsung devices
|
||||
|
||||
|
||||
CHANGES IN VERSION 1.4.1 (from 1.4)
|
||||
===================================
|
||||
|
||||
#### Chromedriver
|
||||
- have the appium-chromedriver manage the chromedriver binary download/install
|
||||
|
||||
|
||||
CHANGES IN VERSION 1.4 (from 1.3.7)
|
||||
======================================
|
||||
|
||||
#### General
|
||||
@@ -7,10 +36,15 @@ CHANGES IN VERSION 1.4
|
||||
- updated sample code
|
||||
- updated documentation
|
||||
- updated test suite
|
||||
- deprecate node 0.10
|
||||
|
||||
#### iOS
|
||||
- Support for iOS 8.3
|
||||
- fix for issues relating to finding xcode folder
|
||||
- support for iOS 8.3
|
||||
- deprecate iOS 6.1 and iOS 7.0 (support will be removed soon, probably 1.5)
|
||||
- deprecate Xcodes less than 6.3 (moving to support of latest versions only,
|
||||
and Xcode 6.3+ together allows automation of all supported iOS versions). The
|
||||
only exception to this is the combination of Xcode 6.0.1 + iOS 8.0.
|
||||
- fix for issues relating to finding xcode folder and Info.plist
|
||||
- new sever flag `--instruments <path>` to specify custom path to instruments commandLine tool
|
||||
- fix for getOrientation
|
||||
- fix for iOS crash log retrieval
|
||||
@@ -24,6 +58,9 @@ CHANGES IN VERSION 1.4
|
||||
- new capability `disableAndroidWatchers`
|
||||
- deprecated capability `stopAppOnReset`
|
||||
- new capability `dontStopAppOnReset`
|
||||
- fix a crash possibly encountered during extracting app strings
|
||||
- new server argument `suppress-adb-kill-server`
|
||||
- fix issue with `keys()`; now it correctly targets currently-focused element
|
||||
|
||||
#### Selendroid
|
||||
- Support for installApp, isAppInstalled and removeApp
|
||||
@@ -31,10 +68,14 @@ CHANGES IN VERSION 1.4
|
||||
#### Android+Chrome
|
||||
- Chormedriver version updated to 2.15
|
||||
- fix for driver.quit()
|
||||
- use the correct ADB path already identified by Appium
|
||||
|
||||
#### iOS+Safari
|
||||
- fix for handling real device object
|
||||
- fix for safariAllowPopups for iOS 8.x
|
||||
- reduce logging in remote debugger for real devices, and don't use console.log
|
||||
- fix issues with SafariLauncher on real devices
|
||||
- fix execute_async so that it now works at all
|
||||
|
||||
CHANGES IN VERSION 1.3.7
|
||||
======================================
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
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).
|
||||
|
||||
**Note:** we have just recently released Appium 1.0. If you already have a bunch of Appium tests, you might want to check out the [Migrating to 1.0](/docs/en/advanced-concepts/migrating-to-1-0.md) doc!
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
* iOS
|
||||
@@ -88,7 +86,7 @@ You can run an Appium server using node.js or using the application, see below.
|
||||
|
||||
#### Using Node.js
|
||||
|
||||
```center
|
||||
```
|
||||
$ npm install -g appium
|
||||
$ appium &
|
||||
```
|
||||
@@ -133,6 +131,10 @@ Gecko-based platforms.
|
||||
Please take a look at our [contribution documentation](CONTRIBUTING.md)
|
||||
for instructions on how to build, test and run Appium from source.
|
||||
|
||||
### Roadmap
|
||||
|
||||
Interested in where Appium is heading in the future? Check out the [Roadmap](ROADMAP.md)
|
||||
|
||||
### Project Credits & Inspiration
|
||||
|
||||
[Credits](/docs/en/contributing-to-appium/credits.md)
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
Appium Roadmap
|
||||
---------------
|
||||
|
||||
This document is designed to be a living record of the current and projected
|
||||
priorities for the Appium project. It is, of course, always subject to change
|
||||
and exists solely to give the community an idea of where we're going. The
|
||||
roadmap is set by the Appium core team. If you have feature suggestions for
|
||||
Appium, please submit a [GitHub issue](https://github.com/appium/appium/issues)
|
||||
and, if of sufficient size and approved by the team, it will be added here. If
|
||||
you have concerns about the roadmap, you can e-mail the Appium devs at
|
||||
`appium-developers@googlegroups.com`. Note that bugfixes and miscellaneous
|
||||
features are not considered "roadmap projects", and will be addressed in the
|
||||
due course of normal Appium development. This page exists to outline
|
||||
large-scale future plans for Appium.
|
||||
|
||||
## Current Roadmap Projects
|
||||
|
||||
| Project | Description | ETD | Projected Appium Release |
|
||||
|---------|-------------|-----|--------------------------|
|
||||
|[Architecture Overhaul](https://github.com/appium/appium/issues/5169)|Appium's architecture has grown unwieldy enough that adding new features and preventing regressions has become difficult. A rearchitecture is in order to solve these issues as well as migrate the codebase to a form of JS much easier to work in and understand.|8/2015|1.5|
|
||||
|
||||
## Upcoming Projects (in rough order of priority)
|
||||
|
||||
| Project | Description |
|
||||
|---------|-------------|
|
||||
|iOS 8.4 support|Appium should support iOS 8.4 / Xcode 6.2|
|
||||
|Apple Watch support|Appium should support Apple Watch apps via the Watch simulator|
|
||||
|Onboarding Overhaul|The current Appium docs leave a lot to be desired. The format is confusing (it would be better for API docs), the content is outdated, and many things are still undocumented. This project would go back to the drawing board in all of these areas and put together something genuinely helpful for the community, which hopefully reduces the kind of support issues we're currently flooded by.|
|
||||
|New Appium GUIs|The current Appium GUIs (Appium.app and Appium.exe) have a number of issues. They aren't maintained by the core Appium team. They're written in Objective-C and C# respectively: two languages, and specifically, two languages other than Appium's core language. Because of this they lag behind the Appium server and are often broken. However, users love them primarily because of the Inspector feature (the thing that lets them graphically navigate their app), so we have to field a ton of complaints about these projects. There's an opportunity to start over. With [Electron](http://electron.atom.io/), we'd be able to take the new version of Appium we're working on and embed it directly inside a Node-based GUI app written using the languages and technologies the core team is experienced with. We'd have one codebase and could distribute the app on Mac, Windows, and Linux. The GUIs would become an official part of Appium's release cycle so they wouldn't be broken.|
|
||||
|DroidDriver backend integration|Appium's current UiAutomator-based approach to Android automation has a number of issues. We have been investigating [DroidDriver](https://github.com/appium/droiddriver) as a potential replacement. This project (dependend on the rearchitecture) would add DroidDriver as a new `automationBackend`, ideally aiming for feature parity with the current Android support. If successful, the UiAutomator backend would be deprecated and eventually removed.|
|
||||
|Unity3d backend POC|This is a research project to determine whether we can add some kind of support for 3d game automation, probably based on the Unity3d game development platform.|
|
||||
|Dynamic Test App|Our current test applications (the ones used to test Appium's iOS and Android support) are not ideal. We use too many of them and the ones we use have a random hodgepodge of app SDK features, sometimes not enough to sufficiently test automation support. Using [Titanium](https://github.com/appcelerator/titanium_mobile), we can develop an app that allows us to inject whatever native code we need, as we need it. The app thus changes itself test-by-test to have whatever UI features we need for that particular test. It gives us much more fine-grained control over what we're testing. The app code would also be cross-platform and written in JS, which would make it easier for the core team to write and maintain.|
|
||||
|
||||
## Projects Under Consideration (unprioritized)
|
||||
|
||||
| Project | Description |
|
||||
|---------|-------------|
|
||||
|Non-UIAutomation iOS backend|Instruments + UIAutomation has been working well for many Appium users, but requires crazy workarounds to deal with bugs which remain unfixed on the Apple side. We should explore a different approach (a la [Calabash](https://github.com/calabash/calabash-ios)), even though it's in tension with Appium's philosophy of not modifying your app, in order to see if the overall experience is more stable. This new backend would exist alongside the UIAutomation backend.|
|
||||
|Windows Phone Support|There has been some work done on automating Windows Phone apps. We could take this all the way to integration with Appium in the form of a WindowsDriver.|
|
||||
|FirefoxOS Support|Our initial support for FirefoxOS has not been updated. It should be re-added as a FirefoxOSDriver in the new architecture|
|
||||
|
||||
## Completed Roadmap Projects
|
||||
|
||||
| Project | Description | Completed | Appium Release | Notes |
|
||||
|---------|-------------|-----------|----------------|-------|
|
||||
|Appium CI|Appium has unique needs for Continuous Integration because of the highly specialized environments required (iOS simulators, Xcode, Android emulators, the Android SDK, etc...). We therefore need to create a custom CI solution (perhaps sitting underneath Cloudbees or some other OSS-friendly CI server) that can be used for all Appium projects which need integration tests.|5/2015|_N/A_|Usage instructions at [appium/appium-ci](https://github.com/appium/appium-ci)|
|
||||
+3
-12
@@ -7,18 +7,9 @@ else
|
||||
branch="$1"
|
||||
fi
|
||||
|
||||
XCODE_PATH=$(xcode-select -print-path)
|
||||
if test -d $XCODE_PATH/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk ||
|
||||
[ -e /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS\ 7.1.simruntime ]; then
|
||||
echo "Confirmed iOS7.1 SDK available"
|
||||
else
|
||||
echo "You don't have iOS 7.1 SDK available. Switch to Xcode 5?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node --version | grep "v0.10" >/dev/null
|
||||
node --version | grep "v0.12" >/dev/null
|
||||
if [ $? -gt 0 ]; then
|
||||
echo "You need to publish Appium using Node 0.10.x, not $(node --version)"
|
||||
echo "You need to publish Appium using Node 0.12.x, not $(node --version)"
|
||||
exit 1;
|
||||
else
|
||||
echo "Node version OK"
|
||||
@@ -50,7 +41,7 @@ set -e
|
||||
echo "Getting latest from upstream:$branch"
|
||||
git pull upstream $branch
|
||||
echo "Resetting"
|
||||
./reset.sh --hardcore --real-safari --chromedriver-install-all --chromedriver-version 2.15
|
||||
./reset.sh --hardcore --chromedriver-install-all --chromedriver-version 2.15
|
||||
version=$(cat package.json | $(npm bin)/underscore extract version | sed 's/\"//g')
|
||||
echo "Clearing npm cache"
|
||||
npm cache clear appium
|
||||
|
||||
+12
-3
@@ -72,10 +72,19 @@ run_ios_tests() {
|
||||
echo "RUNNING IOS $1 TESTS"
|
||||
echo "---------------------"
|
||||
|
||||
tag_regex=$3
|
||||
if $real_device; then
|
||||
tag_regex="$3|@skip-real-device"
|
||||
fi
|
||||
|
||||
DEVICE=$2 time $appium_mocha -g $3 -i \
|
||||
test/functional/common \
|
||||
test/functional/ios
|
||||
if $real_device; then
|
||||
DEVICE=$2 REAL_DEVICE=true time $appium_mocha -g $tag_regex -i \
|
||||
test/functional/ios
|
||||
else
|
||||
DEVICE=$2 time $appium_mocha -g $3 -i \
|
||||
test/functional/common \
|
||||
test/functional/ios
|
||||
fi
|
||||
}
|
||||
|
||||
if $ios6_only || $ios_only || $all_tests; then
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
function as_user {
|
||||
local user="$1"
|
||||
local user_pid=$(ps -axj | awk "/^$user / {print \$2;exit}")
|
||||
local command="sudo launchctl bsexec $user_pid sudo -u '$user' $2"
|
||||
echo "Running:"
|
||||
echo "$command"
|
||||
eval $command
|
||||
}
|
||||
|
||||
function as_current_user {
|
||||
as_user "$(whoami)" "$*"
|
||||
}
|
||||
|
||||
as_current_user "$*"
|
||||
@@ -2,15 +2,15 @@
|
||||
{ set -e; } 2>/dev/null
|
||||
{ set -x; } 2>/dev/null
|
||||
|
||||
# Loading appium-ci-gulper submodule
|
||||
# Loading appium-ci submodule
|
||||
export APPIUM_ROOT="$(pwd)"
|
||||
git submodule update --remote --init submodules/appium-ci-gulper
|
||||
pushd submodules/appium-ci-gulper
|
||||
git submodule update --remote --init submodules/appium-ci
|
||||
pushd submodules/appium-ci
|
||||
git pull origin master
|
||||
{ set +x; } 2>/dev/null
|
||||
popd
|
||||
|
||||
# Running appium-ci-gulper submodule
|
||||
# This sets up node etc...
|
||||
pushd submodules/appium-ci/jenkins-gulper
|
||||
source ./setup
|
||||
{ set -x; } 2>/dev/null
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Appium 是一个自动化测试开源工具,支持 iOS 平台和 Android 平
|
||||
|
||||
那么 Appium 架构是如何实现这个哲学呢?为了满足第一条,Appium 真正的工作引擎其实是第三方自动化框架。这样,我们就不需在你的应用里植入 Appium 相关或者第三方的代码。这意味着你测试使用的应用与最终发布的应用并无二致。我们使用以下的第三方框架:
|
||||
|
||||
* iOS: 苹果的 [UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)
|
||||
* iOS: 苹果的 [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由单独的项目[Selendroid](http://selendroid.io)提供支持 )
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Appimu的时候支持的平台版本和上述测试方式的必备条件。
|
||||
|
||||
* 版本号:6.1,7.0,以及7.1。
|
||||
* 支持设备:iPhone模拟器,iPad模拟器以及iPhones和iPads真机。
|
||||
* 是否支持原生应用:支持。同时支持模拟器中调试应用版本和正确签名的真机ipa。其他相关支持由苹果的[UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)框架提供。
|
||||
* 是否支持原生应用:支持。同时支持模拟器中调试应用版本和正确签名的真机ipa。其他相关支持由苹果的[UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/)框架提供。
|
||||
* 是否支持内置移动浏览器:支持。Safari浏览器已经通过测试。对于真机,则需要安装调试工具ios-webkit-remote-debugger。很遗憾,对于Safari的原生界面的自动化是不支持的。更多信息请移步至[mobile web doc](/docs/cn/writing-running-appium/mobile-web.cn.md) 。
|
||||
* 是否支持混合应用:支持。同样对于真机需要安装调试工具ios-webkit-remote-debugger,更多详情请移步至[hybrid doc](/docs/cn/advanced-concepts/hybrid.cn.md) 查看详情。
|
||||
* 是否支持在同一个session中执行多个应用的自动化:不支持。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## iOS 谓词(Predicate)
|
||||
|
||||
在查看 *'-ios uiautomation'* 搜索策略时了解 **谓词(Predicate)** 十分必要。 [UIAutomation JavaScript API](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)有下列几种非常有用的方法:
|
||||
在查看 *'-ios uiautomation'* 搜索策略时了解 **谓词(Predicate)** 十分必要。 [UIAutomation JavaScript API](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/)有下列几种非常有用的方法:
|
||||
|
||||
```center
|
||||
(UIAElement) UIAElementArray.firstWithPredicate(PredicateString predicateString)
|
||||
@@ -243,4 +243,4 @@ predicate = "name contains[c] '#{text}' || label contains[c] '#{text}' || value
|
||||
element = execute_script(%Q(au.mainApp().getFirstWithPredicate("#{predicate}");))
|
||||
|
||||
puts element.name # Buttons, Various uses of UIButton
|
||||
```
|
||||
```
|
||||
|
||||
@@ -31,7 +31,7 @@ meet requirement #1 by using vendor-provided automation frameworks under the
|
||||
hood. That way, we don't need to compile in any Appium-specific or
|
||||
third-party code or frameworks to your app. This means **you're testing the same app you're shipping**. The vendor-provided frameworks we use are:
|
||||
|
||||
* iOS: Apple's [UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)
|
||||
* 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))
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ instructions.
|
||||
* Devices: iPhone Simulator, iPad Simulator, and real iPhones and iPads
|
||||
* Native app support: Yes, with debug version of .app (simulator),
|
||||
or correctly-signed .ipa (real devices). Underlying support is provided by
|
||||
Apple's [UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)
|
||||
Apple's [UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/)
|
||||
framework.
|
||||
* Mobile web support: Yes, via automation of mobile Safari. For real devices,
|
||||
ios-webkit-remote-debugger is required, and automation of native aspects of
|
||||
|
||||
@@ -67,6 +67,14 @@ own items in the SDK manager. Make sure you install the build-tools and platform
|
||||
a new version of Xcode, as well.
|
||||
* 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
|
||||
UIAutomation (and therefore Appium) by default. You need to set the
|
||||
accessibility status to 'enabled' on them. The way to do this in code is:
|
||||
|
||||
```center
|
||||
[myCustomView setAccessibilityEnabled:YES];
|
||||
```
|
||||
|
||||
* Tests on iOS may exhibit symptoms similar to a memory leak including sluggish
|
||||
performance or hangs. If you experience this problem, it's likely due to a
|
||||
known issue with NSLog. One option is to remove NSLog from your code.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### These are the projects that inspired us, or are depended upon to make Appium awesome.
|
||||
|
||||
* [Apple UIAutomation](http://developer.apple.com/library/ios/#documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)
|
||||
* [Apple UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/)
|
||||
* [GhostDriver](https://github.com/detro/ghostdriver)
|
||||
* [IOS Auto](https://github.com/penguinho/applecart)
|
||||
* [IOS Driver](https://github.com/ios-driver/ios-driver)
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
## Style guide for contributors
|
||||
|
||||
Thanks for your contribution to Appium! Here are the principles we use when
|
||||
writing javascript. Please conform to these so we can merge your pull request
|
||||
without going back and forth about style. The main principle is: *make your
|
||||
code look like the surrounding code*.
|
||||
|
||||
### Rebasing
|
||||
|
||||
Commits in a pull request should consist of [logical changes](https://github.com/appium/appium/pull/920#issuecomment-21588553).
|
||||
If there are multiple authors, make sure each author has their own commit.
|
||||
It's not a good idea to modify author information. Merge commits should be
|
||||
rebased out of pull requests.
|
||||
|
||||
### Linting
|
||||
|
||||
All code (except for code in `bootstrap.js` which uses proprietary Apple
|
||||
methods) must pass JSLint. To check your code, you can simply run `grunt
|
||||
lint` from the Appium repo dir. If you've created a new .js file,
|
||||
please make sure it is covered by the wildcards in `grunt.js` or that it is
|
||||
added specifically.
|
||||
|
||||
It's easy to have your code linted as you type, which makes the whole process
|
||||
much smoother. We like [jshint](http://www.jshint.com),
|
||||
which has integrations with a lot of source code editors. The file `
|
||||
.jshintrc` is checked into the repo, so by adding jshint to your editor, you'll
|
||||
take advantage of the linting.
|
||||
|
||||
Since jshint does not enforce code style anymore, we also use
|
||||
[jscs](https://github.com/mdevils/node-jscs), for which it also exists some
|
||||
source editor integrations.
|
||||
|
||||
These configuration files define the warnings you will see in your favorite
|
||||
editor. See [this page for jshint](http://www.jshint.com/platforms/) and
|
||||
[this page for jscs](https://github.com/mdevils/node-jscs#friendly-packages) to
|
||||
get the list of editors and platforms supported and how setup your editor for
|
||||
automatic linting.
|
||||
|
||||
### Style notes
|
||||
|
||||
We use a future version of JavaScript and take advantage of the Babel
|
||||
transpiler to render it down to what is supported by current versions of
|
||||
Node.js. We use ES2015 (link needed) (formerly called ES6) with some
|
||||
not-yet-standard features, namely `async/await` (link needed). This style guide
|
||||
must be followed diligently in all Appium contributions!
|
||||
|
||||
* Use two spaces for indentation, *no tabs*
|
||||
* Use single spaces around operators
|
||||
|
||||
```javascript
|
||||
var x = 1;
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
var x=1;
|
||||
```
|
||||
|
||||
* Spaces after commas and colons in lists, objects, function calls, etc...
|
||||
|
||||
```javascript
|
||||
var x = myFunc("lol", {foo: bar, baz: boo});
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
var x = myFunc("lol",{foo:bar,baz:boo});
|
||||
```
|
||||
|
||||
* Always end statements with semicolons
|
||||
* Comma-first
|
||||
|
||||
```javascript
|
||||
var x = {
|
||||
foo: 'bar'
|
||||
, baz: 'boo'
|
||||
, wuz: 'foz'
|
||||
};
|
||||
```
|
||||
|
||||
* Brackets for `function`, `if`, etc... go on same line, `else` gets sandwiched
|
||||
|
||||
```javascript
|
||||
if (foo === bar) {
|
||||
// do something
|
||||
} else {
|
||||
// do something else
|
||||
}
|
||||
```
|
||||
|
||||
* Space after `if`, `for`, and `function`:
|
||||
|
||||
```javascript
|
||||
if (foo === bar) {
|
||||
```
|
||||
```javascript
|
||||
for (var i = 0; i < 10; i ++) {
|
||||
```
|
||||
```javascript
|
||||
var lol = function (foo) {
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
if(foo === bar) {
|
||||
```
|
||||
```javascript
|
||||
for(var i = 0; i < 10; i ++) {
|
||||
```
|
||||
```javascript
|
||||
var lol = function(foo) {
|
||||
```
|
||||
|
||||
* Avoid bracketless `if` for one-liners:
|
||||
|
||||
```javascript
|
||||
if (foo === bar) {
|
||||
foo++;
|
||||
}
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
if (foo === bar)
|
||||
foo++;
|
||||
```
|
||||
except in the case of short-circuiting to a callback in the event of an error
|
||||
```javascript
|
||||
if (err) return cb(err);
|
||||
```
|
||||
|
||||
* Use `===`, not `==`, and `!==`, not `!=` for no surprises
|
||||
* Line length shouldn't be longer than 79 characters
|
||||
* Break up long strings like this:
|
||||
|
||||
```javascript
|
||||
myFunc("This is a really long string that's longer " +
|
||||
"than 79 characters so I broke it up, woo");
|
||||
```
|
||||
|
||||
* Comments should line up with code
|
||||
|
||||
```javascript
|
||||
if (foo === 5) {
|
||||
myFunc(foo);
|
||||
// foo++;
|
||||
}
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
if (foo === 5) {
|
||||
myFunc(foo);
|
||||
//foo++;
|
||||
}
|
||||
```
|
||||
|
||||
* Subclassing by extending prototypes
|
||||
|
||||
```javascript
|
||||
var _ = require('underscore');
|
||||
|
||||
var SuperClass = function () {
|
||||
this.init();
|
||||
};
|
||||
|
||||
SuperClass.prototype.init = function () {
|
||||
// initialize
|
||||
};
|
||||
|
||||
// Create a subclass
|
||||
|
||||
var SubClass = function () {
|
||||
this.init();
|
||||
};
|
||||
|
||||
_.extend(SubClass.prototype, SuperClass.prototype);
|
||||
```
|
||||
|
||||
* Callbacks are always last in function definitions
|
||||
|
||||
```javascript
|
||||
var foo = function (arg1, arg2, cb) {
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
* Define functions as variables
|
||||
|
||||
```javascript
|
||||
var myFunc = function (a, b, c) {};
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
function myFunc (a, b, c) {}
|
||||
```
|
||||
|
||||
* Variable names should be camelCased:
|
||||
|
||||
```javascript
|
||||
var myVariable = 42;
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
var my_variable = 42;
|
||||
```
|
||||
|
||||
* Check for undefined
|
||||
|
||||
```javascript
|
||||
typeof myVariable === "undefined"
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
myVariable === undefined
|
||||
```
|
||||
|
||||
* Define a variable with a default value
|
||||
|
||||
```javascript
|
||||
var x = y || z;
|
||||
```
|
||||
not
|
||||
```javascript
|
||||
var x = y ? y : z;
|
||||
```
|
||||
|
||||
### Test Style:
|
||||
|
||||
Keep on the same line if it makes sense semantically and length is not an issue:
|
||||
|
||||
Examples:
|
||||
|
||||
```javascript
|
||||
driver.elementByTagName('el1').should.become("123")
|
||||
.nodeify(done);
|
||||
|
||||
driver
|
||||
.elementsByTagName('el1').should.eventually.have.length(0)
|
||||
.nodeify(done);
|
||||
```
|
||||
|
||||
Alternatively use extra indents to improve readability:
|
||||
|
||||
```javascript
|
||||
h.driver
|
||||
.elementById('comments')
|
||||
.clear()
|
||||
.click()
|
||||
.keys("hello world")
|
||||
.getValue()
|
||||
.should.become("hello world")
|
||||
.elementById('comments')
|
||||
.getValue().should.become("hello world")
|
||||
.nodeify(done);
|
||||
|
||||
h.driver
|
||||
.execute("'nan'--")
|
||||
.should.be.rejectedWith("status: 13")
|
||||
.nodeify(done);
|
||||
```
|
||||
@@ -56,6 +56,7 @@
|
||||
|`noSign`| Skip checking and signing of app with debug keys, will work only with UiAutomator and not with selendroid, default `false`| `true` or `false`|
|
||||
|`ignoreUnimportantViews`| Calls the `setCompressedLayoutHierarchy()` uiautomator function. This capability can speed up test execution, since Accessibility commands will run faster ignoring some elements. The ignored elements will not be findable, which is why this capability has also been implemented as a toggle-able *setting* as well as a capability. Defaults to `false` | `true` or `false`
|
||||
|`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 chrome driver. For more information see [chromeOptions](https://sites.google.com/a/chromium.org/chromedriver/capabilities) | `chromeOptions: {args: ['--disable-popup-blocking']}` |
|
||||
|
||||
### iOS Only
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## iOS Predicate
|
||||
|
||||
It is worth looking at *'-ios uiautomation'* search strategy with **Predicates**.
|
||||
[UIAutomation JavaScript API](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html) has following methods which can are very useful.
|
||||
[UIAutomation JavaScript API](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/) has following methods which can are very useful.
|
||||
|
||||
```center
|
||||
(UIAElement) UIAElementArray.firstWithPredicate(PredicateString predicateString)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Appium server arguments
|
||||
## Appium server arguments
|
||||
|
||||
Usage: `node . [flags]`
|
||||
|
||||
## Server flags
|
||||
### Server flags
|
||||
All flags are optional, but some are required in conjunction with certain others.
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Appium 是一个自动化测试开源工具,支持 iOS 平台和 Android 平
|
||||
这样,我们就不需在你的应用里植入 Appium 特定或者第三方的代码。这就意味着你在测试你将发布的应用。我们
|
||||
使用以下的第三方框架:
|
||||
|
||||
* iOS: 苹果的 [UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)
|
||||
* iOS: 苹果的 [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))
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# iOS Predicate
|
||||
|
||||
I'd like to share my experience in using *'-ios uiautomation'* search strategy with **Predicates**. After using Instruments/UIAutomation tool for a long time I paid attention on the following methods in [UIAutomation JavaScript API](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html):
|
||||
I'd like to share my experience in using *'-ios uiautomation'* search strategy with **Predicates**. After using Instruments/UIAutomation tool for a long time I paid attention on the following methods in [UIAutomation JavaScript API](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/):
|
||||
|
||||
```java
|
||||
(UIAElement) UIAElementArray.firstWithPredicate(PredicateString predicateString)
|
||||
|
||||
@@ -11,7 +11,7 @@ Appimu时候支持的平台版本和上述测试方式的必备条件。
|
||||
* 版本号:6.1 , 7.0 , and 7.1
|
||||
* 支持设备: iPhone 模拟器, iPad 模拟器以及iPhones 和 iPads真机
|
||||
* 是否支持原生应用:支持 。同时支持模拟器中调试应用版本和正确签名的真机ipa。
|
||||
其他相关支持由苹果的[UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html)
|
||||
其他相关支持由苹果的[UIAutomation](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/)
|
||||
框架提供。
|
||||
* 是否支持内嵌浏览器:支持, Safari浏览器已经通过测试。对于
|
||||
真机,则需要安装调试工具 ios-webkit-remote-debugger,很遗憾
|
||||
|
||||
+1
-1
@@ -355,7 +355,7 @@ module.exports.signApp = function (appName, certName, cb) {
|
||||
};
|
||||
|
||||
module.exports.buildSafariLauncherApp = function (cb, sdk, xcconfig) {
|
||||
var appRoot = path.resolve(__dirname, "submodules", "SafariLauncher");
|
||||
var appRoot = path.resolve(require.resolve('safari-launcher'), '..');
|
||||
module.exports.build(appRoot, function (err) {
|
||||
if (err !== null) {
|
||||
console.log(err);
|
||||
|
||||
+119
-42
@@ -62,68 +62,110 @@ gulp.task('test-unit', function () {
|
||||
.on('error', console.warn.bind(console));
|
||||
});
|
||||
|
||||
function splitAndroidE2ETests() {
|
||||
function splitE2ETests(srcGlobPattern) {
|
||||
var testFiles = [];
|
||||
var androidGroups = {};
|
||||
var groups = {};
|
||||
return promisePipe(
|
||||
gulp.src(['test/functional/common/**/*-specs.js', 'test/functional/android/**/*-specs.js',
|
||||
'!test/functional/android/chrome/**'], {read: false})
|
||||
gulp.src(srcGlobPattern , {read: false})
|
||||
.pipe(through(function (file) {
|
||||
testFiles.push(path.relative(file.cwd ,file.path));
|
||||
})).on('close', function () {
|
||||
testFiles.sort();
|
||||
androidGroups = splitArray(testFiles, argv.androidTestSplit);
|
||||
groups = splitArray(testFiles, argv.testSplit);
|
||||
})).then(function () {
|
||||
assert(androidGroups.length === argv.androidTestSplit);
|
||||
return androidGroups;
|
||||
assert(groups.length === argv.testSplit);
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
function splitAndroidE2ETests() {
|
||||
return splitE2ETests([
|
||||
'test/functional/common/**/*-specs.js',
|
||||
'test/functional/android/**/*-specs.js',
|
||||
'!test/functional/android/chrome/**'
|
||||
]);
|
||||
}
|
||||
|
||||
function splitIosE2ETests() {
|
||||
return splitE2ETests([
|
||||
'test/functional/common/**/*-specs.js',
|
||||
'test/functional/ios/**/*-specs.js'
|
||||
]);
|
||||
}
|
||||
|
||||
function killProcs() {
|
||||
_(childProcs).each(function (child) {
|
||||
try { child.kill(); } catch (err) {}
|
||||
return exec("sudo pkill -f 'sudo -u appium node' || true").then(function () { // killing bsexec processes
|
||||
_(childProcs).each(function (child) {
|
||||
try { child.kill(); } catch (err) {}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('kill-procs', function () {
|
||||
killProcs();
|
||||
});
|
||||
|
||||
gulp.task('show-android-e2e-tests-split', function () {
|
||||
return splitAndroidE2ETests()
|
||||
.then(function (androidGroups) {
|
||||
console.log('Android groups:');
|
||||
_(androidGroups).each(function (group, i) {
|
||||
function showSplit(splitPromise, prefix) {
|
||||
return splitPromise
|
||||
.then(function (groups) {
|
||||
console.log(prefix + ' groups:');
|
||||
_(groups).each(function (group, i) {
|
||||
console.log(i + 1, '-->', group);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('show-android-e2e-tests-split', function () {
|
||||
return showSplit(splitAndroidE2ETests(), 'Android');
|
||||
});
|
||||
|
||||
gulp.task('test-android-e2e', function () {
|
||||
return splitAndroidE2ETests().then(function (androidGroups) {
|
||||
gulp.task('show-ios-e2e-tests-split', function () {
|
||||
return showSplit(splitIosE2ETests(), 'iOS');
|
||||
});
|
||||
|
||||
function newMochaE2EOpts() {
|
||||
var opts = newMochaOpts();
|
||||
opts.env.DEVICE='android';
|
||||
opts.env.VERBOSE=1;
|
||||
opts.flags.g = '@skip-android-all|@android-arm-only|@skip-ci';
|
||||
opts.flags.i = true;
|
||||
opts.concurrency = 1;
|
||||
opts.liveOutput = true;
|
||||
opts.liveOutputPrepend= 'client -> ';
|
||||
opts.fileOutput = 'client.log';
|
||||
return opts;
|
||||
}
|
||||
|
||||
gulp.task('test-android-e2e', function () {
|
||||
return splitAndroidE2ETests().then(function (testGroups) {
|
||||
var opts = newMochaE2EOpts();
|
||||
opts.env.DEVICE='android';
|
||||
opts.env.VERBOSE=1;
|
||||
opts.flags.g = '@skip-android-all|@android-arm-only|@skip-ci';
|
||||
opts.flags.i = true;
|
||||
var mocha = mochaStream(opts);
|
||||
var androidGroup = androidGroups[argv.androidTestGroup - 1];
|
||||
console.log('running tests for:' + androidGroup);
|
||||
return promisePipe( gulp.src(androidGroup, {read: false})
|
||||
var testGroup = testGroups[argv.testGroup - 1];
|
||||
console.log('running tests for:' + testGroup);
|
||||
return promisePipe(gulp.src(testGroup, {read: false})
|
||||
.pipe(mocha)
|
||||
.on('error', function (err) {
|
||||
killProcs();
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
).fin(function () {
|
||||
return killProcs();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('launch-appium', function () {
|
||||
gulp.task('test-ios-e2e', function () {
|
||||
return splitIosE2ETests().then(function (testGroups) {
|
||||
var opts = newMochaE2EOpts();
|
||||
opts.env.DEVICE='ios81'; // TODO: make that configurable
|
||||
opts.env.VERBOSE=1;
|
||||
opts.flags.g = '@skip-ios81|@skip-ios8|@skip-ios-all|@skip-ios7up|@skip-ci';
|
||||
opts.flags.i = true;
|
||||
var mocha = mochaStream(opts);
|
||||
var testGroup = testGroups[argv.testGroup - 1];
|
||||
console.log('running tests for:' + testGroup);
|
||||
return promisePipe(gulp.src(testGroup, {read: false})
|
||||
.pipe(mocha)
|
||||
).fin(function () {
|
||||
return killProcs();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function launchAppium(opts) {
|
||||
opts = opts || {};
|
||||
var deferred = Q.defer();
|
||||
var out = new stream.PassThrough();
|
||||
|
||||
@@ -135,14 +177,41 @@ gulp.task('launch-appium', function () {
|
||||
console.log('server -->', line);
|
||||
});
|
||||
out.pipe(fs.createWriteStream('appium.log'));
|
||||
var child = spawn("node", ['.'], { detached: false });
|
||||
childProcs.push(child);
|
||||
child.stdout.pipe(out);
|
||||
child.stderr.pipe(out);
|
||||
child.on('close', function () {
|
||||
deferred.reject('Something went wrong!');
|
||||
});
|
||||
(function () {
|
||||
if (opts.asCurrentUser) {
|
||||
console.log('Running appium as current user.');
|
||||
var currentUser;
|
||||
return exec('whoami').spread(function (stdout) {
|
||||
currentUser = stdout.trim();
|
||||
console.log('currentUser ->', currentUser);
|
||||
var cmd = "ps -axj | grep loginwindow | awk \"/^" + currentUser + " / {print \\$2;exit}\"";
|
||||
return exec(cmd);
|
||||
}).spread(function (stdout) {
|
||||
var userPid = stdout.trim();
|
||||
console.log('userPid ->', userPid);
|
||||
return spawn("sudo", [ 'launchctl', 'bsexec', userPid,
|
||||
'sudo', '-u', currentUser ,'node', '.'], { detached: false });
|
||||
});
|
||||
} else {
|
||||
return new Q(spawn("node", ['.'], { detached: false }));
|
||||
}
|
||||
})().then(function (child) {
|
||||
childProcs.push(child);
|
||||
child.stdout.pipe(out);
|
||||
child.stderr.pipe(out);
|
||||
child.on('close', function () {
|
||||
deferred.reject('Something went wrong!');
|
||||
});
|
||||
}).done();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
gulp.task('launch-appium', function () {
|
||||
launchAppium();
|
||||
});
|
||||
|
||||
gulp.task('launch-appium-as-current-user', function () {
|
||||
launchAppium({asCurrentUser: true});
|
||||
});
|
||||
|
||||
gulp.task('launch-emu', function () {
|
||||
@@ -152,7 +221,7 @@ gulp.task('launch-emu', function () {
|
||||
var emuErrored = false;
|
||||
function waitForEmu() {
|
||||
var INIT_WAIT = 15000;
|
||||
var MAX_WAIT_MS = 180000;
|
||||
var MAX_WAIT_MS = 300000;
|
||||
var POOL_MS = 5000;
|
||||
var startMs = Date.now();
|
||||
function _waitForEmu () {
|
||||
@@ -221,7 +290,15 @@ gulp.task('launch-emu', function () {
|
||||
});
|
||||
|
||||
gulp.task('run-android-e2e', function () {
|
||||
return runSequence(['launch-emu', 'launch-appium'], 'test-android-e2e', 'kill-procs')
|
||||
return runSequence(['launch-emu', 'launch-appium'], 'test-android-e2e')
|
||||
.catch(function (err) {
|
||||
killProcs();
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('run-ios-e2e', function () {
|
||||
return runSequence('launch-appium-as-current-user', 'test-ios-e2e')
|
||||
.catch(function (err) {
|
||||
killProcs();
|
||||
throw err;
|
||||
|
||||
@@ -819,6 +819,7 @@ androidCommon.extractStringsFromApk = function (makeStrings, language, cb) {
|
||||
};
|
||||
|
||||
androidCommon.extractStrings = function (cb, language) {
|
||||
logger.debug("Extracting strings for language: " + (language || "default"));
|
||||
var outputPath = path.resolve(this.args.tmpDir, this.args.appPackage);
|
||||
var stringsJson = 'strings.json';
|
||||
if (!fs.existsSync(this.args.app)) {
|
||||
@@ -835,8 +836,21 @@ androidCommon.extractStrings = function (cb, language) {
|
||||
this.language = null;
|
||||
return cb(err);
|
||||
}
|
||||
var file = fs.readFileSync(path.join(outputPath, stringsJson), 'utf8');
|
||||
this.apkStrings = JSON.parse(file);
|
||||
var file;
|
||||
try {
|
||||
logger.debug("Reading strings from converted strings.json");
|
||||
file = fs.readFileSync(path.join(outputPath, stringsJson)).toString('utf8');
|
||||
this.apkStrings = JSON.parse(file);
|
||||
} catch (e) {
|
||||
var msg = "Could not parse strings from strings.json. Original " +
|
||||
"error: " + e.message;
|
||||
logger.debug(msg);
|
||||
if (file) {
|
||||
logger.debug("Content started with: " + file.slice(0, 300));
|
||||
}
|
||||
return cb(new Error(msg));
|
||||
}
|
||||
logger.debug("Setting language to " + (language || "default"));
|
||||
this.language = language;
|
||||
cb();
|
||||
}.bind(this));
|
||||
|
||||
@@ -202,15 +202,15 @@ androidController.getPageIndex = function (elementId, cb) {
|
||||
cb(new NotYetImplementedError(), null);
|
||||
};
|
||||
|
||||
androidController.keys = function (elementId, keys, cb) {
|
||||
androidController.keys = function (keys, cb) {
|
||||
var params = {
|
||||
elementId: elementId,
|
||||
text: keys
|
||||
text: keys,
|
||||
replace: false
|
||||
};
|
||||
if (this.args.unicodeKeyboard) {
|
||||
params.unicodeKeyboard = true;
|
||||
}
|
||||
this.proxy(["element:setText", params], cb);
|
||||
this.proxy(['setText', params], cb);
|
||||
};
|
||||
|
||||
androidController.frame = function (frame, cb) {
|
||||
@@ -1026,7 +1026,7 @@ androidController.performTouch = function (gestures, cb) {
|
||||
};
|
||||
|
||||
androidController.parseTouch = function (gestures, multi, cb) {
|
||||
if (_.last(gestures).action === 'release') {
|
||||
if (multi && _.last(gestures).action === 'release') {
|
||||
gestures.pop();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
var logger = require('../../server/logger.js').get('appium')
|
||||
, _ = require('underscore')
|
||||
, errors = require('../../server/errors.js')
|
||||
, path = require('path')
|
||||
, system = require('appium-support').system
|
||||
, isWindows = system.isWindows()
|
||||
, isLinux = system.isLinux()
|
||||
, exec = require('child_process').exec
|
||||
, UnknownError = errors.UnknownError
|
||||
, async = require('async')
|
||||
, Chromedriver = require('appium-chromedriver')
|
||||
@@ -277,38 +272,4 @@ androidHybrid.initAutoWebview = function (cb) {
|
||||
}
|
||||
};
|
||||
|
||||
// get the correct chromedriver executable path based on our system
|
||||
// TODO: don't download/build chromedriver in reset.sh, instead let this be
|
||||
// something that the appium-chromedriver package manages
|
||||
androidHybrid.initChromedriverPath = function (cb) {
|
||||
if (this.args.chromedriverExecutable) {
|
||||
cb();
|
||||
} else {
|
||||
var setPath = function (platform, executable) {
|
||||
this.args.chromedriverExecutable = path.resolve(__dirname, "..", "..",
|
||||
"..", "build", "chromedriver", platform, executable);
|
||||
logger.debug("Set chromedriver binary as: " + this.args.chromedriverExecutable);
|
||||
}.bind(this);
|
||||
if (isLinux) {
|
||||
logger.debug("Determining linux architecture");
|
||||
exec("uname -m", function (err, stdout) {
|
||||
var executable;
|
||||
if (err) return cb(err);
|
||||
if (stdout.trim() === "i686") {
|
||||
executable = "chromedriver32";
|
||||
} else {
|
||||
executable = "chromedriver64";
|
||||
}
|
||||
setPath("linux", executable);
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
var executable = isWindows ? "chromedriver.exe" : "chromedriver";
|
||||
var platform = isWindows ? "windows" : "mac";
|
||||
setPath(platform, executable);
|
||||
cb();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = androidHybrid;
|
||||
|
||||
@@ -101,7 +101,6 @@ Android.prototype.start = function (cb, onDie) {
|
||||
this.initAdb.bind(this),
|
||||
this.packageAndLaunchActivityFromManifest.bind(this),
|
||||
this.initUiautomator.bind(this),
|
||||
this.initChromedriverPath.bind(this),
|
||||
this.prepareDevice.bind(this),
|
||||
this.checkApiLevel.bind(this),
|
||||
this.pushStrings.bind(this),
|
||||
@@ -330,12 +329,15 @@ Android.prototype.pushStrings = function (cb, language) {
|
||||
if (err) {
|
||||
if (!fs.existsSync(this.args.app)) {
|
||||
// apk doesn't exist locally so remove old strings.json
|
||||
logger.debug("Could not get strings, but it looks like we had an " +
|
||||
"old strings file anyway, so ignoring");
|
||||
return this.adb.rimraf(remotePath + '/' + stringsJson, function (err) {
|
||||
if (err) return cb(new Error("Could not remove old strings"));
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
// if we can't get strings, just dump an empty json and continue
|
||||
logger.warn("Could not get strings, continuing anyway");
|
||||
var remoteFile = remotePath + '/' + stringsJson;
|
||||
return this.adb.shell("echo '{}' > " + remoteFile, cb);
|
||||
}
|
||||
@@ -573,8 +575,8 @@ Android.prototype.setActualCapabilities = function (cb) {
|
||||
logger.debug("Device is at release version " + version);
|
||||
this.capabilities.platformVersion = version;
|
||||
}
|
||||
return cb();
|
||||
}.bind(this));
|
||||
cb();
|
||||
};
|
||||
|
||||
Android.prototype.resetTimeout = deviceCommon.resetTimeout;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class AndroidElement {
|
||||
public boolean click() throws UiObjectNotFoundException {
|
||||
return el.click();
|
||||
}
|
||||
|
||||
|
||||
public boolean exists() {
|
||||
return el.exists();
|
||||
}
|
||||
@@ -65,13 +65,13 @@ public class AndroidElement {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Point getAbsolutePosition(final Point point)
|
||||
throws UiObjectNotFoundException, InvalidCoordinatesException {
|
||||
final Rect rect = this.getBounds();
|
||||
|
||||
|
||||
Logger.debug("Element bounds: " + rect.toShortString());
|
||||
|
||||
|
||||
return PositionHelper.getAbsolutePosition(point, rect, new Point(rect.left, rect.top), false);
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ public class AndroidElement {
|
||||
if (res.equals("")) {
|
||||
res = getText();
|
||||
}
|
||||
} else if (attr.equals("contentDescription")) {
|
||||
res = getContentDesc();
|
||||
} else if (attr.equals("text")) {
|
||||
res = getText();
|
||||
} else if (attr.equals("className")) {
|
||||
|
||||
+3
-2
@@ -33,8 +33,9 @@ public class GetAttribute extends CommandHandler {
|
||||
try {
|
||||
final AndroidElement el = command.getElement();
|
||||
final String attr = params.get("attribute").toString();
|
||||
if (attr.equals("name") || attr.equals("text")
|
||||
|| attr.equals("className") || attr.equals("resourceId")) {
|
||||
if (attr.equals("name") || attr.equals("contentDescription")
|
||||
|| attr.equals("text") || attr.equals("className")
|
||||
|| attr.equals("resourceId")) {
|
||||
return getSuccessResult(el.getStringAttribute(attr));
|
||||
} else {
|
||||
return getSuccessResult(String.valueOf(el.getBoolAttribute(attr)));
|
||||
|
||||
+55
-43
@@ -2,7 +2,10 @@ package io.appium.android.bootstrap.handler;
|
||||
|
||||
import com.android.uiautomator.core.UiDevice;
|
||||
import com.android.uiautomator.core.UiObjectNotFoundException;
|
||||
import com.android.uiautomator.core.UiSelector;
|
||||
import io.appium.android.bootstrap.*;
|
||||
import io.appium.android.bootstrap.exceptions.ElementNotFoundException;
|
||||
import io.appium.android.bootstrap.handler.Find;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.Hashtable;
|
||||
@@ -26,51 +29,60 @@ public class SetText extends CommandHandler {
|
||||
@Override
|
||||
public AndroidCommandResult execute(final AndroidCommand command)
|
||||
throws JSONException {
|
||||
|
||||
AndroidElement el = null;
|
||||
if (command.isElementCommand()) {
|
||||
// Only makes sense on an element
|
||||
try {
|
||||
final Hashtable<String, Object> params = command.params();
|
||||
final AndroidElement el = command.getElement();
|
||||
boolean replace = Boolean.parseBoolean(params.get("replace").toString());
|
||||
String text = params.get("text").toString();
|
||||
boolean pressEnter = false;
|
||||
if (text.endsWith("\\n")) {
|
||||
pressEnter = true;
|
||||
text = text.replace("\\n", "");
|
||||
Logger.debug("Will press enter after setting text");
|
||||
}
|
||||
boolean unicodeKeyboard = false;
|
||||
if (params.get("unicodeKeyboard") != null) {
|
||||
unicodeKeyboard = Boolean.parseBoolean(params.get("unicodeKeyboard").toString());
|
||||
}
|
||||
String currText = el.getText();
|
||||
new Clear().execute(command);
|
||||
if (!el.getText().isEmpty()) {
|
||||
// clear could have failed, or we could have a hint in the field
|
||||
// we'll assume it is the latter
|
||||
Logger.debug("Text not cleared. Assuming remainder is hint text.");
|
||||
currText = "";
|
||||
}
|
||||
if (!replace) {
|
||||
text = currText + text;
|
||||
}
|
||||
final boolean result = el.setText(text, unicodeKeyboard);
|
||||
if (!result) {
|
||||
return getErrorResult("el.setText() failed!");
|
||||
}
|
||||
if (pressEnter) {
|
||||
final UiDevice d = UiDevice.getInstance();
|
||||
d.pressEnter();
|
||||
}
|
||||
return getSuccessResult(result);
|
||||
} catch (final UiObjectNotFoundException e) {
|
||||
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
|
||||
e.getMessage());
|
||||
} catch (final Exception e) { // handle NullPointerException
|
||||
return getErrorResult("Unknown error");
|
||||
}
|
||||
Logger.debug("Using element passed in.");
|
||||
el = command.getElement();
|
||||
} else {
|
||||
return getErrorResult("Unable to set text without an element.");
|
||||
try {
|
||||
Logger.debug("Using currently-focused element.");
|
||||
AndroidElementsHash elements = AndroidElementsHash.getInstance();
|
||||
el = elements.getElement(new UiSelector().focused(true), "");
|
||||
} catch (ElementNotFoundException e) {
|
||||
Logger.debug("Error retrieving focused element: " + e);
|
||||
return getErrorResult("Unable to set text without a focused element.");
|
||||
}
|
||||
}
|
||||
try {
|
||||
final Hashtable<String, Object> params = command.params();
|
||||
boolean replace = Boolean.parseBoolean(params.get("replace").toString());
|
||||
String text = params.get("text").toString();
|
||||
boolean pressEnter = false;
|
||||
if (text.endsWith("\\n")) {
|
||||
pressEnter = true;
|
||||
text = text.replace("\\n", "");
|
||||
Logger.debug("Will press enter after setting text");
|
||||
}
|
||||
boolean unicodeKeyboard = false;
|
||||
if (params.get("unicodeKeyboard") != null) {
|
||||
unicodeKeyboard = Boolean.parseBoolean(params.get("unicodeKeyboard").toString());
|
||||
}
|
||||
String currText = el.getText();
|
||||
new Clear().execute(command);
|
||||
if (!el.getText().isEmpty()) {
|
||||
// clear could have failed, or we could have a hint in the field
|
||||
// we'll assume it is the latter
|
||||
Logger.debug("Text not cleared. Assuming remainder is hint text.");
|
||||
currText = "";
|
||||
}
|
||||
if (!replace) {
|
||||
text = currText + text;
|
||||
}
|
||||
final boolean result = el.setText(text, unicodeKeyboard);
|
||||
if (!result) {
|
||||
return getErrorResult("el.setText() failed!");
|
||||
}
|
||||
if (pressEnter) {
|
||||
final UiDevice d = UiDevice.getInstance();
|
||||
d.pressEnter();
|
||||
}
|
||||
return getSuccessResult(result);
|
||||
} catch (final UiObjectNotFoundException e) {
|
||||
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
|
||||
e.getMessage());
|
||||
} catch (final Exception e) { // handle NullPointerException
|
||||
return getErrorResult("Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public class UiAutomatorBridge {
|
||||
}
|
||||
|
||||
public boolean injectInputEvent(InputEvent event, boolean sync) {
|
||||
return (Boolean) invoke(method(CLASS_UI_AUTOMATOR_BRIDGE, METHOD_INJECT_INPUT_EVENT, InputEvent.class, Boolean.class),
|
||||
return (Boolean) invoke(method(CLASS_UI_AUTOMATOR_BRIDGE, METHOD_INJECT_INPUT_EVENT, InputEvent.class, boolean.class),
|
||||
uiAutomatorBridge, event, sync);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ var Android = require('./android.js')
|
||||
, deviceCommon = require('../common.js')
|
||||
, jwpSuccess = deviceCommon.jwpSuccess
|
||||
, async = require('async')
|
||||
, ADB = require('./adb.js')
|
||||
, UiAutomator = require('./uiautomator.js');
|
||||
|
||||
var NATIVE_WIN = "NATIVE_APP";
|
||||
@@ -37,7 +36,11 @@ ChromeAndroid.prototype.setChromedriverMode = function () {
|
||||
['POST', new RegExp('^/wd/hub/session/[^/]+/context')],
|
||||
['GET', new RegExp('^/wd/hub/session/[^/]+/context')],
|
||||
['POST', new RegExp('^/wd/hub/session/[^/]+/touch/perform')],
|
||||
['POST', new RegExp('^/wd/hub/session/[^/]+/touch/multi/perform')]
|
||||
['POST', new RegExp('^/wd/hub/session/[^/]+/touch/multi/perform')],
|
||||
['POST', new RegExp('^/wd/hub/session/[^/]+/orientation')],
|
||||
['GET', new RegExp('^/wd/hub/session/[^/]+/orientation')],
|
||||
['POST', new RegExp('^/wd/hub/session/[^/]+/appium')],
|
||||
['GET', new RegExp('^/wd/hub/session/[^/]+/appium')]
|
||||
];
|
||||
};
|
||||
|
||||
@@ -81,16 +84,13 @@ ChromeAndroid.prototype.startAutomation = function (cb) {
|
||||
};
|
||||
|
||||
ChromeAndroid.prototype.start = function (cb, onDie) {
|
||||
this.adb = new ADB(this.args);
|
||||
this.uiautomator = new UiAutomator(this.adb, this.args);
|
||||
this.uiautomator.setExitHandler(this.onUiautomatorExit.bind(this));
|
||||
this.onDieCb = onDie;
|
||||
|
||||
async.series([
|
||||
this.initAdb.bind(this),
|
||||
this.prepareUiAutomator.bind(this),
|
||||
this.initUiautomator.bind(this),
|
||||
this.prepareDevice.bind(this),
|
||||
this.initChromedriverPath.bind(this),
|
||||
this.prepareChromedriver.bind(this),
|
||||
this.pushAndUnlock.bind(this),
|
||||
this.forwardPort.bind(this),
|
||||
@@ -116,6 +116,12 @@ ChromeAndroid.prototype.prepareChromedriver = function (cb) {
|
||||
cb();
|
||||
};
|
||||
|
||||
ChromeAndroid.prototype.prepareUiAutomator = function (cb) {
|
||||
this.uiautomator = new UiAutomator(this.adb, this.args);
|
||||
this.uiautomator.setExitHandler(this.onUiautomatorExit.bind(this));
|
||||
cb();
|
||||
};
|
||||
|
||||
ChromeAndroid.prototype.pushAndUnlock = function (cb) {
|
||||
this.pushUnlock(function (err) {
|
||||
if (err) return cb(err);
|
||||
@@ -149,7 +155,10 @@ ChromeAndroid.prototype.createSession = function (cb) {
|
||||
this.onChromedriverStop(this.onDieCb);
|
||||
}
|
||||
}.bind(this));
|
||||
this.chromedriver.start(caps).nodeify(cb);
|
||||
this.chromedriver.start(caps).nodeify(function (err) {
|
||||
if (err) return cb(err);
|
||||
cb(null, this.chromedriver.sessionId());
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ChromeAndroid.prototype.stop = function (cb) {
|
||||
|
||||
@@ -70,6 +70,7 @@ Selendroid.prototype.init = function () {
|
||||
, ['POST', new RegExp('^/wd/hub/session/[^/]+/network_connection')]
|
||||
, ['POST', new RegExp('^/wd/hub/session/[^/]+/ime')]
|
||||
, ['GET', new RegExp('^/wd/hub/session/[^/]+/ime')]
|
||||
, ['POST', new RegExp('^/wd/hub/session/[^/]+/keys')]
|
||||
];
|
||||
this.curContext = this.defaultContext();
|
||||
};
|
||||
@@ -525,12 +526,11 @@ Selendroid.prototype.defaultWebviewName = function () {
|
||||
return this.WEBVIEW_WIN + "_0";
|
||||
};
|
||||
|
||||
Selendroid.prototype.setValue = function (elementId, value, cb) {
|
||||
logger.debug('Setting text on element \'' + elementId + '\': \'' + value + '\'');
|
||||
var encodeString = function (value, unicode) {
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
var c = value.charCodeAt(i);
|
||||
// if we're using the unicode keyboard, and this is unicode, maybe encode
|
||||
if (this.args.unicodeKeyboard && (c > 127 || c === 38)) {
|
||||
if (unicode && (c > 127 || c === 38)) {
|
||||
// this is not simple ascii, or it is an ampersand (`&`)
|
||||
if (c >= parseInt("E000", 16) && c <= parseInt("E040", 16)) {
|
||||
// Selenium uses a Unicode PUA to cover certain special characters
|
||||
@@ -542,6 +542,12 @@ Selendroid.prototype.setValue = function (elementId, value, cb) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
Selendroid.prototype.setValue = function (elementId, value, cb) {
|
||||
logger.debug('Setting text on element \'' + elementId + '\': \'' + value + '\'');
|
||||
value = encodeString(value, this.args.unicodeKeyboard);
|
||||
var reqUrl = this.proxyHost + ':' + this.proxyPort +
|
||||
'/wd/hub/session/' + this.proxySessionId +
|
||||
'/element/' + elementId + '/value';
|
||||
@@ -554,6 +560,21 @@ Selendroid.prototype.setValue = function (elementId, value, cb) {
|
||||
});
|
||||
};
|
||||
|
||||
Selendroid.prototype.keys = function (value, cb) {
|
||||
logger.debug('Setting text: \'' + value + '\'');
|
||||
value = encodeString(value, this.args.unicodeKeyboard);
|
||||
var reqUrl = this.proxyHost + ':' + this.proxyPort +
|
||||
'/wd/hub/session/' + this.proxySessionId +
|
||||
'/keys';
|
||||
doRequest(reqUrl, 'POST', { value: [value] }, null, function (err) {
|
||||
if (err) return cb(err);
|
||||
cb(null, {
|
||||
status: status.codes.Success.code,
|
||||
value: ''
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Selendroid.prototype.back = function (cb) {
|
||||
this.keyevent(4, null, cb);
|
||||
};
|
||||
|
||||
@@ -22,8 +22,11 @@ var uuid = require('uuid-js')
|
||||
, IOSPerfLog = require('./ios-perf-log')
|
||||
, errors = require('../../server/errors.js')
|
||||
, isWindows = require('appium-support').system.isWindows
|
||||
, localIp = require('appium-support').util.localIp
|
||||
, NotImplementedError = errors.NotImplementedError
|
||||
, NotYetImplementedError = errors.NotYetImplementedError;
|
||||
, NotYetImplementedError = errors.NotYetImplementedError
|
||||
, Parser = require('../../server/parser.js')
|
||||
, url = require('url');
|
||||
|
||||
var iOSController = {};
|
||||
var FLICK_MS = 3000;
|
||||
@@ -41,6 +44,13 @@ var logTypesSupported = {
|
||||
'performance': 'Performance Logs - Debug Timelines on real devices and simulators'
|
||||
};
|
||||
|
||||
var getDefaultCallbackAddress = function () {
|
||||
var parser = new Parser();
|
||||
return parser.rawArgs.filter(function (a) {
|
||||
return a[0][1] === '--address';
|
||||
})[0][1].defaultValue;
|
||||
};
|
||||
|
||||
iOSController.getStatusExtensions = function () {
|
||||
var ext = {};
|
||||
ext.isShuttingDown = this.isShuttingDown; // this is for testing purposes
|
||||
@@ -509,13 +519,12 @@ iOSController._getFullPath = function (remotePath, cb) {
|
||||
var appName = null;
|
||||
|
||||
if (this.args.app) {
|
||||
var appNameRegex = new RegExp("\\" + path.sep + "(\\w+\\.app)");
|
||||
var appNameRegex = new RegExp("\\" + path.sep + "([\\w-]+\\.app)");
|
||||
var appNameMatches = appNameRegex.exec(this.args.app);
|
||||
if (appNameMatches) {
|
||||
appName = appNameMatches[1];
|
||||
}
|
||||
}
|
||||
|
||||
// de-absolutize the path
|
||||
if (isWindows()) {
|
||||
if (remotePath.indexof("://") === 1) {
|
||||
@@ -707,7 +716,7 @@ iOSController.lookForAlert = function (cb, counter, looks, timeout) {
|
||||
// so we can proxy the alert detection functionality
|
||||
this.alertCounter++;
|
||||
this.proxy("au.alertIsPresent()", function (err, res) {
|
||||
if (res !== null) {
|
||||
if (res) {
|
||||
if (res.value === true) {
|
||||
logger.debug("Found an alert, returning control to client");
|
||||
this.returnedFromExecuteAtom[counter] = true;
|
||||
@@ -862,13 +871,18 @@ iOSController.complexTap = function (tapCount, touchCount, duration, x, y, eleme
|
||||
, x: x
|
||||
, y: y
|
||||
};
|
||||
var JSONOpts = JSON.stringify(options);
|
||||
if (elementId !== null) {
|
||||
command = ["au.getElement('", elementId, "').complexTap(", JSONOpts, ')'].join('');
|
||||
if (this.useRobot) {
|
||||
var tapUrl = this.args.robotUrl + "/tap";
|
||||
request.post({url:tapUrl, form: options}, cb);
|
||||
} else {
|
||||
command = ["au.complexTap(", JSONOpts, ")"].join('');
|
||||
var JSONOpts = JSON.stringify(options);
|
||||
if (elementId !== null) {
|
||||
command = ["au.getElement('", elementId, "').complexTap(", JSONOpts, ')'].join('');
|
||||
} else {
|
||||
command = ["au.complexTap(", JSONOpts, ")"].join('');
|
||||
}
|
||||
this.proxy(command, cb);
|
||||
}
|
||||
this.proxy(command, cb);
|
||||
};
|
||||
|
||||
iOSController.clear = function (elementId, cb) {
|
||||
@@ -1618,10 +1632,8 @@ iOSController.setContext = function (name, callback, skipReadyCheck) {
|
||||
});
|
||||
} else {
|
||||
this.curContext = null;
|
||||
//TODO: this condition should be changed to check if the webkit protocol is being used.
|
||||
if (this.args.udid) {
|
||||
this.remote.disconnect();
|
||||
this.remote = null;
|
||||
}
|
||||
cb(null, {
|
||||
status: status.codes.Success.code
|
||||
@@ -1887,6 +1899,21 @@ iOSController.execute = function (script, args, cb) {
|
||||
|
||||
iOSController.executeAsync = function (script, args, responseUrl, cb) {
|
||||
if (this.isWebContext()) {
|
||||
if (this.args.udid) {
|
||||
var defaultHost = getDefaultCallbackAddress();
|
||||
var urlObject = url.parse(responseUrl);
|
||||
if (urlObject.hostname === defaultHost) {
|
||||
logger.debug('Real device safari test and no custom ' +
|
||||
'callback address set, ' +
|
||||
'changing callback address to local ip.');
|
||||
urlObject.hostname = localIp();
|
||||
urlObject.host = null; // set to null, otherwise hostname is ignored
|
||||
responseUrl = url.format(urlObject);
|
||||
} else {
|
||||
logger.debug('Custom callback address set, leaving as is.');
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Response url for executeAsync is " + responseUrl);
|
||||
this.convertElementForAtoms(args, function (err, res) {
|
||||
if (err) {
|
||||
|
||||
@@ -77,9 +77,11 @@ iOSHybrid.listWebFrames = function (cb, exitCb) {
|
||||
if (this.remoteAppKey === null) {
|
||||
return cb(new Error("remote debug app key must be set"));
|
||||
}
|
||||
this.remote.selectApp(this.remoteAppKey, function (res) {
|
||||
this.remote.selectApp(this.remoteAppKey, function (err, perhapsModifiedAppKey, res) {
|
||||
if (err) return onDone(err);
|
||||
this.remoteAppKey = perhapsModifiedAppKey;
|
||||
onDone(null, res);
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
} else {
|
||||
if (this.args.udid !== null) {
|
||||
@@ -107,9 +109,11 @@ iOSHybrid.listWebFrames = function (cb, exitCb) {
|
||||
}
|
||||
logger.debug("Using remote debugger app key: " + appKey);
|
||||
this.remoteAppKey = appKey;
|
||||
this.remote.selectApp(appKey, function (res) {
|
||||
this.remote.selectApp(appKey, function (err, perhapsModifiedAppKey, res) {
|
||||
if (err) return onDone(err);
|
||||
this.remoteAppKey = perhapsModifiedAppKey;
|
||||
onDone(null, res);
|
||||
});
|
||||
}.bind(this));
|
||||
}.bind(this), this.onPageChange.bind(this));
|
||||
var loopCloseRuns = 0;
|
||||
var loopClose = function () {
|
||||
|
||||
+79
-55
@@ -30,7 +30,7 @@ var path = require('path')
|
||||
, UnknownError = errors.UnknownError
|
||||
, binaryPlist = true
|
||||
, Args = require("vargs").Constructor
|
||||
;
|
||||
, logCustomDeprecationWarning = require('../../helpers.js').logCustomDeprecationWarning;
|
||||
|
||||
// XML Plist library helper
|
||||
var parseXmlPlistFile = function (plistFilename, cb) {
|
||||
@@ -155,6 +155,13 @@ IOS.prototype.configure = function (args, caps, cb) {
|
||||
return cb(new Error(msg));
|
||||
}
|
||||
|
||||
if (parseFloat(caps.platformVersion) < 7.1) {
|
||||
logCustomDeprecationWarning('iOS version', caps.platformVersion,
|
||||
'iOS ' + caps.platformVersion + ' support has ' +
|
||||
'been deprecated and will be removed in a ' +
|
||||
'future version of Appium.');
|
||||
}
|
||||
|
||||
return this.configureApp(cb);
|
||||
};
|
||||
|
||||
@@ -321,48 +328,30 @@ IOS.prototype.startInstruments = function (cb) {
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
if (this.shouldIgnoreInstrumentsExit()) {
|
||||
// for safari on a real device, instruments is going to crash, but we
|
||||
// expect that and ignore it
|
||||
logger.debug("Not starting command proxy since we're expecting that " +
|
||||
"Instruments won't be able to talk to it anyway");
|
||||
this.instruments.start(
|
||||
function (err) {
|
||||
if (err) return treatError(err, cb);
|
||||
// instruments will never check into the command proxy, so go ahead
|
||||
// and just call back here once instruments is started, rather than
|
||||
// waiting for it to make the first connection to the command proxy
|
||||
cb();
|
||||
}
|
||||
, function () {
|
||||
// ignore unexpected instruments exit since that's exactly what
|
||||
// we're expecting anyway
|
||||
});
|
||||
} else {
|
||||
logger.debug("Starting command proxy.");
|
||||
this.commandProxy.start(
|
||||
function (err) { // on first connection
|
||||
// first let instruments know so that it does not restart itself
|
||||
this.instruments.launchHandler(err);
|
||||
// then we call the callback
|
||||
cb(err);
|
||||
}.bind(this)
|
||||
//
|
||||
, function (err) { // regular cb
|
||||
if (err) return treatError(err, cb);
|
||||
logger.debug("Starting instruments");
|
||||
this.instruments.start(
|
||||
function (err) {
|
||||
if (err) return treatError(err, cb);
|
||||
// we don't call cb here, waiting for first connection or error
|
||||
}.bind(this)
|
||||
, function (code) {
|
||||
logger.debug("Starting command proxy.");
|
||||
this.commandProxy.start(
|
||||
function onFirstConnection(err) {
|
||||
// first let instruments know so that it does not restart itself
|
||||
this.instruments.launchHandler(err);
|
||||
// then we call the callback
|
||||
cb(err);
|
||||
}.bind(this)
|
||||
, function regularCallback(err) {
|
||||
if (err) return treatError(err, cb);
|
||||
logger.debug("Starting instruments");
|
||||
this.instruments.start(
|
||||
function (err) {
|
||||
if (err) return treatError(err, cb);
|
||||
// we don't call cb here, waiting for first connection or error
|
||||
}.bind(this)
|
||||
, function (code) {
|
||||
if (!this.shouldIgnoreInstrumentsExit()) {
|
||||
this.onUnexpectedInstrumentsExit(code);
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
IOS.prototype.makeInstruments = function (cb) {
|
||||
@@ -372,7 +361,7 @@ IOS.prototype.makeInstruments = function (cb) {
|
||||
var bootstrap = prepareBootstrap({
|
||||
sock: this.sock,
|
||||
interKeyDelay: this.args.interKeyDelay,
|
||||
justLoopInfinitely: this.shouldIgnoreInstrumentsExit(),
|
||||
justLoopInfinitely: false,
|
||||
autoAcceptAlerts: !(!this.args.autoAcceptAlerts || this.args.autoAcceptAlerts === 'false'),
|
||||
autoDismissAlerts: !(!this.args.autoDismissAlerts || this.args.autoDismissAlerts === 'false'),
|
||||
sendKeyStrategy: this.args.sendKeyStrategy || (this.args.udid ? 'grouped' : 'oneByOne')
|
||||
@@ -543,8 +532,22 @@ IOS.prototype.setXcodeVersion = function (cb) {
|
||||
xcode.getVersion(function (err, versionNumber) {
|
||||
if (err) {
|
||||
logger.error("Could not determine Xcode version:" + err.message);
|
||||
} else {
|
||||
var minorVersion = parseFloat(versionNumber.slice(0, 3));
|
||||
var pv = parseFloat(this.args.platformVersion);
|
||||
// we deprecate Xcodes < 6.3, except for iOS 8.0 in which case we
|
||||
// support Xcode 6.0 as well
|
||||
if (minorVersion < 6.3 && (!(minorVersion === 6.0 && pv === 8.0))) {
|
||||
logCustomDeprecationWarning('Xcode version', versionNumber,
|
||||
'Support for Xcode ' + versionNumber + ' ' +
|
||||
'has been deprecated and will be removed ' +
|
||||
'in a future version. Please upgrade ' +
|
||||
'to version 6.3 or higher (or version ' +
|
||||
'6.0.1 for iOS 8.0)');
|
||||
}
|
||||
}
|
||||
this.xcodeVersion = versionNumber;
|
||||
logger.debug("Xcode version set to " + versionNumber);
|
||||
cb();
|
||||
}.bind(this));
|
||||
};
|
||||
@@ -707,9 +710,9 @@ IOS.prototype.instantLaunchAndQuit = function (needSafariDirs, cb) {
|
||||
var condition = function () {
|
||||
var simDirsExist = this.sim.dirsExist();
|
||||
var locServicesExist = settings.locServicesDirsExist(this.sim);
|
||||
var safariDirsExist = this.iOSSDKVersion < 7.0 ||
|
||||
var safariDirsExist = this.args.platformVersion < 7.0 ||
|
||||
(this.sim.safariDirsExist() &&
|
||||
(this.iOSSDKVersion < 8.0 ||
|
||||
(this.args.platformVersion < 8.0 ||
|
||||
this.sim.userSettingsPlistExists())
|
||||
);
|
||||
var okToGo = simDirsExist && locServicesExist &&
|
||||
@@ -808,7 +811,7 @@ IOS.prototype.detectUdid = function (cb) {
|
||||
}
|
||||
|
||||
if (stdout && stdout.length > 2) {
|
||||
this.args.udid = stdout.replace("\n", "");
|
||||
this.args.udid = stdout.split("\n")[0];
|
||||
logger.debug("Detected udid as " + this.args.udid);
|
||||
cb();
|
||||
} else {
|
||||
@@ -1230,7 +1233,7 @@ IOS.prototype.prelaunchSimulator = function (cb) {
|
||||
waitForSimulatorLogs(10);
|
||||
}.bind(this));
|
||||
|
||||
}).bind(this);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
IOS.prototype.parseLocalizableStrings = function (/* language, stringFile, cb */) {
|
||||
@@ -1491,7 +1494,7 @@ IOS.prototype.removeApp = function (bundleId, cb) {
|
||||
if (this.args.udid) {
|
||||
this.realDevice.remove(bundleId, cb);
|
||||
} else {
|
||||
cb(new Error("You can not call removeApp for the iOS simulator!"));
|
||||
this.sim.remove(bundleId, cb);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1499,7 +1502,15 @@ IOS.prototype.installApp = function (unzippedAppPath, cb) {
|
||||
if (this.args.udid) {
|
||||
this.realDevice.install(unzippedAppPath, cb);
|
||||
} else {
|
||||
cb(new Error("You can not call installApp for the iOS simulator!"));
|
||||
this.sim.install(unzippedAppPath, cb);
|
||||
}
|
||||
};
|
||||
|
||||
IOS.prototype.startApp = function (args, cb) {
|
||||
if (this.args.udid) {
|
||||
cb(new Error("You can not call startApp for a real device!"));
|
||||
} else {
|
||||
this.sim.launch(args.appPackage, cb);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1554,12 +1565,22 @@ IOS.prototype.getLatestWebviewContextForTitle = function (titleRegex, cb) {
|
||||
if (err) return cb(err);
|
||||
var matchingCtx;
|
||||
_(contexts).each(function (ctx) {
|
||||
if (ctx.view && ctx.view.url !== "about:blank" && (ctx.view.title || "").match(titleRegex)) {
|
||||
matchingCtx = ctx;
|
||||
if (ctx.view && (ctx.view.title || "").match(titleRegex)) {
|
||||
if (ctx.view.url === "about:blank") {
|
||||
// in the case of Xcode < 5 (i.e., iOS SDK Version less than 7)
|
||||
// and in the case of iOS 7.1 in a webview (not in Safari)
|
||||
// we can have the url be `about:blank`
|
||||
if (parseFloat(this.iOSSDKVersion) < 7 ||
|
||||
(this.args.platformVersion === '7.1' && this.args.app && this.args.app.toLowerCase() !== 'safari')) {
|
||||
matchingCtx = ctx;
|
||||
}
|
||||
} else {
|
||||
matchingCtx = ctx;
|
||||
}
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
cb(null, matchingCtx ? matchingCtx.id : undefined);
|
||||
});
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
// Right now we don't necessarily wait for webview
|
||||
@@ -1574,8 +1595,11 @@ IOS.prototype.useNewSafari = function () {
|
||||
|
||||
IOS.prototype.navToInitialWebview = function (cb) {
|
||||
var timeout = 0;
|
||||
if (this.args.udid) timeout = parseInt(this.iOSSDKVersion, 10) >= 8 ? 4000 : 6000;
|
||||
if (timeout > 0) logger.debug('Waiting for ' + timeout + ' ms before navigating to view.');
|
||||
if (this.args.udid) {
|
||||
timeout = 3000;
|
||||
logger.debug('Waiting for ' + timeout + ' ms before navigating to view.');
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
if (this.useNewSafari()) {
|
||||
return this.typeAndNavToUrl(cb);
|
||||
|
||||
@@ -151,7 +151,7 @@ RemoteDebugger.prototype.selectApp = function (appIdKey, cb, tries) {
|
||||
}
|
||||
if (responded) return;
|
||||
responded = true;
|
||||
cb(this.pageArrayFromDict(pageDict));
|
||||
cb(null, appIdKey, this.pageArrayFromDict(pageDict));
|
||||
this.specialCbs['_rpc_forwardGetListing:'] = this.onPageChange.bind(this);
|
||||
}.bind(this);
|
||||
|
||||
|
||||
+46
-15
@@ -5,8 +5,7 @@ var IOS = require('./ios.js')
|
||||
, path = require('path')
|
||||
, _ = require('underscore');
|
||||
|
||||
var NATIVE_REAL_SAFARI_MIN_VER = 7.0
|
||||
, SAFARI_BUNDLE = 'com.apple.mobilesafari';
|
||||
var SAFARI_BUNDLE = 'com.apple.mobilesafari';
|
||||
|
||||
var Safari = function () {
|
||||
this.init();
|
||||
@@ -58,6 +57,38 @@ Safari.prototype.moveBuiltInApp = function (cb) {
|
||||
}
|
||||
};
|
||||
|
||||
// once safariLauncher is running and instrumented, this function
|
||||
// finds the button which launches safari and clicks it.
|
||||
Safari.prototype._clickButtonToLaunchSafari = function (cb) {
|
||||
this.findElement('accessibility id', 'launch safari',
|
||||
function (err, res) {
|
||||
if (err || res.status !== 0) {
|
||||
var msg = "Error. Could not find button to launch Safari. " +
|
||||
"Make sure you are using the latest version of " +
|
||||
"SafariLauncher that appium is using";
|
||||
logger.error(msg);
|
||||
return cb(new Error(msg));
|
||||
}
|
||||
|
||||
var buttonId = res.value.ELEMENT;
|
||||
|
||||
this.click(buttonId, function (err, res) {
|
||||
if (err || res.status !== 0) {
|
||||
var msg = "Error. For whatever reason, could not click " +
|
||||
"the 'launch safari' button of SafariLauncher";
|
||||
logger.error(msg);
|
||||
return cb(new Error(msg));
|
||||
}
|
||||
|
||||
logger.debug("Clicked button, safari should be launching.");
|
||||
|
||||
// we're done with instruments at this point, we go on to Safari
|
||||
this.navToInitialWebview(cb);
|
||||
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Safari.prototype._start = IOS.prototype.start;
|
||||
Safari.prototype.start = function (cb, onDie) {
|
||||
var newOnDie = function (err) {
|
||||
@@ -69,13 +100,23 @@ Safari.prototype.start = function (cb, onDie) {
|
||||
|
||||
this._start(function (err) {
|
||||
if (err) return cb(err);
|
||||
this.navToInitialWebview(cb);
|
||||
|
||||
if (this.args.udid) {
|
||||
logger.debug("On Safari Launcher. Tapping button to launch Safari");
|
||||
|
||||
this._clickButtonToLaunchSafari(function (err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
this.navToInitialWebview(cb);
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.navToInitialWebview(cb);
|
||||
}
|
||||
}.bind(this), newOnDie);
|
||||
};
|
||||
|
||||
Safari.prototype.shouldIgnoreInstrumentsExit = function () {
|
||||
return !!this.args.udid &&
|
||||
this.iOSSDKVersion < NATIVE_REAL_SAFARI_MIN_VER;
|
||||
return !!this.args.udid;
|
||||
};
|
||||
|
||||
Safari.prototype._click = IOS.prototype.click;
|
||||
@@ -103,16 +144,6 @@ Safari.prototype.setInitialOrientation = function (cb) {
|
||||
this._setInitialOrientation(cb);
|
||||
};
|
||||
|
||||
Safari.prototype._configureBootstrap = IOS.prototype.configureBootstrap;
|
||||
Safari.prototype.configureBootstrap = function (cb) {
|
||||
if (this.shouldIgnoreInstrumentsExit()) {
|
||||
logger.debug("Not setting bootstrap config because we're on " +
|
||||
"SafariLauncher");
|
||||
return cb();
|
||||
}
|
||||
this._configureBootstrap(cb);
|
||||
};
|
||||
|
||||
Safari.prototype.installToRealDevice = function (cb) {
|
||||
if (this.args.udid) {
|
||||
try {
|
||||
|
||||
+19
-41
@@ -181,47 +181,25 @@ settings.updateLocationSettings = function (sim, bundleId, authorized) {
|
||||
});
|
||||
var curSettings = settings.getSettings(sim, 'locationClients');
|
||||
_.each(curSettings, function (settingSet, file) {
|
||||
/*
|
||||
* Support location services for 8.x platform.
|
||||
*/
|
||||
if (parseFloat(sim.platformVer) >= 8) {
|
||||
logger.debug("Using 8.X format locationd plist format.");
|
||||
_.extend(newPrefs, {
|
||||
Authorization: 2,
|
||||
SupportedAuthorizationMask: 3,
|
||||
});
|
||||
// now add our app's data
|
||||
if (!_.has(settingSet, bundleId)) {
|
||||
settingSet[bundleId] = {};
|
||||
}
|
||||
_.extend(settingSet[bundleId], newPrefs);
|
||||
if (!_.has(settingSet[bundleId], 'Executable')) {
|
||||
settingSet[bundleId].Executable = "";
|
||||
}
|
||||
if (!_.has(settingSet[bundleId], 'Registered')) {
|
||||
settingSet[bundleId].Registered = "";
|
||||
}
|
||||
} else {
|
||||
// add this random data to the clients.plist since it always seems to be there
|
||||
if (!_.has(settingSet, weirdLocKey)) {
|
||||
settingSet[weirdLocKey] = {
|
||||
BundlePath: "/System/Library/PrivateFrameworks/AOSNotification.framework",
|
||||
Whitelisted: false,
|
||||
Executable: "",
|
||||
Registered: ""
|
||||
};
|
||||
}
|
||||
// now add our app's data
|
||||
if (!_.has(settingSet, bundleId)) {
|
||||
settingSet[bundleId] = {};
|
||||
}
|
||||
_.extend(settingSet[bundleId], newPrefs);
|
||||
if (!_.has(settingSet[bundleId], 'Executable')) {
|
||||
settingSet[bundleId].Executable = "";
|
||||
}
|
||||
if (!_.has(settingSet[bundleId], 'Registered')) {
|
||||
settingSet[bundleId].Registered = "";
|
||||
}
|
||||
// add this random data to the clients.plist since it always seems to be there
|
||||
if (!_.has(settingSet, weirdLocKey)) {
|
||||
settingSet[weirdLocKey] = {
|
||||
BundlePath: "/System/Library/PrivateFrameworks/AOSNotification.framework",
|
||||
Whitelisted: false,
|
||||
Executable: "",
|
||||
Registered: ""
|
||||
};
|
||||
}
|
||||
// now add our app's data
|
||||
if (!_.has(settingSet, bundleId)) {
|
||||
settingSet[bundleId] = {};
|
||||
}
|
||||
_.extend(settingSet[bundleId], newPrefs);
|
||||
if (!_.has(settingSet, 'Executable')) {
|
||||
settingSet.Executable = "";
|
||||
}
|
||||
if (!_.has(settingSet, 'Registered')) {
|
||||
settingSet.Registered = "";
|
||||
}
|
||||
prefSetPerFile[file] = settingSet;
|
||||
});
|
||||
|
||||
@@ -450,6 +450,7 @@ Simulator.prototype.cleanSim = function (keepKeychains, tempDir, cb) {
|
||||
};
|
||||
|
||||
Simulator.prototype.moveBuiltInApp = function (appPath, appName, newAppDir, cb) {
|
||||
safeRimRafSync(newAppDir);
|
||||
ncp(appPath, newAppDir, function (err) {
|
||||
if (err) return cb(err);
|
||||
logger.debug("Copied " + appName + " to " + newAppDir);
|
||||
@@ -496,7 +497,7 @@ Simulator.prototype.prepareBuiltInApp = function (appName, tmpDir, cb) {
|
||||
fs.stat(newAppDir, function (err, s) {
|
||||
if (err) {
|
||||
logger.warn("App is also not at " + newAppDir);
|
||||
return cb(new Error("Couldn't find built in app in its home " +
|
||||
return cb(new Error("Couldn't find built in app " + appName + " in its home " +
|
||||
"or temp dir!"));
|
||||
}
|
||||
if (checkApp(s, appPath, cb)) {
|
||||
@@ -647,4 +648,45 @@ Simulator.prototype.deleteOtherSims = function (cb) {
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Simulator.prototype.getBootedDevice = function (cb) {
|
||||
simctl.getDevices(function (err, devices) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var booted = null;
|
||||
|
||||
_.detect(devices, function (sdkDevices) {
|
||||
booted = _.findWhere(sdkDevices, {state: "Booted"});
|
||||
|
||||
return !!booted;
|
||||
});
|
||||
|
||||
if (booted) {
|
||||
cb(null, booted.udid);
|
||||
} else {
|
||||
cb("Simctl couldn't find any booted device");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Simulator.prototype.remove = function (bundleId, cb) {
|
||||
this.getBootedDevice(function (err, udid) {
|
||||
if (err) return cb(err);
|
||||
simctl.removeApp(udid, bundleId, cb);
|
||||
});
|
||||
};
|
||||
|
||||
Simulator.prototype.install = function (appPath, cb) {
|
||||
this.getBootedDevice(function (err, udid) {
|
||||
if (err) return cb(err);
|
||||
simctl.installApp(udid, appPath, cb);
|
||||
});
|
||||
};
|
||||
|
||||
Simulator.prototype.launch = function (bundleId, cb) {
|
||||
this.getBootedDevice(function (err, udid) {
|
||||
if (err) return cb(err);
|
||||
simctl.launch(udid, bundleId, cb);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Simulator;
|
||||
|
||||
@@ -44,10 +44,14 @@ WebKitRemoteDebugger.prototype.connect = function (pageId, cb, pageChangeCb) {
|
||||
}
|
||||
}.bind(this));
|
||||
this.socket.on('error', function (exception) {
|
||||
console.log('Debugger web socket error %s', exception);
|
||||
});
|
||||
this.logger.debug('Debugger web socket error: ' + exception.message);
|
||||
}.bind(this));
|
||||
this.socket.on('message', function (data) {
|
||||
console.log('Debugger web socket received data : %s', data);
|
||||
var truncated = data || '';
|
||||
if (truncated.length > 300) {
|
||||
truncated = truncated.slice(0, 300) + '...';
|
||||
}
|
||||
this.logger.debug('Debugger web socket received data: ' + truncated);
|
||||
this.receive(data);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
+9
-8
@@ -34,15 +34,16 @@ var nodeifyFn = function (fn, bindObj) {
|
||||
|
||||
// if we've imported es6 Promise type functions, ensure we can still use
|
||||
// Q/B-like "nodeify" on them in our transitional code
|
||||
if (typeof Promise === "function") {
|
||||
Promise.prototype.nodeify = function (cb) {
|
||||
this.then(function (res) {
|
||||
cb(null, res);
|
||||
}, function (err) {
|
||||
cb(err);
|
||||
});
|
||||
};
|
||||
if (typeof Promise !== "function") {
|
||||
require('es6-promise').polyfill();
|
||||
}
|
||||
Promise.prototype.nodeify = function (cb) {
|
||||
this.then(function (res) {
|
||||
cb(null, res);
|
||||
}, function (err) {
|
||||
cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
simctl: nodeify(simctl),
|
||||
|
||||
@@ -72,6 +72,12 @@ var http = require('http')
|
||||
|
||||
|
||||
var main = function (args, readyCb, doneCb) {
|
||||
|
||||
var nodeVer = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||
if (nodeVer < 0.10) {
|
||||
logger.error("Appium requires Node >= 0.10");
|
||||
process.exit(1);
|
||||
}
|
||||
if (args.asyncTrace) {
|
||||
require('longjohn').async_trace_limit = -1;
|
||||
}
|
||||
@@ -85,6 +91,12 @@ var main = function (args, readyCb, doneCb) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (nodeVer < 0.12) {
|
||||
logger.warn("Appium support for versions of node < 0.12 has been " +
|
||||
"deprecated and will be removed in a future version. Please " +
|
||||
"upgrade!");
|
||||
}
|
||||
|
||||
checkArgs(parser, args);
|
||||
if (typeof doneCb === "undefined") {
|
||||
doneCb = function () {};
|
||||
|
||||
@@ -61,7 +61,7 @@ var args = [
|
||||
, dest: 'callbackAddress'
|
||||
, defaultValue: null
|
||||
, example: "127.0.0.1"
|
||||
, help: 'callback IP Address (default: same as address)'
|
||||
, help: 'callback IP Address (default: same as --address)'
|
||||
}],
|
||||
|
||||
[['-cp', '--callback-port'], {
|
||||
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
"use strict";
|
||||
var au;
|
||||
|
||||
if (typeof au === "undefined") {
|
||||
au = {};
|
||||
}
|
||||
|
||||
au.getNodeIndex = function (seg) {
|
||||
var index = /\[(\d+|last\(\))\]/;
|
||||
var match = index.exec(seg);
|
||||
if (match) {
|
||||
|
||||
if (match[1].indexOf("last") !== -1) {
|
||||
return -1; // last() is internally an index of -1
|
||||
}
|
||||
|
||||
// -1 is an invalid index when supplied by the user.
|
||||
// set to 0 which is always invalid so parseXpath returns false
|
||||
var idx = parseInt(match[1], 10);
|
||||
if (idx < 1) {
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
au.getXpathSearchMethod = function (pathSeg, root) {
|
||||
if (typeof root === "undefined") {
|
||||
root = false;
|
||||
}
|
||||
if (root && pathSeg[0] !== "/") {
|
||||
return "desc";
|
||||
}
|
||||
if (pathSeg.substr(0, 2) === "//") {
|
||||
return "desc";
|
||||
}
|
||||
return "child";
|
||||
};
|
||||
|
||||
au.getXpathExtPath = function (matchedExt) {
|
||||
var path = [];
|
||||
if (matchedExt) {
|
||||
// first split on '//'
|
||||
matchedExt = matchedExt.replace(/\//g, "|/");
|
||||
matchedExt = matchedExt.replace(/\|\/\|\//g, "|//");
|
||||
var splits = matchedExt.split("|");
|
||||
for (var i = 0; i < splits.length; i++) {
|
||||
if (splits[i] !== "") {
|
||||
path.push({
|
||||
node: splits[i].replace(/\/+/, '').replace(/\[\d+\]/, '')
|
||||
, search: au.getXpathSearchMethod(splits[i])
|
||||
, index: au.getNodeIndex(splits[i])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
au.parseXpath = function (xpath) {
|
||||
// e.g. "//button" or "button" or "/button"
|
||||
var index = "(\\[(?:\\d+|last\\(\\))\\])?";
|
||||
var root = "^(/?/?(?:[a-zA-Z.]+|\\*)" + index + ")";
|
||||
var ext = "((//?[a-zA-Z.]+" + index + ")*)"; // e.g. "/text" or "/cell//button/text"
|
||||
var attrEq = "(@[a-zA-Z0-9_]+=[^\\]]+)"; // e.g. [@name="foo"]
|
||||
// e.g. [contains(@name, "foo")]
|
||||
var attrContains = "(contains\\(@[a-zA-Z0-9_]+, ?[^\\]]+\\))";
|
||||
var attr = "(\\[(" + attrEq + "|" + attrContains + ")\\])?$";
|
||||
var xpathRe = new RegExp(root + ext + attr); // all together now
|
||||
var match = xpathRe.exec(xpath);
|
||||
if (match) {
|
||||
var matchedRoot = match[1]
|
||||
, matchedExt = match[3]
|
||||
, matchedAttrEq = match[8]
|
||||
, matchedContains = match[9]
|
||||
, attrName = null
|
||||
, attrConstraint = null
|
||||
, substrMatch = false
|
||||
, path = []
|
||||
, rootSearch = au.getXpathSearchMethod(matchedRoot, true)
|
||||
, parts = null;
|
||||
path.push({
|
||||
node: matchedRoot.replace(/\/+/, '').replace(/\[\d+\]/, '').replace(/\[last\(\)\]/, '')
|
||||
, search: rootSearch
|
||||
, index: au.getNodeIndex(matchedRoot)
|
||||
});
|
||||
path = path.concat(au.getXpathExtPath(matchedExt));
|
||||
if (matchedAttrEq || matchedContains) {
|
||||
if (matchedAttrEq) {
|
||||
parts = matchedAttrEq.split("=");
|
||||
attrName = parts[0].substr(1);
|
||||
} else if (matchedContains) {
|
||||
substrMatch = true;
|
||||
parts = matchedContains.split(",");
|
||||
attrName = parts[0].substr(10);
|
||||
parts[1] = parts[1].substr(0, parts[1].length - 1);
|
||||
parts[1] = parts[1].replace(/^ /, '');
|
||||
}
|
||||
if (parts[1][0] === "'" || parts[1][0] === '"') {
|
||||
attrConstraint = parts[1].substr(1, parts[1].length - 2);
|
||||
} else {
|
||||
attrConstraint = parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we're using 1-indexing
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
if (path[i].index !== null && path[i].index < 1 && path[i].index !== -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var ret = {
|
||||
path: path
|
||||
, attr: attrName
|
||||
, constraint: attrConstraint
|
||||
, substr: substrMatch
|
||||
};
|
||||
return ret;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = au;
|
||||
}
|
||||
+10
-7
@@ -11,7 +11,7 @@
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"version": "1.4.0-beta",
|
||||
"version": "1.4.3-beta",
|
||||
"author": "https://github.com/appium",
|
||||
"licenses": [
|
||||
{
|
||||
@@ -43,23 +43,24 @@
|
||||
"adm-zip": "~0.4.7",
|
||||
"appium-adb": "=1.7.5",
|
||||
"appium-atoms": "=0.0.5",
|
||||
"appium-chromedriver": "^1.0.1",
|
||||
"appium-instruments": "=2.0.0",
|
||||
"appium-support": "=1.0.4",
|
||||
"appium-chromedriver": "^2.0.0",
|
||||
"appium-instruments": "2.0.4",
|
||||
"appium-support": "=1.0.5",
|
||||
"appium-uiauto": "=1.10.8",
|
||||
"appium-xcode": "=2.0.0",
|
||||
"appium-xcode": "^2.0.5",
|
||||
"argparse": "~1.0.1",
|
||||
"async": "~0.9.0",
|
||||
"binary-cookies": "~0.1.1",
|
||||
"body-parser": "~1.12.0",
|
||||
"bplist-creator": "~0.0.5",
|
||||
"bplist-parser": "~0.0.6",
|
||||
"bplist-parser": "^0.1.0",
|
||||
"bufferpack": "0.0.6",
|
||||
"bytes": "~1.0.0",
|
||||
"camel-back-promise": "^1.0.0",
|
||||
"colors": "~1.0.3",
|
||||
"date-utils": "~1.2.16",
|
||||
"difflib": "~0.2.4",
|
||||
"es6-promise": "^2.1.1",
|
||||
"express": "~4.11.2",
|
||||
"glob": "~4.4.0",
|
||||
"grunt": "~0.4.5",
|
||||
@@ -74,7 +75,7 @@
|
||||
"namp": "0.2.25",
|
||||
"ncp": "~2.0.0",
|
||||
"node-idevice": "~0.1.5",
|
||||
"node-simctl": "=2.0.0",
|
||||
"node-simctl": "^2.0.1",
|
||||
"node-uuid": "~1.4.2",
|
||||
"npmlog": "~1.1.0",
|
||||
"path": "~0.11.14",
|
||||
@@ -83,6 +84,7 @@
|
||||
"q": "~1.1.2",
|
||||
"request": "~2.53.0",
|
||||
"rimraf": "~2.2.8",
|
||||
"safari-launcher": "=2.0.5",
|
||||
"sandboxed-module": "~2.0.0",
|
||||
"serve-favicon": "~2.2.0",
|
||||
"socket.io": "~1.3.4",
|
||||
@@ -131,6 +133,7 @@
|
||||
"promisepipe": "^1.0.1",
|
||||
"rewire": "^2.3.1",
|
||||
"run-sequence": "^1.0.2",
|
||||
"sample-apps": "=2.0.1",
|
||||
"saucelabs": "~0.1.1",
|
||||
"sinon": "~1.12.2",
|
||||
"sinon-chai": "~2.7.0",
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"packages": {
|
||||
"appium-core": [
|
||||
"appium"
|
||||
],
|
||||
"appium-contrib": [
|
||||
"android-apidemos",
|
||||
"appium-adb",
|
||||
"appium-android-bootstrap",
|
||||
"appium-android-ime",
|
||||
"appium-atoms",
|
||||
"appium-base-driver",
|
||||
"appium-chromedriver",
|
||||
"appium-jsonwp-proxy",
|
||||
"appium-logger",
|
||||
"appium-instruments",
|
||||
"appium-support",
|
||||
"appium-express",
|
||||
"appium-uiauto",
|
||||
"appium-uiautomator",
|
||||
"appium-xcode",
|
||||
"io.appium.settings",
|
||||
"jsonwp-status",
|
||||
"mobile-json-wire-protocol",
|
||||
"node-simctl"
|
||||
],
|
||||
"appium-tools": [
|
||||
"appium-ci-standalone-tools",
|
||||
"appium-gulp-plugins",
|
||||
"appium-transpile-runtime",
|
||||
"appium-jenkins",
|
||||
"node-teen_process",
|
||||
"packweb",
|
||||
"turtledeps"
|
||||
],
|
||||
"appium-apps": [
|
||||
"gps-demo-app",
|
||||
"io.appium.gappium.sampleapp",
|
||||
"ios-test-app",
|
||||
"ios-uicatalog",
|
||||
"ios-webview-app",
|
||||
"toggle-test-app",
|
||||
"sample-apps"
|
||||
],
|
||||
"extra-jlipps": [
|
||||
"asyncbox",
|
||||
"mochawait"
|
||||
],
|
||||
"extra-wd": [
|
||||
"wd"
|
||||
]
|
||||
},
|
||||
"owners": {
|
||||
"appium-core": [
|
||||
"imurchie",
|
||||
"jlipps",
|
||||
"jonahss",
|
||||
"moizjv",
|
||||
"sebv"
|
||||
],
|
||||
"appium-contrib": [
|
||||
"imurchie",
|
||||
"jlipps",
|
||||
"jonahss",
|
||||
"moizjv",
|
||||
"sebv",
|
||||
"bootstraponline"
|
||||
],
|
||||
"appium-tools": [
|
||||
"imurchie",
|
||||
"jlipps",
|
||||
"jonahss",
|
||||
"moizjv",
|
||||
"sebv"
|
||||
],
|
||||
"appium-apps": [
|
||||
"imurchie",
|
||||
"jlipps",
|
||||
"jonahss",
|
||||
"moizjv",
|
||||
"sebv",
|
||||
"bootstraponline"
|
||||
],
|
||||
"extra-jlipps": [
|
||||
"jlipps"
|
||||
],
|
||||
"extra-wd": [
|
||||
"admc",
|
||||
"jlipps",
|
||||
"sebv",
|
||||
"sourishkrout"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -139,9 +139,11 @@ reset_general() {
|
||||
echo "* Setting git revision data"
|
||||
run_cmd "$grunt" setGitRev
|
||||
if $include_dev ; then
|
||||
echo "* Linking git pre-commit hook"
|
||||
echo "* Linking git hooks"
|
||||
run_cmd rm -rf "$(pwd)"/.git/hooks/pre-commit
|
||||
run_cmd rm -rf "$(pwd)"/.git/hooks/pre-push
|
||||
run_cmd ln -s "$(pwd)"/test/pre-commit-hook.sh "$(pwd)"/.git/hooks/pre-commit
|
||||
run_cmd ln -s "$(pwd)"/test/pre-push-hook.sh "$(pwd)"/.git/hooks/pre-push
|
||||
fi
|
||||
else
|
||||
echo "* Nothing to do, not a git repo"
|
||||
@@ -186,43 +188,22 @@ reset_ios() {
|
||||
echo "* Cloning/npm linking appium-uiauto"
|
||||
run_cmd ./bin/npmlink.sh -l appium-uiauto
|
||||
fi
|
||||
if $ios7_active || $ios8_active ; then
|
||||
if $hardcore ; then
|
||||
echo "* Clearing out old UICatalog download"
|
||||
run_cmd rm -rf ./sample-code/apps/UICatalog/
|
||||
fi
|
||||
if [ ! -d "./sample-code/apps/UICatalog" ]; then
|
||||
echo "* Unzipping UICatalog app source"
|
||||
run_cmd pushd ./sample-code/apps
|
||||
run_cmd unzip UICatalog.zip
|
||||
run_cmd popd
|
||||
fi
|
||||
echo "* Cleaning/rebuilding iOS test app: UICatalog"
|
||||
run_cmd "$grunt" buildApp:UICatalog:iphonesimulator:$sdk_ver
|
||||
fi
|
||||
echo "* Cleaning/rebuilding iOS test app: TestApp"
|
||||
run_cmd "$grunt" buildApp:TestApp:iphonesimulator:$sdk_ver
|
||||
echo "* Cleaning/rebuilding iOS test app: WebViewApp"
|
||||
run_cmd "$grunt" buildApp:WebViewApp:iphonesimulator$sdk_ver
|
||||
fi
|
||||
if $should_reset_realsafari; then
|
||||
echo "* Cloning/updating SafariLauncher"
|
||||
run_cmd git submodule update --init submodules/SafariLauncher
|
||||
echo "* Building SafariLauncher for real devices"
|
||||
run_cmd rm -rf build/SafariLauncher
|
||||
run_cmd mkdir -p build/SafariLauncher
|
||||
run_cmd rm -f submodules/SafariLauncher/target.xcconfig
|
||||
touch submodules/SafariLauncher/target.xcconfig
|
||||
echo "BUNDLE_ID = com.bytearc.SafariLauncher" >> submodules/SafariLauncher/target.xcconfig
|
||||
touch build/SafariLauncher/target.xcconfig
|
||||
echo "BUNDLE_ID = com.bytearc.SafariLauncher" >> build/SafariLauncher/target.xcconfig
|
||||
if [[ ! -z $code_sign_identity ]]; then
|
||||
echo "IDENTITY_NAME = " $code_sign_identity >> submodules/SafariLauncher/target.xcconfig
|
||||
echo "IDENTITY_NAME = " $code_sign_identity >> build/SafariLauncher/target.xcconfig
|
||||
else
|
||||
echo "IDENTITY_NAME = iPhone Developer" >> submodules/SafariLauncher/target.xcconfig
|
||||
echo "IDENTITY_NAME = iPhone Developer" >> build/SafariLauncher/target.xcconfig
|
||||
fi
|
||||
echo "IDENTITY_CODE = " $provisioning_profile >> submodules/SafariLauncher/target.xcconfig
|
||||
run_cmd "$grunt" buildSafariLauncherApp:iphoneos:"target.xcconfig"
|
||||
echo "IDENTITY_CODE = " $provisioning_profile >> build/SafariLauncher/target.xcconfig
|
||||
run_cmd "$grunt" buildSafariLauncherApp:iphoneos:"$appium_home/build/SafariLauncher/target.xcconfig"
|
||||
echo "* Copying SafariLauncher for real devices to build"
|
||||
run_cmd zip -r build/SafariLauncher/SafariLauncher submodules/SafariLauncher/build/Release-iphoneos/SafariLauncher.app
|
||||
run_cmd zip -r build/SafariLauncher/SafariLauncher node_modules/safari-launcher/build/Release-iphoneos/SafariLauncher.app
|
||||
fi
|
||||
echo "* Cloning/updating libimobiledevice-macosx"
|
||||
run_cmd git submodule update --init submodules/libimobiledevice-macosx
|
||||
@@ -480,65 +461,13 @@ reset_gappium() {
|
||||
}
|
||||
|
||||
reset_chromedriver() {
|
||||
echo "RESETTING CHROMEDRIVER"
|
||||
machine=$(run_cmd_output uname -m)
|
||||
if [ "$machine" == "i686" ]; then
|
||||
machine="32"
|
||||
else
|
||||
machine="64"
|
||||
if $chromedriver_install_all ; then
|
||||
echo "RESETTING CHROMEDRIVER"
|
||||
echo "* Installing all chromedrivers, not just the ones for this system"
|
||||
run_cmd pushd node_modules/appium-chromedriver/
|
||||
run_cmd npm run-script chromedriver_all
|
||||
run_cmd popd
|
||||
fi
|
||||
if [ -d "$appium_home"/build/chromedriver ]; then
|
||||
echo "* Clearing old ChromeDriver(s)"
|
||||
run_cmd rm -rf "$appium_home"/build/chromedriver/*
|
||||
else
|
||||
run_cmd rm -rf "$appium_home"/build/chromedriver # could have been an old binary
|
||||
run_cmd mkdir "$appium_home"/build/chromedriver
|
||||
fi
|
||||
if [ "$chromedriver_version" == false ]; then
|
||||
echo "* Finding latest version"
|
||||
chromedriver_version=$(run_cmd_output curl -L http://chromedriver.storage.googleapis.com/LATEST_RELEASE)
|
||||
fi
|
||||
if ! $chromedriver_install_all ; then
|
||||
if [ "$platform" == "mac" ]; then
|
||||
chromedriver_file="chromedriver_mac32.zip"
|
||||
run_cmd mkdir "$appium_home"/build/chromedriver/mac
|
||||
install_chromedriver $platform $chromedriver_version $chromedriver_file
|
||||
else
|
||||
chromedriver_file="chromedriver_linux$machine.zip"
|
||||
binary="chromedriver$machine"
|
||||
run_cmd mkdir "$appium_home"/build/chromedriver/linux
|
||||
install_chromedriver $platform $chromedriver_version $chromedriver_file $binary
|
||||
fi
|
||||
else
|
||||
echo "* Building directory structure"
|
||||
run_cmd mkdir "$appium_home"/build/chromedriver/mac
|
||||
run_cmd mkdir "$appium_home"/build/chromedriver/linux
|
||||
run_cmd mkdir "$appium_home"/build/chromedriver/windows
|
||||
|
||||
install_chromedriver "mac" $chromedriver_version "chromedriver_mac32.zip"
|
||||
install_chromedriver "linux" $chromedriver_version "chromedriver_linux32.zip" "chromedriver32"
|
||||
install_chromedriver "linux" $chromedriver_version "chromedriver_linux64.zip" "chromedriver64"
|
||||
install_chromedriver "windows" $chromedriver_version "chromedriver_win32.zip"
|
||||
fi
|
||||
}
|
||||
|
||||
install_chromedriver() {
|
||||
platform=$1
|
||||
version=$2
|
||||
file=$3
|
||||
binary=$4
|
||||
|
||||
echo "* Downloading ChromeDriver version $version for $platform"
|
||||
run_cmd curl -L http://chromedriver.storage.googleapis.com/$version/$file -o "$appium_home"/build/chromedriver/$platform/chromedriver.zip
|
||||
run_cmd pushd "$appium_home"/build/chromedriver/$platform
|
||||
|
||||
echo "* Unzipping ChromeDriver"
|
||||
run_cmd unzip chromedriver.zip
|
||||
run_cmd rm chromedriver.zip
|
||||
if [[ $binary != "" ]]; then
|
||||
run_cmd mv chromedriver $binary
|
||||
fi
|
||||
run_cmd popd
|
||||
}
|
||||
|
||||
reset_firefoxos() {
|
||||
|
||||
+1
-1
Submodule submodules/ApiDemos updated: ba0df7a21b...d1af847057
Submodule submodules/SafariLauncher deleted from cc13a35970
Submodule
+1
Submodule submodules/appium-ci added at 83235d2f4a
Submodule submodules/appium-ci-gulper deleted from 1dc26d844a
Submodule submodules/appium-instruments updated: 9420760b9a...ee74b689ed
Submodule submodules/io.appium.android.ime updated: 32d5faa2be...33e1b31c39
@@ -34,6 +34,11 @@ describe("apidemos - attributes", function () {
|
||||
.back()
|
||||
.nodeify(done);
|
||||
});
|
||||
it('should be able to find content description attribute', function (done) {
|
||||
driver.elementByName('Animation').getAttribute('contentDescription')
|
||||
.should.become("Animation")
|
||||
.nodeify(done);
|
||||
});
|
||||
it('should be able to find displayed attribute', function (done) {
|
||||
driver
|
||||
.elementByName('Animation').getAttribute('displayed')
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var androidKeyboardTests = require('../../common/android-keyboard-base');
|
||||
|
||||
describe('android - keyboard', androidKeyboardTests);
|
||||
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var keyEventsTests = require('../../../common/android-keyboard-base').keyEventsTests;
|
||||
|
||||
describe('android - keyboard', keyEventsTests);
|
||||
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
var textFieldTests = require('../../../common/android-keyboard-base').textFieldTests;
|
||||
|
||||
describe('android - keyboard', function () {
|
||||
// TODO: inputing &- doesn't work on the Linux VMs with api 18
|
||||
var exclude = process.env.APPIUM_CI ? ['&-'] : undefined;
|
||||
|
||||
textFieldTests(exclude);
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var unicodeLanguagesTests = require('../../../common/android-keyboard-base').unicodeLanguagesTests;
|
||||
|
||||
describe('android - keyboard', unicodeLanguagesTests);
|
||||
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
var unicodeTextFieldTests = require('../../../common/android-keyboard-base').unicodeTextFieldTests;
|
||||
|
||||
describe('android - keyboard', function () {
|
||||
// TODO: inputing &- doesn't work on the Linux VMs with api 18
|
||||
var exclude = process.env.APPIUM_CI ? ['&-'] : undefined;
|
||||
|
||||
unicodeTextFieldTests(exclude);
|
||||
});
|
||||
@@ -35,13 +35,16 @@ describe("apidemos - lock", function () {
|
||||
.should.eventually.be.ok
|
||||
.nodeify(done);
|
||||
});
|
||||
it('should unlock the screen', function (done) {
|
||||
|
||||
// TODO: flaky test
|
||||
it('should unlock the screen @skip-ci', function (done) {
|
||||
driver
|
||||
.lockDevice(3)
|
||||
.waitFor(isLockedAsserter({expected: true}), 5000, 500)
|
||||
.should.eventually.be.ok
|
||||
.unlockDevice()
|
||||
.waitFor(isLockedAsserter({expected: false}), 5000, 500)
|
||||
.sleep(1000)
|
||||
.waitFor(isLockedAsserter({expected: false}), 10000, 1000)
|
||||
.should.not.eventually.be.ok
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
@@ -57,3 +57,38 @@ describe("apidemo - touch - press", function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("apidemo - touch - longpress - release", function () {
|
||||
var driver;
|
||||
setup(this, _.defaults({
|
||||
appActivity: '.view.SecureView'
|
||||
}, desired)).then(function (d) { driver= d;});
|
||||
describe('press released after pause should call release', function () {
|
||||
it('should open an alert dialog', function (done) {
|
||||
driver
|
||||
.elementByName("Don't click! It'll cost you!")
|
||||
.then(function (el) {
|
||||
var action = new TouchAction(driver);
|
||||
return action.press({el: el}).wait(1000).release().perform();
|
||||
})
|
||||
.elementByName("Oops...").should.eventually.exist
|
||||
.then(function (el) {
|
||||
el.click();
|
||||
})
|
||||
.nodeify(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('longPress should call release', function () {
|
||||
it('should open an alert dialog', function (done) {
|
||||
driver
|
||||
.elementByName("Don't click! It'll cost you!")
|
||||
.then(function (el) {
|
||||
var action = new TouchAction(driver);
|
||||
return action.longPress({el: el}).release().perform();
|
||||
})
|
||||
.elementByName("Oh no!").should.eventually.exist
|
||||
.nodeify(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@ describe("gpsDemo - location @skip-real-device", function () {
|
||||
var driver;
|
||||
setup(this, desired).then(function (d) { driver = d; });
|
||||
|
||||
it('should set geo location', function (done) {
|
||||
// TODO:flaky test
|
||||
it('should set geo location @skip-ci', function (done) {
|
||||
|
||||
var getText = function () {
|
||||
return driver
|
||||
|
||||
@@ -3,230 +3,262 @@
|
||||
var env = require('../../helpers/env')
|
||||
, setup = require("./setup-base")
|
||||
, _ = require('underscore')
|
||||
, getAppPath = require('../../helpers/app').getAppPath
|
||||
, status = require('../../../lib/server/status');
|
||||
, getAppPath = require('../../helpers/app').getAppPath;
|
||||
|
||||
|
||||
var desired = {
|
||||
app: getAppPath('ApiDemos'),
|
||||
appActivity: '.view.TextFields',
|
||||
newCommandTimeout: 90
|
||||
};
|
||||
if (env.SELENDROID) {
|
||||
desired.automationName = 'selendroid';
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
var runTextEditTest = function (driver, testText, keys, done) {
|
||||
var el;
|
||||
driver
|
||||
.waitForElementsByClassName('android.widget.EditText')
|
||||
// use a text field with no hint text, so clear is faster
|
||||
.then(function (els) {
|
||||
el = _.last(els);
|
||||
return el;
|
||||
})
|
||||
.clear()
|
||||
.then(function () {
|
||||
if (keys) {
|
||||
return driver.keys(testText);
|
||||
} else {
|
||||
return el.sendKeys(testText);
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
if (env.SELENDROID) {
|
||||
// in Selendroid mode we sometimes get the text before
|
||||
// it is fully sent to the element
|
||||
return driver.sleep(300);
|
||||
}
|
||||
})
|
||||
.then(function () { return el.text(); })
|
||||
.then(function (text) {
|
||||
// For samsung samsung S5 text is appended with ". Editing."
|
||||
text = text.replace(". Editing.", "");
|
||||
return text.should.be.equal(testText);
|
||||
})
|
||||
.nodeify(done);
|
||||
};
|
||||
|
||||
var runEditAndClearTest = function (driver, testText, keys, done) {
|
||||
var el;
|
||||
driver
|
||||
.waitForElementsByClassName('android.widget.EditText')
|
||||
.then(function (els) {
|
||||
el = _.last(els);
|
||||
return el;
|
||||
})
|
||||
.clear()
|
||||
.then(function () {
|
||||
if (keys) {
|
||||
return driver.keys(testText);
|
||||
} else {
|
||||
return el.sendKeys(testText);
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
el.text().should.become(testText);
|
||||
})
|
||||
.then(function () {
|
||||
return el.clear().should.not.be.rejected;
|
||||
})
|
||||
.then(function () {
|
||||
return el.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
// For samsung samsung S5 text is appended with ". Editing."
|
||||
text = text.replace("Editing.", "");
|
||||
text.should.be.equal("");
|
||||
})
|
||||
.nodeify(done);
|
||||
};
|
||||
|
||||
var runKeyboardTests = function (driverPromise, testText) {
|
||||
var driver;
|
||||
driverPromise.then(function (d) { driver = d; });
|
||||
|
||||
var runTextEditTest = function (testText, done) {
|
||||
var el;
|
||||
driver
|
||||
.waitForElementByClassName('android.widget.EditText')
|
||||
.then(function (_el) { el = _el; })
|
||||
.then(function () {
|
||||
if (env.SELENDROID) {
|
||||
return el.clear();
|
||||
}
|
||||
})
|
||||
.then(function () { return el.sendKeys(testText); })
|
||||
.then(function () { return el.text().should.become(testText); })
|
||||
.nodeify(done);
|
||||
};
|
||||
it('should work with sendKeys', function (done) {
|
||||
runTextEditTest(driver, testText, false, done);
|
||||
});
|
||||
it('should work with keys', function (done) {
|
||||
runTextEditTest(driver, testText, true, done);
|
||||
});
|
||||
};
|
||||
|
||||
var runEditAndClearTest = function (testText, done) {
|
||||
var el;
|
||||
var runKeyEventTests = function (driverPromise) {
|
||||
var driver;
|
||||
driverPromise.then(function (d) { driver = d; });
|
||||
|
||||
var editTextField = 'android.widget.TextView';
|
||||
if (env.SELENDROID) {
|
||||
// with Selendroid we can't find classes by their parent class
|
||||
// and with uiautomator we can't find the subclass.
|
||||
editTextField = 'io.appium.android.apis.text.LogTextBox';
|
||||
}
|
||||
|
||||
// skip selendroid because selendroid implements keyevent with an adb
|
||||
// call, and we are unable to send metastate that way
|
||||
it('should be able to send combination keyevents @skip-selendroid-all', function (done) {
|
||||
driver
|
||||
.waitForElementByClassName('android.widget.EditText')
|
||||
.then(function (_el) { el = _el; })
|
||||
.then(function () {
|
||||
if (env.SELENDROID) {
|
||||
return el.clear();
|
||||
}
|
||||
.elementById('clear').click()
|
||||
.pressDeviceKey(29, 193)
|
||||
.elementsByClassName(editTextField)
|
||||
.then(function (els) {
|
||||
return _.last(els).text();
|
||||
})
|
||||
.then(function () { return el.sendKeys(testText).text().should.become(testText); })
|
||||
.then(function () {
|
||||
return el.clear().should.not.be.rejected;
|
||||
})
|
||||
.then(function () {
|
||||
// Selendroid and uiautomator have different ways of dealing with
|
||||
// hint text. In particular, Selendroid does not return it
|
||||
// and uiautomator does.
|
||||
var expectedText = "hint text";
|
||||
if (env.SELENDROID) {
|
||||
expectedText = "";
|
||||
}
|
||||
return el.text().should.become(expectedText);
|
||||
.then(function (txt) {
|
||||
txt.should.include('keyCode=KEYCODE_A');
|
||||
txt.should.include('metaState=META_SHIFT_ON');
|
||||
})
|
||||
.nodeify(done);
|
||||
};
|
||||
|
||||
describe('editing ascii text field', function () {
|
||||
setup(this, desired).then(function (d) { driver = d; });
|
||||
|
||||
it('should be able to edit a text field', function (done) {
|
||||
var testText = "Life, the Universe and Everything.";
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
|
||||
it('should be able to edit and manually clear a text field', function (done) {
|
||||
var testText = "The answer is 42.";
|
||||
runEditAndClearTest(testText, done);
|
||||
});
|
||||
|
||||
it('should be able to send &-', function (done) {
|
||||
var testText = '&-';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
|
||||
it('should be able to send & and - in other text', function (done) {
|
||||
var testText = 'In the mid-1990s he ate fish & chips as mayor-elect.';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
|
||||
it('should be able to send - in text', function (done) {
|
||||
var testText = 'Super-test.';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
|
||||
describe('pressing device key', function () {
|
||||
it('should be able to send keyevents', function (done) {
|
||||
driver
|
||||
.waitForElementByClassName('android.widget.EditText')
|
||||
.elementById('com.android.launcher:id/search_button')
|
||||
.should.be.rejectedWith(status.codes.NoSuchElement.code)
|
||||
.pressDeviceKey(3)
|
||||
.waitForElementById('com.android.launcher:id/search_button')
|
||||
.elementByClassName('android.widget.EditText')
|
||||
.should.be.rejectedWith(status.codes.NoSuchElement.code)
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
// skip selendroid because selendroid implements keyevent with an adb
|
||||
// call, and we are unable to send metastate that way
|
||||
it('should be able to send combination keyevents @skip-selendroid-all', function (done) {
|
||||
driver
|
||||
.waitForElementByClassName('android.widget.EditText')
|
||||
.clear()
|
||||
.pressDeviceKey(29, 193)
|
||||
.elementByClassName('android.widget.EditText')
|
||||
.text().should.become('A')
|
||||
.nodeify(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('editing unicode text field', function () {
|
||||
setup(this, _.defaults({
|
||||
unicodeKeyboard: true,
|
||||
resetKeyboard: true
|
||||
}, desired)).then(function (d) { driver = d; });
|
||||
it('should be able to send keyevents', function (done) {
|
||||
driver
|
||||
.elementById('clear').click()
|
||||
.pressDeviceKey(82)
|
||||
.elementsByClassName(editTextField)
|
||||
.then(function (els) {
|
||||
return _.last(els).text();
|
||||
})
|
||||
.then(function (txt) {
|
||||
txt.should.include('[keycode=82]');
|
||||
txt.should.include('keyCode=KEYCODE_MENU');
|
||||
})
|
||||
.nodeify(done);
|
||||
});
|
||||
};
|
||||
|
||||
it('should be able to edit a text field', function (done) {
|
||||
var testText = "Life, the Universe and Everything.";
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
var runManualClearTests = function (driverPromise) {
|
||||
var driver;
|
||||
driverPromise.then(function (d) { driver = d; });
|
||||
|
||||
it('should be able to edit and manually clear a text field', function (done) {
|
||||
var testText = "The answer is 42.";
|
||||
runEditAndClearTest(testText, done);
|
||||
});
|
||||
var testText = "The answer is 42.";
|
||||
it('should work with sendKeys', function (done) {
|
||||
runEditAndClearTest(driver, testText, false, done);
|
||||
});
|
||||
it('should work with keys', function (done) {
|
||||
runEditAndClearTest(driver, testText, true, done);
|
||||
});
|
||||
};
|
||||
|
||||
it('should be able to send &-', function (done) {
|
||||
var testText = '&-';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
var appActivity = '.view.TextFields';
|
||||
var tests = [
|
||||
{ label: 'editing a text field', text: 'Life, the Universe and Everything.' },
|
||||
{ label: 'sending &-', text: '&-' },
|
||||
{ label: 'sending & and - in other text', text: 'In the mid-1990s he ate fish & chips as mayor-elect.' },
|
||||
{ label: 'sending - in text', text: 'Super-test.' },
|
||||
];
|
||||
|
||||
it('should be able to send & and - in other text', function (done) {
|
||||
var testText = 'In the mid-1990s he ate fish & chips as mayor-elect.';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
var unicodeTests = _.union(tests, [
|
||||
{ label: 'should be able to send - in unicode text', text: 'परीक्षा-परीक्षण' },
|
||||
{ label: 'should be able to send & in text', text: 'Fish & chips' },
|
||||
{ label: 'should be able to send & in unicode text', text: 'Mīna & chips' },
|
||||
{ label: 'should be able to send roman characters with diacritics', text: 'Áé Œ ù ḍ' },
|
||||
{ label: 'should be able to send a u with an umlaut', text: 'ü' },
|
||||
]);
|
||||
|
||||
it('should be able to send - in text', function (done) {
|
||||
var testText = 'Super-test.';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
var languageTests = [
|
||||
{ label: 'should be able to send Tamil', text: 'சோதனை' },
|
||||
{ label: 'should be able to send Gujarati', text: 'પરીક્ષણ' },
|
||||
{ label: 'should be able to send Chinese', text: '测试' },
|
||||
{ label: 'should be able to send Russian', text: 'тестирование' },
|
||||
// skip rtl languages, which don't clear correctly atm
|
||||
// { label: 'should be able to send Arabic', 'تجريب'],
|
||||
// { label: 'should be able to send Hebrew', 'בדיקות'],
|
||||
];
|
||||
|
||||
it('should be able to send - in unicode text', function (done) {
|
||||
var testText = 'परीक्षा-परीक्षण';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
exports.textFieldTests = function (exclude) {
|
||||
describe('editing a text field', function () {
|
||||
describe('ascii', function () {
|
||||
var driverPromise = setup(this, _.defaults({
|
||||
appActivity: appActivity
|
||||
}, desired));
|
||||
|
||||
it('should be able to send & in text', function (done) {
|
||||
var testText = 'Fish & chips';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
_(tests).chain().filter(function (test) {
|
||||
return !exclude || exclude.indexOf(test.text) < 0;
|
||||
}).each(function (test) {
|
||||
describe(test.label, function () {
|
||||
runKeyboardTests(driverPromise, test.text);
|
||||
});
|
||||
}).value();
|
||||
|
||||
it('should be able to send & in unicode text', function (done) {
|
||||
var testText = 'Mīna & chips';
|
||||
runTextEditTest(testText, done);
|
||||
describe('editing and manually clearing a text field', function () {
|
||||
runManualClearTests(driverPromise);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
it('should be able to send roman characters with diacritics', function (done) {
|
||||
var testText = 'Áé Œ ù ḍ';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
exports.unicodeTextFieldTests = function (exclude) {
|
||||
describe('unicode', function () {
|
||||
var driverPromise = setup(this, _.defaults({
|
||||
appActivity: appActivity,
|
||||
unicodeKeyboard: true,
|
||||
resetKeyboard: true
|
||||
}, desired));
|
||||
|
||||
it('should be able to send a u with an umlaut', function (done) {
|
||||
var testText = 'ü';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
_(unicodeTests).chain().filter(function (test) {
|
||||
return !exclude || exclude.indexOf(test.text) < 0;
|
||||
}).each(function (test) {
|
||||
describe(test.label, function () {
|
||||
runKeyboardTests(driverPromise, test.text);
|
||||
});
|
||||
}).value();
|
||||
|
||||
// skipping because clear doesn't work reliably on RTL scripts
|
||||
it.skip('should be able to send Arabic', function (done) {
|
||||
var testText = 'تجريب';
|
||||
runTextEditTest(testText, done);
|
||||
describe('editing and manually clearing a text field', function () {
|
||||
runManualClearTests(driverPromise);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// skipping because clear doesn't work reliably on RTL scripts
|
||||
it.skip('should be able to send Hebrew', function (done) {
|
||||
var testText = 'בדיקות';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
exports.unicodeLanguagesTests = function () {
|
||||
describe('unicode', function () {
|
||||
var driverPromise = setup(this, _.defaults({
|
||||
appActivity: appActivity,
|
||||
unicodeKeyboard: true,
|
||||
resetKeyboard: true
|
||||
}, desired));
|
||||
|
||||
it('should be able to send Tamil', function (done) {
|
||||
var testText = 'சோதனை';
|
||||
runTextEditTest(testText, done);
|
||||
_.each(languageTests, function (test) {
|
||||
describe(test.label, function () {
|
||||
runKeyboardTests(driverPromise, test.text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to send Gujarati', function (done) {
|
||||
var testText = 'પરીક્ષણ';
|
||||
runTextEditTest(testText, done);
|
||||
describe('editing and manually clearing a text field', function () {
|
||||
runManualClearTests(driverPromise);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
it('should be able to send Chinese', function (done) {
|
||||
var testText = '测试';
|
||||
runTextEditTest(testText, done);
|
||||
});
|
||||
|
||||
it('should be able to send Russian', function (done) {
|
||||
var testText = 'тестирование';
|
||||
runTextEditTest(testText, done);
|
||||
exports.keyEventsTests = function () {
|
||||
describe('key events', function () {
|
||||
var appActivity = '.text.KeyEventText';
|
||||
describe('ascii', function () {
|
||||
var driverPromise = setup(this, _.defaults({
|
||||
appActivity: appActivity
|
||||
}, desired));
|
||||
describe('pressing device key', function () {
|
||||
runKeyEventTests(driverPromise);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pressing device key with unicode keyboard', function () {
|
||||
it('should be able to send keyevents', function (done) {
|
||||
driver
|
||||
.waitForElementByClassName('android.widget.EditText')
|
||||
.elementById('com.android.launcher:id/search_button')
|
||||
.should.be.rejectedWith(status.codes.NoSuchElement.code)
|
||||
.pressDeviceKey(3)
|
||||
.waitForElementById('com.android.launcher:id/search_button')
|
||||
.elementByClassName('android.widget.EditText')
|
||||
.should.be.rejectedWith(status.codes.NoSuchElement.code)
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
// skip selendroid because selendroid implements keyevent with an adb
|
||||
// call, and we are unable to send metastate that way
|
||||
it('should be able to send combination keyevents @skip-selendroid-all', function (done) {
|
||||
driver
|
||||
.waitForElementByClassName('android.widget.EditText')
|
||||
.clear()
|
||||
.pressDeviceKey(29, 193)
|
||||
.elementByClassName('android.widget.EditText')
|
||||
.text().should.become('A')
|
||||
.nodeify(done);
|
||||
describe('unicode', function () {
|
||||
var driverPromise = setup(this, _.defaults({
|
||||
appActivity: appActivity,
|
||||
unicodeKeyboard: true,
|
||||
resetKeyboard: true
|
||||
}, desired));
|
||||
describe('pressing device key', function () {
|
||||
runKeyEventTests(driverPromise);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ var setup = require("../common/setup-base")
|
||||
, _ = require("underscore")
|
||||
, getAppPath = require('../../helpers/app').getAppPath;
|
||||
|
||||
describe('crash recovery', function () {
|
||||
describe('crash recovery @skip-real-device', function () {
|
||||
var driver;
|
||||
var desired = {
|
||||
app: getAppPath('TestApp')
|
||||
@@ -25,7 +25,7 @@ describe('crash recovery', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('crash commands', function () {
|
||||
describe('crash commands @skip-real-device', function () {
|
||||
|
||||
var driver;
|
||||
var desired = {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
var setup = require("../common/setup-base")
|
||||
, env = require('../../helpers/env')
|
||||
, getAppPath = require('../../helpers/app').getAppPath
|
||||
, getAppPath = require('sample-apps')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, Readable = require('stream').Readable
|
||||
@@ -10,12 +10,13 @@ var setup = require("../common/setup-base")
|
||||
, xcode = require('../../../lib/future.js').xcode
|
||||
, exec = require('child_process').exec
|
||||
, getSimUdid = require('../../helpers/sim-udid.js').getSimUdid
|
||||
, Unzip = require('unzip');
|
||||
, Unzip = require('unzip')
|
||||
, _ = require('underscore');
|
||||
|
||||
describe('file movements - pullFile and pushFile', function () {
|
||||
describe('file movements - pullFile and pushFile @skip-real-device', function () {
|
||||
var driver;
|
||||
var desired = {
|
||||
app: getAppPath('TestApp')
|
||||
app: getAppPath('TestApp', env.REAL_DEVICE)
|
||||
};
|
||||
setup(this, desired).then(function (d) { driver = d; });
|
||||
|
||||
@@ -63,6 +64,9 @@ describe('file movements - pullFile and pushFile', function () {
|
||||
var fileContent = "IAmTheVeryModelOfAModernMajorTestingTool";
|
||||
var fileName = "someFile.tmp";
|
||||
var fullPath = "";
|
||||
var appPath = getAppPath('TestApp', env.REAL_DEVICE);
|
||||
var appName = _.last(appPath.split(path.sep));
|
||||
|
||||
before(function (done) {
|
||||
var pv = env.CAPS.platformVersion || '7.1';
|
||||
var ios8 = parseFloat(pv) >= 8;
|
||||
@@ -88,10 +92,10 @@ describe('file movements - pullFile and pushFile', function () {
|
||||
basePath = path.resolve(simRoots[0], 'Applications');
|
||||
}
|
||||
basePath = basePath.replace(/\s/g, '\\ ');
|
||||
var findCmd = 'find ' + basePath + ' -name "TestApp.app"';
|
||||
var findCmd = 'find ' + basePath + ' -name "' + appName + '"';
|
||||
exec(findCmd, function (err, stdout) {
|
||||
if (err) return done(err);
|
||||
if (!stdout) return done(new Error("Could not find testapp.app"));
|
||||
if (!stdout) return done(new Error("Could not find", appName));
|
||||
var appRoot = stdout.replace(/\n$/, '');
|
||||
fullPath = path.resolve(appRoot, fileName);
|
||||
fs.writeFile(fullPath, fileContent, done);
|
||||
@@ -109,7 +113,7 @@ describe('file movements - pullFile and pushFile', function () {
|
||||
});
|
||||
|
||||
it('should be able to fetch a file from the app directory', function (done) {
|
||||
var arg = path.resolve('/TestApp.app', fileName);
|
||||
var arg = path.resolve('/' + appName, fileName);
|
||||
driver
|
||||
.pullFile(arg)
|
||||
.then(function (data) {
|
||||
|
||||
@@ -10,7 +10,7 @@ var desired = {
|
||||
app: 'settings'
|
||||
};
|
||||
|
||||
describe("prefs @skip-ios6", function () {
|
||||
describe("prefs @skip-ios6 @skip-real-device", function () {
|
||||
// TODO: cannot install settings app on ios6
|
||||
describe('settings app', function () {
|
||||
var driver;
|
||||
@@ -33,4 +33,3 @@ describe("prefs @skip-ios6", function () {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ var desired = {
|
||||
bundleId: 'com.apple.Preferences'
|
||||
};
|
||||
|
||||
describe('settings app @skip-ios6 @skip-ios7', function () {
|
||||
describe('settings app @skip-ios6 @skip-ios7 @skip-real-device', function () {
|
||||
var driver;
|
||||
setup(this, desired).then(function (d) { driver = d; });
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ var desired = {
|
||||
app: 'settings'
|
||||
};
|
||||
|
||||
describe("prefs @skip-ios6", function () {
|
||||
describe("prefs @skip-ios6 @skip-real-device", function () {
|
||||
// TODO: cannot install settings app on ios6
|
||||
|
||||
var checkLocServ = function (driver, expected, cb) {
|
||||
@@ -44,4 +44,3 @@ describe("prefs @skip-ios6", function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var env = require('../../../helpers/env'),
|
||||
setup = require("../../common/setup-base");
|
||||
|
||||
describe('safari - basics @skip-ios6', function () {
|
||||
describe('safari - basics @skip-ios6 @skip-real-device', function () {
|
||||
if (env.IOS8 && !env.IOS80) {
|
||||
describe('default init' ,function () {
|
||||
var driver;
|
||||
@@ -28,7 +28,7 @@ describe('safari - basics @skip-ios6', function () {
|
||||
describe('default init' ,function () {
|
||||
var driver;
|
||||
setup(this, {browserName: 'safari'}).then(function (d) { driver = d; });
|
||||
it('it should use appium default init page', function (done) {
|
||||
it('it should use safari default init page', function (done) {
|
||||
driver
|
||||
.source().should.eventually.include('Apple')
|
||||
.nodeify(done);
|
||||
|
||||
@@ -13,7 +13,8 @@ describe('safari - screenshots @skip-ios6', function () {
|
||||
.should.eventually.exist
|
||||
.nodeify(done);
|
||||
});
|
||||
it('should get an app screenshot in landscape mode', function (done) {
|
||||
// TODO: fails in ci env, investigate
|
||||
it('should get an app screenshot in landscape mode @skip-ci', function (done) {
|
||||
driver.takeScreenshot().then(function (screenshot1) {
|
||||
screenshot1.should.exist;
|
||||
return driver
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
var desired = require('./desired');
|
||||
|
||||
describe("safari - webview @skip-ios6", function () {
|
||||
describe("safari - webview @skip-ios6 @skip-real-device", function () {
|
||||
require('../../../common/webview/alerts-base')(desired);
|
||||
});
|
||||
|
||||
@@ -36,7 +36,8 @@ describe('safari - webview - special capabilities @skip-ios6', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance logs', function () {
|
||||
// skipped on real devices, see https://github.com/appium/appium/issues/5166
|
||||
describe('performance logs @skip-real-device', function () {
|
||||
var driver;
|
||||
var specialCaps = _.clone(desired);
|
||||
specialCaps.loggingPrefs = {performance: 'ALL'};
|
||||
|
||||
@@ -50,9 +50,12 @@ describe('safari - windows and frames (' + env.DEVICE + ') @skip-ios6"', functio
|
||||
.elementById('only_on_page_2')
|
||||
.nodeify(done);
|
||||
});
|
||||
it("should be able to open js popup windows with safariAllowPopups set to true", function (done) {
|
||||
|
||||
// broken on real devices, see https://github.com/appium/appium/issues/5167
|
||||
it("should be able to open js popup windows with safariAllowPopups set to true @skip-real-device", function (done) {
|
||||
driver
|
||||
.execute("window.open('/test/guinea-pig2.html', null);")
|
||||
.elementByLinkText('i am a new window link')
|
||||
.click()
|
||||
.then(function () { return spinTitle("I am another page title", driver, 30); })
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
var getAppPath = require('../../../helpers/app').getAppPath;
|
||||
var getAppPath = require('sample-apps');
|
||||
var env = require('../../../helpers/env.js');
|
||||
|
||||
module.exports = {
|
||||
app: getAppPath('TestApp')
|
||||
app: getAppPath('TestApp', env.REAL_DEVICE)
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('testapp - device', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid deviceName @skip-ios6', function () {
|
||||
describe('invalid deviceName @skip-ios6 @skip-real-device', function () {
|
||||
var newDesired = _.extend(_.clone(desired), {deviceName: "iFailure 3.5-inch"});
|
||||
var session = initSession(newDesired, {'no-retry': true});
|
||||
|
||||
@@ -60,23 +60,32 @@ describe('testapp - device', function () {
|
||||
});
|
||||
});
|
||||
|
||||
_.each(['iPhone', 'iPad'], function (device) {
|
||||
describe('generic ' + device + ' deviceName @skip-ios6', function () {
|
||||
var newDesired = _.extend(_.clone(desired), {
|
||||
deviceName: device + " Simulator"
|
||||
});
|
||||
var session = initSession(newDesired, {'no-retry': true});
|
||||
describe('generic deviceName @skip-ios6', function () {
|
||||
var session;
|
||||
|
||||
after(function (done) {
|
||||
session.tearDown(this.currentTest.state === 'passed').nodeify(done);
|
||||
});
|
||||
|
||||
it('should work with a generic ' + device + ' deviceName', function (done) {
|
||||
session.setUp()
|
||||
.nodeify(done);
|
||||
});
|
||||
afterEach(function (done) {
|
||||
session.tearDown(this.currentTest.state === 'passed').nodeify(done);
|
||||
});
|
||||
|
||||
it('should work with a generic iPhone deviceName', function (done) {
|
||||
var newDesired = _.extend(_.clone(desired), {
|
||||
deviceName: "iPhone Simulator"
|
||||
});
|
||||
|
||||
session = initSession(newDesired, {'no-retry': true});
|
||||
session.setUp()
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
it('should work with a generic iPad deviceName', function (done) {
|
||||
var newDesired = _.extend(_.clone(desired), {
|
||||
deviceName: "iPad Simulator"
|
||||
});
|
||||
|
||||
session = initSession(newDesired, {'no-retry': true});
|
||||
session.setUp()
|
||||
.nodeify(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe("real device", function () {
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('localization - locale', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('changing locale', function () {
|
||||
describe('changing locale @skip-real-device', function () {
|
||||
var driver;
|
||||
setup(this, _.defaults({locale: 'fr'}, desired)).then(function (d) { driver = d; });
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('localization - locale', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('changing back', function () {
|
||||
describe('changing back @skip-real-device', function () {
|
||||
var driver;
|
||||
setup(this, _.defaults({locale: 'en_US'}, desired)).then(function (d) { driver = d; });
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('testapp - location - 3 @skip-ci', function () {
|
||||
});
|
||||
|
||||
// TODO: location tests are not working well on sauce
|
||||
describe('testapp - location - 4 @skip-ci', function () {
|
||||
describe('testapp - location - 4 @skip-ci @skip-real-device', function () {
|
||||
var driver;
|
||||
var newDesired = _.clone(desired);
|
||||
_.extend(newDesired, {
|
||||
|
||||
@@ -138,7 +138,8 @@ describe('testapp - swipe actions', function () {
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
it('should work with: press {element}, moveTo {destEl}', function (done) {
|
||||
// TODO: For some reason it does not swipe to 100% in ci env, investigate
|
||||
it('should work with: press {element}, moveTo {destEl} @skip-ci', function (done) {
|
||||
driver
|
||||
// test: press {element}, moveTo {destEl}
|
||||
.performTouchAction((new TouchAction())
|
||||
@@ -166,7 +167,8 @@ describe('testapp - swipe actions', function () {
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
it('should work with: {element, x, y}, moveTo {destEl, x, y}', function (done) {
|
||||
// TODO: Crashes in ci env, investigate
|
||||
it('should work with: {element, x, y}, moveTo {destEl, x, y} @skip-ci', function (done) {
|
||||
driver
|
||||
// test: press {element, x, y}, moveTo {destEl, x, y}
|
||||
.performTouchAction((new TouchAction())
|
||||
@@ -176,7 +178,8 @@ describe('testapp - swipe actions', function () {
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
it("should work with press {x, y}, moveTo {destEl}", function (done) {
|
||||
// TODO: For some reason it does not swipe to 100% in ci env, investigate
|
||||
it("should work with press {x, y}, moveTo {destEl} @skip-ci", function (done) {
|
||||
driver
|
||||
// test: press {x, y}, moveTo {destEl}
|
||||
.performTouchAction((new TouchAction())
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
var getAppPath = require('../../../helpers/app').getAppPath;
|
||||
var getAppPath = require('sample-apps');
|
||||
var env = require('../../../helpers/env.js');
|
||||
|
||||
module.exports = {
|
||||
app: getAppPath('UICatalog')
|
||||
app: getAppPath('UICatalog', env.REAL_DEVICE)
|
||||
};
|
||||
|
||||
@@ -38,9 +38,8 @@ describe('webview - basics', function () {
|
||||
});
|
||||
}).nodeify(done);
|
||||
});
|
||||
it('setting context to \'WEBVIEW_1\' should work', function (done) {
|
||||
it('setting context without getting contexts should work', function (done) {
|
||||
driver
|
||||
.contexts().should.eventually.have.length.above(0)
|
||||
.context("WEBVIEW_1")
|
||||
.sleep(500)
|
||||
.get(env.GUINEA_TEST_END_POINT)
|
||||
@@ -49,15 +48,18 @@ describe('webview - basics', function () {
|
||||
.should.eventually.equal("I am a page title")
|
||||
.nodeify(done);
|
||||
});
|
||||
it('setting context to \'WEBVIEW_1\' should work without first getting contexts', function (done) {
|
||||
driver
|
||||
.context("WEBVIEW_1")
|
||||
.sleep(500)
|
||||
.get(env.GUINEA_TEST_END_POINT)
|
||||
.sleep(500)
|
||||
.title()
|
||||
.should.eventually.equal("I am a page title")
|
||||
.nodeify(done);
|
||||
it('setting context to \'WEBVIEW_X\' should work', function (done) {
|
||||
driver.contexts().then(function (contexts) {
|
||||
contexts.should.have.length.above(0);
|
||||
driver
|
||||
.context(contexts[1])
|
||||
.sleep(500)
|
||||
.get(env.GUINEA_TEST_END_POINT)
|
||||
.sleep(1000)
|
||||
.title()
|
||||
.should.eventually.equal("I am a page title")
|
||||
.nodeify(done);
|
||||
});
|
||||
});
|
||||
it('setting context to \'WEBVIEW\' should work', function (done) {
|
||||
driver
|
||||
@@ -92,25 +94,26 @@ describe('webview - basics', function () {
|
||||
|
||||
it('setting context to non-existent context should return \'NoSuchContext\' (status: 35)', function (done) {
|
||||
driver
|
||||
.context("WEBVIEW_42")
|
||||
.context("WEBVIEW_420")
|
||||
.should.be.rejectedWith(/status: 35/)
|
||||
.nodeify(done);
|
||||
});
|
||||
|
||||
it('switching back and forth between native and webview contexts should work @skip-ios6', function (done) {
|
||||
driver
|
||||
.contexts()
|
||||
.context("WEBVIEW_1")
|
||||
driver.contexts().then(function (contexts) {
|
||||
driver
|
||||
.context(contexts[1])
|
||||
.get(env.GUINEA_TEST_END_POINT)
|
||||
.sleep(3000)
|
||||
.title()
|
||||
.should.eventually.equal("I am a page title")
|
||||
.context("NATIVE_APP")
|
||||
.context("WEBVIEW_1")
|
||||
.context(contexts[1])
|
||||
.get(env.GUINEA_TEST_END_POINT)
|
||||
.sleep(3000)
|
||||
.elementByLinkText('i am a link').click()
|
||||
.elementById('only_on_page_2').should.eventually.exist
|
||||
.nodeify(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
var env = require('../../../helpers/env')
|
||||
, getAppPath = require('../../../helpers/app').getAppPath;
|
||||
var getAppPath = require('sample-apps');
|
||||
var env = require('../../../helpers/env.js');
|
||||
|
||||
module.exports = {
|
||||
app: env.IOS6 ? "assets/WebViewApp6.1.app.zip" : getAppPath('WebViewApp')
|
||||
app: getAppPath('TestApp', env.REAL_DEVICE)
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
app: env.IOS6 ? "assets/WebViewApp6.1.app.zip" :
|
||||
getAppPath('WebViewApp', env.REAL_DEVICE)
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var selendroidKeyboardTests = require('../common/android-keyboard-base');
|
||||
|
||||
describe('selendroid - keyboard', selendroidKeyboardTests);
|
||||
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var keyEventsTests = require('../../common/android-keyboard-base').keyEventsTests;
|
||||
|
||||
describe('selendroid - keyboard', keyEventsTests);
|
||||
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var textFieldTests = require('../../common/android-keyboard-base').textFieldTests;
|
||||
|
||||
describe('selendroid - keyboard', textFieldTests);
|
||||
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var unicodeLanguagesTests = require('../../common/android-keyboard-base').unicodeLanguagesTests;
|
||||
|
||||
describe('selendroid - keyboard', unicodeLanguagesTests);
|
||||
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var unicodeTextFieldTests = require('../../common/android-keyboard-base').unicodeTextFieldTests;
|
||||
|
||||
describe('selendroid - keyboard', unicodeTextFieldTests);
|
||||
+7
-17
@@ -1,8 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
var path = require('path')
|
||||
, _ = require('underscore')
|
||||
, os = require('os');
|
||||
, localIp = require('appium-support').util.localIp;
|
||||
|
||||
var env = {};
|
||||
|
||||
@@ -251,24 +250,15 @@ if (env.SAUCE && env.TARBALL) {
|
||||
env.CAPS.tags = [env.DEVICE];
|
||||
}
|
||||
|
||||
// rest enf points
|
||||
env.localIP = function () {
|
||||
var ip = _.chain(os.networkInterfaces())
|
||||
.values()
|
||||
.flatten()
|
||||
.filter(function (val) {
|
||||
return (val.family === 'IPv4' && val.internal === false);
|
||||
})
|
||||
.pluck('address')
|
||||
.first().value();
|
||||
return ip;
|
||||
};
|
||||
|
||||
env.LOCAL_APPIUM_PORT = env.SAUCE ? 4443 : env.APPIUM_PORT;
|
||||
env.TEST_END_POINT = 'http://localhost:' + env.LOCAL_APPIUM_PORT + '/test/';
|
||||
if (env.REAL_DEVICE) {
|
||||
env.TEST_END_POINT = 'http://' + localIp() + ':' + env.LOCAL_APPIUM_PORT + '/test/';
|
||||
} else {
|
||||
env.TEST_END_POINT = 'http://localhost:' + env.LOCAL_APPIUM_PORT + '/test/';
|
||||
}
|
||||
env.GUINEA_TEST_END_POINT = env.TEST_END_POINT + 'guinea-pig';
|
||||
if (env.REAL_DEVICE) {
|
||||
env.CHROME_TEST_END_POINT = 'http://' + env.localIP() + ':' + env.LOCAL_APPIUM_PORT + '/test/';
|
||||
env.CHROME_TEST_END_POINT = 'http://' + localIp() + ':' + env.LOCAL_APPIUM_PORT + '/test/';
|
||||
} else {
|
||||
env.CHROME_TEST_END_POINT = 'http://10.0.2.2:' + env.LOCAL_APPIUM_PORT + '/test/';
|
||||
}
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Prevents force-pushing to master
|
||||
|
||||
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||
PUSH_COMMAND=`ps -ocommand= -p $PPID`
|
||||
PROTECTED_BRANCHES="^(master)"
|
||||
FORCE_PUSH="force|delete|-f"
|
||||
|
||||
if [[ "$BRANCH" =~ $PROTECTED_BRANCHES && "$PUSH_COMMAND" =~ $FORCE_PUSH ]]; then
|
||||
echo "Prevented force-push to protected branch \"$BRANCH\" by pre-push hook"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -17,16 +17,19 @@ describe('android-controller', function () {
|
||||
{ action: 'moveTo', options: { x: -40, y: -41 } },
|
||||
{ action: 'release', options: {} } ];
|
||||
controller.parseTouch(actions, false, function (err, touchStates) {
|
||||
touchStates.length.should.equal(4);
|
||||
touchStates.length.should.equal(5);
|
||||
|
||||
var actions = [{action: 'press', x: 100, y: 101},
|
||||
{action: 'moveTo', x: 150, y: 152},
|
||||
{action: 'wait', x: 150, y: 152},
|
||||
{action: 'moveTo', x: 110, y: 111}];
|
||||
{action: 'moveTo', x: 110, y: 111},
|
||||
{action: 'release'}];
|
||||
_.each(touchStates, function (state, index) {
|
||||
state.action.should.equal(actions[index].action);
|
||||
state.options.x.should.equal(actions[index].x);
|
||||
state.options.y.should.equal(actions[index].y);
|
||||
if (actions[index].action !== 'release') {
|
||||
state.options.x.should.equal(actions[index].x);
|
||||
state.options.y.should.equal(actions[index].y);
|
||||
}
|
||||
});
|
||||
|
||||
done();
|
||||
|
||||
Reference in New Issue
Block a user