From 3da51d05cf6aa8a29e11ed94b0495d1d510dabf2 Mon Sep 17 00:00:00 2001 From: Jonathan Lipps Date: Thu, 21 May 2015 20:50:36 -0700 Subject: [PATCH] move all old code out of git --- .gitignore | 1 + Gruntfile.js | 127 - bin/appium-doctor.js | 109 - bin/appium.js | 58 - bin/authorize-ios.js | 23 - bin/ios-webkit-debug-proxy-launcher.js | 60 - bin/npmlink.sh | 76 - bin/publish.sh | 56 - bin/test.ps1 | 15 - bin/test.sh | 174 -- ci/as_current_user | 14 - ci/setup | 16 - grunt-helpers.js | 804 ------ gulpfile.js | 310 --- install-beta.md | 32 - install-from-npm.md | 32 - lib/appium.js | 410 --- lib/cookies.js | 93 - lib/devices/android/adb.js | 8 - lib/devices/android/android-common.js | 1093 -------- .../android/android-context-controller.js | 100 - lib/devices/android/android-controller.js | 1178 -------- lib/devices/android/android-hybrid.js | 275 -- lib/devices/android/android.js | 593 ---- lib/devices/android/bootstrap/.project | 23 - .../.settings/org.eclipse.jdt.core.prefs | 292 -- .../.settings/org.eclipse.jdt.ui.prefs | 109 - .../.settings/org.eclipse.m2e.core.prefs | 4 - lib/devices/android/bootstrap/README.md | 19 - lib/devices/android/bootstrap/build.xml | 92 - lib/devices/android/bootstrap/pom.xml | 56 - .../uiautomator/common/UiWatchers.java | 160 -- .../android/bootstrap/AndroidCommand.java | 118 - .../bootstrap/AndroidCommandExecutor.java | 75 - .../bootstrap/AndroidCommandResult.java | 59 - .../android/bootstrap/AndroidCommandType.java | 9 - .../android/bootstrap/AndroidElement.java | 261 -- .../bootstrap/AndroidElementsHash.java | 162 -- .../appium/android/bootstrap/Bootstrap.java | 27 - .../android/bootstrap/CommandHandler.java | 48 - .../io/appium/android/bootstrap/Dynamic.java | 211 -- .../io/appium/android/bootstrap/Logger.java | 23 - .../android/bootstrap/OrientationEnum.java | 33 - .../android/bootstrap/PositionHelper.java | 74 - .../android/bootstrap/SocketServer.java | 187 -- .../io/appium/android/bootstrap/WDStatus.java | 57 - .../exceptions/CommandTypeException.java | 15 - .../exceptions/ElementNotFoundException.java | 19 - .../InvalidCoordinatesException.java | 14 - .../exceptions/InvalidSelectorException.java | 8 - .../exceptions/InvalidStrategyException.java | 17 - .../exceptions/NoAttributeFoundException.java | 15 - .../exceptions/PairCreationException.java | 8 - .../exceptions/SocketServerException.java | 22 - .../exceptions/UiSelectorSyntaxException.java | 17 - .../exceptions/UnallowedTagNameException.java | 14 - .../android/bootstrap/handler/Clear.java | 179 -- .../android/bootstrap/handler/Click.java | 64 - .../handler/CompressedLayoutHierarchy.java | 32 - .../android/bootstrap/handler/Drag.java | 148 - .../android/bootstrap/handler/Find.java | 407 --- .../android/bootstrap/handler/Flick.java | 112 - .../bootstrap/handler/GetAttribute.java | 56 - .../android/bootstrap/handler/GetDataDir.java | 28 - .../bootstrap/handler/GetDeviceSize.java | 43 - .../bootstrap/handler/GetLocation.java | 42 - .../android/bootstrap/handler/GetName.java | 39 - .../android/bootstrap/handler/GetSize.java | 48 - .../android/bootstrap/handler/GetText.java | 41 - .../bootstrap/handler/LongPressKeyCode.java | 67 - .../handler/MultiPointerGesture.java | 136 - .../bootstrap/handler/OpenNotification.java | 43 - .../bootstrap/handler/Orientation.java | 135 - .../android/bootstrap/handler/Pinch.java | 69 - .../android/bootstrap/handler/PressBack.java | 31 - .../bootstrap/handler/PressKeyCode.java | 56 - .../android/bootstrap/handler/ScrollTo.java | 79 - .../android/bootstrap/handler/SetText.java | 88 - .../android/bootstrap/handler/Source.java | 49 - .../android/bootstrap/handler/Swipe.java | 67 - .../bootstrap/handler/TakeScreenshot.java | 42 - .../android/bootstrap/handler/TouchDown.java | 24 - .../android/bootstrap/handler/TouchEvent.java | 128 - .../bootstrap/handler/TouchLongClick.java | 62 - .../android/bootstrap/handler/TouchMove.java | 24 - .../android/bootstrap/handler/TouchUp.java | 24 - .../bootstrap/handler/UpdateStrings.java | 60 - .../bootstrap/handler/WaitForIdle.java | 42 - .../android/bootstrap/handler/Wake.java | 35 - .../android/bootstrap/selector/Strategy.java | 41 - .../appium/android/bootstrap/utils/API.java | 9 - .../bootstrap/utils/ClassInstancePair.java | 32 - .../bootstrap/utils/ElementHelpers.java | 64 - .../bootstrap/utils/NotImportantViews.java | 17 - .../appium/android/bootstrap/utils/Point.java | 71 - .../bootstrap/utils/ReflectionUtils.java | 108 - .../android/bootstrap/utils/TheWatchers.java | 37 - .../bootstrap/utils/UiAutomatorParser.java | 91 - .../bootstrap/utils/UiScrollableParser.java | 364 --- .../bootstrap/utils/UiSelectorParser.java | 192 -- .../bootstrap/utils/UnicodeEncoder.java | 38 - .../android/bootstrap/utils/XMLHierarchy.java | 189 -- .../core/AccessibilityNodeInfoDumper.java | 226 -- .../core/AccessibilityNodeInfoHelper.java | 47 - .../core/InteractionController.java | 49 - .../uiautomator/core/QueryController.java | 23 - .../uiautomator/core/UiAutomatorBridge.java | 59 - .../bootstrap/utils/XMLHierarchyTest.java | 90 - lib/devices/android/chrome.js | 230 -- lib/devices/android/selendroid.js | 582 ---- lib/devices/android/uiautomator.js | 223 -- lib/devices/common.js | 382 --- lib/devices/device-settings.js | 64 - lib/devices/device.js | 189 -- lib/devices/firefoxos/atoms/gaia_apps.js | 239 -- lib/devices/firefoxos/firefoxos-atoms.js | 22 - lib/devices/firefoxos/firefoxos.js | 347 --- lib/devices/ios/instruments.js | 9 - lib/devices/ios/ios-controller.js | 2411 ----------------- lib/devices/ios/ios-crash-log.js | 72 - lib/devices/ios/ios-hybrid.js | 237 -- lib/devices/ios/ios-log.js | 270 -- lib/devices/ios/ios-perf-log.js | 48 - lib/devices/ios/ios.js | 1707 ------------ lib/devices/ios/remote-debugger.js | 729 ----- lib/devices/ios/remote-messages.js | 132 - lib/devices/ios/safari.js | 201 -- lib/devices/ios/settings.js | 271 -- lib/devices/ios/simulator.js | 651 ----- lib/devices/ios/uiauto.js | 9 - lib/devices/ios/webkit-remote-debugger.js | 187 -- lib/doctor/android.js | 68 - lib/doctor/common.js | 159 -- lib/doctor/dev.js | 73 - lib/doctor/ios.js | 313 --- lib/future.js | 52 - lib/helpers.js | 353 --- lib/server/capabilities.js | 207 -- lib/server/controller.js | 1288 --------- lib/server/errors.js | 30 - lib/server/grid-register.js | 111 - lib/server/helpers.js | 328 --- lib/server/logger.js | 239 -- lib/server/main.js | 180 -- lib/server/middleware.js | 15 - lib/server/parser.js | 669 ----- lib/server/proxy.js | 111 - lib/server/responses.js | 162 -- lib/server/routing.js | 174 -- lib/server/static/favicon.ico | Bin 1150 -> 0 bytes lib/server/static/js/jquery.min.js | 5 - lib/server/static/test.jpeg | Bin 10519 -> 0 bytes lib/server/static/test/frameset.html | 10 - lib/server/static/test/guinea-pig2.html | 12 - lib/server/static/test/iframes.html | 14 - lib/server/static/test/subframe1.html | 9 - lib/server/static/test/subframe2.html | 8 - lib/server/static/test/subframe3.html | 11 - lib/server/static/test/touch.html | 56 - lib/server/status.js | 121 - lib/server/templates/guinea-pig.html | 78 - lib/server/templates/welcome.html | 9 - reset.bat | 271 -- reset.sh | 538 ---- trigger.txt | 2 - 165 files changed, 1 insertion(+), 27039 deletions(-) delete mode 100644 Gruntfile.js delete mode 100755 bin/appium-doctor.js delete mode 100755 bin/appium.js delete mode 100755 bin/authorize-ios.js delete mode 100755 bin/ios-webkit-debug-proxy-launcher.js delete mode 100755 bin/npmlink.sh delete mode 100755 bin/publish.sh delete mode 100644 bin/test.ps1 delete mode 100755 bin/test.sh delete mode 100755 ci/as_current_user delete mode 100755 ci/setup delete mode 100644 grunt-helpers.js delete mode 100644 gulpfile.js delete mode 100644 install-beta.md delete mode 100644 install-from-npm.md delete mode 100644 lib/appium.js delete mode 100644 lib/cookies.js delete mode 100644 lib/devices/android/adb.js delete mode 100644 lib/devices/android/android-common.js delete mode 100644 lib/devices/android/android-context-controller.js delete mode 100644 lib/devices/android/android-controller.js delete mode 100644 lib/devices/android/android-hybrid.js delete mode 100644 lib/devices/android/android.js delete mode 100644 lib/devices/android/bootstrap/.project delete mode 100644 lib/devices/android/bootstrap/.settings/org.eclipse.jdt.core.prefs delete mode 100644 lib/devices/android/bootstrap/.settings/org.eclipse.jdt.ui.prefs delete mode 100644 lib/devices/android/bootstrap/.settings/org.eclipse.m2e.core.prefs delete mode 100644 lib/devices/android/bootstrap/README.md delete mode 100644 lib/devices/android/bootstrap/build.xml delete mode 100644 lib/devices/android/bootstrap/pom.xml delete mode 100644 lib/devices/android/bootstrap/src/com/android/uiautomator/common/UiWatchers.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommand.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandResult.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandType.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElementsHash.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Bootstrap.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/CommandHandler.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Dynamic.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Logger.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/OrientationEnum.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/PositionHelper.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/SocketServer.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/WDStatus.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/CommandTypeException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/ElementNotFoundException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidCoordinatesException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidSelectorException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidStrategyException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/NoAttributeFoundException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/PairCreationException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/SocketServerException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UiSelectorSyntaxException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UnallowedTagNameException.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Clear.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Click.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/CompressedLayoutHierarchy.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Drag.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Flick.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetAttribute.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDataDir.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDeviceSize.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetLocation.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetName.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetSize.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetText.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/LongPressKeyCode.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/MultiPointerGesture.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/OpenNotification.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Orientation.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Pinch.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressBack.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressKeyCode.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/SetText.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Swipe.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TakeScreenshot.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchDown.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchEvent.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchLongClick.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchMove.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchUp.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/UpdateStrings.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/WaitForIdle.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Wake.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/selector/Strategy.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/API.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ClassInstancePair.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ElementHelpers.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/NotImportantViews.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/Point.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ReflectionUtils.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/TheWatchers.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiAutomatorParser.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiScrollableParser.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiSelectorParser.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UnicodeEncoder.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/XMLHierarchy.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoDumper.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoHelper.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/uiautomator/core/InteractionController.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/uiautomator/core/QueryController.java delete mode 100644 lib/devices/android/bootstrap/src/io/appium/uiautomator/core/UiAutomatorBridge.java delete mode 100644 lib/devices/android/bootstrap/test/io/appium/android/bootstrap/utils/XMLHierarchyTest.java delete mode 100644 lib/devices/android/chrome.js delete mode 100644 lib/devices/android/selendroid.js delete mode 100644 lib/devices/android/uiautomator.js delete mode 100644 lib/devices/common.js delete mode 100644 lib/devices/device-settings.js delete mode 100644 lib/devices/device.js delete mode 100644 lib/devices/firefoxos/atoms/gaia_apps.js delete mode 100644 lib/devices/firefoxos/firefoxos-atoms.js delete mode 100644 lib/devices/firefoxos/firefoxos.js delete mode 100644 lib/devices/ios/instruments.js delete mode 100644 lib/devices/ios/ios-controller.js delete mode 100644 lib/devices/ios/ios-crash-log.js delete mode 100644 lib/devices/ios/ios-hybrid.js delete mode 100644 lib/devices/ios/ios-log.js delete mode 100644 lib/devices/ios/ios-perf-log.js delete mode 100644 lib/devices/ios/ios.js delete mode 100644 lib/devices/ios/remote-debugger.js delete mode 100644 lib/devices/ios/remote-messages.js delete mode 100644 lib/devices/ios/safari.js delete mode 100644 lib/devices/ios/settings.js delete mode 100644 lib/devices/ios/simulator.js delete mode 100644 lib/devices/ios/uiauto.js delete mode 100644 lib/devices/ios/webkit-remote-debugger.js delete mode 100644 lib/doctor/android.js delete mode 100644 lib/doctor/common.js delete mode 100644 lib/doctor/dev.js delete mode 100644 lib/doctor/ios.js delete mode 100644 lib/future.js delete mode 100644 lib/helpers.js delete mode 100644 lib/server/capabilities.js delete mode 100644 lib/server/controller.js delete mode 100644 lib/server/errors.js delete mode 100644 lib/server/grid-register.js delete mode 100644 lib/server/helpers.js delete mode 100644 lib/server/logger.js delete mode 100644 lib/server/main.js delete mode 100644 lib/server/middleware.js delete mode 100644 lib/server/parser.js delete mode 100644 lib/server/proxy.js delete mode 100644 lib/server/responses.js delete mode 100644 lib/server/routing.js delete mode 100644 lib/server/static/favicon.ico delete mode 100644 lib/server/static/js/jquery.min.js delete mode 100644 lib/server/static/test.jpeg delete mode 100644 lib/server/static/test/frameset.html delete mode 100644 lib/server/static/test/guinea-pig2.html delete mode 100644 lib/server/static/test/iframes.html delete mode 100644 lib/server/static/test/subframe1.html delete mode 100644 lib/server/static/test/subframe2.html delete mode 100644 lib/server/static/test/subframe3.html delete mode 100644 lib/server/static/test/touch.html delete mode 100644 lib/server/status.js delete mode 100644 lib/server/templates/guinea-pig.html delete mode 100644 lib/server/templates/welcome.html delete mode 100644 reset.bat delete mode 100755 reset.sh delete mode 100644 trigger.txt diff --git a/.gitignore b/.gitignore index a9666d61..b0158f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ build/ .idea *DerivedData __pycache__ +old diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 7ca50ea9..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,127 +0,0 @@ -"use strict"; - -var path = require('path') - , gruntHelpers = require('./grunt-helpers.js') - , authorize = gruntHelpers.authorize - , tail = gruntHelpers.tail - , buildApp = gruntHelpers.buildApp - , buildSafariLauncherApp = gruntHelpers.buildSafariLauncherApp - , signApp = gruntHelpers.signApp - , setupAndroidBootstrap = gruntHelpers.setupAndroidBootstrap - , setupAndroidApp = gruntHelpers.setupAndroidApp - , buildAndroidBootstrap = gruntHelpers.buildAndroidBootstrap - , buildSelendroidServer = gruntHelpers.buildSelendroidServer - , buildAndroidApp = gruntHelpers.buildAndroidApp - , buildSelendroidAndroidApp = gruntHelpers.buildSelendroidAndroidApp - , fixSelendroidAndroidManifest = gruntHelpers.fixSelendroidAndroidManifest - , installAndroidApp = gruntHelpers.installAndroidApp - , generateServerDocs = gruntHelpers.generateServerDocs - , generateAppiumIo = gruntHelpers.generateAppiumIo - , setDeviceConfigVer = gruntHelpers.setDeviceConfigVer - , setBuildTime = gruntHelpers.setBuildTime - , getSampleCode = gruntHelpers.getSampleCode - , setGitRev = gruntHelpers.setGitRev - , getGitRev = require('./lib/helpers').getGitRev; - -var GULP_BIN = 'node_modules/.bin/gulp'; - -module.exports = function (grunt) { - grunt.initConfig({ - mochaTest: { - appiumutils: ['test/functional/appium/appiumutils.js'] - } - , mochaTestConfig: { - options: { - timeout: 60000, - reporter: 'spec' - } - } - , maxBuffer: 2000 * 1024 - , exec: { - 'gulp-test-unit': GULP_BIN + ' test-unit --color', - 'gulp-jshint': GULP_BIN + ' jshint --color', - 'gulp-jscs': GULP_BIN + ' jscs --color', - 'gulp-lint': GULP_BIN + ' lint --color' - }, - }); - - grunt.loadNpmTasks('grunt-mocha-test'); - grunt.loadNpmTasks('grunt-exec'); - grunt.registerTask('jshint', 'exec:gulp-jshint'); - grunt.registerTask('jscs', 'exec:gulp-jscs'); - grunt.registerTask('lint', 'exec:gulp-lint'); - grunt.registerTask('test', 'exec:gulp-test-unit'); - grunt.registerTask('unit', 'exec:gulp-test-unit'); - grunt.registerTask('default', ['test']); - grunt.registerTask('travis', ['jshint','jscs', 'unit']); - grunt.registerTask('buildApp', "Build the test app", function (appDir, sdk) { - buildApp(appDir, this.async(), sdk); - }); - grunt.registerTask('buildSafariLauncherApp', "Build the SafariLauncher app", function (sdk, xcconfig) { - buildSafariLauncherApp(this.async(), sdk, xcconfig); - }); - grunt.registerTask('signApp', "Sign the test app", function (certName) { - signApp("TestApp", certName, this.async()); - }); - grunt.registerTask('authorize', "Authorize developer", function (insecure) { - authorize(grunt, insecure, this.async()); - }); - grunt.registerTask('log', "Tail appium.log", function () { - tail(grunt, path.resolve(__dirname, "appium.log"), this.async()); - }); - grunt.registerTask('configAndroidBootstrap', function () { - setupAndroidBootstrap(grunt, this.async()); - }); - grunt.registerTask('buildAndroidBootstrap', function () { - buildAndroidBootstrap(grunt, this.async()); - }); - grunt.registerTask('buildSelendroidServer', function () { - buildSelendroidServer(this.async()); - }); - grunt.registerTask('fixSelendroidAndroidManifest', function () { - var destDir = path.resolve(__dirname, "build", "selendroid"); - var dstManifest = path.resolve(destDir, "AndroidManifest.xml"); - fixSelendroidAndroidManifest(dstManifest, this.async()); - }); - grunt.registerTask('configAndroidApp', function (appName) { - setupAndroidApp(grunt, appName, this.async()); - }); - grunt.registerTask('buildAndroidApp', function (appName) { - buildAndroidApp(grunt, appName, this.async()); - }); - grunt.registerTask('buildSelendroidAndroidApp', function (appName) { - buildSelendroidAndroidApp(grunt, appName, this.async()); - }); - grunt.registerTask('installAndroidApp', function (appName) { - installAndroidApp(grunt, appName, this.async()); - }); - grunt.registerTask('docs', function () { - generateServerDocs(grunt, this.async()); - }); - grunt.registerTask('generateAppiumIo', function () { - generateAppiumIo(grunt, this.async()); - }); - grunt.registerTask('setConfigVer', function (device) { - setDeviceConfigVer(grunt, device, this.async()); - }); - grunt.registerTask('setBuildTime', function () { - setBuildTime(grunt, this.async()); - }); - grunt.registerTask('getSampleCode', function (hardcore) { - if (typeof hardcore !== "undefined" && hardcore === "hardcore") { - hardcore = true; - } - getSampleCode(grunt, hardcore, this.async()); - }); - grunt.registerTask('setGitRev', function (rev) { - var done = this.async(); - if (typeof rev === "undefined") { - getGitRev(function (err, rev) { - if (err) throw err; - setGitRev(grunt, rev, done); - }); - } else { - setGitRev(grunt, rev, done); - } - }); -}; diff --git a/bin/appium-doctor.js b/bin/appium-doctor.js deleted file mode 100755 index d90fb13e..00000000 --- a/bin/appium-doctor.js +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -var IOSChecker = require('../lib/doctor/ios.js').IOSChecker - , AndroidChecker = require('../lib/doctor/android.js').AndroidChecker - , DevChecker = require('../lib/doctor/dev.js').DevChecker - , common = require("../lib/doctor/common.js") - , eol = require('os').EOL - , async = require('async') - , isMac = process.platform === 'darwin'; - // , isWindows = process.platform === 'win32'; - -var argv = process.argv - , doAndroid = argv.indexOf('--android') > -1 - , doIOS = argv.indexOf('--ios') > -1 - , doDev = argv.indexOf('--dev') > -1 - // , verbose = argv.indexOf('--verbose') > -1 - , broadcast = argv.indexOf('--port') > -1 - , port = null; - -if (broadcast) { - port = argv[argv.indexOf("--port") + 1]; -} - -if (!doIOS && !doAndroid) { - doIOS = isMac; - doAndroid = true; -} - -var log = new common.Log(port); - -var runiOSChecks = function (cb) { - if (doIOS) { - if (!isMac) { - log.fail("iOS Checks cannot be run on Windows."); - log.exitDoctor(); - } - var iosChecker = new IOSChecker(log); - log.comment("Running iOS Checks"); - iosChecker.runAllChecks(function (err) { - if (!err) { - log.pass("iOS Checks were successful." + eol); - cb(); - } else { - log.exitDoctor(); - } - }); - } else { - cb(); - } -}; - -var runAndroidChecks = function (cb) { - if (doAndroid) { - var androidChecker = new AndroidChecker(log); - log.comment("Running Android Checks"); - androidChecker.runAllChecks(function (err) { - if (!err) { - log.pass("Android Checks were successful." + eol); - cb(); - } else { - log.exitDoctor(); - } - }); - } else { - cb(); - } -}; - -var runDevChecks = function (cb) { - if (doDev) { - var devChecker = new DevChecker(log); - log.comment("Running Dev Checks"); - devChecker.runAllChecks(function (err) { - if (!err) { - log.pass("Dev Checks were successful." + eol); - cb(); - } else { - log.exitDoctor(); - } - }); - } else { - cb(); - } -}; - -if (require.main === module) { - - var mainMethod = function () { - async.series([ - runiOSChecks, - runAndroidChecks, - runDevChecks - ], function (err) { - if (!err) { - log.pass("All Checks were successful"); - log.stopBroadcast(); - } else { - log.exitDoctor(); - } - }); - }; - - if (log.broadcast) { - log.startBroadcast(mainMethod); - } else { - mainMethod(); - } -} diff --git a/bin/appium.js b/bin/appium.js deleted file mode 100755 index e146fdd5..00000000 --- a/bin/appium.js +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -var net = require('net') - , repl = require('repl') - , logFactory = require('../lib/server/logger.js') - , parser = require('../lib/server/parser.js'); - -require('colors'); - -var args = parser().parseArgs(); -logFactory.init(args); - -var appium = require('../lib/server/main.js'); - -var startRepl = function () { - var help = function () { - console.log("\nWelcome to the Appium CLI".cyan); - console.log(" - Access the appium object via the object: 'appium'".grey); - console.log(" - appium.run is the function to start the server".grey); - console.log(" - args is the default params data structure".grey); - console.log(" - set args.app then run appium.run(args);\n".grey); - return 'Thanks for asking'; - }; - - help(); - - var r = repl.start('(appium): '); - r.context.appium = appium; - r.context.parser = parser(); - r.context.help = help; - r.context.args = { - app: '/path/to/test/app' - , udid: null - , address: '127.0.0.1' - , port: 4723 - }; - - var connections = 0; - var server = net.createServer(function (socket) { - connections += 1; - socket.setTimeout(5 * 60 * 1000, function () { - socket.destroy(); - }); - repl.start("(appium): ", socket); - }).listen(process.platform === "win32" ? "\\\\.\\pipe\\node-repl-sock-" + process.pid : "/tmp/node-repl-sock-" + process.pid); - - r.on('exit', function () { - server.close(); - process.exit(); - }); -}; - -if (process.argv[2] && process.argv[2].trim() === "--shell") { - startRepl(); -} else { - appium.run(args, function () { /* console.log('Rock and roll.'.grey); */ }); -} diff --git a/bin/authorize-ios.js b/bin/authorize-ios.js deleted file mode 100755 index 4e3e15d9..00000000 --- a/bin/authorize-ios.js +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -var authorize = require('../grunt-helpers.js').authorize; - -var gruntMock = { - fatal: function (msg) { - console.error(msg); - process.exit(0); - }, - log: { - writeln: function (msg) { - console.log(msg); - } - } -}; - -if (require.main === module) { - authorize(gruntMock, false, function (err) { - if (err) throw err; - console.log("Authorization successful"); - }); -} diff --git a/bin/ios-webkit-debug-proxy-launcher.js b/bin/ios-webkit-debug-proxy-launcher.js deleted file mode 100755 index 5260be7f..00000000 --- a/bin/ios-webkit-debug-proxy-launcher.js +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node - -/* - * Small tool, launching and monitoring ios-web-kit-proxy, and relauching - * on predefined errors. - * - * Usage: - * ./bin/ios-webkit-debug-proxy-launcher.js [args] - * args: ios-webkit-debug-proxy args (they will be passed over) - * - * Example: - * ./bin/ios-webkit-debug-proxy-launcher.js -c :27753 -d - * - * Note: - * For iOS8.1 try this first: - * brew install --HEAD ideviceinstaller - */ - -"use strict"; - -var spawn = require('child_process').spawn, - _ = require('underscore'); - -var args = process.argv.slice(2); - -var RESTART_ON_MESSAGES = [ - 'Invalid message _rpc_applicationUpdated', - 'Invalid message _rpc_applicationSentListing']; - -var PROXY_CMD = 'ios_webkit_debug_proxy'; - -function startProxy() { - console.log('RUNNING:', PROXY_CMD, args.join(' ')); - - var proxy = spawn(PROXY_CMD, args); - - proxy.stdout.on('data', function (data) { - console.log('stdout: ' + data); - }); - - proxy.stderr.on('data', function (data) { - console.log('stderr: ' + data); - var restartMessage = _(RESTART_ON_MESSAGES).find(function (message) { - return ('' + data).indexOf(message) >= 0; - }); - if (restartMessage) { - console.log('Detected error message:', restartMessage); - console.log('Killing proxy!'); - proxy.kill('SIGTERM'); - process.nextTick(startProxy); - } - }); - - proxy.on('close', function (code) { - console.log('child process exited with code ' + code); - }); -} - -startProxy(); - diff --git a/bin/npmlink.sh b/bin/npmlink.sh deleted file mode 100755 index 4e46cf81..00000000 --- a/bin/npmlink.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash - -ACTION='usage' - -ALL_PACKAGES=( - 'appium-atoms' - 'appium-instruments' - 'appium-uiauto' - 'appium-adb' -) - -PACKAGES=() - -for i in "$@" -do - case $i in - -l|--link) - ACTION='link' - shift - ;; - -u|--unlink) - ACTION='unlink' - shift - ;; - -m|--master) - MASTER=true - shift - ;; - *) - PACKAGES+=($i) - shift - ;; - esac -done - -if [[ ${#PACKAGES[*]} == 0 ]]; then - PACKAGES=("${ALL_PACKAGES[@]}") -fi - -function npmlink { - git submodule update --init submodules/$1 - pushd submodules/$1 - if [[ $MASTER ]]; then - git checkout master - fi - npm link - popd - npm link $1 -} - -if [[ $ACTION == 'usage' ]]; then - echo 'Usage:' - echo ' link all: dev.sh --link [--master]' - echo ' unlink all: dev.sh --unlink' - echo ' link specific packages: dev.sh --link [--master] ...' - echo ' unlink specific packages: dev.sh --unlink ...' - echo 'Short verion:' - echo ' link all: dev.sh -l [-m]' - echo ' unlink all: dev.sh -u' - echo ' link specific packages: dev.sh -l [-m] ...' - echo ' unlink specific packages: dev.sh -u ...' -fi - -if [[ $ACTION == 'link' ]]; then - for i in "${PACKAGES[@]}" - do - npmlink $i - done -fi - -if [[ $ACTION == 'unlink' ]]; then - for i in "${PACKAGES[@]}" - do - npm unlink $i - done -fi diff --git a/bin/publish.sh b/bin/publish.sh deleted file mode 100755 index 06362a3d..00000000 --- a/bin/publish.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash -set +e - -if [[ "$1" = "" ]]; then - branch="master" -else - branch="$1" -fi - -node --version | grep "v0.12" >/dev/null -if [ $? -gt 0 ]; then - echo "You need to publish Appium using Node 0.12.x, not $(node --version)" - exit 1; -else - echo "Node version OK" -fi - -git remote | grep "upstream" >/dev/null -if [ $? -gt 0 ]; then - echo "You need to have an 'upstream' remote to pull from / push tags to" - exit 1 -else - echo "Git remote OK" -fi -git status | grep -E "nothing to commit.+working directory clean" >/dev/null -if [ $? -gt 0 ]; then - echo "Working directory isn't clean, commit/clean then publish" - exit 1 -else - echo "Working directory clean" -fi -git status | grep "Your branch is ahead" >/dev/null -if [ $? -eq 0 ]; then - echo "Your branch isn't in sync with origin" - exit 1 -else - echo "In sync with origin" -fi - -set -e -echo "Getting latest from upstream:$branch" -git pull upstream $branch -echo "Resetting" -./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 -echo "Publishing on npm" -if [[ "$version" =~ "beta" ]]; then - npm publish --tag beta -else - npm publish -fi -echo "Git tagging" -git tag -a "v$version" -m "tag appium@$version for npm publish" -git push --tags upstream $branch diff --git a/bin/test.ps1 b/bin/test.ps1 deleted file mode 100644 index 954ace01..00000000 --- a/bin/test.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -param([string]$mochaArgs = "", [switch]$android, [switch]$selendroid) -$all = !($android -or $selendroid) -$appiumMocha="mocha --recursive -t 90000 -R spec $mochaArgs " - -if ($android -or $all){ - "RUNNING ANDROID TESTS" - "---------------------" - Invoke-Expression ($appiumMocha + "test\functional\android test\functional\common -g '@skip-android-all' -i") -} - -if ($selendroid -or $all){ - "RUNNING SELENDROID TESTS" - "------------------------" - Invoke-Expression ($appiumMocha + "test\functional\selendroid -g '@skip-selendroid-all' -i") -} \ No newline at end of file diff --git a/bin/test.sh b/bin/test.sh deleted file mode 100755 index 59808fe5..00000000 --- a/bin/test.sh +++ /dev/null @@ -1,174 +0,0 @@ -#!/bin/sh -set -e -mocha_args="" -ios_only=false -ios6_only=false -ios7_only=false -ios71_only=false -ios8_only=false -ios81_only=false -ios82_only=false -ios83_only=false -ios84_only=false -android_only=false -android_chrome=false -selendroid_only=false -gappium_only=false -real_device=false -all_tests=true - -for arg in "$@"; do - if [ "$arg" = "--ios" ]; then - ios_only=true - all_tests=false - elif [ "$arg" = "--android" ]; then - android_only=true - all_tests=false - elif [ "$arg" = "--android-chrome" ]; then - android_chrome=true - all_tests=false - elif [ "$arg" = "--selendroid" ]; then - selendroid_only=true - all_tests=false - elif [ "$arg" = "--gappium" ]; then - gappium_only=true - all_tests=false - elif [ "$arg" = "--ios6" ]; then - ios6_only=true - all_tests=false - elif [ "$arg" = "--ios7" ]; then - ios7_only=true - all_tests=false - elif [ "$arg" = "--ios71" ]; then - ios71_only=true - all_tests=false - elif [ "$arg" = "--ios8" ]; then - ios8_only=true - all_tests=false - elif [ "$arg" = "--ios81" ]; then - ios81_only=true - all_tests=false - elif [ "$arg" = "--ios82" ]; then - ios82_only=true - all_tests=false - elif [ "$arg" = "--ios83" ]; then - ios83_only=true - all_tests=false - elif [ "$arg" = "--ios84" ]; then - ios84_only=true - all_tests=false - elif [ "$arg" = "--real-device" ]; then - real_device=true - elif [ "$arg" =~ " " ]; then - mocha_args="$mocha_args \"$arg\"" - else - mocha_args="$mocha_args $arg" - fi -done - -appium_mocha="./node_modules/.bin/mocha --recursive $mocha_args" - -run_ios_tests() { - echo "RUNNING IOS $1 TESTS" - echo "---------------------" - - tag_regex=$3 - if $real_device; then - tag_regex="$3|@skip-real-device" - fi - - 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 - run_ios_tests "6.1" "ios6" "@skip-ios6|@skip-ios-all" -fi - -if $ios7_only || $all_tests; then - run_ios_tests "7.0" "ios7" "@skip-ios7|@skip-ios-all" -fi - -if $ios71_only || $all_tests; then - run_ios_tests "7.1" "ios71" "@skip-ios71|@skip-ios7|@skip-ios-all|@skip-ios7up" -fi - -if $ios8_only || $all_tests; then - run_ios_tests "8.0" "ios8" "@skip-ios8|@skip-ios-all|@skip-ios7up" -fi - -if $ios81_only || $all_tests; then - run_ios_tests "8.1" "ios81" "@skip-ios81|@skip-ios8|@skip-ios-all|@skip-ios7up" -fi - -if $ios82_only || $all_tests; then - run_ios_tests "8.2" "ios82" "@skip-ios82|@skip-ios81|@skip-ios8|@skip-ios-all|@skip-ios7up" -fi - -if $ios83_only || $all_tests; then - run_ios_tests "8.3" "ios83" "@skip-ios83|@skip-ios82|@skip-ios81|@skip-ios8|@skip-ios-all|@skip-ios7up" -fi - -if $ios84_only || $all_tests; then - run_ios_tests "8.4" "ios84" "@skip-ios84|@skip-ios82|@skip-ios81|@skip-ios8|@skip-ios-all|@skip-ios7up" -fi - -if $android_only || $all_tests; then - echo "RUNNING ANDROID TESTS" - echo "---------------------" - - if $real_device; then - DEVICE=android REAL_DEVICE=true time $appium_mocha \ - -g '@skip-android-all|@android-arm-only|@skip-real-device' -i \ - test/functional/common \ - test/functional/android - else - DEVICE=android time $appium_mocha \ - -g '@skip-android-all|@android-arm-only' -i \ - test/functional/common \ - test/functional/android - fi -fi - -if $android_chrome; then - echo "RUNNING ANDROID CHROME TESTS" - echo "---------------------" - - if $real_device; then - DEVICE=android REAL_DEVICE=true time $appium_mocha \ - -g '@skip-chrome|@skip-android-all' -i \ - test/functional/android/chrome - else - DEVICE=android time $appium_mocha \ - -g '@skip-chrome|@skip-android-all' -i \ - test/functional/android/chrome - fi -fi - -if $selendroid_only || $all_tests; then - echo "RUNNING SELENDROID TESTS" - echo "---------------------" - DEVICE=selendroid time $appium_mocha -g '@skip-selendroid-all' -i \ - test/functional/selendroid -fi - -if $gappium_only || $all_tests; then - echo "RUNNING GAPPIUM TESTS" - echo "---------------------" - DEVICE=ios81 time $appium_mocha test/functional/gappium - # disabling, ios6 not working yet xcode 6 - #DEVICE=ios6 time $appium_mocha test/functional/gappium - echo "Start the android emulator api 19 and press Enter." - read - DEVICE=android time $appium_mocha test/functional/gappium - # disabling gappium test, see https://github.com/selendroid/selendroid/issues/658 - #echo "Start the android emulator api 16 and press Enter." - #read - #DEVICE=selendroid time $appium_mocha test/functional/gappium -fi diff --git a/ci/as_current_user b/ci/as_current_user deleted file mode 100755 index 1cd88eeb..00000000 --- a/ci/as_current_user +++ /dev/null @@ -1,14 +0,0 @@ -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 "$*" diff --git a/ci/setup b/ci/setup deleted file mode 100755 index 522406e0..00000000 --- a/ci/setup +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -{ set -e; } 2>/dev/null -{ set -x; } 2>/dev/null - -# Loading appium-ci-gulper submodule -export APPIUM_ROOT="$(pwd)" -git submodule update --remote --init submodules/appium-ci-gulper -pushd submodules/appium-ci-gulper -git pull origin master -{ set +x; } 2>/dev/null - -# Running appium-ci-gulper submodule -# This sets up node etc... -source ./setup -{ set -x; } 2>/dev/null - diff --git a/grunt-helpers.js b/grunt-helpers.js deleted file mode 100644 index 33d44fdf..00000000 --- a/grunt-helpers.js +++ /dev/null @@ -1,804 +0,0 @@ -"use strict"; - -var _ = require("underscore") - , server = require('./lib/server/main.js') - , rimraf = require('rimraf') - , path = require('path') - // , temp = require('temp') - , mkdirp = require('mkdirp') - , ncp = require('ncp') - // , difflib = require('difflib') - // , prompt = require('prompt') - , exec = require('child_process').exec - , spawn = require('win-spawn') - , parser = require('./lib/server/parser.js') - , namp = require('namp') - , parseXmlString = require('xml2js').parseString - , appiumVer = require('./package.json').version - , fs = require('fs') - , xcode = require('./lib/future.js').xcode - , isWindows = require('appium-support').system.isWindows() - , MAX_BUFFER_SIZE = 524288 - , SELENDROID_MAX_BUFFER_SIZE = 4 * MAX_BUFFER_SIZE; - -module.exports.startAppium = function (appName, verbose, readyCb, doneCb) { - var app; - if (appName) { - app = (fs.existsSync(appName)) ? appName: - path.resolve(__dirname, "sample-code", "apps", appName, "build", "Release-iphonesimulator", appName + ".app"); - } else { - app = null; - } - return server.run({ - app: app - , udid: null - , quiet: !verbose - , port: 4723 - , nativeInstrumentsLib: false - , fullReset: false - , noReset: false - , launch: app ? true : false - , log: path.resolve(__dirname, "appium.log") - , address: '127.0.0.1' - , androidDeviceReadyTimeout: 5 - , nodeconfig: null - , robotPort: -1 - , robotAddresss: '0.0.0.0' - , keepArtifacts: false - , ipa: null - , avd: null - } - , readyCb - , doneCb - ); -}; - -var execWithOutput = function (cmd, cb) { - exec(cmd, function (err, stdout, stderr) { - if (err) { - console.error("Command failed"); - console.error("stdout:"); - console.error(stdout); - console.error("stderr:"); - console.error(stderr); - } - cb(err, stdout, stderr); - }); -}; - -module.exports.getSampleCode = function (grunt, hardcore, cb) { - var submodulesDir = path.resolve(__dirname, "submodules"); - var sampleCodeGit = path.resolve(submodulesDir, "sample-code"); - var sampleCodeDir = path.resolve(__dirname, "sample-code"); - var sampleCodeExists = fs.existsSync(sampleCodeDir); - var updateCmd = "git submodule update --init \"" + sampleCodeGit + "\""; - console.log("Cloning/updating Appium sample-code submodule"); - execWithOutput(updateCmd, function (err, stdout, stderr) { - if (err) return cb(err); - var updated = false; - if (stdout + stderr !== "") { - // there were submodule updates - console.log("There were updates to the submodule"); - updated = true; - } - if (hardcore || updated) { - console.log("Removing old sample-code"); - console.log("Please remember to rebuild test apps"); - rimraf.sync(sampleCodeDir); - } - if (hardcore || updated || !sampleCodeExists) { - console.log("Copying sample-code out for use"); - ncp(path.resolve(sampleCodeGit, "sample-code"), sampleCodeDir, function (err) { - if (err) return cb(err); - console.log("Test apps are ready for building"); - cb(); - }); - } else { - console.log("Sample code was not updated, doing nothing"); - } - }); -}; - -module.exports.runTestsWithServer = function (grunt, appName, testType, deviceType, verbose, cb) { - if (typeof verbose === "undefined") { - verbose = false; - } - var exitCode = null; - var appServer = module.exports.startAppium(appName, verbose, function () { - module.exports.runMochaTests(grunt, appName, testType, deviceType, function (code) { - appServer.close(); - exitCode = code; - }); - }, function () { - console.log("Appium server exited"); - cb(exitCode === 0); - }); -}; - -module.exports.runMochaTests = function (grunt, appName, testType, deviceType, cb) { - - // load the options if they are specified - var options = grunt.config(['mochaTestConfig', testType, 'options']); - if (typeof options !== 'object') { - options = grunt.config(['mochaTestConfig', 'options']); - } - if (typeof options.timeout === "undefined") { - options.timeout = 60000; - } - if (typeof options.reporter === "undefined") { - options.reporter = "tap"; - } - var args = ['-t', options.timeout, '-R', options.reporter, '--colors']; - var mochaFiles = []; - var fileConfig = grunt.config(['mochaTestWithServer']); - var configAppDevice, nameOk, deviceOk, configAppTests; - _.each(fileConfig, function (config, configAppName) { - configAppDevice = config[0]; - configAppTests = config[1]; - nameOk = !appName || appName === configAppName; - deviceOk = !deviceType || deviceType === configAppDevice; - if (nameOk && deviceOk) { - _.each(configAppTests, function (testFiles, testKey) { - if (testType === "*" || testType === testKey) { - _.each(testFiles, function (file) { - _.each(grunt.file.expand(file), function (file) { - mochaFiles.push(file); - }); - }); - } - }); - } - }); - - var exitCodes = []; - var runMochaProc = function () { - var file = mochaFiles.shift(); - if (typeof file !== "undefined") { - var mochaProc = spawn('mocha', args.concat(file), {cwd: __dirname}); - mochaProc.on("error", function () { - grunt.fatal("Unable to spawn mocha process: mocha not installed?"); - }); - mochaProc.stdout.setEncoding('utf8'); - mochaProc.stderr.setEncoding('utf8'); - mochaProc.stdout.on('data', function (data) { - grunt.log.write(data); - }); - mochaProc.stderr.on('data', function (data) { - grunt.log.write(data); - }); - mochaProc.on('exit', function (code) { - exitCodes.push(code); - runMochaProc(); - }); - } else { - cb(_.max(exitCodes)); - } - }; - runMochaProc(); -}; - -module.exports.tail = function (grunt, filename, cb) { - var proc = spawn('tail', ['-f', filename]); - proc.on("error", function (err) { - grunt.fatal("Unable to spawn \"tail\": " + err.message); - }); - proc.stdout.setEncoding('utf8'); - proc.stdout.on('data', function (data) { - grunt.log.write(data); - }); - proc.on('exit', function (code) { - cb(code); - }); -}; - -module.exports.setDeviceConfigVer = function (grunt, device, cb) { - var value = {version: appiumVer}; - exports.writeConfigKey(grunt, device, value, cb); -}; - -module.exports.writeConfigKey = function (grunt, key, value, cb) { - var configPath = path.resolve(__dirname, ".appiumconfig.json"); - fs.readFile(configPath, function (err, data) { - var writeConfig = function (config) { - config[key] = value; - grunt.log.write("\n"); - grunt.log.write(JSON.stringify(config)); - fs.writeFile(configPath, JSON.stringify(config), cb); - }; - if (err) { - grunt.log.write("Config file doesn't exist, creating it"); - var config = {}; - writeConfig(config); - } else { - grunt.log.write("Config file exists, updating it"); - writeConfig(JSON.parse(data.toString('utf8'))); - } - }); -}; - -module.exports.setGitRev = function (grunt, rev, cb) { - exports.writeConfigKey(grunt, "git-sha", rev, cb); -}; - -module.exports.setBuildTime = function (grunt, cb) { - var time = new Date(); - exports.writeConfigKey(grunt, "built", time.toISOString(), cb); -}; - -var auth_enableDevTools = function (grunt, cb) { - grunt.log.writeln("Enabling DevToolsSecurity"); - exec('DevToolsSecurity --enable', function (err) { - if (err) grunt.fatal(err); - cb(); - }); -}; - -var auth_updateSecurityDb = function (grunt, insecure, cb) { - grunt.log.writeln("Updating security db for " + (insecure ? "insecure" : - "developer") + " access"); - var cmd = "security authorizationdb write system.privilege.taskport " + - (insecure ? "allow" : "is-developer"); - exec(cmd, function (err) { - if (err) grunt.fatal(err); - cb(); - }); -}; - -var auth_chmodApps = function (grunt, cb) { - grunt.log.writeln("Granting access to built-in simulator apps"); - var user; - if (!process.env.HOME) { - grunt.fatal(new Error("Could not determine your $HOME")); - } else { - user = /\/([^\/]+)$/.exec(process.env.HOME)[1]; - } - xcode.getPath(function (err, xcodeDir) { - if (err) return cb(err); - var glob = path.resolve(xcodeDir, "Platforms/iPhoneSimulator.platform/" + - "Developer/SDKs/iPhoneSimulator*.sdk/Applications"); - glob += " "; - glob += path.resolve("/Library/Developer/CoreSimulator/" + - "Profiles/Runtimes/iOS\\ *.simruntime/" + - "Contents/Resources/RuntimeRoot/Applications/"); - var cmd = "chown -R " + user + ": " + glob; - exec(cmd, function (err) { - if (err) { - grunt.log.writeln("Encountered an issue chmodding iOS sim app dirs. " + - "This may be because they don't exist on your " + - "system, which is not necessarily a problem. The " + - "error was: " + err.message); - } - cb(); - }); - }); -}; - -module.exports.authorize = function (grunt, insecure, cb) { - auth_enableDevTools(grunt, function () { - auth_updateSecurityDb(grunt, insecure, function () { - auth_chmodApps(grunt, cb); - }); - }); -}; - -module.exports.build = function (appRoot, cb, sdk, xcconfig) { - var next = function () { - var cmd = 'xcodebuild -sdk ' + sdk + ' clean'; - console.log('Using sdk: ' + sdk + '...'); - console.log("Cleaning build..."); - var xcode = exec(cmd, {cwd: appRoot, maxBuffer: MAX_BUFFER_SIZE}, function (err, stdout, stderr) { - if (err) { - console.log("Failed cleaning app, maybe it doesn't exist?"); - return cb(stdout + "\n" + stderr); - } - console.log("Building app..."); - var args = ['-sdk', sdk]; - if (typeof xcconfig !== "undefined") { - args = args.concat(['-xcconfig', xcconfig]); - } - xcode = spawn('xcodebuild', args, { - cwd: appRoot - }); - xcode.on("error", function (err) { - cb(new Error("Failed spawning xcodebuild: " + err.message)); - }); - var output = ''; - var collect = function (data) { output += data; }; - xcode.stdout.on('data', collect); - xcode.stderr.on('data', collect); - xcode.on('exit', function (code) { - if (code === 0) { - cb(null); - } else { - console.log("Failed building app, maybe it doesn't exist?"); - cb(output); - } - }); - }); - }; - if (typeof sdk === "undefined") { - xcode.getVersion(function (err, version) { - if (err) return cb(err); - var sdkVersion = version[0] === "5" ? "7.0" : "6.1"; - sdk = 'iphonesimulator' + sdkVersion; - next(); - }); - } else { - next(); - } -}; - -module.exports.buildApp = function (appDir, cb, sdk) { - var appRoot = path.resolve(__dirname, "sample-code", "apps", appDir); - module.exports.build(appRoot, function (err) { - if (err !== null) { - console.log(err); - cb(false); - } else { - cb(true); - } - }, sdk); -}; - -module.exports.signApp = function (appName, certName, cb) { - var appPath = path.resolve(__dirname, "sample-code", "apps", appName, - "build", "Release-iphonesimulator"); - exec("codesign -f -s \"" + certName + "\" -v " + appName + ".app", {cwd: appPath, maxBuffer: MAX_BUFFER_SIZE}, function (err, stdout, stderr) { - console.log(stdout); - console.log(stderr); - if (err) { - cb(false); - } else { - cb(true); - } - }); -}; - -module.exports.buildSafariLauncherApp = function (cb, sdk, xcconfig) { - var appRoot = path.resolve(require.resolve('safari-launcher'), '..'); - module.exports.build(appRoot, function (err) { - if (err !== null) { - console.log(err); - cb(false); - } else { - cb(true); - } - }, sdk, xcconfig); -}; - - -var setupAndroidProj = function (grunt, projPath, args, cb) { - if (!process.env.ANDROID_HOME) { - grunt.fatal("Could not find Android SDK, make sure to export ANDROID_HOME"); - } - var tool = "android"; - if (isWindows) { - tool = "android.bat"; - } - var cmd = path.resolve(process.env.ANDROID_HOME, "tools", tool); - if (!fs.existsSync(cmd)) { - grunt.fatal("The `android` command was not found at \"" + cmd + "\", are you sure ANDROID_HOME is set properly?"); - } - var proc = spawn(cmd, args, {cwd: projPath}); - proc.on("error", function (err) { - grunt.fatal("Unable to spawn android: " + err.message); - }); - proc.stdout.setEncoding('utf8'); - proc.stderr.setEncoding('utf8'); - proc.stdout.on('data', function (data) { - grunt.log.write(data); - }); - proc.stderr.on('data', function (data) { - grunt.log.write(data); - }); - proc.on('exit', function (code) { - cb(code === 0 ? null : new Error("Setup cmd " + cmd + " failed with code " + code)); - }); -}; - -module.exports.setupAndroidBootstrap = function (grunt, cb) { - var projPath = path.resolve(__dirname, "lib", "devices", "android", - "bootstrap"); - var args = ["create", "uitest-project", "-n", "AppiumBootstrap", "-t", - "android-19", "-p", "."]; - // TODO: possibly check output of `android list target` to make sure api level 19 is available? - setupAndroidProj(grunt, projPath, args, cb); -}; - -module.exports.setupAndroidApp = function (grunt, appName, cb) { - var appPath = path.resolve(__dirname, "sample-code", "apps", appName); - var args = ["update", "project", "--subprojects", "-t", "android-19", "-p", ".", "-n", appName]; - setupAndroidProj(grunt, appPath, args, cb); -}; - -var buildAndroidProj = function (grunt, projPath, target, cb) { - var cmdName = 'ant'; - if (!fs.existsSync(path.resolve(projPath, "build.xml")) && - fs.existsSync(path.resolve(projPath, "pom.xml"))) { - cmdName = 'mvn'; - } - var whichCmd = 'which '; - if (isWindows) { - whichCmd = 'where '; - } - exec(whichCmd + cmdName, { maxBuffer: MAX_BUFFER_SIZE }, function (err, stdout) { - if (err) { - grunt.fatal("Error finding " + cmdName + " binary, is it on your path?"); - } else { - if (stdout) { - var cmd = stdout.split('\r\n')[0].trim(); - if (isWindows && cmdName === 'ant') { - cmd = cmd + '.bat'; - } - grunt.log.write("Using " + cmdName + " found at " + cmd + "\n"); - var proc = spawn(cmd, [target], {cwd: projPath}); - proc.on("error", function () { - grunt.fatal("Unable to spawn \"" + cmdName + "\""); - }); - proc.stdout.setEncoding('utf8'); - proc.stderr.setEncoding('utf8'); - proc.stdout.on('data', function (data) { - grunt.log.write(data); - }); - proc.stderr.on('data', function (data) { - grunt.log.write(data); - }); - proc.on('exit', function (code) { - cb(code ? new Error("Building project exited with " + code) : null); - }); - } else { - grunt.fatal("Could not find " + cmdName + " installed; please make sure it's on PATH"); - } - } - }); -}; - -module.exports.buildAndroidBootstrap = function (grunt, cb) { - var projPath = path.resolve(__dirname, "lib", "devices", "android", - "bootstrap"); - var binSrc = path.resolve(projPath, "bin", "AppiumBootstrap.jar"); - var binDestDir = path.resolve(__dirname, "build", "android_bootstrap"); - var binDest = path.resolve(binDestDir, "AppiumBootstrap.jar"); - buildAndroidProj(grunt, projPath, "build", function (err) { - if (err) { - console.log("Could not build android bootstrap"); - return cb(err); - } - mkdirp(binDestDir, function (err) { - if (err) { - console.log("Could not mkdirp " + binDestDir); - return cb(err); - } - rimraf(binDest, function (err) { - if (err) { - console.log("Could not delete old " + binDest); - return cb(err); - } - ncp(binSrc, binDest, function (err) { - if (err) { - console.log("Could not copy " + binSrc + " to " + binDest); - return cb(err); - } - cb(); - }); - }); - }); - }); -}; - -var fixSelendroidAndroidManifest = function (dstManifest, cb) { - console.log("Modifying manifest for no icons"); - fs.readFile(dstManifest, function (err, data) { - if (err) { - console.error("Could not open new manifest"); - return cb(err); - } - data = data.toString('utf8'); - console.log(data); - var iconRe = /application[\s\S]+android:icon="[^"]+"/; - data = data.replace(iconRe, "application"); - fs.writeFile(dstManifest, data, function (err) { - if (err) { - console.error("Could not write modified manifest"); - return cb(err); - } - cb(null); - }); - }); -}; -module.exports.fixSelendroidAndroidManifest = fixSelendroidAndroidManifest; - - -module.exports.buildSelendroidServer = function (cb) { - getSelendroidVersion(function (err, version) { - if (err) return cb(err); - console.log("Installing Cordova"); - var cordovaDir = path.resolve(__dirname, "submodules", "selendroid", - "third-party", "cordova-3.7.0"); - var cmd = "mvn install:install-file -Dfile=classes.jar -DgroupId=org.apache.cordova " + - "-DartifactId=cordova -Dversion=3.7.0 -Dclassifier=android -Dpackaging=jar"; - exec(cmd, {cwd: cordovaDir}, function (err, stdout, stderr) { - if (err) { - console.error("Unable to install Cordova. Stdout was: "); - console.error(stdout); - console.error(stderr); - return cb(err); - } - }); - console.log("Building selendroid server"); - var buildDir = path.resolve(__dirname, "submodules", "selendroid"); - var target = path.resolve(buildDir, "selendroid-server", "target", - "selendroid-server-" + version + ".apk"); - var destDir = path.resolve(__dirname, "build", "selendroid"); - var destBin = path.resolve(destDir, "selendroid.apk"); - var srcManifest = path.resolve(__dirname, "submodules", "selendroid", - "selendroid-server", "AndroidManifest.xml"); - var dstManifest = path.resolve(destDir, "AndroidManifest.xml"); - cmd = "mvn clean package -DskipTests=true"; - exec(cmd, {cwd: buildDir, maxBuffer: SELENDROID_MAX_BUFFER_SIZE}, function (err, stdout, stderr) { - if (err) { - console.error("Unable to build selendroid server. Stdout was: "); - console.error(stdout); - console.error(stderr); - return cb(err); - } - console.log("Making sure target exists"); - fs.stat(target, function (err) { - if (err) { - console.error("Selendroid doesn't exist! Not sure what to do."); - return cb(err); - } - console.log("Selendroid server built successfully, copying to build/selendroid"); - rimraf(destDir, function (err) { - if (err) { - console.error("Could not remove " + destDir); - return cb(err); - } - mkdirp(destDir, function (err) { - if (err) { - console.error("Could not create " + destDir); - return cb(err); - } - ncp(target, destBin, function (err) { - if (err) { - console.error("Could not copy " + target + " to " + destBin); - return cb(err); - } - console.log("Copying selendroid manifest as well"); - ncp(srcManifest, dstManifest, function (err) { - if (err) { - console.error("Could not copy manifest"); - return cb(err); - } - fixSelendroidAndroidManifest(dstManifest, cb); - }); - }); - }); - }); - }); - }); - }); -}; - -var getSelendroidVersion = function (cb) { - console.log("Getting Selendroid version"); - var pomXml = path.resolve(__dirname, "submodules", "selendroid", - "selendroid-server", "pom.xml"); - fs.readFile(pomXml, function (err, xmlData) { - if (err) { - console.error("Could not find selendroid's pom.xml at"); - return cb(err); - } - parseXmlString(xmlData.toString('utf8'), function (err, res) { - if (err) { - console.error("Error parsing selendroid's pom.xml"); - return cb(err); - } - var version = res.project.parent[0].version[0]; - if (typeof version === "string") { - console.log("Selendroid version is " + version); - cb(null, version); - } else { - cb(new Error("Version " + version + " was not valid")); - } - }); - }); -}; - -module.exports.buildAndroidApp = function (grunt, appName, cb) { - var appPath = path.resolve(__dirname, "sample-code", "apps", appName); - buildAndroidProj(grunt, appPath, "clean", function (err) { - if (err) return cb(err); - buildAndroidProj(grunt, appPath, "debug", cb); - }); -}; - -module.exports.buildSelendroidAndroidApp = function (grunt, appName, cb) { - var appPath = path.resolve(__dirname, "sample-code", "apps" + appName); - buildAndroidProj(grunt, appPath, "package", cb); -}; - -module.exports.installAndroidApp = function (grunt, appName, cb) { - var pkgMap = {'ApiDemos': 'com.example.android.apis'}; - if (!_.has(pkgMap, appName)) { - var msg = "We don't know about appName " + appName + ", please edit " + - "grunt-helpers.js:installAndroidApp() to add it and its " + - "package identifier"; - grunt.fatal(new Error(msg)); - } - - var appPath = path.resolve(__dirname, "sample-code", "apps", appName, - "bin/" + appName + "-debug.apk"); - exec("adb uninstall " + pkgMap[appName], { maxBuffer: MAX_BUFFER_SIZE }, function (err, stdout) { - if (err) return grunt.fatal(err); - grunt.log.write(stdout); - exec("adb install -r " + appPath, { maxBuffer: MAX_BUFFER_SIZE }, function (err, stdout) { - if (err) return grunt.fatal(err); - grunt.log.write(stdout); - cb(); - }); - }); -}; - -module.exports.generateServerDocs = function (grunt, cb) { - var p = parser(); - var docFile = path.resolve(__dirname, "docs/en/writing-running-appium/server-args.md"); - var md = "# Appium server arguments\n\n"; - md += "Usage: `node . [flags]`\n\n"; - md += "## Server flags\n"; - md += "All flags are optional, but some are required in conjunction with " + - "certain others.\n\n"; - md += "\n\n\n\n"; - md += "|Flag|Default|Description|Example|\n"; - md += "|----|-------|-----------|-------|\n"; - _.each(p.rawArgs, function (arg) { - var argNames = arg[0]; - var exampleArg = typeof arg[0][1] === "undefined" ? arg[0][0] : arg[0][1]; - var argOpts = arg[1]; - md += "|`" + argNames.join("`, `") + "`"; - md += "|" + ((typeof argOpts.defaultValue === "undefined") ? "" : argOpts.defaultValue); - md += "|" + argOpts.help; - md += "|" + ((typeof argOpts.example === "undefined") ? "" : "`" + exampleArg + " " + argOpts.example + "`"); - md += "|\n"; - }); - fs.writeFile(docFile, md, function (err) { - if (err) { - console.log(err.stack); - grunt.fatal(err); - } else { - grunt.log.write("New docs written! Don't forget to commit and push"); - cb(); - } - }); -}; - -module.exports.generateAppiumIo = function (grunt, cb) { - getAppiumIoFiles(function (err, template, readme) { - if (err) { - return grunt.fatal(err); - } - var readmeLex = namp.lexer(readme) - , headers = getMarkdownHeaders(readmeLex) - , sidebarHtml = generateSidebarHtml(headers) - , warning = "\n" - , bodyHtml = generateBodyHtml(readmeLex, headers) - , submod = path.resolve(__dirname, "submodules", "appium.io") - , outfile = submod + "/getting-started.html"; - - var newDoc = template.replace("{{ SIDENAV }}", sidebarHtml) - .replace("{{ README_SECTIONS }}", bodyHtml) - .replace("{{ WARNING }}", warning); - fs.writeFile(outfile, newDoc, function (err) { - if (err) { - grunt.fatal(err); - } else { - grunt.log.write("Pushing changes to appium.io..."); - var cmd = 'git commit -am "updating getting-started via grunt" && ' + - 'git pull --rebase origin master && ' + - 'git push origin master && ' + - 'git checkout gh-pages && ' + - 'git pull origin gh-pages && ' + - 'git merge master && ' + - 'git push origin gh-pages && ' + - 'git checkout master'; - exec(cmd, {cwd: submod, maxBuffer: MAX_BUFFER_SIZE}, function (err, stdout, stderr) { - if (err) { - console.log(stdout); - console.log(stderr); - grunt.fatal(err); - } else { - grunt.log.write("success!"); - cb(); - } - }); - } - }); - }); -}; - -var getAppiumIoFiles = function (cb) { - var templateFile = path.resolve(__dirname, "submodules", "appium.io", "getting-started-template.html") - , readmeFile = path.resolve(__dirname, "README.md"); - - fs.readFile(templateFile, function (err, templateData) { - if (err) { - cb(err); - } else { - fs.readFile(readmeFile, function (err, readmeData) { - if (err) cb(err); else cb(null, templateData.toString(), readmeData.toString()); - }); - } - }); - -}; - -var getMarkdownHeaders = function (mdTree) { - var headers = {}; - _.each(mdTree, function (mdObj) { - if (mdObjIsHeader(mdObj)) { - headers[mdObj.text] = sanitizeMdHeader(mdObj.text); - } - }); - return headers; -}; - -var mdObjIsHeader = function (mdObj) { - return mdObj.depth === 2 && mdObj.type === 'heading'; -}; - -var sanitizeMdHeader = function (header) { - var re = new RegExp(/[^a-zA-Z0-9]/g); - return header.replace(re, "-") - .replace(/-$/, "") - .replace(/-+/g, "-") - .toLowerCase(); -}; - -var generateSidebarHtml = function (headers) { - var html = ''; - _.each(headers, function (link, header) { - header = namp(header).html; - header = header.replace(/<[^>]+>/ig, ""); - html += '
  • ' + - header + '
  • \n'; - }); - return html; -}; - -var generateBodyHtml = function (readmeMd, headers) { - var html = '' - , inBetweens = [] - , inBetweenHtml = '' - , inSection = false; - var addInBetweenHtml = function () { - if (inBetweens.length) { - inBetweenHtml = namp.parser(inBetweens)[0]; - html += inBetweenHtml; - inBetweens = []; - } - }; - - for (var i = 1; i < readmeMd.length; i++) { - var mdObj = readmeMd[i]; - if (mdObjIsHeader(mdObj)) { - addInBetweenHtml(); - var headerHtml = namp.parser([mdObj])[0]; - headerHtml = headerHtml.replace(/<.?p>/ig, ''); - if (inSection) { - html += '\n'; - } - html += '
    \n'; - html += '

    \n\n' + - '\n\n' + headerHtml + '\n

    \n\n'; - inSection = true; - } else if (inSection) { - inBetweens.push(mdObj); - } - if (i === readmeMd.length - 1) { - addInBetweenHtml(); - } - } - html += '
    '; - return html; -}; diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 94f50b3d..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,310 +0,0 @@ -"use strict"; - -var argv = require('yargs').argv; - -var gulp = require('gulp'), - path = require('path'), - mochaStream = require('spawn-mocha-parallel').mochaStream, - jshint = require('gulp-jshint'), - jshintStylish = require('jshint-stylish'), - jscs = require('gulp-jscs'), - Q = require('q'), - runSequence = Q.denodeify(require('run-sequence')), - cp = require('child_process'), - spawn = cp.spawn, - exec = Q.denodeify(cp.exec), - stream = require('stream'), - fs = require('fs'), - split = require('split'), - os = require('os'), - _ = require('underscore'), - through = require('through'), - promisePipe = require("promisepipe"), - assert = require('assert'), - splitArray = require('./test/helpers/split-array'); - -var childProcs = []; - -function newMochaOpts() { - return { - flags: { - u: 'bdd-with-opts', - R: process.env.MOCHA_REPORTER || 'nyan', - 'c': true - }, - env: _.clone(process.env), - bin: path.join(__dirname, 'node_modules/.bin/mocha'), - concurrency: 5 - }; -} - -var JS_SOURCES = ['*.js', 'bin/**/*.js', 'ci/**/*.js', 'new-ci/**/*.js', 'lib/**/*.js', 'test/**/*.js']; - -gulp.task('jshint', function () { - return gulp.src(JS_SOURCES) - .pipe(jshint()) - .pipe(jshint.reporter(jshintStylish)) - .pipe(jshint.reporter('fail')); -}); - -gulp.task('jscs', function () { - return gulp.src(JS_SOURCES) - .pipe(jscs({configPath: __dirname + '/.jscsrc'})); -}); - -gulp.task('lint', ['jshint', 'jscs']); - -gulp.task('test-unit', function () { - var opts = newMochaOpts(); - var mocha = mochaStream(opts); - return gulp.src('test/unit/**/*-specs.js', {read: false}) - .pipe(mocha) - .on('error', console.warn.bind(console)); -}); - -function splitE2ETests(srcGlobPattern) { - var testFiles = []; - var groups = {}; - return promisePipe( - gulp.src(srcGlobPattern , {read: false}) - .pipe(through(function (file) { - testFiles.push(path.relative(file.cwd ,file.path)); - })).on('close', function () { - testFiles.sort(); - groups = splitArray(testFiles, argv.testSplit); - })).then(function () { - 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() { - return exec("sudo pkill -f 'sudo -u appium node' || true").then(function () { // killing bsexec processes - _(childProcs).each(function (child) { - try { child.kill(); } catch (err) {} - }); - }); -} - -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('show-ios-e2e-tests-split', function () { - return showSplit(splitIosE2ETests(), 'iOS'); -}); - -function newMochaE2EOpts() { - var opts = newMochaOpts(); - 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 testGroup = testGroups[argv.testGroup - 1]; - console.log('running tests for:' + testGroup); - return promisePipe(gulp.src(testGroup, {read: false}) - .pipe(mocha) - ).fin(function () { - return killProcs(); - }); - }); -}); - -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(); - - out.pipe(split()) - .on('data', function (line) { - if (line.match(/Appium REST http interface listener started/)) { - deferred.resolve(); - } - console.log('server -->', line); - }); - out.pipe(fs.createWriteStream('appium.log')); - (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 () { - var LOG_PREPEND= 'emu --> '; - var deferred = Q.defer(); - - var emuErrored = false; - function waitForEmu() { - var INIT_WAIT = 15000; - var MAX_WAIT_MS = 300000; - var POOL_MS = 5000; - var startMs = Date.now(); - function _waitForEmu () { - function retry() { - if (emuErrored) { - throw new Error('Emulator errored'); - } - if (Date.now() - startMs > MAX_WAIT_MS) { - throw new Error('Emulator did not show up'); - } - console.log(LOG_PREPEND + 'Waiting for emu...'); - return Q.delay(POOL_MS).then(_waitForEmu); - } - return exec('adb shell getprop sys.boot_completed') - .spread(function (stdout) { - if (stdout && stdout.trim() === '1') { - console.log(LOG_PREPEND + 'emulator started'); - return; - } - return retry(); - }, function (err) { - if (err.toString().match(/device not found/)) { - console.log(LOG_PREPEND + 'Device not found, it should be there, killing adb server.'); - return exec('adb kill-server').then(retry); - } - return retry(); - }); - } - return Q.delay(INIT_WAIT).then(_waitForEmu) - .then(function () {deferred.resolve();}); - } - - var out = new stream.PassThrough(); - out.pipe(split()) - .on('data', function (line) { - console.log(LOG_PREPEND + line); - }); - out.pipe(fs.createWriteStream('emulator.log')); - var emuBin = os.platform() === 'linux' ? 'emulator64-x86' : 'emulator'; - var emuArgs = [ - '-avd', argv.avd, - '-no-snapshot-load', '-no-snapshot-save', - '-no-audio', '-netfast' - ]; - if (os.platform() === 'linux') { - emuArgs = emuArgs.concat([ - '-qemu', '-m', '512', '-enable-kvm' - ]); - } - console.log(LOG_PREPEND + 'executing', emuBin, emuArgs.join(' ')); - var child = spawn(emuBin, emuArgs); - childProcs.push(child); - child.stdout.pipe(out); - child.stderr.pipe(out); - child.on('error', function (err) { - emuErrored = true; - deferred.reject(err); - }); - child.on('close', function () { - deferred.reject('Something went wrong!'); - }); - return Q.all([ - waitForEmu(), - deferred.promise - ]); -}); - -gulp.task('run-android-e2e', function () { - 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; - }); -}); - -gulp.task('default', function () { - return runSequence('lint', 'test-unit'); -}); diff --git a/install-beta.md b/install-beta.md deleted file mode 100644 index fff7f967..00000000 --- a/install-beta.md +++ /dev/null @@ -1,32 +0,0 @@ -#appium beta install - -## global install - -Install with: - -``` -npm install -g appium@beta -``` - -Start with: - -``` -appium -``` - -## local install - -Install with: - -``` -mkdir beta -cd beta -npm init # press -npm install appium@beta -``` - -Start with -``` -"node_modules/.bin/appium" -``` - diff --git a/install-from-npm.md b/install-from-npm.md deleted file mode 100644 index abb31658..00000000 --- a/install-from-npm.md +++ /dev/null @@ -1,32 +0,0 @@ -#appium npm install - -## global install - -Install with: - -``` -npm install -g appium -``` - -Start with: - -``` -appium -``` - -## local install - -Install with: - -``` -mkdir appium-local -cd appium-local -npm init # press -npm install appium -``` - -Start with -``` -"node_modules/.bin/appium" -``` - diff --git a/lib/appium.js b/lib/appium.js deleted file mode 100644 index 3b9a8f0a..00000000 --- a/lib/appium.js +++ /dev/null @@ -1,410 +0,0 @@ -"use strict"; - -var loggerjs = require('./server/logger.js') - , logger = loggerjs.get('appium') - , UUID = require('uuid-js') - , _ = require('underscore') - , Capabilities = require('./server/capabilities') - , IOS = require('./devices/ios/ios.js') - , Safari = require('./devices/ios/safari.js') - , Android = require('./devices/android/android.js') - , Selendroid = require('./devices/android/selendroid.js') - , Chrome = require('./devices/android/chrome.js') - , FirefoxOs = require('./devices/firefoxos/firefoxos.js') - , jwpResponse = require('./devices/common.js').jwpResponse - , status = require("./server/status.js"); - -var DT_IOS = "ios" - , DT_SAFARI = "safari" - , DT_ANDROID = "android" - , DT_CHROME = "chrome" - , DT_SELENDROID = "selendroid" - , DT_FIREFOX_OS = "firefoxos"; - -var Appium = function (args) { - this.args = _.clone(args); - this.args.callbackAddress = this.args.callbackAddress || this.args.address; - this.args.callbackPort = this.args.callbackPort || this.args.port; - // we need to keep an unmodified copy of the args so that we can restore - // any server arguments between sessions to their default values - // (otherwise they might be overridden by session-level caps) - this.serverArgs = _.clone(this.args); - this.rest = null; - this.webSocket = null; - this.deviceType = null; - this.device = null; - this.sessionId = null; - this.dyingSessionId = null; - this.desiredCapabilities = {}; - this.oldDesiredCapabilities = {}; - this.session = null; - this.preLaunched = false; - this.sessionOverride = this.args.sessionOverride; - this.resetting = false; - this.defCommandTimeoutMs = this.args.defaultCommandTimeout * 1000; - this.commandTimeoutMs = this.defCommandTimeoutMs; - this.commandTimeout = null; -}; - -Appium.prototype.attachTo = function (rest) { - this.rest = rest; -}; - -Appium.prototype.attachSocket = function (webSocket) { - this.webSocket = webSocket; -}; - -Appium.prototype.registerConfig = function (configObj) { - this.serverConfig = configObj; -}; - -Appium.prototype.deviceIsRegistered = function (deviceType) { - if (deviceType === DT_SAFARI) deviceType = DT_IOS; - if (deviceType === DT_CHROME) deviceType = DT_ANDROID; - return _.has(this.serverConfig, deviceType); -}; - -Appium.prototype.preLaunch = function (cb) { - logger.info("Pre-launching app"); - var caps = {}; - this.start(caps, function (err) { - if (err) { - cb(err, null); - } else { - this.preLaunched = true; - cb(null, this); - } - }.bind(this)); -}; - -Appium.prototype.setArgFromCap = function (arg, cap) { - if (typeof this.desiredCapabilities[cap] !== "undefined") { - this.args[arg] = this.desiredCapabilities[cap]; - } -}; - -Appium.prototype.updateResetArgsFromCaps = function () { - this.setArgFromCap("noReset", "noReset"); - this.setArgFromCap("fullReset", "fullReset"); - - // user can set noReset or fullReset - var caps = this.desiredCapabilities; - if (caps.noReset === true) this.args.fullReset = false; - if (caps.fullReset === true) this.args.noReset = false; - - // not user visible via caps - this.args.fastReset = !this.args.fullReset && !this.args.noReset; - this.args.skipUninstall = this.args.fastReset || this.args.noReset; -}; - -Appium.prototype.start = function (desiredCaps, cb) { - - if (this.args.debugLogSpacing) { - for (var i = 0; i < 8; i++) { - logger.info("*************************************"); - } - for (i = 0; i < 8; i++) { - logger.info("**** NEW SESSION ***"); - } - for (i = 0; i < 8; i++) { - logger.info("*************************************"); - } - } - - var configureAndStart = function () { - this.desiredCapabilities = new Capabilities(desiredCaps); - this.updateResetArgsFromCaps(); - this.args.webSocket = this.webSocket; // allow to persist over many sessions - this.configure(this.args, this.desiredCapabilities, function (err) { - if (err) { - logger.debug("Got configuration error, not starting session"); - this.cleanupSession(); - cb(err, null); - } else { - this.invoke(cb); - } - }.bind(this)); - }.bind(this); - if (this.sessionId === null) { - configureAndStart(); - } else if (this.sessionOverride) { - logger.info("Found an existing session to clobber, shutting it down " + - "first..."); - this.stop(function (err) { - if (err) return cb(err); - logger.info("Old session shut down OK, proceeding to new session"); - configureAndStart(); - }); - } else { - return cb(new Error("Requested a new session but one was in progress")); - } -}; - -Appium.prototype.getDeviceType = function (args, caps) { - var platform = caps.platformName || args.platformName; - platform = platform ? platform.toString().toLowerCase() : ''; - var browser = caps.browserName || args.browserName; - browser = browser ? browser.toString().toLowerCase() : ''; - var automation = caps.automationName || args.automationName; - automation = automation ? automation.toString().toLowerCase() : ''; - var app = args.app || caps.app; - app = app ? app.toString().toLowerCase() : ''; - var pkg = args.androidPackage || caps.appPackage; - pkg = pkg ? pkg.toString().toLowerCase() : ''; - - var validPlatforms = ['ios', 'android', 'firefoxos']; - if (platform && !_.contains(validPlatforms, platform)) { - throw new Error("Could not determine your device. You sent in a " + - "platformName capability of '" + platform + "' but that " + - "is not a supported platform. Supported platforms are: " + - "iOS, Android and FirefoxOS"); - } - - // TODO: Set DT_SELENDROID type based on platformVersion - var type = this.getDeviceTypeFromPlatform(platform); - - if (type === DT_IOS) { - // Detect Safari browser from browserName, app and args - if ( - args.safari || - this.isSafariBrowser(browser) || - this.isSafariBrowser(app) - ) { - type = DT_SAFARI; - } - } else if (type === DT_ANDROID) { - // Detect Chrome browser from browserName, app and pkg - if ( - this.isChromeBrowser(browser) || - this.isChromeBrowser(app) || - this.isChromePackage(pkg) - ) { - type = DT_CHROME; - } else if (this.isSelendroidAutomation(automation)) { - type = DT_SELENDROID; - } - } - - if (!type) { - throw new Error("Could not determine your device from Appium arguments " + - "or desired capabilities. Please make sure to specify the " + - "'deviceName' and 'platformName' capabilities"); - } - return type; -}; - -Appium.prototype.isSelendroidAutomation = function (automation) { - return automation.indexOf('selendroid') !== -1; -}; - -Appium.prototype.isChromeBrowser = function (browser) { - return _.contains(["chrome", "chromium", "chromebeta", "browser"], browser); -}; - -Appium.prototype.isChromePackage = function (pkg) { - var chromePkgs = [ - "com.android.chrome" - , "com.chrome.beta" - , "org.chromium.chrome.shell" - , "com.android.browser" - ]; - return _.contains(chromePkgs, pkg); -}; - -Appium.prototype.isSafariBrowser = function (browser) { - return browser === "safari"; -}; - -Appium.prototype.getDeviceTypeFromPlatform = function (caps) { - var device = null; - switch (caps) { - case 'ios': - device = DT_IOS; - break; - case 'android': - device = DT_ANDROID; - break; - case 'firefoxos': - device = DT_FIREFOX_OS; - break; - } - return device; -}; - -Appium.prototype.configure = function (args, desiredCaps, cb) { - var deviceType; - try { - deviceType = this.getDeviceType(args, desiredCaps); - if (!args.launch) desiredCaps.checkValidity(deviceType, args.enforceStrictCaps); - } catch (e) { - logger.error(e.message); - return cb(e); - } - - if (!this.deviceIsRegistered(deviceType)) { - logger.error("Trying to run a session for device '" + deviceType + "' " + - "but that device hasn't been configured. Run config"); - return cb(new Error("Device " + deviceType + " not configured yet")); - } - this.device = this.getNewDevice(deviceType); - this.device.configure(args, desiredCaps, cb); - // TODO: better collaboration between the Appium and Device objects - this.device.onResetTimeout = function () { this.resetTimeout(); }.bind(this); -}; - -Appium.prototype.invoke = function (cb) { - this.sessionId = UUID.create().hex; - logger.debug('Creating new appium session ' + this.sessionId); - - if (this.device.args.autoLaunch === false) { - // if user has passed in desiredCaps.autoLaunch = false - // meaning they will manage app install / launching - if (typeof this.device.noLaunchSetup === "function") { - this.device.noLaunchSetup(function (err) { - if (err) return cb(err); - cb(null, this.device); - }.bind(this)); - } else { - cb(null, this.device); - } - } else { - // the normal case, where we launch the device for folks - - var onStart = function (err, sessionIdOverride) { - if (sessionIdOverride) { - this.sessionId = sessionIdOverride; - logger.debug("Overriding session id with " + - JSON.stringify(sessionIdOverride)); - } - if (err) return this.cleanupSession(err, cb); - logger.debug("Device launched! Ready for commands"); - this.setCommandTimeout(this.desiredCapabilities.newCommandTimeout); - cb(null, this.device); - }.bind(this); - - this.device.start(onStart, _.once(this.cleanupSession.bind(this))); - } -}; - -Appium.prototype.getNewDevice = function (deviceType) { - var DeviceClass = (function () { - switch (deviceType) { - case DT_IOS: - return IOS; - case DT_SAFARI: - return Safari; - case DT_ANDROID: - return Android; - case DT_CHROME: - return Chrome; - case DT_SELENDROID: - return Selendroid; - case DT_FIREFOX_OS: - return FirefoxOs; - default: - throw new Error("Tried to start a device that doesn't exist: " + - deviceType); - } - })(); - return new DeviceClass(); -}; - -Appium.prototype.timeoutWaitingForCommand = function () { - logger.debug("Didn't get a new command in " + (this.commandTimeoutMs / 1000) + - " secs, shutting down..."); - this.stop(function () { - logger.debug("We shut down because no new commands came in"); - }.bind(this)); -}; - -Appium.prototype.cleanupSession = function (err, cb) { - logger.debug("Cleaning up appium session"); - if (this.commandTimeout) { - clearTimeout(this.commandTimeout); - this.commandTimeout = null; - } - this.commandTimeoutMs = this.defCommandTimeoutMs; - if (this.sessionId) { - // for some reason, stop has not been called - this.dyingSessionId = this.sessionId; - this.sessionId = null; - } - this.device = null; - this.args = _.clone(this.serverArgs); - this.oldDesiredCapabilities = _.clone(this.desiredCapabilities.desired); - this.desiredCapabilities = {}; - if (cb) { - if (err) return cb(err); - cb(null, {status: status.codes.Success.code, value: null, - sessionId: this.dyingSessionId}); - } -}; - -Appium.prototype.resetTimeout = function () { - if (this.commandTimeout) { - clearTimeout(this.commandTimeout); - } - if (this.commandTimeoutMs && this.commandTimeoutMs > 0) { - this.commandTimeout = setTimeout(this.timeoutWaitingForCommand.bind(this), - this.commandTimeoutMs); - } -}; - -Appium.prototype.setCommandTimeout = function (secs, cb) { - if (typeof secs === "undefined") { - secs = this.defCommandTimeoutMs / 1000; - logger.debug("Setting command timeout to the default of " + secs + " secs"); - } else { - logger.debug("Setting command timeout to " + secs + " secs"); - } - this.commandTimeoutMs = secs * 1000; - this.resetTimeout(); - if (typeof cb === "function") { - jwpResponse(null, secs, cb); - } -}; - -Appium.prototype.stop = function (cb) { - if (this.sessionId === null || this.device === null) { - logger.debug("Trying to stop appium but there's no session, doing nothing"); - return cb(); - } - logger.info('Shutting down appium session'); - this.dyingSessionId = this.sessionId; - this.sessionId = null; - this.device.stop(function (err) { - this.cleanupSession(err, cb); - }.bind(this)); -}; - -Appium.prototype.reset = function (cb) { - logger.debug("Resetting app mid-session"); - if (typeof this.device.resetAndStartApp === "function") { - logger.debug("Running device specific reset"); - this.device.resetAndStartApp(function (err) { - jwpResponse(err, cb); - }); - } else { - logger.debug("Running generic full reset"); - var oldImpWait = this.device.implicitWaitMs - , oldCommandTimeoutMs = this.commandTimeoutMs - , oldId = this.sessionId; - - this.resetting = true; - this.stop(function () { - logger.debug("Restarting app"); - this.start(this.oldDesiredCapabilities, function (err) { - if (err) return cb(err); - this.resetting = false; - this.device.implicitWaitMs = oldImpWait; - this.sessionId = oldId; - this.dyingSessionId = null; - this.setCommandTimeout(oldCommandTimeoutMs / 1000, cb); - }.bind(this)); - }.bind(this)); - } -}; - -module.exports = function (args) { - return new Appium(args); -}; diff --git a/lib/cookies.js b/lib/cookies.js deleted file mode 100644 index 518f00af..00000000 --- a/lib/cookies.js +++ /dev/null @@ -1,93 +0,0 @@ -"use strict"; - -var _ = require('underscore'); - -/* - * derived from jQuery Cookie Plugin v1.4.1 - * https://github.com/carhartl/jquery-cookie - */ - -module.exports = function (cookieString) { - - var pluses = /\+/g; - - function encode(s) { - return encodeURIComponent(s); - } - - function decode(s) { - return decodeURIComponent(s); - } - - function parseCookieValue(s) { - if (s.indexOf('"') === 0) { - // This is a quoted cookie as according to RFC2068, unescape... - s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); - } - - try { - // Replace server-side written pluses with spaces. - // If we can't decode the cookie, ignore it, it's unusable. - // If we can't parse the cookie, ignore it, it's unusable. - return decodeURIComponent(s.replace(pluses, ' ')); - } catch (e) {} - } - - function read(s, converter) { - var value = parseCookieValue(s); - return _.isFunction(converter) ? converter(value) : value; - } - - this.cookie = this.cookies = function (key, value, options) { - - // Write - - if (arguments.length > 1 && !_.isFunction(value)) { - options = _.extend({}, options); - - var ret = [ - encode(key), '=', value, - options.expires ? '; expires=' + options.expires : '', // use expires attribute, max-age is not supported by IE - options.path ? '; path=' + options.path : '', - options.domain ? '; domain=' + options.domain : '', - options.secure ? '; secure' : '' - ].join(''); - return ret; - } - - // Read - - var result = key ? undefined : {}; - - // To prevent the for loop in the first place assign an empty array - // in case there are no cookies at all. Also prevents odd result when - // calling this.cookie(). - var cookies = cookieString ? cookieString.split('; ') : []; - - for (var i = 0, l = cookies.length; i < l; i++) { - var parts = cookies[i].split('='); - var name = decode(parts.shift()); - var cookie = parts.join('='); - - if (key && key === name) { - // If second argument (value) is a function it's a converter... - result = read(cookie, value); - break; - } - - // Prevent storing a cookie that we couldn't decode. - if (!key && (cookie = read(cookie)) !== undefined) { - result[name] = cookie; - } - } - - return result; - }; - - this.removeCookie = function (key, options) { - // Must not alter options, thus extending a fresh object... - return this.cookie(key, '', _.extend({}, options, { - expires: "Thu, 01 Jan 1970 00:00:00 GMT" })); - }; - -}; diff --git a/lib/devices/android/adb.js b/lib/devices/android/adb.js deleted file mode 100644 index 57349c25..00000000 --- a/lib/devices/android/adb.js +++ /dev/null @@ -1,8 +0,0 @@ -// Gets adb from npm package and setup logger -"use strict"; - -var ADB = require('appium-adb'); - -ADB.logger.init(require('../../server/logger').get('appium')); - -module.exports = ADB; diff --git a/lib/devices/android/android-common.js b/lib/devices/android/android-common.js deleted file mode 100644 index 28a3d1ea..00000000 --- a/lib/devices/android/android-common.js +++ /dev/null @@ -1,1093 +0,0 @@ -"use strict"; - -var logger = require('../../server/logger.js').get('appium') - , _ = require('underscore') - , status = require("../../server/status.js") - , fs = require('fs') - , path = require('path') - , exec = require('child_process').exec - , md5 = require('md5calculator') - , async = require('async') - , errors = require('../../server/errors.js') - , Device = require('../device.js') - , NotYetImplementedError = errors.NotYetImplementedError - , Args = require("vargs").Constructor - , ADB = require('./adb.js'); - -var logTypesSupported = { - 'logcat' : 'Logs for Android applications on real device and emulators ' + - 'via ADB' -}; - -var androidCommon = {}; - -androidCommon.configure = function (args, caps, cb) { - this._deviceConfigure(args, caps); - this.setAndroidArgs(); - if (!this.args.androidActivity) { - logger.debug("No appActivity desired capability or server param. " + - "Parsing from apk."); - } - if (!this.args.androidPackage) { - logger.debug("No appPackage desired capability or server param. " + - "Parsing from apk."); - } - - if (this.args.app) { - this.configureApp(cb); - } else if (this.args.androidPackage) { - this.args.app = null; - logger.debug("Didn't get app but did get Android package, will attempt " + - "to launch it on the device"); - cb(null); - } else { - var msg = "No app set; either start appium with --app or pass in an " + - "'app' value in desired capabilities, or set androidPackage " + - "to launch pre-existing app on device"; - logger.error(msg); - cb(new Error(msg)); - } -}; - -androidCommon.configureApp = function (cb) { - var _cb = cb; - cb = function (err) { - if (err) { - err = new Error("Bad app: " + this.args.app + ". App paths need to be " + - "absolute, or relative to the appium server install " + - "dir, or a URL to compressed file, or a special app " + - "name. cause: " + err); - } - _cb(err); - }.bind(this); - - if (this.appIsPackageOrBundle(this.args.app)) { - // we have a package instead of app - this.args.appPackage = this.args.app; - this.args.app = null; - if (!this.args.appActivity) { - return cb(new Error("You passed in an app package as the 'app' " + - "capability, but didn't include appActivity. We " + - "need to know that too in order to start your app")); - } - logger.debug("App is an Android package, will attempt to run on device"); - cb(); - } else { - Device.prototype.configureApp.call(this, cb); - } -}; - -androidCommon.setAndroidArgs = function () { - this.setArgFromCap("androidPackage", "appPackage"); - this.setArgFromCap("androidActivity", "appActivity"); - this.setArgFromCap("androidWaitPackage", "appWaitPackage"); - this.setArgFromCap("androidWaitActivity", "appWaitActivity"); - this.setArgFromCap("androidDeviceReadyTimeout", "deviceReadyTimeout"); - this.setArgFromCap("androidCoverage", "androidCoverage"); - this.args.systemPort = this.args.bootstrapPort; - this.args.appPackage = this.args.androidPackage; - this.args.appActivity = this.args.androidActivity; - this.args.appWaitPackage = this.args.androidWaitPackage || - this.args.appPackage; - this.args.appWaitActivity = this.args.androidWaitActivity || - this.args.appActivity; - this.appProcess = this.args.appPackage; - this.args.appDeviceReadyTimeout = this.args.androidDeviceReadyTimeout; - this.args.avdLaunchTimeout = this.args.avdLaunchTimeout || 120000; - this.args.avdReadyTimeout = this.args.avdReadyTimeout || 120000; - this.args.noSign = this.args.noSign || false; -}; - -androidCommon.background = function (secs, cb) { - this.adb.getFocusedPackageAndActivity(function (err, pack, activity) { - if (err) return cb(err); - - this.adb.keyevent("3", function (err) { - if (err) return cb(err); - - setTimeout(function () { - var onStart = function (err) { - if (err) return cb(err); - cb(null,{ - status: status.codes.Success.code, - value: null - }); - }; - this.adb.startApp({ - pkg: this.args.appPackage, - activity: this.args.appActivity, - action: this.args.intentAction, - category: this.args.intentCategory, - flags: this.args.intentFlags, - waitPkg: pack, - waitActivity: activity, - optionalIntentArguments: this.args.optionalIntentArguments, - retry: true, - stopApp: !this.args.dontStopAppOnReset - }, onStart); - }.bind(this), secs * 1000); - }.bind(this)); - }.bind(this)); -}; - -androidCommon.openSettingsActivity = function (setting, cb) { - this.adb.getFocusedPackageAndActivity(function (err, foundPackage, - foundActivity) { - var cmd = 'am start -a android.settings.' + setting; - this.adb.shell(cmd, function (err) { - if (err) { - cb(err); - } else { - this.adb.waitForNotActivity(foundPackage, foundActivity, 5000, cb); - } - }.bind(this)); - }.bind(this)); -}; - -androidCommon.toggleSetting = function (setting, preKeySeq, ocb) { - var doKey = function (key) { - return function (cb) { - setTimeout(function () { - this.adb.keyevent(key, cb); - }.bind(this), 2000); - }.bind(this); - }.bind(this); - - var settPkg, settAct; - - var back = function (cb) { - this.adb.back(function (err) { - if (err) { - cb(err); - } else { - this.adb.waitForNotActivity(settPkg, settAct, 5000, cb); - } - }.bind(this)); - }.bind(this); - - /* - * preKeySeq is the keyevent sequence to send over ADB in order - * to position the cursor on the right option. - * By default it's [up, up, down] because we usually target the 1st item in - * the screen, and sometimes when opening settings activities the cursor is - * already positionned on the 1st item, but we can't know for sure - */ - if (preKeySeq === null) preKeySeq = [19, 19, 20]; // up, up, down - - var sequence = [ - function (cb) { - this.openSettingsActivity(setting, cb); - }.bind(this) - ]; - var len = preKeySeq.length; - - for (var i = 0; i < len; i++) { - sequence.push(doKey(preKeySeq[i])); - } - - sequence.push( - function (cb) { - this.adb.getFocusedPackageAndActivity(function (err, foundPackage, - foundActivity) { - settPkg = foundPackage; - settAct = foundActivity; - cb(err); - }.bind(this)); - }.bind(this) - , function (cb) { - /* - * Click and handle potential ADB disconnect that occurs on official - * emulator when the network connection is disabled - */ - this.wrapActionAndHandleADBDisconnect(doKey(23), cb); - }.bind(this) - , function (cb) { - /* - * In one particular case (enable Location Services), a pop-up is - * displayed on some platforms so the user accepts or refuses that Google - * collects location data. So we wait for that pop-up to open, if it - * doesn't then proceed - */ - this.adb.waitForNotActivity(settPkg, settAct, 5000, function (err) { - if (err) { - cb(null); - } else { - // Click on right button, "Accept" - async.series([ - doKey(22) // right - , doKey(23) // click - , function (cb) { - // Wait for pop-up to close - this.adb.waitForActivity(settPkg, settAct, 5000, cb); - }.bind(this) - ], function (err) { - cb(err); - }.bind(this)); - } - }.bind(this)); - }.bind(this) - , back - ); - - async.series(sequence, function (err) { - if (err) return ocb(err); - ocb(null, { status: status.codes.Success.code }); - }.bind(this)); -}; - -androidCommon.toggleData = function (cb) { - this.adb.isDataOn(function (err, dataOn) { - if (err) return cb(err); - - this.wrapActionAndHandleADBDisconnect(function (ncb) { - this.adb.setData(dataOn ? 0 : 1, ncb); - }.bind(this), function (err) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - }); - }); - }.bind(this)); -}; - -androidCommon.toggleFlightMode = function (ocb) { - this.adb.isAirplaneModeOn(function (err, airplaneModeOn) { - if (err) return ocb(err); - - async.series([ - function (cb) { - this.wrapActionAndHandleADBDisconnect(function (ncb) { - this.adb.setAirplaneMode(airplaneModeOn ? 0 : 1, ncb); - }.bind(this), cb); - }.bind(this), - function (cb) { - this.wrapActionAndHandleADBDisconnect(function (ncb) { - this.adb.broadcastAirplaneMode(airplaneModeOn ? 0 : 1, ncb); - }.bind(this), cb); - }.bind(this) - ], function (err) { - if (err) return ocb(err); - ocb(null, { - status: status.codes.Success.code - }); - }.bind(this)); - }.bind(this)); -}; - -androidCommon.toggleWiFi = function (cb) { - this.adb.isWifiOn(function (err, dataOn) { - if (err) return cb(err); - - this.wrapActionAndHandleADBDisconnect(function (ncb) { - this.adb.setWifi(dataOn ? 0 : 1, ncb); - }.bind(this), function (err) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - }); - }); - }.bind(this)); -}; - -androidCommon.toggleLocationServices = function (ocb) { - this.adb.getApiLevel(function (err, api) { - if (api > 15) { - var seq = [19, 19]; // up, up - if (api === 16) { - // This version of Android has a "parent" button in its action bar - seq.push(20); // down - } else if (api >= 19) { - // Newer versions of Android have the toggle in the Action bar - seq = [22, 22, 19]; // right, right, up - /* - * Once the Location services switch is OFF, it won't receive focus - * when going back to the Location Services settings screen unless we - * send a dummy keyevent (UP) *before* opening the settings screen - */ - this.adb.keyevent(19, function (/*err*/) { - this.toggleSetting('LOCATION_SOURCE_SETTINGS', seq, ocb); - }.bind(this)); - return; - } - this.toggleSetting('LOCATION_SOURCE_SETTINGS', seq, ocb); - } else { - // There's no global location services toggle on older Android versions - ocb(new NotYetImplementedError(), null); - } - }.bind(this)); -}; - -androidCommon.prepareDevice = function (onReady) { - logger.debug("Using fast reset? " + this.args.fastReset); - logger.debug("Preparing device for session"); - async.series([ - function (cb) { this.checkAppPresent(cb); }.bind(this), - function (cb) { this.prepareEmulator(cb); }.bind(this), - function (cb) { this.prepareActiveDevice(cb); }.bind(this), - function (cb) { this.adb.waitForDevice(cb); }.bind(this), - function (cb) { this.adb.startLogcat(cb); }.bind(this) - ], onReady); -}; - -androidCommon.checkAppPresent = function (cb) { - if (this.args.app === null) { - logger.debug("Not checking whether app is present since we are assuming " + - "it's already on the device"); - cb(); - } else { - logger.debug("Checking whether app is actually present"); - fs.stat(this.args.app, function (err) { - if (err) { - logger.error("Could not find app apk at " + this.args.app); - cb(err); - } else { - cb(); - } - }.bind(this)); - } -}; - -androidCommon.prepareEmulator = function (cb) { - if (this.args.avd !== null) { - var avdName = this.args.avd.replace('@', ''); - this.adb.getRunningAVD(avdName, function (err, runningAVD) { - if (err && err.message.indexOf('No devices') === -1 && - err.message.indexOf('No emulators') === -1) return cb(err); - if (runningAVD !== null) { - logger.debug("Did not launch AVD because it was already running."); - return this.ensureDeviceLocale(cb); - } - this.adb.launchAVD(this.args.avd, this.args.avdArgs, this.args.language, this.args.locale, - this.args.avdLaunchTimeout, this.args.avdReadyTimeout, cb); - }.bind(this)); - } else { - this.ensureDeviceLocale(cb); - } -}; - -androidCommon.ensureDeviceLocale = function (cb) { - var haveLanguage = this.args.language && typeof this.args.language === "string"; - var haveCountry = this.args.locale && typeof this.args.locale === "string"; - if (!haveLanguage && !haveCountry) return cb(); - this.getDeviceLanguage(function (err, language) { - if (err) return cb(err); - this.getDeviceCountry(function (err, country) { - if (err) return cb(err); - var adbCmd = ""; - if (haveLanguage && this.args.language !== language) { - logger.debug("Setting Android Device Language to " + this.args.language); - adbCmd += "setprop persist.sys.language " + this.args.language.toLowerCase() + ";"; - } - if (haveCountry && this.args.locale !== country) { - logger.debug("Setting Android Device Country to " + this.args.locale); - adbCmd += "setprop persist.sys.country " + this.args.locale.toUpperCase() + ";"; - } - if (adbCmd === "") return cb(); - this.adb.shell(adbCmd, function (err) { - if (err) return cb(err); - this.adb.reboot(cb); - }.bind(this)); - }.bind(this)); - }.bind(this)); -}; - -androidCommon.prepareActiveDevice = function (cb) { - if (this.adb.curDeviceId) { - // deviceId is already setted - return cb(); - } - logger.info('Retrieving device'); - this.adb.getDevicesWithRetry(function (err, devices) { - if (err) return cb(err); - var deviceId = null; - if (this.adb.udid) { - if (!_.contains(_.pluck(devices, 'udid'), this.adb.udid)) { - return cb(new Error("Device " + this.adb.udid + " was not in the list " + - "of connected devices")); - } - deviceId = this.adb.udid; - } else { - deviceId = devices[0].udid; - var emPort = this.adb.getPortFromEmulatorString(deviceId); - this.adb.setEmulatorPort(emPort); - } - logger.info('Found device', deviceId); - this.adb.setDeviceId(deviceId); - cb(); - }.bind(this)); -}; - -androidCommon.resetApp = function (cb) { - if (this.args.fastReset) { - logger.debug("Running fast reset (stop and clear)"); - this.adb.stopAndClear(this.args.appPackage, cb); - } else { - logger.debug("Running old fashion reset (reinstall)"); - this.remoteApkExists(function (err, remoteApk) { - if (err) return cb(err); - if (!remoteApk) { - return cb(new Error("Can't run reset if remote apk doesn't exist")); - } - this.adb.uninstallApk(this.args.appPackage, function (err) { - if (err) return cb(err); - this.adb.installRemote(remoteApk, cb); - }.bind(this)); - }.bind(this)); - } -}; - -androidCommon.getRemoteApk = function (cb) { - var next = function () { - cb(null, this.remoteTempPath() + this.appMd5Hash + '.apk', this.appMd5Hash); - }.bind(this); - - if (this.appMd5Hash) { - next(); - } else { - this.getAppMd5(function (err, md5Hash) { - if (err) return cb(err); - this.appMd5Hash = md5Hash; - next(); - }.bind(this)); - } -}; - -androidCommon.remoteApkExists = function (cb) { - this.getRemoteApk(function (err, remoteApk) { - if (err) return cb(err); - this.adb.shell("ls " + remoteApk, function (err, stdout) { - if (err) return cb(err); - if (stdout.indexOf("No such file") !== -1) { - return cb(new Error("remote apk did not exist")); - } - cb(null, stdout.trim()); - }); - }.bind(this)); -}; -androidCommon.uninstallApp = function (cb) { - var next = function () { - this.adb.uninstallApk(this.args.appPackage, function (err) { - if (err) return cb(err); - cb(null); - }.bind(this)); - }.bind(this); - - if (this.args.skipUninstall) { - logger.debug("Not uninstalling app since server not started with " + - "--full-reset"); - cb(); - } else { - next(); - } -}; - -androidCommon.installAppForTest = function (cb) { - if (this.args.app === null) { - logger.debug("Skipping install since we launched with a package instead " + - "of an app path"); - return cb(); - } - var afterSigning = function (err) { - if (err) return cb(err); - this.remoteApkExists(function (err, remoteApk) { - // err is set if the remote apk doesn't exist so don't check it. - this.adb.isAppInstalled(this.args.appPackage, function (err, installed) { - if (installed && this.args.fastReset && remoteApk) { - logger.info('App is already installed, resetting app'); - this.resetApp(cb); - } else if (!installed || (this.args.fastReset && !remoteApk)) { - logger.info('Installing App'); - this.adb.mkdir(this.remoteTempPath(), function (err) { - if (err) return cb(err); - this.getRemoteApk(function (err, remoteApk, md5Hash) { - if (err) return cb(err); - this.removeTempApks([md5Hash], function (err, appExists) { - if (err) return cb(err); - var install = function (err) { - if (err) return cb(err); - this.installRemoteWithRetry(remoteApk, cb); - }.bind(this); - if (appExists) { - install(); - } else { - this.adb.push(this.args.app, remoteApk, install); - } - }.bind(this)); - }.bind(this)); - }.bind(this)); - } else { - cb(); - } - }.bind(this)); - }.bind(this)); - }.bind(this); - // Skipping sign apk for noSign true - if (this.args.noSign) { - logger.debug('noSign capability set to true, skipping checking and signing of app'); - afterSigning(); - } else { - this.adb.checkAndSignApk(this.args.app, this.args.appPackage, afterSigning); - } -}; - -androidCommon.installRemoteWithRetry = function (remoteApk, cb) { - this.adb.uninstallApk(this.args.appPackage, function (err) { - if (err) logger.warn("Uninstalling apk failed, continuing"); - this.adb.installRemote(remoteApk, function (err) { - if (err) { - logger.warn("Installing remote apk failed, going to uninstall and try " + - "again"); - this.removeTempApks([], function () { - this.adb.push(this.args.app, remoteApk, function (err) { - if (err) return cb(err); - logger.debug("Attempting to install again for the last time"); - this.adb.installRemote(remoteApk, cb); - }.bind(this)); - }.bind(this)); - } else { - cb(); - } - }.bind(this)); - }.bind(this)); -}; - -androidCommon.getAppMd5 = function (cb) { - try { - md5(this.args.app, function (err, md5Hash) { - if (err) return cb(err); - logger.debug("MD5 for app is " + md5Hash); - cb(null, md5Hash); - }.bind(this)); - } catch (e) { - logger.error("Problem calculating md5: " + e); - return cb(e); - } -}; - -androidCommon.remoteTempPath = function () { - return "/data/local/tmp/"; -}; - -androidCommon.removeTempApks = function (exceptMd5s, cb) { - logger.debug("Removing any old apks"); - if (typeof exceptMd5s === "function") { - cb = exceptMd5s; - exceptMd5s = []; - } - - var listApks = function (cb) { - var cmd = 'ls /data/local/tmp/*.apk'; - this.adb.shell(cmd, function (err, stdout) { - if (err || stdout.indexOf("No such file") !== -1) { - return cb(null, []); - } - var apks = stdout.split("\n"); - cb(null, apks); - }); - }.bind(this); - - var removeApks = function (apks, cb) { - if (apks.length < 1) { - logger.debug("No apks to examine"); - return cb(); - } - var matchingApkFound = false; - var noMd5Matched = true; - var removes = []; - _.each(apks, function (path) { - path = path.trim(); - if (path !== "") { - noMd5Matched = true; - _.each(exceptMd5s, function (md5Hash) { - if (path.indexOf(md5Hash) !== -1) { - noMd5Matched = false; - } - }); - if (noMd5Matched) { - removes.push('rm "' + path + '"'); - } else { - logger.debug("Found an apk we want to keep at " + path); - matchingApkFound = true; - } - } - }); - - // Invoking adb shell with an empty string will open a shell console - // so return here if there's nothing to remove. - if (removes.length < 1) { - logger.debug("Couldn't find any apks to remove"); - return cb(null, matchingApkFound); - } - - var cmd = removes.join(" && "); - this.adb.shell(cmd, function () { - cb(null, matchingApkFound); - }); - }.bind(this); - - async.waterfall([ - function (cb) { listApks(cb); }, - function (apks, cb) { removeApks(apks, cb); } - ], function (err, matchingApkFound) { cb(null, matchingApkFound); }); -}; - -androidCommon.forwardPort = function (cb) { - this.adb.forwardPort(this.args.systemPort, this.args.devicePort, cb); -}; - -androidCommon.pushUnicodeIME = function (cb) { - logger.debug("Pushing unicode ime to device..."); - var imePath = path.resolve(__dirname, "..", "..", "..", "build", - "unicode_ime_apk", "UnicodeIME-debug.apk"); - fs.stat(imePath, function (err) { - if (err) { - cb(new Error("Could not find Unicode IME apk; please run " + - "'reset.sh --android' to build it.")); - } else { - this.adb.install(imePath, false, cb); - } - }.bind(this)); -}; - -androidCommon.pushSettingsApp = function (cb) { - logger.debug("Pushing settings apk to device..."); - var settingsPath = path.resolve(__dirname, "..", "..", "..", "build", - "settings_apk", "settings_apk-debug.apk"); - - fs.stat(settingsPath, function (err) { - if (err) { - cb(new Error("Could not find settings apk; please run " + - "'reset.sh --android' to build it.")); - } else { - this.adb.install(settingsPath, false, cb); - } - }.bind(this)); -}; - -androidCommon.packageAndLaunchActivityFromManifest = function (cb) { - if (!this.args.app) { - logger.warn("No app capability, can't parse package/activity"); - return cb(); - } - if (this.args.appPackage && this.args.appActivity) return cb(); - - logger.debug("Parsing package and activity from app manifest"); - this.adb.packageAndLaunchActivityFromManifest(this.args.app, function (err, pkg, act) { - if (err) { - logger.error("Problem parsing package and activity from manifest: " + - err); - return cb(err); - } - if (pkg && !this.args.appPackage) this.args.appPackage = pkg; - if (!this.args.appWaitPackage) this.args.appWaitPackage = this.args.appPackage; - // Retrying to parse activity from AndroidManifest.xml - if (act === null) { - var appiumApkToolsJarPath = this.adb.jars['appium_apk_tools.jar']; - var outputPath = path.resolve(this.args.tmpDir, pkg); - var getLaunchActivity = ['java -jar "', appiumApkToolsJarPath, - '" "', 'printLaunchActivity', - '" "', this.args.app, '" "', outputPath, '"'].join(''); - exec(getLaunchActivity, { maxBuffer: 524288 }, function (err, stdout, stderr) { - if (err || stderr) { - logger.warn(stderr); - return cb(new Error("Cannot parse launchActivity from manifest." + err)); - } - var apkActivity = new RegExp(/Launch activity parsed:([^']+)/g).exec(stdout); - if (apkActivity && apkActivity.length >= 2) { - act = apkActivity[1]; - } else { - act = null; - } - if (act && !this.args.appActivity) this.args.appActivity = act; - if (!this.args.appWaitActivity) this.args.appWaitActivity = this.args.appActivity; - logger.debug("Parsed package and activity are: " + pkg + "/" + act); - cb(); - }.bind(this)); - } - else { - if (act && !this.args.appActivity) this.args.appActivity = act; - if (!this.args.appWaitActivity) this.args.appWaitActivity = this.args.appActivity; - logger.debug("Parsed package and activity are: " + pkg + "/" + act); - cb(); - } - }.bind(this)); -}; - -androidCommon.getLog = function (logType, cb) { - // Check if passed logType is supported - if (!_.has(logTypesSupported, logType)) { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Unsupported log type '" + logType + "', supported types : " + JSON.stringify(logTypesSupported) - }); - } - var logs; - // Check that current logType and instance is compatible - if (logType === 'logcat') { - try { - logs = this.adb.getLogcatLogs(); - } catch (e) { - return cb(e); - } - } - // If logs captured sucessfully send response with data, else send error - if (logs) { - return cb(null, { - status: status.codes.Success.code - , value: logs - }); - } else { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Incompatible logType for this device" - }); - } -}; - -androidCommon.getLogTypes = function (cb) { - return cb(null, { - status: status.codes.Success.code - , value: _.keys(logTypesSupported) - }); -}; - -androidCommon.getCurrentActivity = function (cb) { - this.adb.getFocusedPackageAndActivity(function (err, curPackage, activity) { - if (err) { - return cb(null, { - status: status.codes.UnknownError.code - , value: err.message - }); - } - cb(null, { - status: status.codes.Success.code - , value: activity - }); - }); -}; - -androidCommon.getDeviceProperty = function (property, cb) { - this.adb.shell("getprop " + property, function (err, stdout) { - if (err) { - logger.error("Error getting device property " + property + ": " + err); - cb(err, null); - } else { - logger.debug("Current device " + property + ": " + stdout.trim()); - cb(null, stdout.trim()); - } - }.bind(this)); -}; - -androidCommon.getDeviceLanguage = function (cb) { - this.getDeviceProperty("persist.sys.language", cb); -}; - -androidCommon.getDeviceCountry = function (cb) { - this.getDeviceProperty("persist.sys.country", cb); -}; - -androidCommon.extractLocalizedStrings = function (language, outputPath, cb) { - var appiumApkToolsJarPath = this.adb.jars['appium_apk_tools.jar']; - if (!this.args.appPackage) { - return cb(new Error("Parameter 'appPackage' is required for launching application")); - } - var makeStrings = ['java -jar "', appiumApkToolsJarPath, '" "', 'stringsFromApk', - '" "', this.args.app, '" "', outputPath, '"'].join(''); - - if (language) { - this.extractStringsFromApk(makeStrings, language, cb); - } else { - // If language is not set, use device language - this.getDeviceLanguage(function (err, language) { - this.extractStringsFromApk(makeStrings, language, cb); - }.bind(this)); - } -}; - -androidCommon.extractStringsFromApk = function (makeStrings, language, cb) { - var makeLanguageStrings = makeStrings; - if (language !== null) makeLanguageStrings = [makeStrings, language].join(' '); - logger.debug(makeLanguageStrings); - exec(makeLanguageStrings, { maxBuffer: 524288 }, function (err, stdout, stderr) { - if (err && language !== null) { - logger.debug("No strings.xml for language '" + language + "', getting default strings.xml"); - this.extractStringsFromApk(makeStrings, null, cb); - } else { - cb(err, stdout, stderr); - } - }.bind(this)); -}; - -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)) { - logger.debug("Apk doesn't exist locally"); - this.apkStrings = {}; - this.language = null; - return cb(new Error("Apk doesn't exist locally")); - } else { - this.extractLocalizedStrings(language, outputPath, function (err, stdout, stderr) { - if (err) { - logger.warn("Error getting strings.xml from apk"); - logger.debug(stderr); - this.apkStrings = {}; - this.language = null; - return cb(err); - } - 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)); - } -}; - -androidCommon.initUnicode = function (cb) { - if (this.args.unicodeKeyboard) { - logger.debug('Enabling Unicode keyboard support'); - this.pushUnicodeIME(function (err) { - if (err) return cb(err); - this.adb.defaultIME(function (err, engine) { - if (err) return cb(err); - // save the previously set IME - this.defaultIME = engine; - logger.debug('Unsetting IME \'' + this.defaultIME + '\''); - logger.debug('Setting IME to \'io.appium.android.ime/.UnicodeIME\''); - this.adb.enableIME('io.appium.android.ime/.UnicodeIME', function (err) { - if (err) return cb(err); - this.adb.setIME('io.appium.android.ime/.UnicodeIME', cb); - }.bind(this)); - }.bind(this)); - }.bind(this)); - } else { - cb(); - } -}; - -androidCommon.getNetworkConnection = function (cb) { - logger.info('Getting network connection'); - this.adb.isAirplaneModeOn(function (err, airplaneModeOn) { - if (err) return cb(err); - var connection = airplaneModeOn ? 1 : 0; - if (airplaneModeOn) { - // airplane mode on implies wifi and data off - return cb(null, { - status: status.codes.Success.code, - value: connection - }); - } - this.adb.isWifiOn(function (err, wifiOn) { - if (err) return cb(err); - connection += (wifiOn ? 2 : 0); - this.adb.isDataOn(function (err, dataOn) { - if (err) return cb(err); - connection += (dataOn ? 4 : 0); - cb(null, { - status: status.codes.Success.code, - value: connection - }); - }.bind(this)); - }.bind(this)); - }.bind(this)); -}; - -androidCommon.setNetworkConnection = function (type, ocb) { - logger.info('Setting network connection'); - // decode the input - var airplaneMode = type % 2; - type >>= 1; - var wifi = type % 2; - type >>= 1; - var data = type % 2; - - var series = []; - // do airplane mode stuff first, since it will change the other statuses - series.push(function (cb) { - this.wrapActionAndHandleADBDisconnect(function (ncb) { - this.adb.setAirplaneMode(airplaneMode, ncb); - }.bind(this), cb); - }.bind(this)); - series.push(function (cb) { - this.wrapActionAndHandleADBDisconnect(function (ncb) { - this.adb.broadcastAirplaneMode(airplaneMode, ncb); - }.bind(this), cb); - }.bind(this)); - // no need to do anything else if we are in or going into airplane mode - if (airplaneMode === 0) { - series.push(function (cb) { - this.wrapActionAndHandleADBDisconnect(function (ncb) { - this.adb.setWifiAndData({ - wifi: wifi, - data: data - }, ncb); - }.bind(this), cb); - }.bind(this)); - } - - async.series(series, function (err) { - if (err) return ocb(err); - return this.getNetworkConnection(ocb); - }.bind(this)); -}; - -androidCommon.isIMEActivated = function (cb) { - // IME is always activated on Android devices - cb(null, { - status: status.codes.Success.code, - value: true - }); -}; - -androidCommon.availableIMEEngines = function (cb) { - logger.debug('Retrieving available IMEs'); - this.adb.availableIMEs(function (err, engines) { - if (err) return cb(err); - logger.debug('Engines: ' + JSON.stringify(engines)); - - cb(null, { - status: status.codes.Success.code, - value: engines - }); - }); -}; - -androidCommon.getActiveIMEEngine = function (cb) { - logger.debug('Retrieving current default IME'); - this.adb.defaultIME(function (err, engine) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code, - value: engine - }); - }); -}; - -androidCommon.activateIMEEngine = function (imeId, cb) { - logger.debug('Attempting to activate IME \'' + imeId + '\''); - this.adb.availableIMEs(function (err, engines) { - if (err) return cb(err); - if (engines.indexOf(imeId) !== -1) { - logger.debug('Found installed IME, attempting to activate.'); - this.adb.enableIME(imeId, function (err) { - if (err) return cb(err); - this.adb.setIME(imeId, function (err) { - if (err) return cb(err); - return cb(null, { - status: status.codes.Success.code, - value: null - }); - }); - }.bind(this)); - } else { - logger.debug('IME not found, failing.'); - return cb(null, { - status: status.codes.IMENotAvailable.code, - message: 'Unable to find requested IME \'' + imeId + '\'.' - }); - } - }.bind(this)); -}; - -androidCommon.deactivateIMEEngine = function (cb) { - logger.debug('Retrieving current default IME'); - this.adb.defaultIME(function (err, engine) { - if (err) return cb(err); - logger.debug('Attempting to deactivate \'' + engine + '\''); - this.adb.disableIME(engine, function (err) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code, - value: null - }); - }); - }.bind(this)); -}; - -androidCommon.hideKeyboard = function () { - // parameters only used for iOS. Please ignore them for android. - var args = new Args(arguments); - var cb = args.callback; - this.adb.isSoftKeyboardPresent(function (err, isKeyboardPresent, canCloseKeyboard) { - if (err) return cb(err); - if (isKeyboardPresent) { - if (canCloseKeyboard) { - this.back(cb); - } else { - cb(null, { - status: status.codes.Success.code - , value: "Keyboard has no UI; no closing necessary" - }); - } - } else { - return cb(new Error("Soft keyboard not present, cannot hide keyboard")); - } - }.bind(this)); -}; - -androidCommon.parseJavaVersion = function (stderr) { - var lines = stderr.split("\n"); - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - if (new RegExp("java version").test(line)) { - return line.split(" ")[2].replace(/"/g, ''); - } - } - return null; -}; - -androidCommon.getJavaVersion = function (cb) { - var extractVersion = function (err, stdout, stderr) { - var javaVersion = null; - if (err) { - return cb(new Error("'java -version' failed. " + err)); - } else if (stderr) { - javaVersion = this.parseJavaVersion(stderr); - } - if (javaVersion === null) { - return cb(new Error("Could not get the Java version. Is Java installed?")); - } - return cb(null, javaVersion); - }.bind(this); - - exec("java -version", extractVersion); -}; - -androidCommon.initJavaVersion = function (cb) { - if (this.args.javaVersion) return cb(); - logger.debug("Getting Java version"); - this.getJavaVersion(function (err, javaVersion) { - if (err) return cb(err); - logger.info("Java version is: " + javaVersion); - this.args.javaVersion = javaVersion; - return cb(); - }.bind(this)); -}; - -androidCommon.initAdb = function (cb) { - if (this.adb === null) { - ADB.createADB(this.args, function (e, adb) { - if (e) return cb(e); - this.adb = adb; - cb(); - }.bind(this)); - } else { - cb(); - } -}; - -module.exports = androidCommon; diff --git a/lib/devices/android/android-context-controller.js b/lib/devices/android/android-context-controller.js deleted file mode 100644 index 7cf375e1..00000000 --- a/lib/devices/android/android-context-controller.js +++ /dev/null @@ -1,100 +0,0 @@ -"use strict"; - -var _ = require('underscore') - , logger = require('../../server/logger.js').get('appium') - , jwpSuccess = require('../common.js').jwpSuccess - , status = require('../../server/status.js') - , NotYetImplementedError = require('../../server/errors.js').NotYetImplementedError - , warnDeprecated = require('../../helpers.js').logDeprecationWarning; - -var androidContextController = {}; - -androidContextController.NATIVE_WIN = "NATIVE_APP"; -androidContextController.WEBVIEW_WIN = "WEBVIEW"; -androidContextController.WEBVIEW_BASE = androidContextController.WEBVIEW_WIN + "_"; -androidContextController.CHROMIUM_WIN = "CHROMIUM"; - -androidContextController.defaultContext = function () { - return this.NATIVE_WIN; -}; - -androidContextController.leaveWebView = function (cb) { - warnDeprecated('function', 'leaveWebView', 'context(null)'); - this.setWindow(this.defaultContext(), cb); -}; - -androidContextController.getCurrentContext = function (cb) { - var response = { - status: status.codes.Success.code - , value: this.curContext || null - }; - cb(null, response); -}; - -androidContextController.getContexts = function (cb) { - this.listWebviews(function (err, webviews) { - if (err) return cb(err); - this.contexts = _.union([this.NATIVE_WIN], webviews); - logger.debug("Available contexts: " + this.contexts); - cb(null, { - status: status.codes.Success.code - , value: this.contexts - }); - }.bind(this)); -}; - -androidContextController.isChromedriverContext = function (viewName) { - return viewName.indexOf(this.WEBVIEW_WIN) !== -1 || viewName === this.CHROMIUM_WIN; -}; - -androidContextController.setContext = function (name, cb) { - if (name === null) { - name = this.defaultContext(); - } else if (name === this.WEBVIEW_WIN) { - // handle setContext "WEBVIEW" - name = this.defaultWebviewName(); - } - this.getContexts(function () { - if (!_.contains(this.contexts, name)) { - return cb(null, { - status: status.codes.NoSuchContext.code - , value: "Context '" + name + "' does not exist" - }); - } - if (name === this.curContext) { - return jwpSuccess(cb); - } - var next = function (err) { - if (err) return cb(err); - this.curContext = name; - jwpSuccess(cb); - }.bind(this); - - // current ChromeDriver doesn't handle more than a single web view - if (this.isChromedriverContext(name)) { - this.startChromedriverProxy(name, next); - } else if (this.isChromedriverContext(this.curContext)) { - this.suspendChromedriverProxy(next); - } else if (this.isProxy) { // e.g. WebView context handled in Selendroid - this.proxyTo('wd/hub/session/' + this.proxySessionId + '/context', 'POST', {name: name}, next); - } - }.bind(this)); -}; - -androidContextController.isWebContext = function () { - return this.curContext !== null && this.curContext !== 'NATIVE_APP'; -}; - -androidContextController.getWindowHandle = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidContextController.getWindowHandles = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidContextController.setWindow = function (name, cb) { - cb(new NotYetImplementedError(), null); -}; - -module.exports = androidContextController; diff --git a/lib/devices/android/android-controller.js b/lib/devices/android/android-controller.js deleted file mode 100644 index 994d0161..00000000 --- a/lib/devices/android/android-controller.js +++ /dev/null @@ -1,1178 +0,0 @@ -"use strict"; - -var errors = require('../../server/errors.js') - , _ = require('underscore') - , logger = require('../../server/logger.js').get('appium') - , deviceCommon = require('../common.js') - , helpers = require('../../helpers.js') - , status = require('../../server/status.js') - , NotYetImplementedError = errors.NotYetImplementedError - , fs = require('fs') - , temp = require('temp') - , async = require('async') - , mkdirp = require('mkdirp') - , path = require('path') - , AdmZip = require("adm-zip") - , helpers = require('../../helpers.js'); - -var androidController = {}; - -androidController.pressKeyCode = function (keycode, metastate, cb) { - this.proxy(["pressKeyCode", {keycode: keycode, metastate: metastate}], cb); -}; - -androidController.longPressKeyCode = function (keycode, metastate, cb) { - this.proxy(["longPressKeyCode", {keycode: keycode, metastate: metastate}], cb); -}; - -androidController.keyevent = function (keycode, metastate, cb) { - helpers.logDeprecationWarning('function', 'keyevent', 'pressKeyCode'); - this.pressKeyCode(keycode, metastate, cb); -}; - -androidController.findElement = function (strategy, selector, cb) { - this.findUIElementOrElements(strategy, selector, false, "", cb); -}; - -androidController.findElements = function (strategy, selector, cb) { - this.findUIElementOrElements(strategy, selector, true, "", cb); -}; - -androidController.findUIElementOrElements = function (strategy, selector, many, context, cb) { - if (!deviceCommon.checkValidLocStrat(strategy, false, cb)) { - return; - } - if (strategy === "xpath" && context) { - return cb(new Error("Cannot use xpath locator strategy from an element. " + - "It can only be used from the root element")); - } - var params = { - strategy: strategy - , selector: selector - , context: context - , multiple: many - }; - - var doFind = function (findCb) { - this.proxy(["find", params], function (err, res) { - this.handleFindCb(err, res, many, findCb); - }.bind(this)); - }.bind(this); - this.implicitWaitForCondition(doFind, cb); -}; - -androidController.handleFindCb = function (err, res, many, findCb) { - if (err) { - findCb(false, err, res); - } else { - if (!many && res.status === 0 && res.value !== null) { - findCb(true, err, res); - } else if (many && typeof res.value !== 'undefined' && res.value.length > 0) { - findCb(true, err, res); - } else { - findCb(false, err, res); - } - } -}; - -androidController.findElementFromElement = function (element, strategy, selector, cb) { - this.findUIElementOrElements(strategy, selector, false, element, cb); -}; - -androidController.findElementsFromElement = function (element, strategy, selector, cb) { - this.findUIElementOrElements(strategy, selector, true, element, cb); -}; - -androidController.setValueImmediate = function (elementId, value, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.setValue = function (elementId, value, cb) { - var params = { - elementId: elementId, - text: value, - replace: false - }; - if (this.args.unicodeKeyboard) { - params.unicodeKeyboard = true; - } - this.proxy(["element:setText", params], cb); -}; - -androidController.replaceValue = function (elementId, value, cb) { - var params = { - elementId: elementId, - text: value, - replace: true - }; - if (this.args.unicodeKeyboard) { - params.unicodeKeyboard = true; - } - this.proxy(["element:setText", params], cb); -}; - -androidController.click = function (elementId, cb) { - this.proxy(["element:click", {elementId: elementId}], cb); -}; - -androidController.touchLongClick = function (elementId, x, y, duration, cb) { - var opts = {}; - if (elementId) opts.elementId = elementId; - if (x) opts.x = x; - if (y) opts.y = y; - if (duration) opts.duration = duration; - this.proxy(["element:touchLongClick", opts], cb); -}; - -androidController.touchDown = function (elementId, x, y, cb) { - var opts = {}; - if (elementId) opts.elementId = elementId; - if (x) opts.x = x; - if (y) opts.y = y; - this.proxy(["element:touchDown", opts], cb); -}; - -androidController.touchUp = function (elementId, x, y, cb) { - var opts = {}; - if (elementId) opts.elementId = elementId; - if (x) opts.x = x; - if (y) opts.y = y; - this.proxy(["element:touchUp", opts], cb); -}; - -androidController.touchMove = function (elementId, x, y, cb) { - var opts = {}; - if (elementId) opts.elementId = elementId; - if (x) opts.x = x; - if (y) opts.y = y; - this.proxy(["element:touchMove", opts], cb); -}; - -androidController.complexTap = function (tapCount, touchCount, duration, x, y, elementId, cb) { - this.proxy(["click", {x: x, y: y}], cb); -}; - -androidController.clear = function (elementId, cb) { - this.proxy(["element:clear", {elementId: elementId}], cb); -}; - -androidController.submit = function (elementId, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.getName = function (elementId, cb) { - var p = {elementId: elementId, attribute: "className"}; - this.proxy(["element:getAttribute", p], cb); -}; - -androidController.getText = function (elementId, cb) { - this.proxy(["element:getText", {elementId: elementId}], cb); -}; - -androidController.getAttribute = function (elementId, attributeName, cb) { - var p = {elementId: elementId, attribute: attributeName}; - this.proxy(["element:getAttribute", p], cb); -}; - -androidController.getLocation = function (elementId, cb) { - this.proxy(["element:getLocation", {elementId: elementId}], cb); -}; - -androidController.getSize = function (elementId, cb) { - this.proxy(["element:getSize", {elementId: elementId}], cb); -}; - -androidController.getWindowSize = function (windowHandle, cb) { - this.proxy(["getDeviceSize"], cb); -}; - -androidController.back = function (cb) { - this.proxy(["pressBack"], cb); -}; - -androidController.forward = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.refresh = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.getPageIndex = function (elementId, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.keys = function (keys, cb) { - var params = { - text: keys, - replace: false - }; - if (this.args.unicodeKeyboard) { - params.unicodeKeyboard = true; - } - this.proxy(['setText', params], cb); -}; - -androidController.frame = function (frame, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.implicitWait = function (ms, cb) { - this.implicitWaitMs = parseInt(ms, 10); - logger.debug("Set Android implicit wait to " + ms + "ms"); - cb(null, { - status: status.codes.Success.code - , value: null - }); -}; - -androidController.asyncScriptTimeout = function (ms, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.pageLoadTimeout = function (ms, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.executeAsync = function (script, args, responseUrl, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.elementDisplayed = function (elementId, cb) { - var p = {elementId: elementId, attribute: "displayed"}; - this.proxy(["element:getAttribute", p], function (err, res) { - if (err) return cb(err); - var displayed = res.value === 'true'; - cb(null, { - status: status.codes.Success.code - , value: displayed - }); - }); -}; - -androidController.elementEnabled = function (elementId, cb) { - var p = {elementId: elementId, attribute: "enabled"}; - this.proxy(["element:getAttribute", p], function (err, res) { - if (err) return cb(err); - var enabled = res.value === 'true'; - cb(null, { - status: status.codes.Success.code - , value: enabled - }); - }); -}; - -androidController.elementSelected = function (elementId, cb) { - var p = {elementId: elementId, attribute: "selected"}; - this.proxy(["element:getAttribute", p], function (err, res) { - if (err) return cb(err); - var selected = res.value === 'true'; - cb(null, { - status: status.codes.Success.code - , value: selected - }); - }); -}; - -androidController.getCssProperty = function (elementId, propertyName, cb) { - cb(new NotYetImplementedError(), null); -}; - -var _getNodeClass = function (node) { - var nodeClass = null; - _.each(node.attributes, function (attr) { - if (attr.name === "class") { - nodeClass = attr.value; - } - }); - return nodeClass; -}; - -var _copyNodeAttributes = function (oldNode, newNode) { - _.each(oldNode.attributes, function (attr) { - newNode.setAttribute(attr.name, attr.value); - }); -}; - -// recursively annotate xml nodes. Update tag name to be Android UIElement class name. Add an "instance" identifier which increments for each class separately. -var _annotateXmlNodes = function (newDom, newParent, oldNode, instances) { - if (!instances) { - instances = {}; - } - var newNode; - var nodeClass = _getNodeClass(oldNode); - if (nodeClass) { - newNode = newDom.createElement(nodeClass); - _copyNodeAttributes(oldNode, newNode); - - // we keep track of the number of instances of each className. We use these to create queries on the bootstrap side. - if (!instances[nodeClass]) { - instances[nodeClass] = 0; - } - newNode.setAttribute('instance', instances[nodeClass]++); - } else { - newNode = oldNode.cloneNode(false); - } - newParent.appendChild(newNode); - if (oldNode.hasChildNodes()) { - _.each(oldNode.childNodes, function (childNode) { - _annotateXmlNodes(newDom, newNode, childNode, instances); - }); - } -}; - -androidController.getPageSource = function (cb) { - this.proxy(["source", {}], cb); -}; - -androidController.getAlertText = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.setAlertText = function (text, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.postAcceptAlert = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.postDismissAlert = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.lock = function (secs, cb) { - this.adb.lock(function (err) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - , value: null - }); - }); -}; - -androidController.isLocked = function (cb) { - this.adb.isScreenLocked(function (err, isLocked) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - , value: isLocked - }); - }); -}; - -androidController.pushUnlock = function (cb) { - logger.debug("Pushing unlock helper app to device..."); - var unlockPath = path.resolve(__dirname, "..", "..", "..", "build", - "unlock_apk", "unlock_apk-debug.apk"); - fs.stat(unlockPath, function (err) { - if (err) { - cb(new Error("Could not find unlock.apk; please run " + - "'reset.sh --android' to build it.")); - } else { - this.adb.install(unlockPath, false, cb); - } - }.bind(this)); -}; - -androidController.unlock = function (cb) { - this.adb.isScreenLocked(function (err, isLocked) { - if (err) return cb(err); - if (isLocked) { - logger.info("Unlocking screen"); - var timeoutMs = 10000; - var start = Date.now(); - var unlockAndCheck = function () { - logger.debug("Screen is locked, trying to unlock"); - var onStart = function (err) { - if (err) return cb(err); - this.adb.isScreenLocked(function (err, isLocked) { - if (err) return cb(err); - if (!isLocked) { - logger.debug("Screen is unlocked, continuing"); - return cb(null, { - status: status.codes.Success.code - , value: null - }); - } - if ((Date.now() - timeoutMs) > start) { - return cb(new Error("Screen did not unlock")); - } else { - setTimeout(unlockAndCheck, 1000); - } - }.bind(this)); - }.bind(this); - this.adb.startApp({ - pkg: "io.appium.unlock", - activity: ".Unlock", - action: "android.intent.action.MAIN", - category: "android.intent.category.LAUNCHER", - flags: "0x10200000" - }, onStart); - }.bind(this); - unlockAndCheck(); - } else { - logger.debug('Screen already unlocked, continuing.'); - return cb(null, { - status: status.codes.Success.code - , value: null - }); - } - }.bind(this)); -}; - -androidController.equalsWebElement = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.getOrientation = function (cb) { - this.proxy(["orientation", {}], cb); -}; - -androidController.setOrientation = function (orientation, cb) { - this.proxy(["orientation", {orientation: orientation}], cb); -}; - -androidController.endCoverage = function (intentToBroadcast, ecOnDevicePath, cb) { - var localfile = temp.path({prefix: 'appium', suffix: '.ec'}); - if (fs.existsSync(localfile)) fs.unlinkSync(localfile); - var b64data = ""; - - async.series([ - function (cb) { - // ensure the ec we're pulling is newly created as a result of the intent. - this.adb.rimraf(ecOnDevicePath, function () { cb(); }); - }.bind(this), - function (cb) { - this.adb.broadcastProcessEnd(intentToBroadcast, this.appProcess, cb); - }.bind(this), - function (cb) { - this.adb.pull(ecOnDevicePath, localfile, cb); - }.bind(this), - function (cb) { - fs.readFile(localfile, function (err, data) { - if (err) return cb(err); - b64data = new Buffer(data).toString('base64'); - cb(); - }); - }.bind(this), - ], - function (err) { - if (fs.existsSync(localfile)) fs.unlinkSync(localfile); - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - , value: b64data - }); - }); -}; - -androidController.pullFile = function (remotePath, cb) { - var localFile = temp.path({prefix: 'appium', suffix: '.tmp'}); - var b64data = ""; - - async.series([ - function (cb) { - this.adb.pull(remotePath, localFile, cb); - }.bind(this), - function (cb) { - fs.readFile(localFile, function (err, data) { - if (err) return cb(err); - b64data = new Buffer(data).toString('base64'); - cb(); - }); - }.bind(this), - ], - function (err) { - if (fs.existsSync(localFile)) fs.unlinkSync(localFile); - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - , value: b64data - }); - }); -}; - -androidController.pushFile = function (base64Data, remotePath, cb) { - var localFile = temp.path({prefix: 'appium', suffix: '.tmp'}); - mkdirp.sync(path.dirname(localFile)); - - async.series([ - function (cb) { - var content = new Buffer(base64Data, 'base64'); - var fd = fs.openSync(localFile, 'w'); - fs.writeSync(fd, content, 0, content.length, 0); - fs.closeSync(fd); - - // adb push creates folders and overwrites existing files. - this.adb.push(localFile, remotePath, cb); - }.bind(this), - ], - function (err) { - if (fs.existsSync(localFile)) fs.unlinkSync(localFile); - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - }); - }); -}; - -androidController.pullFolder = function (remotePath, cb) { - var localFolder = temp.path({prefix: 'appium'}); - - var bufferOnSuccess = function (buffer) { - logger.debug("Converting in-memory zip file to base64 encoded string"); - var data = buffer.toString('base64'); - logger.debug("Returning in-memory zip file as base54 encoded string"); - cb(null, {status: status.codes.Success.code, value: data}); - }; - - var bufferOnFail = function (err) { - cb(new Error(err)); - }; - - this.adb.pull(remotePath, localFolder, function (err) { - if (err) return cb(new Error(err)); - var zip = new AdmZip(); - zip.addLocalFolder(localFolder); - zip.toBuffer(bufferOnSuccess, bufferOnFail); - }); -}; - -androidController.getScreenshot = function (cb) { - var localfile = temp.path({prefix: 'appium', suffix: '.png'}); - var b64data = ""; - - async.series([ - function (cb) { - var png = "/data/local/tmp/screenshot.png"; - var cmd = ['"/system/bin/rm', png + ';', '/system/bin/screencap -p', - png, '"'].join(' '); - this.adb.shell(cmd, cb); - }.bind(this), - function (cb) { - if (fs.existsSync(localfile)) fs.unlinkSync(localfile); - this.adb.pull('/data/local/tmp/screenshot.png', localfile, cb); - }.bind(this), - function (cb) { - fs.readFile(localfile, function (err, data) { - if (err) return cb(err); - b64data = new Buffer(data).toString('base64'); - cb(); - }); - }, - function (cb) { - fs.unlink(localfile, function (err) { - if (err) return cb(err); - cb(); - }); - } - ], - // Top level cb - function (err) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - , value: b64data - }); - }); -}; - -androidController.fakeFlick = function (xSpeed, ySpeed, swipe, cb) { - this.proxy(["flick", {xSpeed: xSpeed, ySpeed: ySpeed}], cb); -}; - -androidController.fakeFlickElement = function (elementId, xoffset, yoffset, speed, cb) { - this.proxy(["element:flick", {xoffset: xoffset, yoffset: yoffset, speed: speed, elementId: elementId}], cb); -}; - -androidController.swipe = function (startX, startY, endX, endY, duration, touchCount, elId, cb) { - if (startX === 'null') { - startX = 0.5; - } - if (startY === 'null') { - startY = 0.5; - } - var swipeOpts = { - startX: startX - , startY: startY - , endX: endX - , endY: endY - , steps: Math.round(duration * this.swipeStepsPerSec) - }; - - // going the long way and checking for undefined and null since - // we can't be assured `elId` is a string and not an int - if (typeof elId !== "undefined" && elId !== null) { - swipeOpts.elementId = elId; - this.proxy(["element:swipe", swipeOpts], cb); - } else { - this.proxy(["swipe", swipeOpts], cb); - } -}; - -androidController.rotate = function (x, y, radius, rotation, duration, touchCount, elId, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.pinchClose = function (startX, startY, endX, endY, duration, percent, steps, elId, cb) { - var pinchOpts = { - direction: 'in' - , elementId: elId - , percent: percent - , steps: steps - }; - this.proxy(["element:pinch", pinchOpts], cb); -}; - -androidController.pinchOpen = function (startX, startY, endX, endY, duration, percent, steps, elId, cb) { - var pinchOpts = { - direction: 'out' - , elementId: elId - , percent: percent - , steps: steps - }; - this.proxy(["element:pinch", pinchOpts], cb); -}; - -androidController.flick = function (startX, startY, endX, endY, touchCount, elId, cb) { - if (startX === 'null') { - startX = 0.5; - } - if (startY === 'null') { - startY = 0.5; - } - var swipeOpts = { - startX: startX - , startY: startY - , endX: endX - , endY: endY - , steps: Math.round(0.2 * this.swipeStepsPerSec) - }; - if (elId !== null) { - swipeOpts.elementId = elId; - this.proxy(["element:swipe", swipeOpts], cb); - } else { - this.proxy(["swipe", swipeOpts], cb); - } -}; - -androidController.drag = function (startX, startY, endX, endY, duration, touchCount, elementId, destElId, cb) { - var dragOpts = { - elementId: elementId - , destElId: destElId - , startX: startX - , startY: startY - , endX: endX - , endY: endY - , steps: Math.round(duration * this.dragStepsPerSec) - }; - - if (elementId) { - this.proxy(["element:drag", dragOpts], cb); - } else { - this.proxy(["drag", dragOpts], cb); - } -}; - -androidController.scrollTo = function (elementId, text, direction, cb) { - // instead of the elementId as the element to be scrolled too, - // it's the scrollable view to swipe until the uiobject that has the - // text is found. - var opts = { - text: text - , direction: direction - , elementId: elementId - }; - this.proxy(["element:scrollTo", opts], cb); -}; - -androidController.scroll = function (direction, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.shake = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.setLocation = function (latitude, longitude, altitude, horizontalAccuracy, verticalAccuracy, course, speed, cb) { - var cmd = "geo fix " + longitude + " " + latitude; - this.adb.sendTelnetCommand(cmd, function (err, res) { - if (err) { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Could not set geolocation via telnet to device" - }); - } - cb(null, { - status: status.codes.Success.code - , value: res - }); - }); -}; - -androidController.url = function (url, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.active = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.closeWindow = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.clearWebView = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.execute = function (script, args, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.convertElementForAtoms = deviceCommon.convertElementForAtoms; - -androidController.title = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.moveTo = function (element, xoffset, yoffset, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.clickCurrent = function (button, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.getCookies = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.setCookie = function (cookie, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.deleteCookie = function (cookie, cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.deleteCookies = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -androidController.resetAndStartApp = function (cb) { - async.series([ - this.resetApp.bind(this), - this.waitForActivityToStop.bind(this), - this.startAppUnderTest.bind(this) - ], cb); -}; - -// controller.js#isAppInstalled expects weird response, hence this hack -androidController.isAppInstalled = function (appPackage, cb) { - this.adb.isAppInstalled(appPackage, function (err, installed) { - if (installed) { - return cb(null, [true]); - } - cb(err, []); - }); -}; - -androidController.removeApp = function (appPackage, cb) { - var removeCommand = null; - if (this.args.udid) { - removeCommand = 'adb -s ' + this.args.udid + ' uninstall ' + appPackage; - } else { - removeCommand = 'adb uninstall ' + appPackage; - } - deviceCommon.removeApp(removeCommand, this.args.udid, appPackage, cb); -}; - -androidController.installApp = function (appPath, cb) { - var installationCommand = null; - if (this.args.udid) { - installationCommand = 'adb -s ' + this.args.udid + ' install ' + appPath; - } else { - installationCommand = 'adb install ' + appPath; - } - deviceCommon.installApp(installationCommand, this.args.udid, appPath, cb); -}; - -androidController.unpackApp = function (req, cb) { - deviceCommon.unpackApp(req, '.apk', cb); -}; - -androidController.tap = function (elementId, x, y, count, cb) { - if (typeof x === "undefined" || x === null) x = 0; - if (typeof y === "undefined" || y === null) y = 0; - if (typeof count === "undefined" || count === null) count = 1; - - var i = 0; - var opts = {}; - var loop = function (err, res) { - if (err) return cb(err); - if (i++ >= count) return cb(err, res); - - this.proxy(opts, loop); - }.bind(this); - - if (elementId) { - // we are either tapping on the default location of the element - // or an offset from the top left corner - if (x !== 0 || y !== 0) { - opts = ["element:click", {elementId: elementId, x: x, y: y}]; - } else { - opts = ["element:click", {elementId: elementId}]; - } - loop(); - } else { - // we have absolute coordinates - opts = ["click", {x: x, y: y}]; - loop(); - } -}; - -androidController.doTouchAction = function (action, opts, cb) { - switch (action) { - case 'tap': - return this.tap(opts.element, opts.x, opts.y, opts.count, cb); - case 'press': - return this.touchDown(opts.element, opts.x, opts.y, cb); - case 'release': - return this.touchUp(opts.element, opts.x, opts.y, cb); - case 'moveTo': - return this.touchMove(opts.element, opts.x, opts.y, cb); - case 'wait': - return setTimeout(function () { - cb(null, {"value": true, "status": status.codes.Success.code}); - }, opts.ms); - case 'longPress': - if (typeof opts.duration === 'undefined' || !opts.duration) { - opts.duration = 1000; - } - return this.touchLongClick(opts.element, opts.x, opts.y, opts.duration, cb); - case 'cancel': - // TODO: clarify behavior of 'cancel' action and fix this - logger.warn("Cancel action currently has no effect"); - break; - default: - return cb("unknown action '" + action + "'"); - } -}; - -androidController.performTouch = function (gestures, cb) { - var actions = _.pluck(gestures, "action"); - - // drag is *not* press-move-release, so we need to translate - // drag works fine for scroll, as well - var doTouchDrag = function (gestures, cb) { - var getStartLocation = function (elementId, x, y, ncb) { - var startX = x || 0 - , startY = y || 0; - if (elementId) { - this.getLocation(elementId, function (err, res) { - if (err) return ncb(err); - - startX += res.value.x || 0; - startY += res.value.y || 0; - - return ncb(null, startX, startY); - }.bind(this)); - } else { - return ncb(null, startX, startY); - } - }.bind(this); - var getEndLocation = function (elementId, x, y, ncb) { - var endX = x || 0 - , endY = y || 0; - if (elementId) { - this.getLocation(elementId, function (err, res) { - if (err) return ncb(err); - - endX += res.value.x || 0; - endY += res.value.y || 0; - - return ncb(null, endX, endY); - }.bind(this)); - } else { - return ncb(null, endX, endY); - } - }.bind(this); - - var longPress = gestures[0]; - getStartLocation(longPress.options.element, longPress.options.x, longPress.options.y, function (err, startX, startY) { - if (err) return cb(err); - - var moveTo = gestures[1]; - getEndLocation(moveTo.options.element, moveTo.options.x, moveTo.options.y, function (err, endX, endY) { - this.adb.getApiLevel(function (err, apiLevel) { - // lollipop takes a little longer to get things rolling - var duration = apiLevel >= 5 ? 2 : 1; - // `drag` will take care of whether there is an element or not at that level - return this.drag(startX, startY, endX, endY, duration, 1, longPress.options.element, moveTo.options.element, cb); - }.bind(this)); - }.bind(this)); - }.bind(this)); - }.bind(this); - - - // Fix last release action - var fixRelease = function (cb) { - if (actions[actions.length - 1] === 'release') { - var release = gestures[actions.length - 1]; - // sometimes there are no options - release.options = release.options || {}; - - // nothing to do if release options are already set - if (release.options.element || (release.options.x && release.options.y)) return; - - // without coordinates, `release` uses the center of the screen, which, - // generally speaking, is not what we want - // therefore: loop backwards and use the last command with an element and/or - // offset coordinates - var ref = _(gestures).chain().initial().filter(function (gesture) { - var opts = gesture.options; - return opts.element || (opts.x && opts.y); - }).last().value(); - if (ref) { - var opts = ref.options || {}; - if (opts.element) { - // we retrieve the element location, might be useful in - // case the element becomes invalid - return async.parallel([ - this.getLocation.bind(this, opts.element), - this.getSize.bind(this, opts.element) - ], function (err, res) { - if (err) return cb(err); - var loc = res[0].value, size = res[1].value; - release.options = { - element: opts.element, - x: loc.x + size.width / 2, - y: loc.y + size.height / 2 - }; - cb(); - }); - } - if (opts.x && opts.y) release.options = _.pick(opts, 'x', 'y'); - } - } - cb(); - }.bind(this); - - // Perform one gesture - var performGesture = function (gesture, cb) { - async.waterfall([ - this.doTouchAction.bind(this, gesture.action, gesture.options || {}), - function (res, cb) { - // sometime the element is not available when releasing, retry without it - if (res && res.status === 7 && - gesture.action === 'release' && gesture.options.element) { - delete gesture.options.element; - logger.debug('retrying release without element opts:', gestures.options, '.'); - return this.doTouchAction(gesture.action, gesture.options || {}, cb); - } - // otherwise continue normally - return cb(null, res); - }.bind(this)], function (err, res) { - if (err) return cb(err); - // check result, we wrap json errors - if (res.status !== 0) { - err = new Error(); - err.res = res; - return cb(err); - } - cb(null, res); - }); - }.bind(this); - - // wrapping callback - cb = _.wrap(cb, function (cb, err) { - if (err) { - if (err.res) return cb(null, err.res); - logger.error(err, err.stack); - return cb(err); - } - // success - cb(null, { value: true, status: 0 }); - }); - - if (actions[0] === 'longPress' && actions[1] === 'moveTo' && actions[2] === 'release') { - // some things are special - doTouchDrag(gestures, cb); - } else { - // `press` without a wait is too slow and gets interpretted as a `longPress` - if (actions[actions.length - 2] === 'press' && actions[actions.length - 1] === 'release') { - actions[actions.length - 2] = 'tap'; - gestures[gestures.length - 2].action = 'tap'; - } - - // the `longPress` and `tap` methods release on their own - if ((actions[actions.length - 2] === 'tap' || - actions[actions.length - 2] === 'longPress') && actions[actions.length - 1] === 'release') { - gestures.pop(); - actions.pop(); - } - - // fix release action then perform all actions - fixRelease(function (err) { - if (err) return cb(err); - this.parseTouch(gestures, false, function (err, fixedGestures) { - if (err) return cb(err); - async.eachSeries(fixedGestures, performGesture, cb); - }); - }.bind(this)); - } -}; - -androidController.parseTouch = function (gestures, multi, cb) { - if (multi && _.last(gestures).action === 'release') { - gestures.pop(); - } - - var needsPoint = function (action) { - return _.contains(['press', 'moveTo', 'tap', 'longPress'], action); - }; - - var touchStateObjects = []; - async.eachSeries(gestures, function (gesture, done) { - var options = gesture.options; - if (needsPoint(gesture.action)) { - options.offset = false; - var elementId = gesture.options.element; - if (elementId) { - this.getLocation(elementId, function (err, res) { - if (err) return done(err); // short circuit and quit - - var pos = { x: res.value.x, y: res.value.y }; - this.getSize(elementId, function (err, res) { - if (err) return done(err); - var size = {w: res.value.width, h: res.value.height}; - - if (gesture.options.x || gesture.options.y) { - options.x = pos.x + (gesture.options.x || 0); - options.y = pos.y + (gesture.options.y || 0); - } else { - options.x = pos.x + (size.w / 2); - options.y = pos.y + (size.h / 2); - } - - var touchStateObject = { - action: gesture.action, - options: options, - timeOffset: 0.005, - }; - touchStateObjects.push(touchStateObject); - done(); - }); - }.bind(this)); - } else { - // expects absolute coordinates, so we need to save these as offsets - // and then translate when everything is done - options.offset = true; - options.x = (gesture.options.x || 0); - options.y = (gesture.options.y || 0); - - touchStateObject = { - action: gesture.action, - options: options, - timeOffset: 0.005, - }; - touchStateObjects.push(touchStateObject); - done(); - } - } else { - var offset = 0.005; - if (gesture.action === 'wait') { - options = gesture.options; - offset = (parseInt(gesture.options.ms) / 1000); - } - var touchStateObject = { - action: gesture.action, - options: options, - timeOffset: offset, - }; - touchStateObjects.push(touchStateObject); - done(); - } - }.bind(this), function (err) { - if (err) return cb(err); - - // we need to change the time (which is now an offset) - // and the position (which may be an offset) - var prevPos = null, - time = 0; - _.each(touchStateObjects, function (state) { - if (typeof state.options.x === 'undefined' && typeof state.options.x === 'undefined') { - // this happens with wait - state.options.x = prevPos.x; - state.options.y = prevPos.y; - } - if (state.options.offset && prevPos) { - // the current position is an offset - state.options.x += prevPos.x; - state.options.y += prevPos.y; - } - delete state.options.offset; - prevPos = state.options; - - if (multi) { - var timeOffset = state.timeOffset; - time += timeOffset; - state.time = helpers.truncateDecimals(time, 3); - - // multi gestures require 'touch' rather than 'options' - state.touch = state.options; - delete state.options; - } - - delete state.timeOffset; - }); - - cb(null, touchStateObjects); - }); -}; - -androidController.performMultiAction = function (elementId, actions, cb) { - // Android needs at least two actions to be able to perform a multi pointer gesture - if (actions.length === 1) { - return cb(new Error("Multi Pointer Gestures need at least two actions. " + - "Use Touch Actions for a single action.")); - } - - var states = []; - async.eachSeries(actions, function (action, done) { - this.parseTouch(action, true, function (err, val) { - if (err) return done(err); - - states.push(val); - done(); - }.bind(this)); - }.bind(this), function (err) { - if (err) return cb(err); - - var opts; - if (elementId) { - opts = { - elementId: elementId, - actions: states - }; - return this.proxy(["element:performMultiPointerGesture", opts], cb); - } else { - opts = { - actions: states - }; - return this.proxy(["performMultiPointerGesture", opts], cb); - } - }.bind(this)); -}; - -androidController.openNotifications = function (cb) { - this.proxy(["openNotification"], cb); -}; - -androidController.getUrl = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -module.exports = androidController; diff --git a/lib/devices/android/android-hybrid.js b/lib/devices/android/android-hybrid.js deleted file mode 100644 index 346ec523..00000000 --- a/lib/devices/android/android-hybrid.js +++ /dev/null @@ -1,275 +0,0 @@ -"use strict"; - -var logger = require('../../server/logger.js').get('appium') - , _ = require('underscore') - , errors = require('../../server/errors.js') - , UnknownError = errors.UnknownError - , async = require('async') - , Chromedriver = require('appium-chromedriver') - , status = require("../../server/status.js"); - -var androidHybrid = {}; - -androidHybrid.chromedriver = null; -androidHybrid.sessionChromedrivers = {}; - -androidHybrid.listWebviews = function (cb) { - logger.debug("Getting a list of available webviews"); - var webviews = []; - var definedDeviceSocket = this.args.androidDeviceSocket; - this.adb.shell("cat /proc/net/unix", function (err, out) { - if (err) return cb(err); - _.each(out.split("\n"), function (line) { - line = line.trim(); - var webviewPid = line.match(/@?webview_devtools_remote_(\d+)/); - if (definedDeviceSocket) { - if (line.indexOf("@" + definedDeviceSocket) === - line.length - definedDeviceSocket.length - 1) { - if (webviewPid) { - webviews.push(this.WEBVIEW_BASE + webviewPid[1]); - } else { - webviews.push(this.CHROMIUM_WIN); - } - } - } else if (webviewPid) { - // for multiple webviews a list of 'WEBVIEW_' will be returned - // where is zero based (same is in selendroid) - webviews.push(this.WEBVIEW_BASE + webviewPid[1]); - } - }.bind(this)); - webviews = _.uniq(webviews); - - if (definedDeviceSocket) { - return cb(null, webviews); - } - - var webviewsTmp = webviews; - webviews = []; - - var getProcessNameFromWebview = function (view, cb) { - this.getProcessNameFromWebview(view, function (err, pkg) { - if (err) return cb(err); - webviews.push(this.WEBVIEW_BASE + pkg); - cb(); - }.bind(this)); - }.bind(this); - - async.each(webviewsTmp, getProcessNameFromWebview, function (err) { - if (err) return cb(err); - logger.debug("Available contexts: " + this.contexts); - logger.debug(JSON.stringify(webviews)); - cb(null, webviews); - }.bind(this)); - }.bind(this)); -}; - -var previousState = {}; - -// remember whether we were previously proxying to a chromedriver or not -androidHybrid.rememberProxyState = function () { - previousState.isProxy = this.isProxy; -}; - -androidHybrid.restoreProxyState = function () { - this.isProxy = previousState.isProxy; -}; - -androidHybrid.getProcessNameFromWebview = function (webview, cb) { - // webview_devtools_remote_4296 => 4296 - var pid = webview.match(/\d+$/); - if (!pid) return cb("No pid for webview " + webview); - pid = pid[0]; - logger.debug(webview + " mapped to pid " + pid); - - logger.debug("Getting process name for webview"); - this.adb.shell("ps", function (err, out) { - if (err) return cb(err); - var pkg = "unknown"; - - var lines = out.split(/\r?\n/); - /* - USER PID PPID VSIZE RSS WCHAN PC NAME - u0_a136 6248 179 946000 48144 ffffffff 4005903e R com.example.test - */ - var header = lines[0].trim().split(/\s+/); - // the column order may not be identical on all androids - // dynamically locate the pid and name column. - var pidColumn = header.indexOf("PID"); - var pkgColumn = header.indexOf("NAME") + 1; - - _.find(lines, function (line) { - line = line.trim().split(/\s+/); - if (line[pidColumn].indexOf(pid) !== -1) { - logger.debug("Parsed pid: " + line[pidColumn] + " pkg: " + line[pkgColumn]); - logger.debug("from: " + line); - pkg = line[pkgColumn]; - return pkg; // exit from _.find - } - }); - - logger.debug("returning process name: " + pkg); - cb(null, pkg); - }); -}; - -androidHybrid.startChromedriverProxy = function (context, cb) { - cb = _.once(cb); - logger.debug("Connecting to chrome-backed webview"); - if (this.chromedriver !== null) { - return cb(new Error("We already have a chromedriver instance running")); - } - - if (this.sessionChromedrivers[context]) { - // in the case where we've already set up a chromedriver for a context, - // we want to reconnect to it, not create a whole new one - this.setupExistingChromedriver(context, cb); - } else { - this.setupNewChromedriver(context, cb); - } -}; - -androidHybrid.setupNewChromedriver = function (context, cb) { - var chromeArgs = { - port: this.args.chromeDriverPort, - executable: this.args.chromedriverExecutable - }; - this.chromedriver = new Chromedriver(chromeArgs); - this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver); - this.rememberProxyState(); - this.isProxy = true; - var caps = { - chromeOptions: { - androidPackage: this.args.appPackage, - androidUseRunningApp: true - } - }; - if (this.args.enablePerformanceLogging) { - caps.loggingPrefs = {performance: 'ALL'}; - } - // For now the only known arg passed this way is androidDeviceSocket used - // by Operadriver (deriving from Chromedriver) // We don't know how other - // Chromium embedders will call this argument so for now it's name needs to - // be configurable. When Google adds the androidDeviceSocket argument to - // the original Chromedriver then we will be sure about its name for all - // Chromium embedders (as their Webdrivers will derive from Chromedriver) - if (this.args.specialChromedriverSessionArgs) { - _.each(this.args.specialChromedriverSessionArgs, function (val, option) { - logger.debug("This method is being deprecated. Apply chromeOptions " + - "normally to pass along options,see sites.google.com/a/" + - "chromium.org/chromedriver/capabilities for more info"); - caps.chromeOptions[option] = val; - }); - } - caps = this.decorateChromeOptions(caps); - this.chromedriver.on(Chromedriver.EVENT_CHANGED, function (msg) { - if (msg.state === Chromedriver.STATE_STOPPED) { - // bind our stop/exit handler, passing in context so we know which - // one stopped unexpectedly - this.onChromedriverStop(context); - } - }.bind(this)); - this.chromedriver.start(caps).nodeify(function (err) { - if (err) return cb(err); - // save the chromedriver object under the context - this.sessionChromedrivers[context] = this.chromedriver; - cb(); - }.bind(this)); -}; - - -androidHybrid.setupExistingChromedriver = function (context, cb) { - logger.debug("Found existing Chromedriver for context '" + context + "'." + - " Using it."); - this.rememberProxyState(); - this.chromedriver = this.sessionChromedrivers[context]; - this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver); - this.isProxy = true; - - // check the status by sending a simple window-based command to ChromeDriver - // if there is an error, we want to recreate the ChromeDriver session - this.chromedriver.hasWorkingWebview().nodeify(function (err, works) { - if (err) return cb(err); - if (works) return cb(); - logger.debug("ChromeDriver is not associated with a window. " + - "Re-initializing the session."); - this.chromedriverRestartingContext = context; - this.chromedriver.restart().nodeify(function (err) { - if (err) return cb(err); - this.chromedriverRestartingContext = null; - cb(); - }.bind(this)); - }.bind(this)); -}; - -androidHybrid.onChromedriverStop = function (context) { - logger.warn("Chromedriver for context " + context + " stopped unexpectedly"); - if (context === this.curContext) { - // if we don't have a stop callback, we exited unexpectedly and so want - // to shut down the session and respond with an error - // TODO: this kind of thing should be emitted and handled by a higher-level - // controlling function - var error = new UnknownError("Chromedriver quit unexpectedly during session"); - logger.error(error.message); - if (typeof this.cbForCurrentCmd === "function") { - this.shutdown(function () { - this.cbForCurrentCmd(error, null); - }.bind(this)); - } - } else if (context !== this.chromedriverRestartingContext) { - // if a Chromedriver in the non-active context barfs, we don't really - // care, we'll just make a new one next time we need the context. - // The only time we ignore this is if we know we're in the middle of a - // Chromedriver restart - logger.warn("Chromedriver quit unexpectedly, but it wasn't the active " + - "context, ignoring"); - delete this.sessionChromedrivers[context]; - } -}; - -androidHybrid.suspendChromedriverProxy = function (cb) { - this.chromedriver = null; - this.restoreProxyState(); - cb(); -}; - -androidHybrid.stopChromedriverProxies = function (ocb) { - async.eachSeries(Object.keys(this.sessionChromedrivers), function (context, cb) { - logger.debug("Stopping chromedriver for context " + context); - // stop listening for the stopped state event - this.sessionChromedrivers[context].removeAllListeners(Chromedriver.EVENT_CHANGED); - this.sessionChromedrivers[context].stop().nodeify(function (err) { - if (err) logger.warn("Error stopping Chromedriver: " + err.message); - // chromedriver isn't valid anymore, so remove it from context list - delete this.sessionChromedrivers[context]; - cb(); - }.bind(this)); - }.bind(this), function (err) { - // if one of these fails, go back to last proxy state and error out - this.restoreProxyState(); - ocb(err); - }.bind(this)); -}; - -androidHybrid.defaultWebviewName = function () { - return this.WEBVIEW_BASE + this.appProcess; -}; - -androidHybrid.initAutoWebview = function (cb) { - if (this.args.autoWebview) { - logger.debug('Setting auto webview'); - var viewName = this.defaultWebviewName(); - var timeout = (this.args.autoWebviewTimeout) || 2000; - this.setContext(viewName, function (err, res) { - if (err && res.status !== status.codes.NoSuchContext.code) return cb(err); - if (res.status === status.codes.Success.code) return cb(); - setTimeout(function () { - logger.debug("Retrying context switch with timeout '" + timeout + "'"); - this.setContext(viewName, cb); - }.bind(this), timeout); - }.bind(this)); - } else { - cb(); - } -}; - -module.exports = androidHybrid; diff --git a/lib/devices/android/android.js b/lib/devices/android/android.js deleted file mode 100644 index 5f58be25..00000000 --- a/lib/devices/android/android.js +++ /dev/null @@ -1,593 +0,0 @@ -"use strict"; - -var errors = require('../../server/errors.js') - , path = require('path') - , fs = require('fs') - , Device = require('../device.js') - , _ = require('underscore') - , logger = require('../../server/logger.js').get('appium') - , deviceCommon = require('../common.js') - , status = require("../../server/status.js") - , async = require('async') - , androidController = require('./android-controller.js') - , androidContextController = require('./android-context-controller.js') - , androidCommon = require('./android-common.js') - , androidHybrid = require('./android-hybrid.js') - , UiAutomator = require('./uiautomator.js') - , UnknownError = errors.UnknownError; - -var Android = function () { - this.init(); -}; - -_.extend(Android.prototype, Device.prototype); - -Android.prototype._deviceInit = Device.prototype.init; -Android.prototype.init = function () { - this._deviceInit(); - this.appExt = ".apk"; - this.capabilities = { - platform: 'LINUX' - , browserName: 'Android' - , platformVersion: '4.1' - , webStorageEnabled: false - , takesScreenshot: true - , javascriptEnabled: true - , databaseEnabled: false - , networkConnectionEnabled: true - , locationContextEnabled: false - }; - this.args.devicePort = 4724; - this.appMd5Hash = null; - this.args.avd = null; - this.args.language = null; - this.args.locale = null; - this.args.javaVersion = null; - this.initQueue(); - this.implicitWaitMs = 0; - this.shuttingDown = false; - this.adb = null; - this.uiautomator = null; - this.uiautomatorRestartOnExit = false; - this.uiautomatorIgnoreExit = false; - this.swipeStepsPerSec = 28; - this.dragStepsPerSec = 40; - this.asyncWaitMs = 0; - this.remote = null; - this.contexts = []; - this.curContext = this.defaultContext(); - this.didLaunch = false; - this.launchCb = function () {}; - this.uiautomatorExitCb = function () {}; - this.dataDir = null; - this.isProxy = false; - this.proxyHost = null; - this.proxyPort = null; - this.proxySessionId = null; - this.avoidProxy = [ - ['POST', new RegExp('^/wd/hub/session/[^/]+/context')] - , ['GET', new RegExp('^/wd/hub/session/[^/]+/context')] - , ['GET', new RegExp('^/wd/hub/session/[^/]+/contexts')] - , ['POST', new RegExp('^/wd/hub/session/[^/]+/appium')] - , ['GET', new RegExp('^/wd/hub/session/[^/]+/appium')] - ]; - - // listen for changes to ignoreUnimportantViews - this.settings.on("update", function (update) { - if (update.key === "ignoreUnimportantViews") { - this.setCompressedLayoutHierarchy(update.value, update.callback); - } else { - update.callback(); - } - }.bind(this)); -}; - -Android.prototype._deviceConfigure = Device.prototype.configure; - -Android.prototype.noLaunchSetup = function (cb) { - logger.debug("Setting up Android for 'autoLaunch: false'"); - async.series([ - this.initJavaVersion.bind(this), - this.initAdb.bind(this), - ], function (err) { cb(err); }); -}; - -Android.prototype.start = function (cb, onDie) { - this.launchCb = cb; - this.uiautomatorExitCb = onDie; - logger.info("Starting android appium"); - async.series([ - this.initJavaVersion.bind(this), - this.initAdb.bind(this), - this.packageAndLaunchActivityFromManifest.bind(this), - this.initUiautomator.bind(this), - this.prepareDevice.bind(this), - this.checkApiLevel.bind(this), - this.pushStrings.bind(this), - this.processFromManifest.bind(this), - this.uninstallApp.bind(this), - this.installAppForTest.bind(this), - this.forwardPort.bind(this), - this.pushAppium.bind(this), - this.initUnicode.bind(this), - this.pushSettingsApp.bind(this), - this.pushUnlock.bind(this), - function (cb) {this.uiautomator.start(cb);}.bind(this), - this.wakeUp.bind(this), - this.unlock.bind(this), - this.getDataDir.bind(this), - this.setupCompressedLayoutHierarchy.bind(this), - this.startAppUnderTest.bind(this), - this.initAutoWebview.bind(this), - this.setActualCapabilities.bind(this) - ], function (err) { - if (err) { - this.shutdown(function () { - this.launchCb(err); - }.bind(this)); - } else { - this.didLaunch = true; - this.launchCb(null, this.proxySessionId); - } - }.bind(this)); -}; - -Android.prototype.initUiautomator = function (cb) { - if (this.uiautomator === null) { - this.uiautomator = new UiAutomator(this.adb, this.args); - this.uiautomator.setExitHandler(this.onUiautomatorExit.bind(this)); - } - return cb(); -}; - -Android.prototype.onLaunch = function (err) { - var readyToGo = function () { - this.didLaunch = true; - this.launchCb(); - }.bind(this); - - var giveUp = function (err) { - this.shutdown(function () { - this.launchCb(err); - }.bind(this)); - }.bind(this); - - if (err) { - if (this.checkShouldRelaunch(err)) { - logger.error(err); - logger.error("Above error isn't fatal, maybe relaunching adb will help...."); - this.adb.waitForDevice(function (err) { - if (err) return giveUp(err); - readyToGo(); - }); - } else { - giveUp(err); - } - } else { - readyToGo(); - } -}; - -Android.prototype.restartUiautomator = function (cb) { - async.series([ - this.forwardPort.bind(this) - , this.uiautomator.start.bind(this.uiautomator) - , this.setupCompressedLayoutHierarchy.bind(this) - ], cb); -}; - -/* - * Execute an arbitrary function and handle potential ADB disconnection before - * proceeding - */ -Android.prototype.wrapActionAndHandleADBDisconnect = function (action, ocb) { - async.series([ - function (cb) { - this.uiautomatorIgnoreExit = true; - action(cb); - }.bind(this) - , this.adb.restart.bind(this.adb) - , this.restartUiautomator.bind(this) - ], function (err) { - this.uiautomatorIgnoreExit = false; - ocb(err); - }.bind(this)); -}; - -Android.prototype.onUiautomatorExit = function () { - logger.debug("UiAutomator exited"); - var respondToClient = function () { - this.stopChromedriverProxies(function () { - this.cleanup(); - if (!this.didLaunch) { - var msg = "UiAutomator quit before it successfully launched"; - logger.error(msg); - this.launchCb(new Error(msg)); - return; - } else if (typeof this.cbForCurrentCmd === "function") { - var error = new UnknownError("UiAutomator died while responding to " + - "command, please check appium logs!"); - this.cbForCurrentCmd(error, null); - } - // make sure appium.js knows we crashed so it can clean up - this.uiautomatorExitCb(); - }.bind(this)); - }.bind(this); - - if (this.adb) { - var uninstall = function () { - logger.debug("Attempting to uninstall app"); - this.uninstallApp(function () { - this.shuttingDown = false; - respondToClient(); - }.bind(this)); - }.bind(this); - - if (!this.uiautomatorIgnoreExit) { - this.adb.ping(function (err, ok) { - if (ok) { - uninstall(); - } else { - logger.debug(err); - this.adb.restart(function (err) { - if (err) { - logger.debug(err); - } - if (this.uiautomatorRestartOnExit) { - this.uiautomatorRestartOnExit = false; - this.restartUiautomator(function (err) { - if (err) { - logger.debug(err); - uninstall(); - } - }.bind(this)); - } else { - uninstall(); - } - }.bind(this)); - } - }.bind(this)); - } else { - this.uiautomatorIgnoreExit = false; - } - } else { - logger.debug("We're in uiautomator's exit callback but adb is gone already"); - respondToClient(); - } -}; - -Android.prototype.checkShouldRelaunch = function (launchErr) { - if (launchErr.message === null || typeof launchErr.message === 'undefined') { - logger.error("We're checking if we should relaunch based on something " + - "which isn't an error object. Check the codez!"); - return false; - } - var msg = launchErr.message.toString(); - var relaunchOn = [ - 'Could not find a connected Android device' - , 'Device did not become ready' - ]; - var relaunch = false; - _.each(relaunchOn, function (relaunchMsg) { - relaunch = relaunch || msg.indexOf(relaunchMsg) !== -1; - }); - return relaunch; -}; - -Android.prototype.checkApiLevel = function (cb) { - this.adb.getApiLevel(function (err, apiLevel) { - if (err) return cb(err); - logger.info('Device API level is:', parseInt(apiLevel, 10)); - if (parseInt(apiLevel) < 17) { - var msg = "Android devices must be of API level 17 or higher. Please change your device to Selendroid or upgrade Android on your device."; - logger.error(msg); // logs the error when we encounter it - return cb(new Error(msg)); // send the error up the chain - } - - cb(); - }); -}; - -Android.prototype.decorateChromeOptions = function (caps) { - // add options from appium session caps - if (this.args.chromeOptions) { - _.each(this.args.chromeOptions, function (val, option) { - if (typeof caps.chromeOptions[option] === "undefined") { - caps.chromeOptions[option] = val; - } else { - logger.warn("Cannot pass option " + caps.chromeOptions[option] + " because Appium needs it to make chromeDriver work"); - } - }); - } - - // add device id from adb - caps.chromeOptions.androidDeviceSerial = this.adb.curDeviceId; - - return caps; -}; - -Android.prototype.processFromManifest = function (cb) { - if (!this.args.app) { - return cb(); - } else { // apk must be local to process the manifest. - this.adb.processFromManifest(this.args.app, function (err, process) { - var value = process || this.args.appPackage; - - this.appProcess = value; - logger.debug("Set app process to: " + this.appProcess); - - cb(); - }.bind(this)); - } -}; - -Android.prototype.pushStrings = function (cb, language) { - var outputPath = path.resolve(this.args.tmpDir, this.args.appPackage); - var remotePath = '/data/local/tmp'; - var stringsJson = 'strings.json'; - this.extractStrings(function (err) { - 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); - } - } - var jsonFile = path.resolve(outputPath, stringsJson); - this.adb.push(jsonFile, remotePath, function (err) { - if (err) return cb(new Error("Could not push strings.json")); - cb(); - }); - }.bind(this), language); -}; - -Android.prototype.getStrings = function (language, stringFile, cb) { - if (this.language && this.language === language) { - // Return last strings - return cb(null, { - status: status.codes.Success.code, - value: this.apkStrings - }); - } - - // Extract, push and return strings - return this.pushStrings(function () { - this.proxy(["updateStrings", {}], function (err, res) { - if (err || res.status !== status.codes.Success.code) return cb(err, res); - cb(null, { - status: status.codes.Success.code, - value: this.apkStrings - }); - }.bind(this)); - }.bind(this), language); -}; - -Android.prototype.pushAppium = function (cb) { - logger.debug("Pushing appium bootstrap to device..."); - var binPath = path.resolve(__dirname, "..", "..", "..", "build", - "android_bootstrap", "AppiumBootstrap.jar"); - fs.stat(binPath, function (err) { - if (err) { - cb(new Error("Could not find AppiumBootstrap.jar; please run " + - "'grunt buildAndroidBootstrap'")); - } else { - this.adb.push(binPath, this.remoteTempPath(), cb); - } - }.bind(this)); -}; - -Android.prototype.startAppUnderTest = function (cb) { - this.startApp(this.args, cb); -}; - -Android.prototype.startApp = function (args, cb) { - if (args.androidCoverage) { - this.adb.androidCoverage(args.androidCoverage, args.appWaitPackage, - args.appWaitActivity, cb); - } else { - this.adb.startApp({ - pkg: args.appPackage, - activity: args.appActivity, - action: args.intentAction, - category: args.intentCategory, - flags: args.intentFlags, - waitPkg: args.appWaitPackage, - waitActivity: args.appWaitActivity, - optionalIntentArguments: args.optionalIntentArguments, - stopApp: args.stopAppOnReset || !args.dontStopAppOnReset - }, cb); - } -}; - -Android.prototype.stop = function (cb) { - if (this.shuttingDown) { - logger.debug("Already in process of shutting down."); - return cb(); - } - this.shuttingDown = true; - - var completeShutdown = function (cb) { - if (this.adb) { - this.adb.goToHome(function () { - this.shutdown(cb); - }.bind(this)); - } else { - this.shutdown(cb); - } - }.bind(this); - - if (this.args.fullReset) { - logger.debug("Removing app from device"); - this.uninstallApp(function (err) { - if (err) { - // simply warn on error here, because we don't want to stop the shutdown - // process - logger.warn(err); - } - completeShutdown(cb); - }); - } else { - completeShutdown(cb); - } - - -}; - -Android.prototype.cleanup = function () { - logger.debug("Cleaning up android objects"); - this.adb = null; - this.uiautomator = null; - this.shuttingDown = false; -}; - -Android.prototype.shutdown = function (cb) { - var next = function () { - this.stopChromedriverProxies(function () { - if (this.uiautomator) { - this.uiautomator.shutdown(function () { - this.cleanup(); - cb(); - }.bind(this)); - } else { - this.cleanup(); - cb(); - } - }.bind(this)); - }.bind(this); - - if (this.adb) { - this.adb.endAndroidCoverage(); - if (this.args.unicodeKeyboard && this.args.resetKeyboard && this.defaultIME) { - logger.debug('Resetting IME to \'' + this.defaultIME + '\''); - this.adb.setIME(this.defaultIME, function (err) { - if (err) { - // simply warn on error here, because we don't want to stop the shutdown - // process - logger.warn(err); - } - if (this.adb) { - this.adb.stopLogcat(function () { - next(); - }.bind(this)); - } - }.bind(this)); - } else { - this.adb.stopLogcat(function () { - next(); - }.bind(this)); - } - } else { - next(); - } -}; - -Android.prototype.proxy = deviceCommon.proxy; -Android.prototype.respond = deviceCommon.respond; - -Android.prototype.initQueue = function () { - this.queue = async.queue(function (task, cb) { - var action = task.action, - params = task.params; - - this.cbForCurrentCmd = cb; - - if (this.adb && !this.shuttingDown) { - this.uiautomator.sendAction(action, params, function (response) { - this.cbForCurrentCmd = null; - if (typeof cb === 'function') { - this.respond(response, cb); - } - }.bind(this)); - } else { - this.cbForCurrentCmd = null; - var msg = "Tried to send command to non-existent Android device, " + - "maybe it shut down?"; - if (this.shuttingDown) { - msg = "We're in the middle of shutting down the Android device, " + - "so your request won't be executed. Sorry!"; - } - this.respond({ - status: status.codes.UnknownError.code - , value: msg - }, cb); - } - }.bind(this), 1); -}; - -Android.prototype.push = function (elem) { - this.queue.push({action: elem[0][0], params: elem[0][1] || {}}, elem[1]); -}; - -Android.prototype.wakeUp = function (cb) { - // requires an appium bootstrap connection loaded - logger.debug("Waking up device if it's not alive"); - this.proxy(["wake", {}], cb); -}; - -Android.prototype.getDataDir = function (cb) { - this.proxy(["getDataDir", {}], function (err, res) { - if (err) return cb(err); - this.dataDir = res.value; - logger.debug("dataDir set to: " + this.dataDir); - cb(); - }.bind(this)); -}; - -// Set CompressedLayoutHierarchy on the device based on current settings object -Android.prototype.setupCompressedLayoutHierarchy = function (cb) { - - // setup using cap - if (_.has(this.args, 'ignoreUnimportantViews')) { - // set the setting directly on the internal _settings object, this way we don't trigger an update event - this.settings._settings.ignoreUnimportantViews = this.args.ignoreUnimportantViews; - } - - if (_.isUndefined(this.getSetting("ignoreUnimportantViews"))) { - return cb(); - } - this.setCompressedLayoutHierarchy(this.getSetting("ignoreUnimportantViews"), cb); -}; - -// Set CompressedLayoutHierarchy on the device -Android.prototype.setCompressedLayoutHierarchy = function (compress, cb) { - this.proxy(["compressedLayoutHierarchy", {compressLayout: compress}], cb); -}; - -Android.prototype.waitForActivityToStop = function (cb) { - this.adb.waitForNotActivity(this.args.appWaitPackage, this.args.appWaitActivity, cb); -}; - -Android.prototype.setActualCapabilities = function (cb) { - this.capabilities.deviceName = this.adb.udid || this.adb.curDeviceId; - this.adb.shell("getprop ro.build.version.release", function (err, version) { - if (err) { - logger.warn(err); - } else { - logger.debug("Device is at release version " + version); - this.capabilities.platformVersion = version; - } - return cb(); - }.bind(this)); -}; - -Android.prototype.resetTimeout = deviceCommon.resetTimeout; -Android.prototype.waitForCondition = deviceCommon.waitForCondition; -Android.prototype.implicitWaitForCondition = deviceCommon.implicitWaitForCondition; -Android.prototype.getSettings = deviceCommon.getSettings; -Android.prototype.updateSettings = deviceCommon.updateSettings; - -_.extend(Android.prototype, androidController); -_.extend(Android.prototype, androidContextController); -_.extend(Android.prototype, androidCommon); -_.extend(Android.prototype, androidHybrid); - -module.exports = Android; diff --git a/lib/devices/android/bootstrap/.project b/lib/devices/android/bootstrap/.project deleted file mode 100644 index d77d6710..00000000 --- a/lib/devices/android/bootstrap/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - Appium - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - org.eclipse.jdt.core.javanature - - diff --git a/lib/devices/android/bootstrap/.settings/org.eclipse.jdt.core.prefs b/lib/devices/android/bootstrap/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index d4c4413f..00000000 --- a/lib/devices/android/bootstrap/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,292 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.6 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=true -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=true -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert -org.eclipse.jdt.core.formatter.comment.line_length=80 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true -org.eclipse.jdt.core.formatter.indentation.size=2 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=true -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=80 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=2 -org.eclipse.jdt.core.formatter.use_on_off_tags=true -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff --git a/lib/devices/android/bootstrap/.settings/org.eclipse.jdt.ui.prefs b/lib/devices/android/bootstrap/.settings/org.eclipse.jdt.ui.prefs deleted file mode 100644 index cd5e9444..00000000 --- a/lib/devices/android/bootstrap/.settings/org.eclipse.jdt.ui.prefs +++ /dev/null @@ -1,109 +0,0 @@ -cleanup.add_default_serial_version_id=false -cleanup.add_generated_serial_version_id=true -cleanup.add_missing_annotations=true -cleanup.add_missing_deprecated_annotations=true -cleanup.add_missing_methods=false -cleanup.add_missing_nls_tags=false -cleanup.add_missing_override_annotations=true -cleanup.add_missing_override_annotations_interface_methods=true -cleanup.add_serial_version_id=true -cleanup.always_use_blocks=true -cleanup.always_use_parentheses_in_expressions=false -cleanup.always_use_this_for_non_static_field_access=false -cleanup.always_use_this_for_non_static_method_access=false -cleanup.convert_to_enhanced_for_loop=true -cleanup.correct_indentation=true -cleanup.format_source_code=true -cleanup.format_source_code_changes_only=false -cleanup.make_local_variable_final=true -cleanup.make_parameters_final=true -cleanup.make_private_fields_final=true -cleanup.make_type_abstract_if_missing_method=false -cleanup.make_variable_declarations_final=true -cleanup.never_use_blocks=false -cleanup.never_use_parentheses_in_expressions=true -cleanup.organize_imports=true -cleanup.qualify_static_field_accesses_with_declaring_class=false -cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true -cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true -cleanup.qualify_static_member_accesses_with_declaring_class=true -cleanup.qualify_static_method_accesses_with_declaring_class=false -cleanup.remove_private_constructors=true -cleanup.remove_trailing_whitespaces=true -cleanup.remove_trailing_whitespaces_all=true -cleanup.remove_trailing_whitespaces_ignore_empty=false -cleanup.remove_unnecessary_casts=true -cleanup.remove_unnecessary_nls_tags=true -cleanup.remove_unused_imports=true -cleanup.remove_unused_local_variables=true -cleanup.remove_unused_private_fields=true -cleanup.remove_unused_private_members=false -cleanup.remove_unused_private_methods=true -cleanup.remove_unused_private_types=true -cleanup.sort_members=true -cleanup.sort_members_all=false -cleanup.use_blocks=true -cleanup.use_blocks_only_for_return_and_throw=false -cleanup.use_parentheses_in_expressions=true -cleanup.use_this_for_non_static_field_access=true -cleanup.use_this_for_non_static_field_access_only_if_necessary=true -cleanup.use_this_for_non_static_method_access=true -cleanup.use_this_for_non_static_method_access_only_if_necessary=true -cleanup_profile=_appium_cleanup -cleanup_settings_version=2 -eclipse.preferences.version=1 -editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true -formatter_profile=_appium_format -formatter_settings_version=12 -sp_cleanup.add_default_serial_version_id=true -sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=true -sp_cleanup.add_missing_deprecated_annotations=true -sp_cleanup.add_missing_methods=false -sp_cleanup.add_missing_nls_tags=false -sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=true -sp_cleanup.add_serial_version_id=false -sp_cleanup.always_use_blocks=true -sp_cleanup.always_use_parentheses_in_expressions=false -sp_cleanup.always_use_this_for_non_static_field_access=false -sp_cleanup.always_use_this_for_non_static_method_access=false -sp_cleanup.convert_to_enhanced_for_loop=true -sp_cleanup.correct_indentation=true -sp_cleanup.format_source_code=true -sp_cleanup.format_source_code_changes_only=false -sp_cleanup.make_local_variable_final=true -sp_cleanup.make_parameters_final=true -sp_cleanup.make_private_fields_final=true -sp_cleanup.make_type_abstract_if_missing_method=false -sp_cleanup.make_variable_declarations_final=true -sp_cleanup.never_use_blocks=false -sp_cleanup.never_use_parentheses_in_expressions=true -sp_cleanup.on_save_use_additional_actions=true -sp_cleanup.organize_imports=true -sp_cleanup.qualify_static_field_accesses_with_declaring_class=false -sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_with_declaring_class=true -sp_cleanup.qualify_static_method_accesses_with_declaring_class=false -sp_cleanup.remove_private_constructors=true -sp_cleanup.remove_trailing_whitespaces=true -sp_cleanup.remove_trailing_whitespaces_all=true -sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=true -sp_cleanup.remove_unnecessary_nls_tags=true -sp_cleanup.remove_unused_imports=true -sp_cleanup.remove_unused_local_variables=true -sp_cleanup.remove_unused_private_fields=true -sp_cleanup.remove_unused_private_members=false -sp_cleanup.remove_unused_private_methods=true -sp_cleanup.remove_unused_private_types=true -sp_cleanup.sort_members=true -sp_cleanup.sort_members_all=false -sp_cleanup.use_blocks=true -sp_cleanup.use_blocks_only_for_return_and_throw=false -sp_cleanup.use_parentheses_in_expressions=true -sp_cleanup.use_this_for_non_static_field_access=true -sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true -sp_cleanup.use_this_for_non_static_method_access=true -sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/lib/devices/android/bootstrap/.settings/org.eclipse.m2e.core.prefs b/lib/devices/android/bootstrap/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1..00000000 --- a/lib/devices/android/bootstrap/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/lib/devices/android/bootstrap/README.md b/lib/devices/android/bootstrap/README.md deleted file mode 100644 index 60e27f67..00000000 --- a/lib/devices/android/bootstrap/README.md +++ /dev/null @@ -1,19 +0,0 @@ -Bootstrap Android -=== - -To install the Android Maven dependencies in your local environment, run the following: - -* Clone https://github.com/mosabua/maven-android-sdk-deployer into your workstation -* Set the ANDROID_HOME environment to the location of the Android SDK, eg. `export ANDROID_HOME=/Developer/adt-bundle-mac-x86_64-20130219/sdk/` -* Run `mvn install -P 4.4` from the `maven-android-sdk-deployer` directory. The build will fail if API 19 and some extra packages are not installed. -* Please install all sdk and api versions of android for building `maven-android-sdk-deployer`. - -You can then compile the bootstrap project by running - - mvn package -P 4.4 - -If mvn package fails, try deleting your ANDROID_HOME folder and downloading everything again. If it still doesn't work try: - - android-sdk/tools/android update sdk --no-ui --obsolete --force - -and then run `mvn clean ; mvn install` diff --git a/lib/devices/android/bootstrap/build.xml b/lib/devices/android/bootstrap/build.xml deleted file mode 100644 index a12a2678..00000000 --- a/lib/devices/android/bootstrap/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/devices/android/bootstrap/pom.xml b/lib/devices/android/bootstrap/pom.xml deleted file mode 100644 index c3b4b495..00000000 --- a/lib/devices/android/bootstrap/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - 4.0.0 - - io.appium.android - bootstrap - Maven project for the Appium Android Bootstrap - 1.0.0-SNAPSHOT - - - ${basedir}/src - - - - maven-compiler-plugin - - 1.6 - 1.6 - - - - maven-dependency-plugin - - - - - - - org.json - json - 20080701 - - - - android - android - 4.4.2_r3 - - - - android.test.uiautomator - uiautomator - 4.4.2_r3 - - - - junit - junit - 4.11 - - - - - diff --git a/lib/devices/android/bootstrap/src/com/android/uiautomator/common/UiWatchers.java b/lib/devices/android/bootstrap/src/com/android/uiautomator/common/UiWatchers.java deleted file mode 100644 index 50abf181..00000000 --- a/lib/devices/android/bootstrap/src/com/android/uiautomator/common/UiWatchers.java +++ /dev/null @@ -1,160 +0,0 @@ -// https://android.googlesource.com/platform/frameworks/testing/+/master/uiautomator_test_libraries/src/com/android/uiautomator/common/UiWatchers.java -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.uiautomator.common; - -import android.util.Log; -import com.android.uiautomator.core.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class UiWatchers { - private static final String LOG_TAG = UiWatchers.class.getSimpleName(); - private final List mErrors = new ArrayList(); - - /** - * We can use the UiDevice registerWatcher to register a small script to be - * executed when the framework is waiting for a control to appear. Waiting may - * be the cause of an unexpected dialog on the screen and it is the time when - * the framework runs the registered watchers. This is a sample watcher - * looking for ANR and crashes. it closes it and moves on. You should create - * your own watchers and handle error logging properly for your type of tests. - */ - public void registerAnrAndCrashWatchers() { - - UiDevice.getInstance().registerWatcher("ANR", new UiWatcher() { - @Override - public boolean checkForCondition() { - UiObject window = new UiObject(new UiSelector() - .className("com.android.server.am.AppNotRespondingDialog")); - String errorText = null; - if (window.exists()) { - try { - errorText = window.getText(); - } catch (UiObjectNotFoundException e) { - Log.e(LOG_TAG, "dialog gone?", e); - } - onAnrDetected(errorText); - postHandler(); - return true; // triggered - } - return false; // no trigger - } - }); - - // class names may have changed - UiDevice.getInstance().registerWatcher("ANR2", new UiWatcher() { - @Override - public boolean checkForCondition() { - UiObject window = new UiObject(new UiSelector().packageName("android") - .textContains("isn't responding.")); - if (window.exists()) { - String errorText = null; - try { - errorText = window.getText(); - } catch (UiObjectNotFoundException e) { - Log.e(LOG_TAG, "dialog gone?", e); - } - onAnrDetected(errorText); - postHandler(); - return true; // triggered - } - return false; // no trigger - } - }); - - UiDevice.getInstance().registerWatcher("CRASH", new UiWatcher() { - @Override - public boolean checkForCondition() { - UiObject window = new UiObject(new UiSelector() - .className("com.android.server.am.AppErrorDialog")); - if (window.exists()) { - String errorText = null; - try { - errorText = window.getText(); - } catch (UiObjectNotFoundException e) { - Log.e(LOG_TAG, "dialog gone?", e); - } - onCrashDetected(errorText); - postHandler(); - return true; // triggered - } - return false; // no trigger - } - }); - - UiDevice.getInstance().registerWatcher("CRASH2", new UiWatcher() { - @Override - public boolean checkForCondition() { - UiObject window = new UiObject(new UiSelector().packageName("android") - .textContains("has stopped")); - if (window.exists()) { - String errorText = null; - try { - errorText = window.getText(); - } catch (UiObjectNotFoundException e) { - Log.e(LOG_TAG, "dialog gone?", e); - } - onCrashDetected(errorText); - postHandler(); - return true; // triggered - } - return false; // no trigger - } - }); - - Log.i(LOG_TAG, "Registed GUI Exception watchers"); - } - - public void onAnrDetected(String errorText) { - mErrors.add(errorText); - } - - public void onCrashDetected(String errorText) { - mErrors.add(errorText); - } - - public void reset() { - mErrors.clear(); - } - - public List getErrors() { - return Collections.unmodifiableList(mErrors); - } - - /** - * Current implementation ignores the exception and continues. - */ - public void postHandler() { - // TODO: Add custom error logging here - - String formatedOutput = String.format("UI Exception Message: %-20s\n", - UiDevice.getInstance().getCurrentPackageName()); - Log.e(LOG_TAG, formatedOutput); - - UiObject buttonOK = new UiObject(new UiSelector().text("OK").enabled(true)); - // sometimes it takes a while for the OK button to become enabled - buttonOK.waitForExists(5000); - try { - buttonOK.click(); - } catch (UiObjectNotFoundException e) { - Log.e(LOG_TAG, "Exception", e); - } - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommand.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommand.java deleted file mode 100644 index 5010b16d..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommand.java +++ /dev/null @@ -1,118 +0,0 @@ -package io.appium.android.bootstrap; - -import io.appium.android.bootstrap.exceptions.CommandTypeException; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Hashtable; -import java.util.Iterator; - -/** - * This proxy embodies the command that the handlers execute. - * - */ -public class AndroidCommand { - - JSONObject json; - AndroidCommandType cmdType; - - public AndroidCommand(final String jsonStr) throws JSONException, - CommandTypeException { - json = new JSONObject(jsonStr); - setType(json.getString("cmd")); - } - - /** - * Return the action string for this command. - * - * @return String - * @throws JSONException - */ - public String action() throws JSONException { - if (isElementCommand()) { - return json.getString("action").substring(8); - } - return json.getString("action"); - } - - public AndroidCommandType commandType() { - return cmdType; - } - - /** - * Get the {@link AndroidElement destEl} this command is to operate on (must - * provide the "desElId" parameter). - * - * @return {@link AndroidElement} - * @throws JSONException - */ - public AndroidElement getDestElement() throws JSONException { - String destElId = (String) params().get("destElId"); - return AndroidElementsHash.getInstance().getElement(destElId); - } - - /** - * Get the {@link AndroidElement element} this command is to operate on (must - * provide the "elementId" parameter). - * - * @return {@link AndroidElement} - * @throws JSONException - */ - public AndroidElement getElement() throws JSONException { - String elId = (String) params().get("elementId"); - return AndroidElementsHash.getInstance().getElement(elId); - } - - /** - * Returns whether or not this command is on an element (true) or device - * (false). - * - * @return boolean - */ - public boolean isElementCommand() { - if (cmdType == AndroidCommandType.ACTION) { - try { - return json.getString("action").startsWith("element:"); - } catch (final JSONException e) { - return false; - } - } - return false; - } - - /** - * Return a hash table of name, value pairs as arguments to the handlers - * executing this command. - * - * @return Hashtable - * @throws JSONException - */ - public Hashtable params() throws JSONException { - final JSONObject paramsObj = json.getJSONObject("params"); - final Hashtable newParams = new Hashtable(); - final Iterator keys = paramsObj.keys(); - - while (keys.hasNext()) { - final String param = (String) keys.next(); - newParams.put(param, paramsObj.get(param)); - } - return newParams; - } - - /** - * Set the command {@link AndroidCommandType type} - * - * @param stringType - * The string of the type (i.e. "shutdown" or "action") - * @throws CommandTypeException - */ - public void setType(final String stringType) throws CommandTypeException { - if (stringType.equals("shutdown")) { - cmdType = AndroidCommandType.SHUTDOWN; - } else if (stringType.equals("action")) { - cmdType = AndroidCommandType.ACTION; - } else { - throw new CommandTypeException("Got bad command type: " + stringType); - } - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java deleted file mode 100644 index b494c3a1..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.appium.android.bootstrap; - -import io.appium.android.bootstrap.handler.*; -import org.json.JSONException; - -import java.util.HashMap; - -/** - * Command execution dispatch class. This class relays commands to the various - * handlers. - * - */ -class AndroidCommandExecutor { - - private static HashMap map = new HashMap(); - - static { - map.put("waitForIdle", new WaitForIdle()); - map.put("clear", new Clear()); - map.put("orientation", new Orientation()); - map.put("swipe", new Swipe()); - map.put("flick", new Flick()); - map.put("drag", new Drag()); - map.put("pinch", new Pinch()); - map.put("click", new Click()); - map.put("touchLongClick", new TouchLongClick()); - map.put("touchDown", new TouchDown()); - map.put("touchUp", new TouchUp()); - map.put("touchMove", new TouchMove()); - map.put("getText", new GetText()); - map.put("setText", new SetText()); - map.put("getName", new GetName()); - map.put("getAttribute", new GetAttribute()); - map.put("getDeviceSize", new GetDeviceSize()); - map.put("scrollTo", new ScrollTo()); - map.put("find", new Find()); - map.put("getLocation", new GetLocation()); - map.put("getSize", new GetSize()); - map.put("wake", new Wake()); - map.put("pressBack", new PressBack()); - map.put("pressKeyCode", new PressKeyCode()); - map.put("longPressKeyCode", new LongPressKeyCode()); - map.put("takeScreenshot", new TakeScreenshot()); - map.put("updateStrings", new UpdateStrings()); - map.put("getDataDir", new GetDataDir()); - map.put("performMultiPointerGesture", new MultiPointerGesture()); - map.put("openNotification", new OpenNotification()); - map.put("source", new Source()); - map.put("compressedLayoutHierarchy", new CompressedLayoutHierarchy()); - } - - /** - * Gets the handler out of the map, and executes the command. - * - * @param command - * The {@link AndroidCommand} - * @return {@link AndroidCommandResult} - */ - public AndroidCommandResult execute(final AndroidCommand command) { - try { - Logger.debug("Got command action: " + command.action()); - - if (map.containsKey(command.action())) { - return map.get(command.action()).execute(command); - } else { - return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, - "Unknown command: " + command.action()); - } - } catch (final JSONException e) { - Logger.error("Could not decode action/params of command"); - return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR, - "Could not decode action/params of command, please check format!"); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandResult.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandResult.java deleted file mode 100644 index f404df17..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandResult.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.appium.android.bootstrap; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Results class that converts status to JSON messages. - * - */ -public class AndroidCommandResult { - - JSONObject json; - - public AndroidCommandResult(final WDStatus status) { - try { - json = new JSONObject(); - json.put("status", status.code()); - json.put("value", status.message()); - } catch (final JSONException e) { - Logger.error("Couldn't create android command result!"); - } - } - - public AndroidCommandResult(final WDStatus status, final JSONObject val) { - json = new JSONObject(); - try { - json.put("status", status.code()); - json.put("value", val); - } catch (final JSONException e) { - Logger.error("Couldn't create android command result!"); - } - } - - public AndroidCommandResult(final WDStatus status, final Object val) { - json = new JSONObject(); - try { - json.put("status", status.code()); - json.put("value", val); - } catch (final JSONException e) { - Logger.error("Couldn't create android command result!"); - } - } - - public AndroidCommandResult(final WDStatus status, final String val) { - try { - json = new JSONObject(); - json.put("status", status.code()); - json.put("value", val); - } catch (final JSONException e) { - Logger.error("Couldn't create android command result!"); - } - } - - @Override - public String toString() { - return json.toString(); - } - -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandType.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandType.java deleted file mode 100644 index 24008617..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandType.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.appium.android.bootstrap; - -/** - * Enumeration for all the command types. - * - */ -public enum AndroidCommandType { - ACTION, SHUTDOWN -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java deleted file mode 100644 index 4e214c2b..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java +++ /dev/null @@ -1,261 +0,0 @@ -package io.appium.android.bootstrap; - -import android.graphics.Rect; -import android.view.MotionEvent.PointerCoords; -import android.view.accessibility.AccessibilityNodeInfo; -import com.android.uiautomator.core.Configurator; -import com.android.uiautomator.core.UiObject; -import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.exceptions.NoAttributeFoundException; -import io.appium.android.bootstrap.utils.Point; -import io.appium.android.bootstrap.utils.UnicodeEncoder; - -import static io.appium.android.bootstrap.utils.ReflectionUtils.invoke; -import static io.appium.android.bootstrap.utils.ReflectionUtils.method; -import static io.appium.android.bootstrap.utils.API.API_18; - -/** - * Proxy class for UiObject. - * - */ -public class AndroidElement { - - private final UiObject el; - private String id; - - AndroidElement(final String id, final UiObject el) { - this.el = el; - this.id = id; - } - - public AndroidElement(final UiObject uiObj) { - el = uiObj; - } - - public void clearText() throws UiObjectNotFoundException { - el.clearTextField(); - } - - public boolean click() throws UiObjectNotFoundException { - return el.click(); - } - - public boolean exists() { - return el.exists(); - } - - public boolean dragTo(final int destX, final int destY, final int steps) - throws UiObjectNotFoundException { - if (API_18) { - return el.dragTo(destX, destY, steps); - } else { - Logger.error("Device does not support API >= 18!"); - return false; - } - } - - public boolean dragTo(final UiObject destObj, final int steps) - throws UiObjectNotFoundException { - if (API_18) { - return el.dragTo(destObj, steps); - } else { - Logger.error("Device does not support API >= 18!"); - 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); - } - - public boolean getBoolAttribute(final String attr) - throws UiObjectNotFoundException, NoAttributeFoundException { - boolean res; - if (attr.equals("enabled")) { - res = el.isEnabled(); - } else if (attr.equals("checkable")) { - res = el.isCheckable(); - } else if (attr.equals("checked")) { - res = el.isChecked(); - } else if (attr.equals("clickable")) { - res = el.isClickable(); - } else if (attr.equals("focusable")) { - res = el.isFocusable(); - } else if (attr.equals("focused")) { - res = el.isFocused(); - } else if (attr.equals("longClickable")) { - res = el.isLongClickable(); - } else if (attr.equals("scrollable")) { - res = el.isScrollable(); - } else if (attr.equals("selected")) { - res = el.isSelected(); - } else if (attr.equals("displayed")) { - res = el.exists(); - } else { - throw new NoAttributeFoundException(attr); - } - return res; - } - - public Rect getBounds() throws UiObjectNotFoundException { - return el.getBounds(); - } - - public UiObject getChild(final UiSelector sel) - throws UiObjectNotFoundException { - return el.getChild(sel); - } - - public String getClassName() throws UiObjectNotFoundException { - if (API_18) { - return el.getClassName(); - } else { - Logger.error("Device does not support API >= 18!"); - return ""; - } - } - - public String getResourceId() throws UiObjectNotFoundException { - String resourceId = ""; - - if (!API_18) { - Logger.error("Device does not support API >= 18!"); - return resourceId; - } - - try { - /* - * Unfortunately UiObject does not implement a getResourceId method. - * There is currently no way to determine the resource-id of a given - * element represented by UiObject. Until this support is added to - * UiAutomater, we try to match the implementation pattern that is - * already used by UiObject for getting attributes using reflection. - * The returned string matches exactly what is displayed in the - * UiAutomater inspector. - */ - AccessibilityNodeInfo node = (AccessibilityNodeInfo) invoke( method(el.getClass(), "findAccessibilityNodeInfo", long.class), - el, Configurator.getInstance().getWaitForSelectorTimeout()); - - if (node == null) { - throw new UiObjectNotFoundException(el.getSelector().toString()); - } - - resourceId = node.getViewIdResourceName(); - } catch (final Exception e) { - Logger.error("Exception: " + e + " (" + e.getMessage() + ")"); - } - - return resourceId; - } - - public String getContentDesc() throws UiObjectNotFoundException { - return el.getContentDescription(); - } - - public String getId() { - return id; - } - - public String getStringAttribute(final String attr) - throws UiObjectNotFoundException, NoAttributeFoundException { - String res; - if (attr.equals("name")) { - res = getContentDesc(); - if (res.equals("")) { - res = getText(); - } - } else if (attr.equals("text")) { - res = getText(); - } else if (attr.equals("className")) { - res = getClassName(); - } else if (attr.equals("resourceId")) { - res = getResourceId(); - } else { - throw new NoAttributeFoundException(attr); - } - return res; - } - - public String getText() throws UiObjectNotFoundException { - return el.getText(); - } - - public UiObject getUiObject() { - return el; - } - - public Rect getVisibleBounds() throws UiObjectNotFoundException { - return el.getVisibleBounds(); - } - - public boolean longClick() throws UiObjectNotFoundException { - return el.longClick(); - } - - public boolean pinchIn(final int percent, final int steps) - throws UiObjectNotFoundException { - if (API_18) { - return el.pinchIn(percent, steps); - } else { - Logger.error("Device does not support API >= 18!"); - return false; - } - } - - public boolean pinchOut(final int percent, final int steps) - throws UiObjectNotFoundException { - if (API_18) { - return el.pinchOut(percent, steps); - } else { - Logger.error("Device does not support API >= 18!"); - return false; - } - } - - public void setId(final String id) { - this.id = id; - } - - public boolean setText(final String text) throws UiObjectNotFoundException { - return setText(text, false); - } - - public boolean setText(final String text, boolean unicodeKeyboard) - throws UiObjectNotFoundException { - if (unicodeKeyboard && UnicodeEncoder.needsEncoding(text)) { - Logger.debug("Sending Unicode text to element: " + text); - String encodedText = UnicodeEncoder.encode(text); - Logger.debug("Encoded text: " + encodedText); - return el.setText(encodedText); - } else { - Logger.debug("Sending plain text to element: " + text); - return el.setText(text); - } - } - - public boolean performMultiPointerGesture(PointerCoords[] ...touches) { - try { - if (API_18) { - // The compile-time SDK expects the wrong arguments, but the runtime - // version in the emulator is correct. So we cannot do: - // `return el.performMultiPointerGesture(touches);` - // Instead we need to use Reflection to do it all at runtime. - return (Boolean) invoke(method(el.getClass(), "performMultiPointerGesture", PointerCoords[][].class), - el, (Object)touches); - } else { - Logger.error("Device does not support API < 18!"); - return false; - } - } catch (final Exception e) { - Logger.error("Exception: " + e + " (" + e.getMessage() + ")"); - return false; - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElementsHash.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElementsHash.java deleted file mode 100644 index b1cdeaf6..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElementsHash.java +++ /dev/null @@ -1,162 +0,0 @@ -package io.appium.android.bootstrap; - -import com.android.uiautomator.core.UiObject; -import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.exceptions.ElementNotFoundException; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.regex.Pattern; - -/** - * A cache of elements that the app has seen. - * - */ -public class AndroidElementsHash { - - private static final Pattern endsWithInstancePattern = Pattern.compile(".*INSTANCE=\\d+]$"); - - public static AndroidElementsHash getInstance() { - if (AndroidElementsHash.instance == null) { - AndroidElementsHash.instance = new AndroidElementsHash(); - } - return AndroidElementsHash.instance; - } - - private final Hashtable elements; - private Integer counter; - - private static AndroidElementsHash instance; - - /** - * Constructor - */ - public AndroidElementsHash() { - counter = 0; - elements = new Hashtable(); - } - - /** - * @param element - * @return - */ - public AndroidElement addElement(final UiObject element) { - counter++; - final String key = counter.toString(); - final AndroidElement el = new AndroidElement(key, element); - elements.put(key, el); - return el; - } - - /** - * Return an element given an Id. - * - * @param key - * @return {@link AndroidElement} - */ - public AndroidElement getElement(final String key) { - return elements.get(key); - } - - /** - * Return an elements child given the key (context id), or uses the selector - * to get the element. - * - * @param sel - * @param key - * Element id. - * @return {@link AndroidElement} - * @throws ElementNotFoundException - */ - public AndroidElement getElement(final UiSelector sel, final String key) - throws ElementNotFoundException { - AndroidElement baseEl; - baseEl = elements.get(key); - UiObject el; - - if (baseEl == null) { - el = new UiObject(sel); - } else { - try { - el = baseEl.getChild(sel); - } catch (final UiObjectNotFoundException e) { - throw new ElementNotFoundException(); - } - } - - if (el.exists()) { - return addElement(el); - } else { - throw new ElementNotFoundException(); - } - } - - /** - * Same as {@link #getElement(UiSelector, String)} but for multiple elements - * at once. - * - * @param sel - * @param key - * @return ArrayList<{@link AndroidElement}> - * @throws UiObjectNotFoundException - */ - public ArrayList getElements(final UiSelector sel, - final String key) throws UiObjectNotFoundException { - boolean keepSearching = true; - final String selectorString = sel.toString(); - final boolean useIndex = selectorString.contains("CLASS_REGEX="); - final boolean endsWithInstance = endsWithInstancePattern.matcher(selectorString).matches(); - Logger.debug("getElements selector:" + selectorString); - final ArrayList elements = new ArrayList(); - - // If sel is UiSelector[CLASS=android.widget.Button, INSTANCE=0] - // then invoking instance with a non-0 argument will corrupt the selector. - // - // sel.instance(1) will transform the selector into: - // UiSelector[CLASS=android.widget.Button, INSTANCE=1] - // - // The selector now points to an entirely different element. - if (endsWithInstance) { - Logger.debug("Selector ends with instance."); - // There's exactly one element when using instance. - UiObject instanceObj = new UiObject(sel); - if (instanceObj != null && instanceObj.exists()) { - elements.add(addElement(instanceObj)); - } - return elements; - } - - UiObject lastFoundObj; - final AndroidElement baseEl = this.getElement(key); - - UiSelector tmp; - int counter = 0; - while (keepSearching) { - if (baseEl == null) { - Logger.debug("Element[" + key + "] is null: (" + counter + ")"); - - if (useIndex) { - Logger.debug(" using index..."); - tmp = sel.index(counter); - } else { - tmp = sel.instance(counter); - } - - Logger.debug("getElements tmp selector:" + tmp.toString()); - lastFoundObj = new UiObject(tmp); - } else { - Logger.debug("Element[" + key + "] is " + baseEl.getId() + ", counter: " - + counter); - lastFoundObj = baseEl.getChild(sel.instance(counter)); - } - counter++; - if (lastFoundObj != null && lastFoundObj.exists()) { - elements.add(addElement(lastFoundObj)); - } else { - keepSearching = false; - } - } - return elements; - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Bootstrap.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Bootstrap.java deleted file mode 100644 index 1d3c9ff5..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Bootstrap.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.appium.android.bootstrap; - -import com.android.uiautomator.testrunner.UiAutomatorTestCase; -import io.appium.android.bootstrap.exceptions.SocketServerException; -import io.appium.android.bootstrap.handler.Find; - -/** - * The Bootstrap class runs the socket server. - * - */ -public class Bootstrap extends UiAutomatorTestCase { - - public void testRunServer() { - Find.params = getParams(); - boolean disableAndroidWatchers = Boolean.parseBoolean(getParams().getString("disableAndroidWatchers")); - - SocketServer server; - try { - server = new SocketServer(4724); - server.listenForever(disableAndroidWatchers); - } catch (final SocketServerException e) { - Logger.error(e.getError()); - System.exit(1); - } - - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/CommandHandler.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/CommandHandler.java deleted file mode 100644 index f1ce9026..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/CommandHandler.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.appium.android.bootstrap; - -import android.graphics.Rect; -import com.android.uiautomator.core.UiDevice; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.utils.Point; -import org.json.JSONException; - -import java.util.ArrayList; - -/** - * Base class for all handlers. - * - */ -public abstract class CommandHandler { - - /** - * Abstract method that handlers must implement. - * - * @param command A {@link AndroidCommand} - * @return {@link AndroidCommandResult} - * @throws JSONException - */ - public abstract AndroidCommandResult execute(final AndroidCommand command) - throws JSONException; - - /** - * Returns a generic unknown error message along with your own message. - * - * @param msg - * @return {@link AndroidCommandResult} - */ - protected AndroidCommandResult getErrorResult(final String msg) { - return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, msg); - } - - /** - * Returns success along with the payload. - * - * @param value - * @return {@link AndroidCommandResult} - */ - protected AndroidCommandResult getSuccessResult(final Object value) { - return new AndroidCommandResult(WDStatus.SUCCESS, value); - } - -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Dynamic.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Dynamic.java deleted file mode 100644 index 2895a86e..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Dynamic.java +++ /dev/null @@ -1,211 +0,0 @@ -package io.appium.android.bootstrap; - -import com.android.uiautomator.core.UiSelector; -import org.json.JSONArray; -import org.json.JSONException; - -import java.util.ArrayList; -import java.util.List; - -// Constants from -// https://android.googlesource.com/platform/frameworks/testing/+/master/uiautomator/library/core-src/com/android/uiautomator/core/UiSelector.java -public class Dynamic { - // static final int SELECTOR_NIL = 0; // nothing. - /** text(String text) */ - private static final int SELECTOR_TEXT = 1; - /** textStartsWith(String text) */ - private static final int SELECTOR_START_TEXT = 2; - /** textContains(String text) */ - private static final int SELECTOR_CONTAINS_TEXT = 3; - /** className(String className), className(Class type) */ - private static final int SELECTOR_CLASS = 4; - /** description(String desc) */ - private static final int SELECTOR_DESCRIPTION = 5; - /** descriptionStartsWith(String desc) */ - private static final int SELECTOR_START_DESCRIPTION = 6; - /** descriptionContains(String desc) */ - private static final int SELECTOR_CONTAINS_DESCRIPTION = 7; - /** index(final int index) */ - private static final int SELECTOR_INDEX = 8; - /** instance(final int instance) */ - private static final int SELECTOR_INSTANCE = 9; - /** enabled(boolean val) */ - private static final int SELECTOR_ENABLED = 10; - /** focused(boolean val) */ - private static final int SELECTOR_FOCUSED = 11; - /** focusable(boolean val) */ - private static final int SELECTOR_FOCUSABLE = 12; - /** scrollable(boolean val) */ - private static final int SELECTOR_SCROLLABLE = 13; - /** clickable(boolean val) */ - private static final int SELECTOR_CLICKABLE = 14; - /** checked(boolean val) */ - private static final int SELECTOR_CHECKED = 15; - /** selected(boolean val) */ - private static final int SELECTOR_SELECTED = 16; - // static final int SELECTOR_ID = 17; // nothing. - /** packageName(String name) */ - private static final int SELECTOR_PACKAGE_NAME = 18; - // @formatter:off -// private static final int SELECTOR_CHILD = 19; // childSelector(UiSelector selector) -// private static final int SELECTOR_CONTAINER = 20; // containerSelector(UiSelector selector) -// private static final int SELECTOR_PATTERN = 21; // ! private ! patternSelector(UiSelector selector) -// private static final int SELECTOR_PARENT = 22; // fromParent(UiSelector selector) -// private static final int SELECTOR_COUNT = 23; // nothing. - // @formatter:on - /** longClickable(boolean val) */ - private static final int SELECTOR_LONG_CLICKABLE = 24; - /** textMatches(String regex) */ - private static final int SELECTOR_TEXT_REGEX = 25; - /** classNameMatches(String regex) */ - private static final int SELECTOR_CLASS_REGEX = 26; - /** descriptionMatches(String regex) */ - private static final int SELECTOR_DESCRIPTION_REGEX = 27; - /** packageNameMatches(String regex) */ - private static final int SELECTOR_PACKAGE_NAME_REGEX = 28; - /** resourceId(String id) */ - private static final int SELECTOR_RESOURCE_ID = 29; - /** checkable(boolean val) */ - private static final int SELECTOR_CHECKABLE = 30; - /** resourceIdMatches(String regex) */ - private static final int SELECTOR_RESOURCE_ID_REGEX = 31; - // start internal methods at 100 - /** - * Gets name (content desc) with a fall back to text if name is empty. - * - * getStringAttribute("name") - */ - private static final int GET_NAME = 100; - - public static String finalize(final AndroidElement result, final int finalizer) - throws Exception { - // Invoke the int 100+ method on the resulting element. - String value = ""; - switch (finalizer) { - case GET_NAME: - value = result.getStringAttribute("name"); - break; - default: - break; - } - - return value; - } - - public static List finalize( - final List elements, final int finalizer) - throws Exception { - final ArrayList results = new ArrayList(); - for (final AndroidElement e : elements) { - final String result = finalize(e, finalizer); - Logger.debug("Adding: " + result); - results.add(result); - } - return results; - } - - private UiSelector s = new UiSelector(); - - public UiSelector get(final JSONArray array) throws JSONException { - // Reset selector. - s = new UiSelector(); - // Example pair. - // Find everything containing the text sign. - // [ [3, 'sign'] ] - for (int a = 0; a < array.length(); a++) { - final JSONArray pair = array.getJSONArray(a); - final int int0 = pair.getInt(0); - if (int0 >= 100) { - // 100+ are finalizers only. - continue; - } - final Object param1 = pair.get(1); - Logger.debug("Updating " + int0 + ", " + param1); - update(int0, param1); - } - - return s; - } - - private void update(final int method, final Object param) { - switch (method) { - case SELECTOR_TEXT: - s = s.text((String) param); - break; - case SELECTOR_START_TEXT: - s = s.textStartsWith((String) param); - break; - case SELECTOR_CONTAINS_TEXT: - s = s.textContains((String) param); - break; - case SELECTOR_CLASS: - s = s.className((String) param); - break; - case SELECTOR_DESCRIPTION: - s = s.description((String) param); - break; - case SELECTOR_START_DESCRIPTION: - s = s.descriptionStartsWith((String) param); - break; - case SELECTOR_CONTAINS_DESCRIPTION: - s = s.descriptionContains((String) param); - break; - case SELECTOR_INDEX: - s = s.index((Integer) param); - break; - case SELECTOR_INSTANCE: - s = s.instance((Integer) param); - break; - case SELECTOR_ENABLED: - s = s.enabled((Boolean) param); - break; - case SELECTOR_FOCUSED: - s = s.focused((Boolean) param); - break; - case SELECTOR_FOCUSABLE: - s = s.focusable((Boolean) param); - break; - case SELECTOR_SCROLLABLE: - s = s.scrollable((Boolean) param); - break; - case SELECTOR_CLICKABLE: - s = s.clickable((Boolean) param); - break; - case SELECTOR_CHECKED: - s = s.checked((Boolean) param); - break; - case SELECTOR_SELECTED: - s = s.selected((Boolean) param); - break; - case SELECTOR_PACKAGE_NAME: - s = s.packageName((String) param); - break; - case SELECTOR_LONG_CLICKABLE: - s = s.longClickable((Boolean) param); - break; - case SELECTOR_TEXT_REGEX: - s = s.textMatches((String) param); - break; - case SELECTOR_CLASS_REGEX: - s = s.classNameMatches((String) param); - break; - case SELECTOR_DESCRIPTION_REGEX: - s = s.descriptionMatches((String) param); - break; - case SELECTOR_PACKAGE_NAME_REGEX: - s = s.packageNameMatches((String) param); - break; - case SELECTOR_RESOURCE_ID: - s = s.resourceId((String) param); - break; - case SELECTOR_CHECKABLE: - s = s.checkable((Boolean) param); - break; - case SELECTOR_RESOURCE_ID_REGEX: - s = s.resourceIdMatches((String) param); - break; - default: - break; - } - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Logger.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Logger.java deleted file mode 100644 index 162e21cc..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/Logger.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.appium.android.bootstrap; - -/** - * Log to standard out so that the Appium framework can pick it up. - * - */ -public class Logger { - - private static String prefix = "[APPIUM-UIAUTO]"; - private static String suffix = "[/APPIUM-UIAUTO]"; - - public static void debug(final String msg) { - System.out.println(Logger.prefix + " [debug] " + msg + Logger.suffix); - } - - public static void error(final String msg) { - System.out.println(Logger.prefix + " [error] " + msg + Logger.suffix); - } - - public static void info(final String msg) { - System.out.println(Logger.prefix + " [info] " + msg + Logger.suffix); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/OrientationEnum.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/OrientationEnum.java deleted file mode 100644 index c77593df..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/OrientationEnum.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.appium.android.bootstrap; - -/** - * An enumeration that mirrors {@link android.view.Surface}. - * - */ -public enum OrientationEnum { - ROTATION_0(0), ROTATION_90(1), ROTATION_180(2), ROTATION_270(3); - - public static OrientationEnum fromInteger(final int x) { - switch (x) { - case 0: - return ROTATION_0; - case 1: - return ROTATION_90; - case 2: - return ROTATION_180; - case 3: - return ROTATION_270; - } - return null; - } - - private final int value; - - private OrientationEnum(final int value) { - this.value = value; - } - - public int getValue() { - return value; - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/PositionHelper.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/PositionHelper.java deleted file mode 100644 index 6f2c43fd..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/PositionHelper.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.appium.android.bootstrap; - -import android.graphics.Rect; -import com.android.uiautomator.core.UiDevice; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.utils.Point; - -public abstract class PositionHelper { - - /** - * Given a position, it will return either the position based on percentage - * (by passing in a double between 0 and 1) or absolute position based on the - * coordinates entered. - * - * @param pointCoord The position to translate. - * @param length Length of side to use for percentage positions. - * @param offset Position offset. - * @return - */ - private static double translateCoordinate(double pointCoord, double length, double offset) { - double translatedCoord = 0; - - if (pointCoord == 0) { - translatedCoord = length * 0.5; - } else if (Math.abs(pointCoord) > 0 && Math.abs(pointCoord) < 1) { - translatedCoord = length * pointCoord; - } else { - translatedCoord = pointCoord; - } - - return translatedCoord + offset; - } - - /** - * Translates coordinates relative to an element rectangle into absolute coordinates. - * - * @param point A point in relative coordinates. - * @param displayRect The display rectangle to which the point is relative. - * @param offsets X and Y values by which to offset the point. These are typically - * the absolute coordinates of the display rectangle. - * @param shouldCheckBounds Throw if the translated point is outside displayRect? - * @return - * @throws UiObjectNotFoundException - * @throws InvalidCoordinatesException - */ - public static Point getAbsolutePosition(final Point point, final Rect displayRect, - final Point offsets, final boolean shouldCheckBounds) - throws UiObjectNotFoundException, InvalidCoordinatesException { - final Point absolutePosition = new Point(); - - absolutePosition.x = translateCoordinate(point.x, displayRect.width(), offsets.x); - absolutePosition.y = translateCoordinate(point.y, displayRect.height(), offsets.y); - - if (shouldCheckBounds && - !displayRect.contains(absolutePosition.x.intValue(), absolutePosition.y.intValue())) { - throw new InvalidCoordinatesException("Coordinate " + absolutePosition.toString() + - " is outside of element rect: " + displayRect.toShortString()); - } - - return absolutePosition; - } - - public static Point getDeviceAbsPos(final Point point) - throws UiObjectNotFoundException, InvalidCoordinatesException { - final UiDevice d = UiDevice.getInstance(); - final Rect displayRect = new Rect(0, 0, d.getDisplayWidth(), d.getDisplayHeight()); - - Logger.debug("Display bounds: " + displayRect.toShortString()); - - return getAbsolutePosition(point, displayRect, new Point(), true); - } - -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/SocketServer.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/SocketServer.java deleted file mode 100644 index 9865004c..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/SocketServer.java +++ /dev/null @@ -1,187 +0,0 @@ -package io.appium.android.bootstrap; - -import com.android.uiautomator.common.UiWatchers; -import io.appium.android.bootstrap.exceptions.CommandTypeException; -import io.appium.android.bootstrap.exceptions.SocketServerException; -import io.appium.android.bootstrap.handler.UpdateStrings; -import io.appium.android.bootstrap.utils.TheWatchers; -import org.json.JSONException; - -import java.io.*; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.Timer; -import java.util.TimerTask; -import java.util.NoSuchElementException; - -/** - * The SocketServer class listens on a specific port for commands from Appium, - * and then passes them on to the {@link AndroidCommandExecutor} class. It will - * continue to listen until the command is sent to exit. - */ -class SocketServer { - - ServerSocket server; - Socket client; - BufferedReader in; - BufferedWriter out; - boolean keepListening; - private final AndroidCommandExecutor executor; - private final TheWatchers watchers = TheWatchers.getInstance(); - private final Timer timer = new Timer("WatchTimer"); - - /** - * Constructor - * - * @param port - * @throws SocketServerException - */ - public SocketServer(final int port) throws SocketServerException { - keepListening = true; - executor = new AndroidCommandExecutor(); - try { - server = new ServerSocket(port); - Logger.debug("Socket opened on port " + port); - } catch (final IOException e) { - throw new SocketServerException( - "Could not start socket server listening on " + port); - } - - } - - /** - * Constructs an @{link AndroidCommand} and returns it. - * - * @param data - * @return @{link AndroidCommand} - * @throws JSONException - * @throws CommandTypeException - */ - private AndroidCommand getCommand(final String data) throws JSONException, - CommandTypeException { - return new AndroidCommand(data); - } - - private StringBuilder input = new StringBuilder(); - - /** - * When data is available on the socket, this method is called to run the - * command or throw an error if it can't. - * - * @throws SocketServerException - */ - private void handleClientData() throws SocketServerException { - try { - input.setLength(0); // clear - - String res; - int a; - // (char) -1 is not equal to -1. - // ready is checked to ensure the read call doesn't block. - while ((a = in.read()) != -1 && in.ready()) { - input.append((char) a); - } - String inputString = input.toString(); - Logger.debug("Got data from client: " + inputString); - try { - AndroidCommand cmd = getCommand(inputString); - Logger.debug("Got command of type " + cmd.commandType().toString()); - res = runCommand(cmd); - Logger.debug("Returning result: " + res); - } catch (final CommandTypeException e) { - res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage()) - .toString(); - } catch (final JSONException e) { - res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, - "Error running and parsing command").toString(); - } - out.write(res); - out.flush(); - } catch (final IOException e) { - throw new SocketServerException("Error processing data to/from socket (" - + e.toString() + ")"); - } - } - - /** - * Listens on the socket for data, and calls {@link #handleClientData()} when - * it's available. - * - * @throws SocketServerException - */ - public void listenForever(boolean disableAndroidWatchers) throws SocketServerException { - Logger.debug("Appium Socket Server Ready"); - UpdateStrings.loadStringsJson(); - if (disableAndroidWatchers) { - Logger.debug("Skipped registering crash watchers."); - } else { - dismissCrashAlerts(); - } - final TimerTask updateWatchers = new TimerTask() { - @Override - public void run() { - try { - watchers.check(); - } catch (final Exception e) { - } - } - }; - timer.scheduleAtFixedRate(updateWatchers, 100, 100); - - try { - client = server.accept(); - Logger.debug("Client connected"); - in = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); - out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8")); - while (keepListening) { - handleClientData(); - } - in.close(); - out.close(); - client.close(); - Logger.debug("Closed client connection"); - } catch (final IOException e) { - throw new SocketServerException("Error when client was trying to connect"); - } - } - - public void dismissCrashAlerts() { - try { - new UiWatchers().registerAnrAndCrashWatchers(); - Logger.debug("Registered crash watchers."); - } catch (Exception e) { - Logger.debug("Unable to register crash watchers."); - } - } - - /** - * When {@link #handleClientData()} has valid data, this method delegates the - * command. - * - * @param cmd - * AndroidCommand - * @return Result - */ - private String runCommand(final AndroidCommand cmd) { - AndroidCommandResult res; - if (cmd.commandType() == AndroidCommandType.SHUTDOWN) { - keepListening = false; - res = new AndroidCommandResult(WDStatus.SUCCESS, "OK, shutting down"); - } else if (cmd.commandType() == AndroidCommandType.ACTION) { - try { - res = executor.execute(cmd); - } catch (final NoSuchElementException e) { - res = new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); - } catch (final Exception e) { // Here we catch all possible exceptions and return a JSON Wire Protocol UnknownError - // This prevents exceptions from halting the bootstrap app - Logger.debug("Command returned error:" + e); - res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage()); - } - } else { - // this code should never be executed, here for future-proofing - res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, - "Unknown command type, could not execute!"); - } - return res.toString(); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/WDStatus.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/WDStatus.java deleted file mode 100644 index a5a7c888..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/WDStatus.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.appium.android.bootstrap; - -//@formatter:off -/** - * An enumeration of status codes and messages to be relayed back to the Appium - * server. - * - */ -public enum WDStatus { - SUCCESS (0, "The command executed successfully."), - NO_SUCH_DRIVER (6, "A session is either terminated or not started"), - NO_SUCH_ELEMENT (7, "An element could not be located on the page using the given search parameters."), - NO_SUCH_FRAME (8, "A request to switch to a frame could not be satisfied because the frame could not be found."), - UNKNOWN_COMMAND (9, "The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource."), - STALE_ELEMENT_REFERENCE (10, "An element command failed because the referenced element is no longer attached to the DOM."), - ELEMENT_NOT_VISIBLE (11, "An element command could not be completed because the element is not visible on the page."), - INVALID_ELEMENT_STATE (12, "An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element)."), - UNKNOWN_ERROR (13, "An unknown server-side error occurred while processing the command."), - ELEMENT_IS_NOT_SELECTABLE (15, "An attempt was made to select an element that cannot be selected."), - JAVASCRIPT_ERROR (17, "An error occurred while executing user supplied JavaScript."), - XPATH_LOOKUP_ERROR (19, "An error occurred while searching for an element by XPath."), - TIMEOUT (21, "An operation did not complete before its timeout expired."), - NO_SUCH_WINDOW (23, "A request to switch to a different window could not be satisfied because the window could not be found."), - INVALID_COOKIE_DOMAIN (24, "An illegal attempt was made to set a cookie under a different domain than the current page."), - UNABLE_TO_SET_COOKIE (25, "A request to set a cookie's value could not be satisfied."), - UNEXPECTED_ALERT_OPEN (26, "A modal dialog was open, blocking this operation"), - NO_ALERT_OPEN_ERROR (27, "An attempt was made to operate on a modal dialog when one was not open."), - SCRIPT_TIMEOUT (28, "A script did not complete before its timeout expired."), - INVALID_ELEMENT_COORDINATES (29, "The coordinates provided to an interactions operation are invalid."), - IME_NOT_AVAILABLE (30, "IME was not available."), - IME_ENGINE_ACTIVATION_FAILED (31, "An IME engine could not be started."), - INVALID_SELECTOR (32, "Argument was an invalid selector (e.g. XPath/CSS)."), - SESSION_NOT_CREATED_EXCEPTION (33, "A new session could not be created."), - MOVE_TARGET_OUT_OF_BOUNDS (34, "Target provided for a move action is out of bounds."), - JSON_DECODER_ERROR (35, "Could not decode action/params of command, please check format!"); - // formatter:on - private final int statusCode; - private final String statusMessage; - - private WDStatus(final int code) { - statusCode = code; - statusMessage = ""; - } - - private WDStatus(final int code, final String message) { - statusCode = code; - statusMessage = message; - } - - public int code() { - return statusCode; - } - - public String message() { - return statusMessage; - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/CommandTypeException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/CommandTypeException.java deleted file mode 100644 index c9c0e216..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/CommandTypeException.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - - -@SuppressWarnings("serial") -public class CommandTypeException extends Exception { - /** - * Exception for command type errors. - * - * @param msg - * A descriptive message describing the error. - */ - public CommandTypeException(final String msg) { - super(msg); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/ElementNotFoundException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/ElementNotFoundException.java deleted file mode 100644 index f215b94c..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/ElementNotFoundException.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -/** - * An exception thrown when the element can not be found. - * - */ - -@SuppressWarnings("serial") -public class ElementNotFoundException extends Exception { - final static String error = "Could not find an element using supplied strategy. "; - - public ElementNotFoundException() { - super(error); - } - - public ElementNotFoundException(final String extra) { - super(error + extra); - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidCoordinatesException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidCoordinatesException.java deleted file mode 100644 index d1b01466..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidCoordinatesException.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -@SuppressWarnings("serial") -public class InvalidCoordinatesException extends Exception { - /** - * An exception that is thrown when an invalid coordinate is used. - * - * @param msg - * A descriptive message describing the error. - */ - public InvalidCoordinatesException(final String msg) { - super(msg); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidSelectorException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidSelectorException.java deleted file mode 100644 index 629a4001..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidSelectorException.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -/** - * If an invalid element selector is encountered - */ -public class InvalidSelectorException extends Throwable { - public InvalidSelectorException(String message) { super(message); } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidStrategyException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidStrategyException.java deleted file mode 100644 index f0feba23..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/InvalidStrategyException.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -import io.appium.android.bootstrap.selector.Strategy; - -@SuppressWarnings("serial") -public class InvalidStrategyException extends Exception { - /** - * An exception that is thrown when an invalid strategy is used. - * - * @param msg - * A descriptive message describing the error. - * @see {@link Strategy} - */ - public InvalidStrategyException(final String msg) { - super(msg); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/NoAttributeFoundException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/NoAttributeFoundException.java deleted file mode 100644 index 8a8abb40..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/NoAttributeFoundException.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -@SuppressWarnings("serial") -public class NoAttributeFoundException extends Exception { - /** - * This exception is thrown when the element doesn't have the attribute searched - * for. - * - * @param attr - * The attribute searched for. - */ - public NoAttributeFoundException(final String attr) { - super("This element does not have the '" + attr + "' attribute"); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/PairCreationException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/PairCreationException.java deleted file mode 100644 index a8f4c690..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/PairCreationException.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -/** - * For trying to create a ClassInstancePair and something goes wrong. - */ -public class PairCreationException extends Throwable { - public PairCreationException(String msg) { super(msg); } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/SocketServerException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/SocketServerException.java deleted file mode 100644 index e857f014..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/SocketServerException.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -@SuppressWarnings("serial") -public class SocketServerException extends Exception { - - String reason; - - /** - * Exception for socket errors. - * - * @param msg - * A descriptive message describing the error. - */ - public SocketServerException(final String msg) { - super(msg); - reason = msg; - } - - public String getError() { - return reason; - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UiSelectorSyntaxException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UiSelectorSyntaxException.java deleted file mode 100644 index fa177d03..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UiSelectorSyntaxException.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -import io.appium.android.bootstrap.utils.UiSelectorParser; - -@SuppressWarnings("serial") -public class UiSelectorSyntaxException extends Exception { - - /** - * An exception involving an {@link UiSelectorParser}. - * - * @param msg - * A descriptive message describing the error. - */ - public UiSelectorSyntaxException(final String msg) { - super(msg); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UnallowedTagNameException.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UnallowedTagNameException.java deleted file mode 100644 index 18082c0a..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/exceptions/UnallowedTagNameException.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.appium.android.bootstrap.exceptions; - -@SuppressWarnings("serial") -public class UnallowedTagNameException extends Exception { - /** - * This exception is thrown when the tag name is not supported - * - * @param tag - * The tag that was searched for. - */ - public UnallowedTagNameException(final String tag) { - super("Tag name '" + tag + "' is not supported in Android"); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Clear.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Clear.java deleted file mode 100644 index 1049a196..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Clear.java +++ /dev/null @@ -1,179 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.graphics.Rect; -import android.os.SystemClock; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import com.android.uiautomator.core.UiObject; -import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.AndroidElement; -import io.appium.android.bootstrap.CommandHandler; -import io.appium.android.bootstrap.Logger; -import io.appium.android.bootstrap.WDStatus; -import io.appium.uiautomator.core.InteractionController; -import io.appium.uiautomator.core.UiAutomatorBridge; -import org.json.JSONException; - -import java.lang.reflect.InvocationTargetException; - -/** - * This handler is used to clear elements in the Android UI. - * - * Based on the element Id, clear that element. - * - * UiAutomator method clearText is flaky hence overriding it with custom implementation. - */ -public class Clear extends CommandHandler { - - /* - * Trying to select entire text with correctLongClick and increasing time intervals. - * Checking if element still has text in them and and if true falling back on UiAutomator clearText - * - * @param command The {@link AndroidCommand} - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - if (command.isElementCommand()) { - try { - final AndroidElement el = command.getElement(); - - // first, try to do native clearing - Logger.debug("Attempting to clear using UiObject.clearText()."); - el.clearText(); - if (el.getText().isEmpty()) { - return getSuccessResult(true); - } - - // see if there is hint text - if (hasHintText(el)) { - Logger.debug("Text remains after clearing, " - + "but it appears to be hint text."); - return getSuccessResult(true); - } - - // next try to select everything and delete - Logger.debug("Clearing text not successful. Attempting to clear " + - "by selecting all and deleting."); - if (selectAndDelete(el)) { - return getSuccessResult(true); - } - - // see if there is hint text - if (hasHintText(el)) { - Logger.debug("Text remains after clearing, " - + "but it appears to be hint text."); - return getSuccessResult(true); - } - - // finally try to send delete keys - Logger.debug("Clearing text not successful. Attempting to clear " + - "by sending delete keys."); - if (sendDeleteKeys(el)) { - return getSuccessResult(true); - } - - if (!el.getText().isEmpty()) { - // either there was a failure, or there is hint text - if (hasHintText(el)) { - Logger.debug("Text remains after clearing, " + - "but it appears to be hint text."); - return getSuccessResult(true); - } else if (!el.getText().isEmpty()) { - Logger.debug("Exhausted all means to clear text but '" + - el.getText() + "' remains."); - return getErrorResult("Clear text not successful."); - } - } - return getSuccessResult(true); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } catch (final Exception e) { // handle NullPointerException - return getErrorResult("Unknown error clearing text"); - } - } - return getErrorResult("Unknown error"); - } - - private boolean selectAndDelete(AndroidElement el) - throws UiObjectNotFoundException, IllegalAccessException, - InvocationTargetException, NoSuchMethodException { - Rect rect = el.getVisibleBounds(); - // Trying to select entire text. - TouchLongClick.correctLongClick(rect.left + 20, rect.centerY(), 2000); - UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all")); - if (selectAll.waitForExists(2000)) { - selectAll.click(); - } - // wait for the selection - SystemClock.sleep(500); - // delete it - UiAutomatorBridge.getInstance().getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0); - - return el.getText().isEmpty(); - } - - private boolean sendDeleteKeys(AndroidElement el) - throws UiObjectNotFoundException, IllegalAccessException, - InvocationTargetException, NoSuchMethodException { - String tempTextHolder = ""; - - // Preventing infinite while loop. - while (!el.getText().isEmpty() && !tempTextHolder.equalsIgnoreCase(el.getText())) { - // Trying send delete keys after clicking in text box. - el.click(); - // Sending delete keys asynchronously, both forward and backward - for (int key : new int[] { KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_FORWARD_DEL }) { - tempTextHolder = el.getText(); - final int length = tempTextHolder.length(); - final long eventTime = SystemClock.uptimeMillis(); - KeyEvent deleteEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, - key, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, - InputDevice.SOURCE_KEYBOARD); - for (int count = 0; count < length; count++) { - UiAutomatorBridge.getInstance().injectInputEvent(deleteEvent, false); - } - } - } - - return el.getText().isEmpty(); - } - - private boolean hasHintText(AndroidElement el) - throws UiObjectNotFoundException, IllegalAccessException, - InvocationTargetException, NoSuchMethodException { - // to test if the remaining text is hint text, try sending a single - // delete key and testing if there is any change. - // ignore the off-chance that the delete silently fails and we get a false - // positive. - String currText = el.getText(); - - try { - if (!el.getBoolAttribute("focused")) { - Logger.debug("Could not check for hint text because the element is not focused!"); - return false; - } - } catch (final Exception e) { - Logger.debug("Could not check for hint text: " + e.getMessage()); - return false; - } - - InteractionController interactionController = UiAutomatorBridge.getInstance().getInteractionController(); - interactionController.sendKey(KeyEvent.KEYCODE_DEL, 0); - interactionController.sendKey(KeyEvent.KEYCODE_FORWARD_DEL, 0); - - return currText.equals(el.getText()); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Click.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Click.java deleted file mode 100644 index 60bdc95c..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Click.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.utils.Point; -import org.json.JSONException; - -import java.util.ArrayList; -import java.util.Hashtable; - -/** - * This handler is used to click elements in the Android UI. - * - * Based on the element Id, click that element. - * - */ -public class Click extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - if (command.isElementCommand()) { - try { - final AndroidElement el = command.getElement(); - el.click(); - return getSuccessResult(true); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } catch (final Exception e) { // handle NullPointerException - return getErrorResult("Unknown error"); - } - } else { - final Hashtable params = command.params(); - Point coords = new Point(Double.parseDouble(params.get("x").toString()), - Double.parseDouble(params.get("y").toString()) ); - - try { - coords = PositionHelper.getDeviceAbsPos(coords); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } catch (final InvalidCoordinatesException e) { - return new AndroidCommandResult(WDStatus.INVALID_ELEMENT_COORDINATES, - e.getMessage()); - } - - final boolean res = UiDevice.getInstance().click(coords.x.intValue(), coords.y.intValue()); - return getSuccessResult(res); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/CompressedLayoutHierarchy.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/CompressedLayoutHierarchy.java deleted file mode 100644 index aa35c713..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/CompressedLayoutHierarchy.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; -import io.appium.android.bootstrap.utils.NotImportantViews; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * Calls the uiautomator setCompressedLayoutHierarchy() function. If set to true, ignores some views during all Accessibility operations. - */ -public class CompressedLayoutHierarchy extends CommandHandler { - @Override - public AndroidCommandResult execute(AndroidCommand command) throws JSONException { - - boolean compressLayout; - - try { - final Hashtable params = command.params(); - compressLayout = (Boolean) params.get("compressLayout"); - NotImportantViews.discard(compressLayout); - } catch (ClassCastException e) { - return getErrorResult("must supply a 'compressLayout' boolean parameter"); - } catch (Exception e) { - return getErrorResult("error setting compressLayoutHierarchy " + e.getMessage()); - } - - return getSuccessResult(compressLayout); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Drag.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Drag.java deleted file mode 100644 index 78eb49c7..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Drag.java +++ /dev/null @@ -1,148 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.utils.Point; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Hashtable; - -/** - * This handler is used to drag in the Android UI. - * - */ -public class Drag extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - - private static class DragArguments { - - public AndroidElement el; - public AndroidElement destEl; - public final Point start; - public final Point end; - public final Integer steps; - - public DragArguments(final AndroidCommand command) throws JSONException { - - final Hashtable params = command.params(); - - try { - if (params.get("elementId") != JSONObject.NULL) { - el = command.getElement(); - } - } catch (final Exception e) { - el = null; - } - - try { - if (params.get("destElId") != JSONObject.NULL) { - destEl = command.getDestElement(); - } - } catch (final Exception e) { - destEl = null; - } - start = new Point(params.get("startX"), params.get("startY")); - end = new Point(params.get("endX"), params.get("endY")); - steps = (Integer) params.get("steps"); - } - } - - private AndroidCommandResult drag(final DragArguments dragArgs) { - Point absStartPos = new Point(); - Point absEndPos = new Point(); - final UiDevice device = UiDevice.getInstance(); - - try { - absStartPos = PositionHelper.getDeviceAbsPos(dragArgs.start); - absEndPos = PositionHelper.getDeviceAbsPos(dragArgs.end); - } catch (final InvalidCoordinatesException e) { - return getErrorResult(e.getMessage()); - } catch (final UiObjectNotFoundException e) { - return getErrorResult(e.getMessage()); - } - - Logger.debug("Dragging from " + absStartPos.toString() + " to " - + absEndPos.toString() + " with steps: " + dragArgs.steps.toString()); - final boolean rv = device.drag(absStartPos.x.intValue(), - absStartPos.y.intValue(), absEndPos.x.intValue(), - absEndPos.y.intValue(), dragArgs.steps); - if (!rv) { - return getErrorResult("Drag did not complete successfully"); - } - return getSuccessResult(rv); - } - - private AndroidCommandResult dragElement(final DragArguments dragArgs) { - Point absEndPos = new Point(); - - if (dragArgs.destEl == null) { - try { - absEndPos = PositionHelper.getDeviceAbsPos(dragArgs.end); - } catch (final InvalidCoordinatesException e) { - return getErrorResult(e.getMessage()); - } catch (final UiObjectNotFoundException e) { - return getErrorResult(e.getMessage()); - } - - Logger.debug("Dragging the element with id " + dragArgs.el.getId() - + " to " + absEndPos.toString() + " with steps: " - + dragArgs.steps.toString()); - try { - final boolean rv = dragArgs.el.dragTo(absEndPos.x.intValue(), - absEndPos.y.intValue(), dragArgs.steps); - if (!rv) { - return getErrorResult("Drag did not complete successfully"); - } else { - return getSuccessResult(rv); - } - } catch (final UiObjectNotFoundException e) { - return getErrorResult("Drag did not complete successfully" - + e.getMessage()); - } - } else { - Logger.debug("Dragging the element with id " + dragArgs.el.getId() - + " to destination element with id " + dragArgs.destEl.getId() - + " with steps: " + dragArgs.steps); - try { - final boolean rv = dragArgs.el.dragTo(dragArgs.destEl.getUiObject(), - dragArgs.steps); - if (!rv) { - return getErrorResult("Drag did not complete successfully"); - } else { - return getSuccessResult(rv); - } - } catch (final UiObjectNotFoundException e) { - return getErrorResult("Drag did not complete successfully" - + e.getMessage()); - } - } - - } - - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - // DragArguments is created on each execute which prevents leaking state - // across executions. - final DragArguments dragArgs = new DragArguments(command); - - if (command.isElementCommand()) { - return dragElement(dragArgs); - } else { - return drag(dragArgs); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java deleted file mode 100644 index 15ca38a8..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java +++ /dev/null @@ -1,407 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiSelector; -import android.os.Bundle; -import static io.appium.android.bootstrap.utils.API.API_18; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.AndroidElement; -import io.appium.android.bootstrap.AndroidElementsHash; -import io.appium.android.bootstrap.CommandHandler; -import io.appium.android.bootstrap.Logger; -import io.appium.android.bootstrap.WDStatus; -import io.appium.android.bootstrap.exceptions.ElementNotFoundException; -import io.appium.android.bootstrap.exceptions.InvalidSelectorException; -import io.appium.android.bootstrap.exceptions.InvalidStrategyException; -import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException; -import io.appium.android.bootstrap.selector.Strategy; -import io.appium.android.bootstrap.utils.ClassInstancePair; -import io.appium.android.bootstrap.utils.ElementHelpers; -import io.appium.android.bootstrap.utils.ReflectionUtils; -import io.appium.android.bootstrap.utils.UiAutomatorParser; -import io.appium.android.bootstrap.utils.XMLHierarchy; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; -import java.util.regex.Pattern; - -import javax.xml.parsers.ParserConfigurationException; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * This handler is used to find elements in the Android UI. - *

    - * Based on which {@link Strategy}, {@link UiSelector}, and optionally the - * contextId, the element Id or Ids are returned to the user. - */ -public class Find extends CommandHandler { - // These variables are expected to persist across executions. - AndroidElementsHash elements = AndroidElementsHash.getInstance(); - static JSONObject apkStrings = null; - public static Bundle params = null; - UiAutomatorParser uiAutomatorParser = new UiAutomatorParser(); - /** - * java_package : type / name - * - * com.example.Test:id/enter - * - * ^[a-zA-Z_] - Java package must start with letter or underscore - * [a-zA-Z0-9\._]* - Java package may contain letters, numbers, periods and - * underscores : - : ends the package and starts the type [^\/]+ - type is - * made up of at least one non-/ characters \\/ - / ends the type and starts - * the name [\S]+$ - the name contains at least one non-space character and - * then the line is ended - */ - static final Pattern resourceIdRegex = Pattern - .compile("^[a-zA-Z_][a-zA-Z0-9\\._]*:[^\\/]+\\/[\\S]+$"); - - /** - * Get a JSONArray to represent a collection of AndroidElements - * - * @param els - * collection of AndroidElement objects - * @return elements in the format which appium server returns - * @throws JSONException - */ - private JSONArray elementsToJSONArray(final List els) - throws JSONException { - final JSONArray resArray = new JSONArray(); - for (final AndroidElement el : els) { - resArray.put(ElementHelpers.toJSON(el)); - } - return resArray; - } - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - return execute(command, false); - } - - /** - * execute implementation. - * - * @see io.appium.android.bootstrap.handler.Find#execute(io.appium.android. - * bootstrap.AndroidCommand) - * - * @param command - * The {@link AndroidCommand} used for this handler. - * - * @param isRetry - * Is this invocation a second attempt? - * - * @return {@link AndroidCommandResult} - * @throws JSONException - */ - private AndroidCommandResult execute(final AndroidCommand command, - final boolean isRetry) throws JSONException { - final Hashtable params = command.params(); - - // only makes sense on a device - final Strategy strategy; - try { - strategy = Strategy.fromString((String) params.get("strategy")); - } catch (final InvalidStrategyException e) { - return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage()); - } - - final String contextId = (String) params.get("context"); - final String text = (String) params.get("selector"); - final boolean multiple = (Boolean) params.get("multiple"); - - Logger.debug("Finding " + text + " using " + strategy.toString() - + " with the contextId: " + contextId + " multiple: " + multiple); - boolean found = false; - try { - Object result = null; - final List selectors = getSelectors(strategy, text, multiple); - if (!multiple) { - for (int i = 0; i < selectors.size() && !found; i++) { - try { - Logger.debug("Using: " + selectors.get(i).toString()); - result = fetchElement(selectors.get(i), contextId); - found = result != null; - } catch (final ElementNotFoundException ignored) { - } - } - } else { - List foundElements = new ArrayList(); - for (final UiSelector sel : selectors) { - // With multiple selectors, we expect that some elements may not - // exist. - try { - Logger.debug("Using: " + sel.toString()); - final List elementsFromSelector = fetchElements( - sel, contextId); - foundElements.addAll(elementsFromSelector); - } catch (final UiObjectNotFoundException ignored) { - } - } - if (strategy == Strategy.ANDROID_UIAUTOMATOR) { - foundElements = ElementHelpers.dedupe(foundElements); - } - found = foundElements.size() > 0; - result = elementsToJSONArray(foundElements); - } - - if (!found) { - if (!isRetry) { - Logger - .debug("Failed to locate element. Clearing Accessibility cache and retrying."); - // some control updates fail to trigger AccessibilityEvents, resulting - // in stale AccessibilityNodeInfo instances. In these cases, UIAutomator - // will fail to locate visible elements. As a work-around, force clear - // the AccessibilityInteractionClient's cache and search again. This - // technique also appears to make Appium's searches conclude more quickly. - // See Appium issue #4200 https://github.com/appium/appium/issues/4200 - if (ReflectionUtils.clearAccessibilityCache()) { - return execute(command, true); - } - } - // JSONWP spec does not return NoSuchElement - if (!multiple) { - // If there are no results and we've already retried, return an error. - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - "No element found"); - } - } - - return getSuccessResult(result); - } catch (final InvalidStrategyException e) { - return getErrorResult(e.getMessage()); - } catch (final UiSelectorSyntaxException e) { - return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage()); - } catch (final ElementNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); - } catch (final ParserConfigurationException e) { - return getErrorResult("Error parsing xml hierarchy dump: " - + e.getMessage()); - } catch (final InvalidSelectorException e) { - return new AndroidCommandResult(WDStatus.INVALID_SELECTOR, e.getMessage()); - } - } - - /** - * Get the element from the {@link AndroidElementsHash} and return the element - * id using JSON. - * - * @param sel - * A UiSelector that targets the element to fetch. - * @param contextId - * The Id of the element used for the context. - * @return JSONObject - * @throws JSONException - * @throws ElementNotFoundException - */ - private JSONObject fetchElement(final UiSelector sel, final String contextId) - throws JSONException, ElementNotFoundException { - final JSONObject res = new JSONObject(); - final AndroidElement el = elements.getElement(sel, contextId); - return res.put("ELEMENT", el.getId()); - } - - /** - * Get an array of AndroidElement objects from the {@link AndroidElementsHash} - * - * @param sel - * A UiSelector that targets the element to fetch. - * @param contextId - * The Id of the element used for the context. - * @return ArrayList - * @throws UiObjectNotFoundException - */ - private ArrayList fetchElements(final UiSelector sel, - final String contextId) throws UiObjectNotFoundException { - - return elements.getElements(sel, contextId); - } - - /** - * Create and return a UiSelector based on the strategy, text, and how many - * you want returned. - * - * @param strategy - * The {@link Strategy} used to search for the element. - * @param text - * Any text used in the search (i.e. match, regex, etc.) - * @param many - * Boolean that is either only one element (false), or many (true) - * @return UiSelector - * @throws InvalidStrategyException - * @throws ElementNotFoundException - */ - private List getSelectors(final Strategy strategy, - final String text, final boolean many) throws InvalidStrategyException, - ElementNotFoundException, UiSelectorSyntaxException, - ParserConfigurationException, InvalidSelectorException { - final List selectors = new ArrayList(); - UiSelector sel = new UiSelector(); - - switch (strategy) { - case XPATH: - for (final UiSelector selector : getXPathSelectors(text, many)) { - selectors.add(selector); - } - break; - case CLASS_NAME: - sel = sel.className(text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - break; - case ID: - // There are three types of ids on Android. - // 1. resourceId (API >= 18) - // 2. accessibility id (content description) - // 3. strings.xml id - // - // If text is a resource id then only use the resource id selector. - if (API_18) { - if (resourceIdRegex.matcher(text).matches()) { - sel = sel.resourceId(text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - break; - } else { - // not a fully qualified resource id - // transform "textToBeChanged" into: - // com.example.android.testing.espresso.BasicSample:id/textToBeChanged - // android:id/textToBeChanged - // either it's prefixed with the app package or the android system page. - String pkg = (String) params.get("pkg"); - - if (pkg != null) { - sel = sel.resourceId(pkg + ":id/" + text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - } - - sel = sel.resourceId("android:id/" + text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - } - } - - // must create a new selector or the selector from - // the resourceId search will cause problems - sel = new UiSelector().description(text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - - // resource id and content description failed to match - // so the strings.xml selector is used - final UiSelector stringsXmlSelector = stringsXmlId(many, text); - if (stringsXmlSelector != null) { - selectors.add(stringsXmlSelector); - } - break; - case ACCESSIBILITY_ID: - sel = sel.description(text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - break; - case NAME: - sel = new UiSelector().description(text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - - sel = new UiSelector().text(text); - if (!many) { - sel = sel.instance(0); - } - selectors.add(sel); - break; - case ANDROID_UIAUTOMATOR: - List parsedSelectors; - try { - parsedSelectors = uiAutomatorParser.parse(text); - } catch (final UiSelectorSyntaxException e) { - throw new UiSelectorSyntaxException( - "Could not parse UiSelector argument: " + e.getMessage()); - } - - for (final UiSelector selector : parsedSelectors) { - selectors.add(selector); - } - - break; - case LINK_TEXT: - case PARTIAL_LINK_TEXT: - case CSS_SELECTOR: - default: - throw new InvalidStrategyException("Sorry, we don't support the '" - + strategy.getStrategyName() + "' locator strategy yet"); - } - - return selectors; - } - - /** returns List of UiSelectors for an xpath expression **/ - private List getXPathSelectors(final String expression, - final boolean multiple) throws ElementNotFoundException, - ParserConfigurationException, InvalidSelectorException { - final List selectors = new ArrayList(); - - final ArrayList pairs = XMLHierarchy - .getClassInstancePairs(expression); - - if (!multiple) { - if (pairs.size() == 0) { - throw new ElementNotFoundException(); - } - selectors.add(pairs.get(0).getSelector()); - } else { - for (final ClassInstancePair pair : pairs) { - selectors.add(pair.getSelector()); - } - } - - return selectors; - } - - /** Returns null on failure to match **/ - private UiSelector stringsXmlId(final boolean many, final String text) { - UiSelector sel = null; - try { - final String xmlValue = apkStrings.getString(text); - if (xmlValue == null || xmlValue.isEmpty()) { - return null; - } - sel = new UiSelector().text(xmlValue); - if (!many) { - sel = sel.instance(0); - } - } catch (final JSONException e) { - } finally { - return sel; - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Flick.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Flick.java deleted file mode 100644 index 3a54df4e..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Flick.java +++ /dev/null @@ -1,112 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.utils.Point; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * This handler is used to flick elements in the Android UI. - * - * Based on the element Id, flick that element. - * - */ -public class Flick extends CommandHandler { - - private Point calculateEndPoint(final Point start, final Integer xSpeed, - final Integer ySpeed) { - final UiDevice d = UiDevice.getInstance(); - final Point end = new Point(); - final double speedRatio = (double) xSpeed / ySpeed; - double xOff; - double yOff; - - final double value = Math.min(d.getDisplayHeight(), d.getDisplayWidth()); - - if (speedRatio < 1) { - yOff = value / 4; - xOff = value / 4 * speedRatio; - } else { - xOff = value / 4; - yOff = value / 4 / speedRatio; - } - - xOff = Integer.signum(xSpeed) * xOff; - yOff = Integer.signum(ySpeed) * yOff; - - end.x = start.x + xOff; - end.y = start.y + yOff; - return end; - } - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - Point start = new Point(0.5, 0.5); - Point end = new Point(); - Double steps; - - final Hashtable params = command.params(); - final UiDevice d = UiDevice.getInstance(); - - if (command.isElementCommand()) { - AndroidElement el; - try { - el = command.getElement(); - start = el.getAbsolutePosition(start); - final Integer xoffset = (Integer) params.get("xoffset"); - final Integer yoffset = (Integer) params.get("yoffset"); - final Integer speed = (Integer) params.get("speed"); - - steps = 1250.0 / speed + 1; - end.x = start.x + xoffset; - end.y = start.y + yoffset; - - } catch (final Exception e) { - return getErrorResult(e.getMessage()); - } - } else { - try { - final Integer xSpeed = (Integer) params.get("xSpeed"); - final Integer ySpeed = (Integer) params.get("ySpeed"); - - final Double speed = Math.min(1250.0, - Math.sqrt(xSpeed * xSpeed + ySpeed * ySpeed)); - steps = 1250.0 / speed + 1; - - start = PositionHelper.getDeviceAbsPos(start); - end = calculateEndPoint(start, xSpeed, ySpeed); - } catch (final InvalidCoordinatesException e) { - return getErrorResult(e.getMessage()); - } catch (final UiObjectNotFoundException e) { - return getErrorResult(e.getMessage()); - } - } - - steps = Math.abs(steps); - Logger.debug("Flicking from " + start.toString() + " to " + end.toString() - + " with steps: " + steps.intValue()); - final boolean res = d.swipe(start.x.intValue(), start.y.intValue(), - end.x.intValue(), end.y.intValue(), steps.intValue()); - - if (res) { - return getSuccessResult(res); - } else { - return getErrorResult("Flick did not complete successfully"); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetAttribute.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetAttribute.java deleted file mode 100644 index 82cfcde8..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetAttribute.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import io.appium.android.bootstrap.exceptions.NoAttributeFoundException; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * This handler is used to get an attribute of an element. - * - */ -public class GetAttribute extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - if (command.isElementCommand()) { - // only makes sense on an element - final Hashtable params = command.params(); - - 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")) { - return getSuccessResult(el.getStringAttribute(attr)); - } else { - return getSuccessResult(String.valueOf(el.getBoolAttribute(attr))); - } - } catch (final NoAttributeFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } catch (final Exception e) { // el is null - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } - } else { - return getErrorResult("Unable to get attribute without an element."); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDataDir.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDataDir.java deleted file mode 100644 index 90c43d58..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDataDir.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.os.Environment; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; - -/** - * This handler is used to get the data dir. - * - */ -public class GetDataDir extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) { - return getSuccessResult(Environment.getDataDirectory()); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDeviceSize.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDeviceSize.java deleted file mode 100644 index 8f13fd14..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetDeviceSize.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * This handler is used to get the size of the screen. - * - */ -public class GetDeviceSize extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) { - if (!command.isElementCommand()) { - // only makes sense on a device - final UiDevice d = UiDevice.getInstance(); - final JSONObject res = new JSONObject(); - try { - res.put("height", d.getDisplayHeight()); - res.put("width", d.getDisplayWidth()); - } catch (final JSONException e) { - getErrorResult("Error serializing height/width data into JSON"); - } - return getSuccessResult(res); - } else { - return getErrorResult("Unable to get device size on an element."); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetLocation.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetLocation.java deleted file mode 100644 index e52da204..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetLocation.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.graphics.Rect; -import io.appium.android.bootstrap.*; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * This handler is used to get the text of elements that support it. - * - */ -public class GetLocation extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - if (!command.isElementCommand()) { - return getErrorResult("Unable to get location without an element."); - } - - try { - final JSONObject res = new JSONObject(); - final AndroidElement el = command.getElement(); - final Rect bounds = el.getBounds(); - res.put("x", bounds.left); - res.put("y", bounds.top); - return getSuccessResult(res); - } catch (final Exception e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetName.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetName.java deleted file mode 100644 index 729a3fc9..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetName.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import org.json.JSONException; - -/** - * This handler is used to get the text of elements that support it. - * - */ -public class GetName extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - if (!command.isElementCommand()) { - return getErrorResult("Unable to get name without an element."); - } - - try { - final AndroidElement el = command.getElement(); - return getSuccessResult(el.getContentDesc()); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); - } catch (final Exception e) { // handle NullPointerException - return getErrorResult("Unknown error"); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetSize.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetSize.java deleted file mode 100644 index eb97079b..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetSize.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.graphics.Rect; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * This handler is used to get the size of elements that support it. - * - */ -public class GetSize extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - - if (command.isElementCommand()) { - // Only makes sense on an element - final JSONObject res = new JSONObject(); - try { - final AndroidElement el = command.getElement(); - final Rect rect = el.getBounds(); - res.put("width", rect.width()); - res.put("height", rect.height()); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } catch (final Exception e) { // handle NullPointerException - return getErrorResult("Unknown error"); - } - return getSuccessResult(res); - } else { - return getErrorResult("Unable to get text without an element."); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetText.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetText.java deleted file mode 100644 index 1b0202a1..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/GetText.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import org.json.JSONException; - -/** - * This handler is used to get the text of elements that support it. - * - */ -public class GetText extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - if (command.isElementCommand()) { - // Only makes sense on an element - try { - final AndroidElement el = command.getElement(); - return getSuccessResult(el.getText()); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, - e.getMessage()); - } catch (final Exception e) { // handle NullPointerException - return getErrorResult("Unknown error"); - } - } else { - return getErrorResult("Unable to get text without an element."); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/LongPressKeyCode.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/LongPressKeyCode.java deleted file mode 100644 index f99127b8..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/LongPressKeyCode.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.os.SystemClock; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; -import io.appium.uiautomator.core.InteractionController; -import io.appium.uiautomator.core.UiAutomatorBridge; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Hashtable; - -/** - * This handler is used to LongPressKeyCode. - * - */ -public class LongPressKeyCode extends CommandHandler { - public Integer keyCode; - - public Integer metaState; - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - try { - InteractionController interactionController = UiAutomatorBridge.getInstance().getInteractionController(); - - final Hashtable params = command.params(); - keyCode = (Integer) params.get("keycode"); - metaState = params.get("metastate") != JSONObject.NULL ? (Integer) params - .get("metastate") : 0; - final long eventTime = SystemClock.uptimeMillis(); - // Send an initial down event - final KeyEvent downEvent = new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_DOWN, keyCode, 0, metaState, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); - if (interactionController.injectEventSync(downEvent)) { - // Send a repeat event. This will cause the FLAG_LONG_PRESS to be set. - final KeyEvent repeatEvent = KeyEvent.changeTimeRepeat(downEvent, - eventTime, 1); - interactionController.injectEventSync(repeatEvent); - // Finally, send the up event - final KeyEvent upEvent = new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_UP, keyCode, 0, metaState, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); - interactionController.injectEventSync(upEvent); - } - return getSuccessResult(true); - } catch (final Exception e) { - return getErrorResult(e.getMessage()); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/MultiPointerGesture.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/MultiPointerGesture.java deleted file mode 100644 index 0aefa412..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/MultiPointerGesture.java +++ /dev/null @@ -1,136 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.view.MotionEvent.PointerCoords; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.AndroidElement; -import io.appium.android.bootstrap.CommandHandler; -import io.appium.android.bootstrap.Logger; -import io.appium.android.bootstrap.WDStatus; -import io.appium.uiautomator.core.UiAutomatorBridge; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import static io.appium.android.bootstrap.utils.API.API_18; - -public class MultiPointerGesture extends CommandHandler { - - private double computeLongestTime(final JSONArray actions) - throws JSONException { - double max = 0.0; - for (int i = 0; i < actions.length(); i++) { - final JSONArray gestures = actions.getJSONArray(i); - final double endTime = gestures.getJSONObject(gestures.length() - 1) - .getDouble("time"); - if (endTime > max) { - max = endTime; - } - } - - return max; - } - - private PointerCoords createPointerCoords(final JSONObject obj) - throws JSONException { - final JSONObject o = obj.getJSONObject("touch"); - - final int x = o.getInt("x"); - final int y = o.getInt("y"); - - final PointerCoords p = new PointerCoords(); - p.size = 1; - p.pressure = 1; - p.x = x; - p.y = y; - - return p; - } - - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - try { - final PointerCoords[][] pcs = parsePointerCoords(command); - - if (command.isElementCommand()) { - final AndroidElement el = command.getElement(); - if (el.performMultiPointerGesture(pcs)) { - return getSuccessResult("OK"); - } else { - return getErrorResult("Unable to perform multi pointer gesture"); - } - } else { - if (API_18) { - Boolean rt = UiAutomatorBridge.getInstance().getInteractionController().performMultiPointerGesture(pcs); - if (rt) { - return getSuccessResult("OK"); - } else { - return getErrorResult("Unable to perform multi pointer gesture"); - } - } else { - Logger.error("Device does not support API < 18!"); - return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, - "Cannot perform multi pointer gesture on device below API level 18"); - } - } - } catch (final Exception e) { - Logger.debug("Exception: " + e); - e.printStackTrace(); - return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage()); - } - } - - private PointerCoords[] gesturesToPointerCoords(final double maxTime, - final JSONArray gestures) throws JSONException { - // gestures, e.g.: - // [ - // {"touch":{"y":529.5,"x":120},"time":0.2}, - // {"touch":{"y":529.5,"x":130},"time":0.4}, - // {"touch":{"y":454.5,"x":140},"time":0.6}, - // {"touch":{"y":304.5,"x":150},"time":0.8} - // ] - - // From the docs: - // "Steps are injected about 5 milliseconds apart, so 100 steps may take - // around 0.5 seconds to complete." - final int steps = (int) (maxTime * 200) + 2; - - final PointerCoords[] pc = new PointerCoords[steps]; - - int i = 1; - JSONObject current = gestures.getJSONObject(0); - double currentTime = current.getDouble("time"); - double runningTime = 0.0; - final int gesturesLength = gestures.length(); - for (int j = 0; j < steps; j++) { - if (runningTime > currentTime && i < gesturesLength) { - current = gestures.getJSONObject(i++); - currentTime = current.getDouble("time"); - } - - pc[j] = createPointerCoords(current); - - runningTime += 0.005; - } - - return pc; - } - - private PointerCoords[][] parsePointerCoords(final AndroidCommand command) - throws JSONException { - final JSONArray actions = (org.json.JSONArray) command.params().get( - "actions"); - - final double time = computeLongestTime(actions); - - final PointerCoords[][] pcs = new PointerCoords[actions.length()][]; - for (int i = 0; i < actions.length(); i++) { - final JSONArray gestures = actions.getJSONArray(i); - - pcs[i] = gesturesToPointerCoords(time, gestures); - } - - return pcs; - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/OpenNotification.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/OpenNotification.java deleted file mode 100644 index 0da6fbcb..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/OpenNotification.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; - -import static io.appium.android.bootstrap.utils.API.API_18; - -/** - * This handler is used to open the notification shade on the device. - * - */ -public class OpenNotification extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) { - // method was only introduced in API Level 18 - if (!API_18) { - return getErrorResult("Unable to open notifications on device below API level 18"); - } - - // does not make sense on an element - if (command.isElementCommand()) { - return getErrorResult("Unable to open notifications on an element."); - } - - final UiDevice device = UiDevice.getInstance(); - if (device.openNotification()) { - return getSuccessResult(true); - } else { - return getErrorResult("Device failed to open notifications."); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Orientation.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Orientation.java deleted file mode 100644 index c990a60d..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Orientation.java +++ /dev/null @@ -1,135 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.os.RemoteException; -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.*; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * This handler is used to get or set the orientation of the device. - * - */ -public class Orientation extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - - final Hashtable params = command.params(); - if (params.containsKey("orientation")) { - // Set the rotation - - final String orientation = (String) params.get("orientation"); - try { - return handleRotation(orientation); - } catch (final Exception e) { - return getErrorResult("Unable to rotate screen: " + e.getMessage()); - } - } else { - // Get the rotation - return getRotation(); - } - - } - - /** - * Returns the current rotation - * - * @return {@link AndroidCommandResult} - */ - private AndroidCommandResult getRotation() { - String res = null; - final UiDevice d = UiDevice.getInstance(); - final OrientationEnum currentRotation = OrientationEnum.fromInteger(d - .getDisplayRotation()); - Logger.debug("Current rotation: " + currentRotation); - switch (currentRotation) { - case ROTATION_0: - case ROTATION_180: - res = "PORTRAIT"; - break; - case ROTATION_90: - case ROTATION_270: - res = "LANDSCAPE"; - break; - } - - if (res != null) { - return getSuccessResult(res); - } else { - return getErrorResult("Get orientation did not complete successfully"); - } - } - - /** - * Set the desired rotation - * - * @param orientation - * The rotation desired (LANDSCAPE or PORTRAIT) - * @return {@link AndroidCommandResult} - * @throws RemoteException - * @throws InterruptedException - */ - private AndroidCommandResult handleRotation(final String orientation) - throws RemoteException, InterruptedException { - final UiDevice d = UiDevice.getInstance(); - OrientationEnum desired; - OrientationEnum current = OrientationEnum.fromInteger(d - .getDisplayRotation()); - - Logger.debug("Desired orientation: " + orientation); - Logger.debug("Current rotation: " + current); - - if (orientation.equalsIgnoreCase("LANDSCAPE")) { - switch (current) { - case ROTATION_0: - d.setOrientationRight(); - desired = OrientationEnum.ROTATION_270; - break; - case ROTATION_180: - d.setOrientationLeft(); - desired = OrientationEnum.ROTATION_270; - break; - default: - return getSuccessResult("Already in landscape mode."); - } - } else { - switch (current) { - case ROTATION_90: - case ROTATION_270: - d.setOrientationNatural(); - desired = OrientationEnum.ROTATION_0; - break; - default: - return getSuccessResult("Already in portrait mode."); - } - } - current = OrientationEnum.fromInteger(d.getDisplayRotation()); - // If the orientation has not changed, - // busy wait until the TIMEOUT has expired - final int TIMEOUT = 2000; - final long then = System.currentTimeMillis(); - long now = then; - while (current != desired && now - then < TIMEOUT) { - Thread.sleep(100); - now = System.currentTimeMillis(); - current = OrientationEnum.fromInteger(d.getDisplayRotation()); - } - if (current != desired) { - return getErrorResult("Set the orientation, but app refused to rotate."); - } - return getSuccessResult("Rotation (" + orientation + ") successful."); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Pinch.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Pinch.java deleted file mode 100644 index f93df93e..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Pinch.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * This handler is used to pinch in/out elements in the Android UI. - * - * Based on the element Id, pinch in/out that element. - * - */ -public class Pinch extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - final Hashtable params = command.params(); - - AndroidElement el; - final String direction = params.get("direction").toString(); - final Integer percent = (Integer) params.get("percent"); - final Integer steps = (Integer) params.get("steps"); - try { - el = command.getElement(); - if (el == null) { - return getErrorResult("Could not find an element with elementId: " - + params.get("elementId")); - } - } catch (final Exception e) { // JSONException, NullPointerException, etc. - return getErrorResult("Unknown error:" + e.getMessage()); - } - - Logger.debug("Pinching " + direction + " " + percent.toString() + "%" - + " with steps: " + steps.toString()); - boolean res; - if (direction.equals("in")) { - try { - res = el.pinchIn(percent, steps); - } catch (final UiObjectNotFoundException e) { - return getErrorResult("Selector could not be matched to any UI element displayed"); - } - } else { - try { - res = el.pinchOut(percent, steps); - } catch (final UiObjectNotFoundException e) { - return getErrorResult("Selector could not be matched to any UI element displayed"); - } - } - - if (res) { - return getSuccessResult(res); - } else { - return getErrorResult("Pinch did not complete successfully"); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressBack.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressBack.java deleted file mode 100644 index ee5a68dd..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressBack.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; - -/** - * This handler is used to press back. - * - */ -public class PressBack extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) { - UiDevice.getInstance().pressBack(); - // Press back returns false even when back was successfully pressed. - // Always return true. - return getSuccessResult(true); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressKeyCode.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressKeyCode.java deleted file mode 100644 index c5be54c3..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/PressKeyCode.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Hashtable; - -/** - * This handler is used to PressKeyCode. - * - */ -public class PressKeyCode extends CommandHandler { - public Integer keyCode; - public Integer metaState; - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - try { - final Hashtable params = command.params(); - Object kc = params.get("keycode"); - if (kc instanceof Integer) { - keyCode = (Integer) kc; - } else if (kc instanceof String) { - keyCode = Integer.parseInt((String) kc); - } else { - throw new IllegalArgumentException("Keycode of type " + kc.getClass() + "not supported."); - } - - if (params.get("metastate") != JSONObject.NULL) { - metaState = (Integer) params.get("metastate"); - UiDevice.getInstance().pressKeyCode(keyCode, metaState); - } else { - UiDevice.getInstance().pressKeyCode(keyCode); - } - - return getSuccessResult(true); - } catch (final Exception e) { - return getErrorResult(e.getMessage()); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java deleted file mode 100644 index d4186abc..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObject; -import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.*; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * This handler is used to scroll to elements in the Android UI. - * - * Based on the element Id of the scrollable, scroll to the object with the - * text. - * - */ -public class ScrollTo extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - if (!command.isElementCommand()) { - return getErrorResult("A scrollable view is required for this command."); - } - - try { - Boolean result; - final Hashtable params = command.params(); - final String text = params.get("text").toString(); - final String direction = params.get("direction").toString(); - - final AndroidElement el = command.getElement(); - - if (!el.getUiObject().isScrollable()) { - return getErrorResult("The provided view is not scrollable."); - } - - final UiScrollable view = new UiScrollable(el.getUiObject().getSelector()); - - if (direction.toLowerCase().contentEquals("horizontal") - || view.getClassName().contentEquals( - "android.widget.HorizontalScrollView")) { - view.setAsHorizontalList(); - } - view.scrollToBeginning(100); - view.setMaxSearchSwipes(100); - result = view.scrollTextIntoView(text); - view.waitForExists(5000); - - // make sure we can get to the item - UiObject listViewItem = view.getChildByInstance( - new UiSelector().text(text), 0); - - // We need to make sure that the item exists (visible) - if (!(result && listViewItem.exists())) { - return getErrorResult("Could not scroll element into view: " + text); - } - return getSuccessResult(result); - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); - } catch (final NullPointerException e) { // el is null - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); - } catch (final Exception e) { - return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage()); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/SetText.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/SetText.java deleted file mode 100644 index fbd85020..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/SetText.java +++ /dev/null @@ -1,88 +0,0 @@ -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; - -/** - * This handler is used to set text in elements that support it. - * - */ -public class SetText extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - - AndroidElement el = null; - if (command.isElementCommand()) { - Logger.debug("Using element passed in."); - el = command.getElement(); - } else { - 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 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"); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java deleted file mode 100644 index 6f63933d..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import io.appium.android.bootstrap.utils.ReflectionUtils; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; -import io.appium.android.bootstrap.utils.XMLHierarchy; -import org.json.JSONException; -import org.w3c.dom.Document; - -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import java.io.StringWriter; - -/** - * Get page source. Return as string of XML doc - */ -public class Source extends CommandHandler { - @Override - public AndroidCommandResult execute(final AndroidCommand command) throws JSONException { - ReflectionUtils.clearAccessibilityCache(); - - final Document doc = (Document) XMLHierarchy.getFormattedXMLDoc(); - - final TransformerFactory tf = TransformerFactory.newInstance(); - final StringWriter writer = new StringWriter(); - Transformer transformer; - String xmlString; - - - try { - transformer = tf.newTransformer(); - transformer.transform(new DOMSource(doc), new StreamResult(writer)); - xmlString = writer.getBuffer().toString(); - - } catch (final TransformerConfigurationException e) { - e.printStackTrace(); - throw new RuntimeException("Something went terribly wrong while converting xml document to string"); - } catch (final TransformerException e) { - return getErrorResult("Could not parse xml hierarchy to string: " + e.getMessage()); - } - - return getSuccessResult(xmlString); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Swipe.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Swipe.java deleted file mode 100644 index 4240131e..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Swipe.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.utils.Point; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * This handler is used to swipe. - * - */ -public class Swipe extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - final Hashtable params = command.params(); - final Point start = new Point(params.get("startX"), params.get("startY")); - final Point end = new Point(params.get("endX"), params.get("endY")); - final Integer steps = (Integer) params.get("steps"); - final UiDevice device = UiDevice.getInstance(); - - Point absStartPos = new Point(); - Point absEndPos = new Point(); - - try { - if (command.isElementCommand()) { - final AndroidElement el = command.getElement(); - absStartPos = el.getAbsolutePosition(start); - absEndPos = el.getAbsolutePosition(end); - } else { - absStartPos = PositionHelper.getDeviceAbsPos(start); - absEndPos = PositionHelper.getDeviceAbsPos(end); - } - } catch (final UiObjectNotFoundException e) { - return getErrorResult(e.getMessage()); - } catch (final InvalidCoordinatesException e) { - return getErrorResult(e.getMessage()); - } catch (final Exception e) { // handle NullPointerException - return getErrorResult("Unknown error"); - } - - Logger.debug("Swiping from " + absStartPos.toString() + " to " - + absEndPos.toString() + " with steps: " + steps.toString()); - final boolean rv = device.swipe(absStartPos.x.intValue(), - absStartPos.y.intValue(), absEndPos.x.intValue(), - absEndPos.y.intValue(), steps); - if (!rv) { - return getErrorResult("The swipe did not complete successfully"); - } - return getSuccessResult(rv); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TakeScreenshot.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TakeScreenshot.java deleted file mode 100644 index c9a109cd..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TakeScreenshot.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; - -import java.io.File; - -/** - * This handler is used to TakeScreenshot. - * - */ -public class TakeScreenshot extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) { - final File screenshot = new File("/data/local/tmp/screenshot.png"); - - try { - screenshot.getParentFile().mkdirs(); - } catch (final Exception e) { - } - - if (screenshot.exists()) { - screenshot.delete(); - } - - UiDevice.getInstance().takeScreenshot(screenshot); - return getSuccessResult(screenshot.exists()); - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchDown.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchDown.java deleted file mode 100644 index 0866dd34..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchDown.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.Logger; -import io.appium.uiautomator.core.UiAutomatorBridge; - -/** - * This handler is used to perform a touchDown event on an element in the - * Android UI. - * - */ -public class TouchDown extends TouchEvent { - - @Override - protected boolean executeTouchEvent() throws UiObjectNotFoundException { - printEventDebugLine("TouchDown"); - try { - return UiAutomatorBridge.getInstance().getInteractionController().touchDown(clickX, clickY); - } catch (final Exception e) { - Logger.debug("Problem invoking touchDown: " + e); - return false; - } - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchEvent.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchEvent.java deleted file mode 100644 index 9bfc95a6..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchEvent.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.graphics.Rect; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.*; -import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; -import io.appium.android.bootstrap.utils.Point; -import org.json.JSONException; - -import java.util.ArrayList; -import java.util.Hashtable; - -/** - * This handler is and abstract class that contains all the common code for - * touch event handlers. - * - */ -public abstract class TouchEvent extends CommandHandler { - protected AndroidElement el; - - protected int clickX; - - protected int clickY; - - protected Hashtable params; - - protected boolean isElement; - - /** - * - * @param command - * The {@link AndroidCommand} - * @return {@link AndroidCommandResult} - * @throws JSONException - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - initalize(); - try { - params = command.params(); - - // isElementCommand doesn't check to see if we actually have an element - // so getElement is used instead. - try { - if (command.getElement() != null) { - isElement = true; - } - } catch (final Exception e) { - isElement = false; - } - - if (isElement) { - // extract x and y from the element. - el = command.getElement(); - - // check if element exists without wait - if(! el.exists()) { - throw new UiObjectNotFoundException("TouchEvent element does not exist."); - } - - final Rect bounds = el.getVisibleBounds(); - clickX = bounds.centerX(); - clickY = bounds.centerY(); - } else { // no element so extract x and y from params - final Object paramX = params.get("x"); - final Object paramY = params.get("y"); - - // these will be defaulted to 0.5 when passed to getDeviceAbsPos - double targetX = 0; - double targetY = 0; - - if (paramX != null) { - targetX = Double.parseDouble(paramX.toString()); - } - - if (paramY != null) { - targetY = Double.parseDouble(paramY.toString()); - } - - Point coords = new Point(targetX, targetY); - coords = PositionHelper.getDeviceAbsPos(coords); - - clickX = coords.x.intValue(); - clickY = coords.y.intValue(); - } - - if (executeTouchEvent()) { - return getSuccessResult(true); - } - - } catch (final UiObjectNotFoundException e) { - return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); - } catch (final InvalidCoordinatesException e) { - return new AndroidCommandResult(WDStatus.INVALID_ELEMENT_COORDINATES, - e.getMessage()); - } catch (final Exception e) { - return getErrorResult(e.getMessage()); - } - return getErrorResult("Failed to execute touch event"); - } - - protected abstract boolean executeTouchEvent() - throws UiObjectNotFoundException; - - /** - * Variables persist across executions. initialize must be called at the start - * of execute. - **/ - private void initalize() { - el = null; - clickX = -1; - clickY = -1; - params = null; - isElement = false; - } - - protected void printEventDebugLine(final String methodName, - final Integer... duration) { - String extra = ""; - if (duration.length > 0) { - extra = ", duration: " + duration[0]; - } - Logger.debug("Performing " + methodName + " using element? " + isElement - + " x: " + clickX + ", y: " + clickY + extra); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchLongClick.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchLongClick.java deleted file mode 100644 index 9b94aa84..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchLongClick.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.os.SystemClock; -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.Logger; -import io.appium.uiautomator.core.InteractionController; -import io.appium.uiautomator.core.UiAutomatorBridge; - -/** - * This handler is used to long click elements in the Android UI. - * - */ -public class TouchLongClick extends TouchEvent { - /* - * UiAutomator has a broken longClick, so we'll try to implement it using the - * touchDown / touchUp events. - */ - protected static boolean correctLongClick(final int x, final int y, final int duration) { - try { - /* - * bridge.getClass() returns ShellUiAutomatorBridge on API 18/19 so use - * the super class. - */ - - InteractionController interactionController = UiAutomatorBridge.getInstance().getInteractionController(); - - if (interactionController.touchDown(x, y)) { - SystemClock.sleep(duration); - if (interactionController.touchUp(x, y)) { - return true; - } - } - return false; - - } catch (final Exception e) { - Logger.debug("Problem invoking correct long click: " + e); - return false; - } - } - - @Override - protected boolean executeTouchEvent() throws UiObjectNotFoundException { - final Object paramDuration = params.get("duration"); - int duration = 2000; // two seconds - if (paramDuration != null) { - duration = Integer.parseInt(paramDuration.toString()); - } - - printEventDebugLine("TouchLongClick", duration); - if (correctLongClick(clickX, clickY, duration)) { - return true; - } - // if correctLongClick failed and we have an element - // then uiautomator's longClick is used as a fallback. - if (isElement) { - Logger.debug("Falling back to broken longClick"); - - return el.longClick(); - } - return false; - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchMove.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchMove.java deleted file mode 100644 index 2af3ccfd..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchMove.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.Logger; -import io.appium.uiautomator.core.UiAutomatorBridge; - -/** - * This handler is used to perform a touchMove event on an element in the - * Android UI. - * - */ -public class TouchMove extends TouchEvent { - - @Override - protected boolean executeTouchEvent() throws UiObjectNotFoundException { - printEventDebugLine("TouchMove"); - try { - return UiAutomatorBridge.getInstance().getInteractionController().touchMove(clickX, clickY); - } catch (final Exception e) { - Logger.debug("Problem invoking touchMove: " + e); - return false; - } - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchUp.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchUp.java deleted file mode 100644 index 9d9267b8..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchUp.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiObjectNotFoundException; -import io.appium.android.bootstrap.Logger; -import io.appium.uiautomator.core.UiAutomatorBridge; - -/** - * This handler is used to perform a touchUp event on an element in the Android - * UI. - * - */ -public class TouchUp extends TouchEvent { - - @Override - protected boolean executeTouchEvent() throws UiObjectNotFoundException { - printEventDebugLine("TouchUp"); - try { - return UiAutomatorBridge.getInstance().getInteractionController().touchUp(clickX, clickY); - } catch (final Exception e) { - Logger.debug("Problem invoking touchUp: " + e); - return false; - } - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/UpdateStrings.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/UpdateStrings.java deleted file mode 100644 index f7c6a4cf..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/UpdateStrings.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; -import io.appium.android.bootstrap.Logger; -import org.json.JSONObject; - -import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; - -/** - * This handler is used to update the apk strings. - * - */ -public class UpdateStrings extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) { - if (!loadStringsJson()) { - return getErrorResult("Unable to load json file and update strings."); - } - return getSuccessResult(true); - } - - public static boolean loadStringsJson() { - Logger.debug("Loading json..."); - try { - String filePath = "/data/local/tmp/strings.json"; - final File jsonFile = new File(filePath); - // json will not exist for apks that are only on device - // because the node server can't extract the json from the apk. - if (!jsonFile.exists()) { - return false; - } - final DataInputStream dataInput = new DataInputStream( - new FileInputStream(jsonFile)); - final byte[] jsonBytes = new byte[(int) jsonFile.length()]; - dataInput.readFully(jsonBytes); - // this closes FileInputStream - dataInput.close(); - final String jsonString = new String(jsonBytes, "UTF-8"); - Find.apkStrings = new JSONObject(jsonString); - Logger.debug("json loading complete."); - } catch (final Exception e) { - Logger.error("Error loading json: " + e.getMessage()); - return false; - } - return true; - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/WaitForIdle.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/WaitForIdle.java deleted file mode 100644 index b5ffa7e7..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/WaitForIdle.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; -import org.json.JSONException; - -import java.util.Hashtable; - -/** - * This handler is used to clear elements in the Android UI. - * - * Based on the element Id, clear that element. - * - */ -public class WaitForIdle extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) - throws JSONException { - final Hashtable params = command.params(); - long timeout = 10; - if (params.containsKey("timeout")) { - timeout = (Integer) params.get("timeout"); - } - - UiDevice d = UiDevice.getInstance(); - d.waitForIdle(timeout); - return getSuccessResult(true); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Wake.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Wake.java deleted file mode 100644 index beda8bac..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Wake.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.appium.android.bootstrap.handler; - -import android.os.RemoteException; -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.AndroidCommand; -import io.appium.android.bootstrap.AndroidCommandResult; -import io.appium.android.bootstrap.CommandHandler; - -/** - * This handler is used to power on the device if it's not currently awake. - * - */ -public class Wake extends CommandHandler { - - /* - * @param command The {@link AndroidCommand} used for this handler. - * - * @return {@link AndroidCommandResult} - * - * @throws JSONException - * - * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. - * bootstrap.AndroidCommand) - */ - @Override - public AndroidCommandResult execute(final AndroidCommand command) { - // only makes sense on a device - try { - UiDevice.getInstance().wakeUp(); - return getSuccessResult(true); - } catch (final RemoteException e) { - return getErrorResult("Error waking up device"); - } - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/selector/Strategy.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/selector/Strategy.java deleted file mode 100644 index 9fe74668..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/selector/Strategy.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.appium.android.bootstrap.selector; - -import io.appium.android.bootstrap.exceptions.InvalidStrategyException; - -/** - * An emumeration of possible strategies. - */ -public enum Strategy { - CLASS_NAME("class name"), - CSS_SELECTOR("css selector"), - ID("id"), - NAME("name"), - LINK_TEXT("link text"), - PARTIAL_LINK_TEXT("partial link text"), - XPATH("xpath"), - ACCESSIBILITY_ID("accessibility id"), - ANDROID_UIAUTOMATOR("-android uiautomator"); - - public static Strategy fromString(final String text) - throws InvalidStrategyException { - if (text != null) { - for (final Strategy s : Strategy.values()) { - if (text.equalsIgnoreCase(s.strategyName)) { - return s; - } - } - } - throw new InvalidStrategyException("Locator strategy '" + text - + "' is not supported on Android"); - } - - private final String strategyName; - - private Strategy(final String name) { - strategyName = name; - } - - public String getStrategyName() { - return strategyName; - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/API.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/API.java deleted file mode 100644 index 01157434..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/API.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import android.os.Build; - -public class API { - - /** True if the device is >= API 18 **/ - public static final boolean API_18 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ClassInstancePair.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ClassInstancePair.java deleted file mode 100644 index e4167262..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ClassInstancePair.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import com.android.uiautomator.core.UiSelector; - -/** - * Simple class for holding a String 2-tuple. An android class, and instance number, used for finding elements by xpath. - */ -public class ClassInstancePair { - - private String androidClass; - private String instance; - - public ClassInstancePair(String clazz, String inst) { - androidClass = clazz; - instance = inst; - } - - public String getAndroidClass() { - return androidClass; - } - - public String getInstance() { - return instance; - } - - public UiSelector getSelector() { - String androidClass = getAndroidClass(); - String instance = getInstance(); - - return new UiSelector().className(androidClass).instance(Integer.parseInt(instance)); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ElementHelpers.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ElementHelpers.java deleted file mode 100644 index 88052f7a..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ElementHelpers.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import android.view.accessibility.AccessibilityNodeInfo; -import com.android.uiautomator.core.UiObject; -import io.appium.android.bootstrap.AndroidElement; -import org.json.JSONException; -import org.json.JSONObject; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -import static io.appium.android.bootstrap.utils.ReflectionUtils.method; - -public abstract class ElementHelpers { - - private static Method findAccessibilityNodeInfo; - - private static AccessibilityNodeInfo elementToNode(AndroidElement element) { - AccessibilityNodeInfo result = null; - try { - result = (AccessibilityNodeInfo) findAccessibilityNodeInfo.invoke(element.getUiObject(), 5000L); - } catch (Exception e) { - e.printStackTrace(); - } - return result; - } - - /** - * Remove all duplicate elements from the provided list - * - * @param elements - elements to remove duplicates from - * @return a new list with duplicates removed - */ - public static List dedupe(List elements) { - try { - findAccessibilityNodeInfo = method(UiObject.class, "findAccessibilityNodeInfo", long.class); - } catch (Exception e) { - e.printStackTrace(); - } - - List result = new ArrayList(); - List nodes = new ArrayList(); - - for (AndroidElement element : elements) { - AccessibilityNodeInfo node = elementToNode(element); - if (!nodes.contains(node)) { - nodes.add(node); - result.add(element); - } - } - - return result; - } - - /** - * Return the JSONObject which Appium returns for an element - * - * For example, appium returns elements like [{"ELEMENT":1}, {"ELEMENT":2}] - */ - public static JSONObject toJSON(AndroidElement el) throws JSONException { - return new JSONObject().put("ELEMENT", el.getId()); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/NotImportantViews.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/NotImportantViews.java deleted file mode 100644 index c99c2499..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/NotImportantViews.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import com.android.uiautomator.core.UiDevice; - -import static io.appium.android.bootstrap.utils.API.API_18; - -public abstract class NotImportantViews { - // setCompressedLayoutHeirarchy doesn't exist on API <= 17 - // http://developer.android.com/reference/android/accessibilityservice/AccessibilityServiceInfo.html#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS - private static boolean canDiscard = API_18; - - public static void discard(boolean discard) { - if (canDiscard) { - UiDevice.getInstance().setCompressedLayoutHeirarchy(discard); - } - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/Point.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/Point.java deleted file mode 100644 index a90367d7..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/Point.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.appium.android.bootstrap.utils; - -public class Point { - - public Double x; - public Double y; - - public Point() { - x = 0.0; - y = 0.0; - } - - public Point(final Double x, final Double y) { - this.x = x; - this.y = y; - } - - public Point(final Object x, final Object y) { - this.x = Double.parseDouble(x.toString()); - this.y = Double.parseDouble(y.toString()); - } - - public Point(final Point other) { - x = other.x; - y = other.y; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Point other = (Point) obj; - if (x == null) { - if (other.x != null) { - return false; - } - } else if (!x.equals(other.x)) { - return false; - } - if (y == null) { - if (other.y != null) { - return false; - } - } else if (!y.equals(other.y)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (x == null ? 0 : x.hashCode()); - result = prime * result + (y == null ? 0 : y.hashCode()); - return result; - } - - @Override - public String toString() { - return "[x=" + x + ", y=" + y + "]"; - } - -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ReflectionUtils.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ReflectionUtils.java deleted file mode 100644 index 4527d929..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/ReflectionUtils.java +++ /dev/null @@ -1,108 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import io.appium.android.bootstrap.Logger; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; - -public class ReflectionUtils { - - /** - * Clears the in-process Accessibility cache, removing any stale references. - * Because the AccessibilityInteractionClient singleton stores copies of - * AccessibilityNodeInfo instances, calls to public APIs such as `recycle` do - * not guarantee cached references get updated. See the - * android.view.accessibility AIC and ANI source code for more information. - */ - public static boolean clearAccessibilityCache() { - boolean success = false; - - try { - final Class c = Class - .forName("android.view.accessibility.AccessibilityInteractionClient"); - final Method getInstance = ReflectionUtils.method(c, "getInstance"); - final Object instance = getInstance.invoke(null); - final Method clearCache = ReflectionUtils.method(instance.getClass(), - "clearCache"); - clearCache.invoke(instance); - success = true; - } catch (final Exception ex) { - // Expected: ClassNotFoundException, NoSuchMethodException, - // IllegalAccessException, - // InvocationTargetException, NoSuchFieldException - Logger.error("Failed to clear Accessibility Node cache. " - + ex.getMessage()); - } - - return success; - } - - public static Class getClass(final String name) { - try { - return Class.forName(name); - } catch (final ClassNotFoundException e) { - final String msg = String.format("unable to find class %s", name); - throw new RuntimeException(msg, e); - } - } - - public static Object getField(final Class clazz, final String fieldName, - final Object object) { - try { - final Field field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - - return field.get(object); - } catch (final Exception e) { - final String msg = String.format( - "error while getting field %s from object %s", fieldName, object); - Logger.error(msg + " " + e.getMessage()); - throw new RuntimeException(msg, e); - } - } - - public static Object getField(final String field, final Object object) { - return getField(object.getClass(), field, object); - } - - public static Object getField(final String className, final String field, - final Object object) { - return getField(getClass(className), field, object); - } - - public static Object invoke(final Method method, final Object object, - final Object... parameters) { - try { - return method.invoke(object, parameters); - } catch (final Exception e) { - final String msg = String.format( - "error while invoking method %s on object %s with parameters %s", - method, object, Arrays.toString(parameters)); - Logger.error(msg + " " + e.getMessage()); - throw new RuntimeException(msg, e); - } - } - - public static Method method(final Class clazz, final String methodName, - final Class... parameterTypes) { - try { - final Method method = clazz.getDeclaredMethod(methodName, parameterTypes); - method.setAccessible(true); - - return method; - } catch (final Exception e) { - final String msg = String - .format( - "error while getting method %s from class %s with parameter types %s", - methodName, clazz, Arrays.toString(parameterTypes)); - Logger.error(msg + " " + e.getMessage()); - throw new RuntimeException(msg, e); - } - } - - public static Method method(final String className, final String method, - final Class... parameterTypes) { - return method(getClass(className), method, parameterTypes); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/TheWatchers.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/TheWatchers.java deleted file mode 100644 index 81230aaa..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/TheWatchers.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import com.android.uiautomator.core.UiObject; -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.Logger; - -public class TheWatchers { - private static TheWatchers ourInstance = new TheWatchers(); - private boolean alerted = false; - - public static TheWatchers getInstance() { - return ourInstance; - } - - private TheWatchers() { - } - - public boolean check() { - // Send only one alert message... - if (isDialogPresent() && (!alerted)) { - Logger.debug("Emitting system alert message"); - alerted = true; - } - - // if the dialog went away, make sure we can send an alert again - if (!isDialogPresent() && alerted) { - alerted = false; - } - return alerted; - } - - public boolean isDialogPresent() { - UiObject alertDialog = new UiObject( - new UiSelector().packageName("com.android.systemui")); - return alertDialog.exists(); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiAutomatorParser.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiAutomatorParser.java deleted file mode 100644 index 5461f477..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiAutomatorParser.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.Logger; -import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException; - -import java.util.ArrayList; -import java.util.List; - -/** - * For parsing strings passed in for the "-android uiautomator" locator strategy - */ -public class UiAutomatorParser { - - private String text; - private List selectors; - private UiScrollableParser scrollableParser = new UiScrollableParser(); - private UiSelectorParser selectorParser = new UiSelectorParser(); - - public List parse(String textToParse) throws UiSelectorSyntaxException { - if (textToParse.isEmpty()) { - throw new UiSelectorSyntaxException("Tried to parse an empty string. Expected to see a string consisting of text to be interpreted as UiAutomator java code."); - } - selectors = new ArrayList(); - text = textToParse.trim(); - removeTailingSemicolon(); - trimWhitespace(); - - consumeStatement(); - while (text.length() > 0) { - trimWhitespace(); - consumeSemicolon(); - trimWhitespace(); - consumeStatement(); - } - - return selectors; - } - - private void trimWhitespace() { - text = text.trim(); - } - - private void removeTailingSemicolon() { - if (text.charAt(text.length()-1) == ';') { - text = text.substring(0, text.length()-1); - } - } - - private void consumeSemicolon() throws UiSelectorSyntaxException { - if (text.charAt(0) != ';') { - throw new UiSelectorSyntaxException("Expected ';' but saw '" + text.charAt(0) +"'"); - } - - text = text.substring(1); - } - - private void consumeStatement() throws UiSelectorSyntaxException { - String statement; - int index = 0; - int parenCount = -1; // semicolons could appear inside String arguments, so we make sure we only count occurrences outside of a parenthesis pair - while (index < text.length()) { - if (text.charAt(index) == ';' && parenCount == 0) { - break; - } - if (text.charAt(index) == '(') { - if (parenCount < 0) { - parenCount = 1; - } else { - parenCount++; - } - } - if (text.charAt(index) == ')') { - parenCount--; - } - index++; - } - - statement = text.substring(0, index); - if (UiScrollableParser.isUiScrollable(statement)) { - Logger.debug("Parsing scrollable: " + statement); - selectors.add(scrollableParser.parse(statement)); - } else { - Logger.debug("Parsing selector: " + statement); - selectors.add(selectorParser.parse(statement)); - } - - text = text.substring(index); - } - -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiScrollableParser.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiScrollableParser.java deleted file mode 100644 index bee3fbf8..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiScrollableParser.java +++ /dev/null @@ -1,364 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import com.android.uiautomator.core.UiObject; -import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.Logger; -import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; - -/** - * For parsing strings that create UiScrollable objects into UiScrollable objects - */ -public class UiScrollableParser { - - private String text; - private UiScrollable scrollable; - private UiObject uiObject; - private boolean returnedUiObject; - private final static Method[] methods = UiScrollable.class.getDeclaredMethods(); - private static String[] prefixes = {"new UiScrollable", "UiScrollable"}; - - /* - * Returns whether or not the input string is trying to instantiate a UiScrollable, and use its methods - */ - public static boolean isUiScrollable(String textToParse) { - for (String prefix : prefixes) { - if (textToParse.startsWith(prefix)) { - return true; - } - } - return false; - } - - /* - * Parse a string into a UiSelector, but use UiScrollable class and methods - */ - public UiSelector parse(String textToParse) throws UiSelectorSyntaxException { - text = textToParse.trim(); - returnedUiObject = false; - - consumePrefix(); - consumeConstructor(); - - while (text.length() > 0) { - consumePeriod(); - consumeFunctionCall(); - } - - if (!returnedUiObject) { - throw new UiSelectorSyntaxException("Last method called on a UiScrollable object must return a UiObject object"); - } - - return uiObject.getSelector(); - } - - private void consumePeriod() throws UiSelectorSyntaxException { - if (text.startsWith(".")) { - text = text.substring(1); - } - else { - throw new UiSelectorSyntaxException("Expected \".\" but saw \"" + text.charAt(0) + "\""); - } - } - - /* - * You can start a UiScrollable like: "new UiScrollable(UiSelector).somemethod()" or "Uiscrollable(UiSelector).somemethod()" - */ - private void consumePrefix() throws UiSelectorSyntaxException { - boolean removedPrefix = false; - for (String prefix : prefixes) { - if (text.startsWith(prefix)) { - text = text.substring(prefix.length()); - removedPrefix = true; - break; - } - } - if (!removedPrefix) { - throw new UiSelectorSyntaxException("Was trying to parse as UiScrollable, but didn't start with an acceptable prefix. Acceptable prefixes are: 'new UiScrollable' or 'UiScrollable'. Saw: " + text); - } - } - - /* - * consume UiScrollable constructor argument: parens surrounding a uiSelector. eg - "(new UiSelector().scrollable(true))" - * initialize the UiScrollable object for this parser - */ - private void consumeConstructor() throws UiSelectorSyntaxException { - if (text.charAt(0) != '(') { - throw new UiSelectorSyntaxException("Was expecting \"" + ")" + "\" but instead saw \"" + text.charAt(0) + "\"" ); - } - StringBuilder argument = new StringBuilder(); - - int index = 1; - int parenCount = 1; - while (parenCount > 0) { - try { - switch (text.charAt(index)) { - case ')': - parenCount--; - if (parenCount > 0) { - argument.append(text.charAt(index)); - } - break; - case '(': - parenCount++; - argument.append(text.charAt(index)); - break; - default: - argument.append(text.charAt(index)); - } - } catch (StringIndexOutOfBoundsException e) { - throw new UiSelectorSyntaxException("unclosed paren in expression"); - } - index++; - } - if (argument.length() < 1) { - throw new UiSelectorSyntaxException("UiScrollable constructor expects an argument"); - } - - UiSelector selector = new UiSelectorParser().parse(argument.toString()); - scrollable = new UiScrollable(selector); - - // add two for parentheses surrounding arg - text = text.substring(argument.length() + 2); - } - - /* - * consume [a-z]* then an open paren, this is our methodName - * consume .* and count open/close parens until the original open paren is close, this is our argument - * - */ - private void consumeFunctionCall() throws UiSelectorSyntaxException { - String methodName; - StringBuilder argument = new StringBuilder(); - - int parenIndex = text.indexOf('('); - methodName = text.substring(0, parenIndex); - - int index = parenIndex+1; - int parenCount = 1; - while (parenCount > 0) { - try { - switch (text.charAt(index)) { - case ')': - parenCount--; - if (parenCount > 0) { - argument.append(text.charAt(index)); - } - break; - case '(': - parenCount++; - argument.append(text.charAt(index)); - break; - default: - argument.append(text.charAt(index)); - } - } catch (StringIndexOutOfBoundsException e) { - throw new UiSelectorSyntaxException("unclosed paren in expression"); - } - index++; - } - - ArrayList args = splitArgs(argument.toString()); - - Method method = getUiScrollableMethod(methodName, args); - - applyArgsToMethod(method, args); - - // add two for parentheses surrounding arg - text = text.substring(methodName.length() + argument.length() + 2); - } - - private Method getUiScrollableMethod(String methodName, Collection args) throws UiSelectorSyntaxException { - for (Method method : methods) { - if (method.getName().equals(methodName) && method.getGenericParameterTypes().length == args.size()) { - return method; - } - } - throw new UiSelectorSyntaxException("UiScrollable has no \"" + methodName + "\" method that takes " + args.size() + " arguments"); - } - - private void applyArgsToMethod(Method method, ArrayList arguments) throws UiSelectorSyntaxException { - StringBuilder sb = new StringBuilder(); - for (String arg : arguments) { - sb.append(arg + ", "); - } - Logger.debug("UiScrollable invoking method: " + method + " args: " + sb.toString()); - - if (method.getGenericReturnType() == UiScrollable.class && returnedUiObject) { - throw new UiSelectorSyntaxException("Cannot call UiScrollable method \"" + method.getName() + "\" on a UiObject instance"); - } - - if (method.getGenericParameterTypes().length == 0) { - try { - scrollable = (UiScrollable)method.invoke(scrollable); - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } catch (InvocationTargetException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } catch (ClassCastException e) { - throw new UiSelectorSyntaxException("methods must return UiScrollable or UiObject instances"); - } - } - - else { - ArrayList convertedArgs = new ArrayList(); - Type[] parameterTypes = method.getGenericParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - convertedArgs.add(coerceArgToType(parameterTypes[i], arguments.get(i))); - } - - String methodName = method.getName(); - Logger.debug("Method name: " + methodName); - boolean scrollIntoView = methodName.contentEquals("scrollIntoView"); - - if (method.getGenericReturnType() == UiScrollable.class || scrollIntoView) { - if (convertedArgs.size() > 1) { - throw new UiSelectorSyntaxException("No UiScrollable method that returns type UiScrollable takes more than 1 argument"); - } - try { - if (scrollIntoView) { - Logger.debug("Setting uiObject for scrollIntoView"); - UiSelector arg = (UiSelector) convertedArgs.get(0); - returnedUiObject = true; - uiObject = new UiObject(arg); - // scrollIntoView must return the object if it's already in view. - // without the exists check, the parser will error because there's no scrollable. - if (uiObject.exists()) { - return; - } - Logger.debug("Invoking method: " + method + " with: " + uiObject); - method.invoke(scrollable, uiObject); - Logger.debug("Invoke complete."); - } else { - scrollable = (UiScrollable)method.invoke(scrollable, convertedArgs.get(0)); - } - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } catch (InvocationTargetException e) { - // Ignoring UiObjectNotFoundException as this handled during actual find. - if (e.getCause() instanceof UiObjectNotFoundException) { - Logger.debug("Ignoring UiObjectNotFoundException when using reflection to invoke method."); - return; - } - Logger.error(e.getCause().toString()); // we're only interested in the cause. InvocationTarget wraps the underlying problem. - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } - } - - else if (method.getGenericReturnType() == UiObject.class) { - returnedUiObject = true; - - if (convertedArgs.size() == 2) { - try { - uiObject = (UiObject)method.invoke(scrollable, convertedArgs.get(0), convertedArgs.get(1)); - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } catch (InvocationTargetException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } - } else if (convertedArgs.size() == 3) { - try { - uiObject = (UiObject)method.invoke(scrollable, convertedArgs.get(0), convertedArgs.get(1), convertedArgs.get(2)); - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } catch (InvocationTargetException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } - } - else { - throw new UiSelectorSyntaxException("UiScrollable methods which return a UiObject have 2-3 args"); - } - } - - else { - throw new UiSelectorSyntaxException("Must only call the 'scrollIntoView' method OR methods on UiScrollable which return UiScrollable or UiObject objects"); - } - } - } - - private Object coerceArgToType(Type type, String argument) throws UiSelectorSyntaxException { - Logger.debug("UiScrollable coerce type: " + type + " arg: " + argument); - if (type == boolean.class) { - if (argument.equals("true")) { - return true; - } - if (argument.equals("false")) { - return false; - } - throw new UiSelectorSyntaxException(argument + " is not a boolean"); - } - - if (type == String.class) { - if (argument.charAt(0) != '"' || argument.charAt(argument.length()-1) != '"') { - throw new UiSelectorSyntaxException(argument + " is not a string"); - } - return argument.substring(1, argument.length()-1); - } - - if (type == int.class) { - return Integer.parseInt(argument); - } - - if (type.toString().equals("java.lang.Class")) { - try { - return Class.forName(argument); - } catch (ClassNotFoundException e) { - throw new UiSelectorSyntaxException(argument + " class could not be found"); - } - } - - if (type == UiSelector.class || type == UiObject.class) { - UiSelectorParser parser = new UiSelectorParser(); - return parser.parse(argument); - } - - throw new UiSelectorSyntaxException("Could not coerce " + argument + " to any sort of Type"); - } - - private ArrayList splitArgs(String argumentString) throws UiSelectorSyntaxException { - ArrayList args = new ArrayList(); - if (argumentString.isEmpty()) { - return args; - } - if (argumentString.charAt(0) == ',' || argumentString.charAt(argumentString.length()-1) == ',') { - throw new UiSelectorSyntaxException("Missing argument. Trying to parse: " + argumentString); - } - - int prevIndex = 0; - int index = 1; - boolean inQuotes = false; - while (index < argumentString.length()) { - switch (argumentString.charAt(index)) { - case ',': - if (!inQuotes) { - if (prevIndex == index) { - throw new UiSelectorSyntaxException("Missing argument. Trying to parse: " + argumentString); - } - args.add(argumentString.substring(prevIndex, index).trim()); - prevIndex = index+1; - } - case '"': - inQuotes = !inQuotes; - break; - } - index++; - } - args.add(argumentString.substring(prevIndex, index).trim()); - - return args; - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiSelectorParser.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiSelectorParser.java deleted file mode 100644 index afe451d2..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UiSelectorParser.java +++ /dev/null @@ -1,192 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import com.android.uiautomator.core.UiSelector; -import io.appium.android.bootstrap.Logger; -import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; - -/** - * For parsing strings which create new UiSelector objects into UiSelector object. - */ -public class UiSelectorParser { - - private String text; - private UiSelector selector; - private final static Method[] methods = UiSelector.class.getDeclaredMethods(); - - public UiSelector parse(String textToParse) throws UiSelectorSyntaxException { - selector = new UiSelector(); - text = cleanseText(textToParse); - - while (text.length() > 0) { - consumePeriod(); - consumeFunctionCall(); - } - - return selector; - } - - // prepares text for the main parsing loop - private String cleanseText(String dirtyText) { - String cleanText = dirtyText.trim(); - - if (cleanText.startsWith("new UiSelector()")) { - cleanText = cleanText.substring(16); - } - else if (cleanText.startsWith("UiSelector()")) { - cleanText = cleanText.substring(12); - } - else if (!cleanText.startsWith(".")){ - cleanText = "." + cleanText; - } - - return cleanText; - } - - private void consumePeriod() throws UiSelectorSyntaxException { - if (text.startsWith(".")) { - text = text.substring(1); - } - else { - throw new UiSelectorSyntaxException("Expected \".\" but saw \"" + text.charAt(0) + "\""); - } - } - - /* - * consume [a-z]* then an open paren, this is our methodName - * consume .* and count open/close parens until the original open paren is close, this is our argument - * - */ - private void consumeFunctionCall() throws UiSelectorSyntaxException { - String methodName; - StringBuilder argument = new StringBuilder(); - - int parenIndex = text.indexOf('('); - methodName = text.substring(0, parenIndex); - - int index = parenIndex+1; - int parenCount = 1; - while (parenCount > 0) { - try { - switch (text.charAt(index)) { - case ')': - parenCount--; - if (parenCount > 0) { - argument.append(text.charAt(index)); - } - break; - case '(': - parenCount++; - argument.append(text.charAt(index)); - break; - default: - argument.append(text.charAt(index)); - } - } catch (StringIndexOutOfBoundsException e) { - throw new UiSelectorSyntaxException("unclosed paren in expression"); - } - index++; - } - if (argument.length() < 1) { - throw new UiSelectorSyntaxException(methodName + " method expects an argument"); - } - - // add two for parentheses surrounding arg - text = text.substring(methodName.length() + argument.length() + 2); - - ArrayList overloadedMethods = getSelectorMethods(methodName); - if (overloadedMethods.size() < 1) { - throw new UiSelectorSyntaxException("UiSelector has no " + methodName + " method"); - } - - selector = applyArgToMethods(overloadedMethods, argument.toString()); - } - - private ArrayList getSelectorMethods(String methodName) { - ArrayList ret = new ArrayList(); - for (Method method : methods) { - if (method.getName().equals(methodName)) { - ret.add(method); - } - } - return ret; - } - - private UiSelector applyArgToMethods(ArrayList methods, String argument) throws UiSelectorSyntaxException { - - Object arg = null; - Method ourMethod = null; - UiSelectorSyntaxException exThrown = null; - for (Method method : methods) { - try { - Type parameterType = method.getGenericParameterTypes()[0]; - arg = coerceArgToType(parameterType, argument); - ourMethod = method; - } catch (UiSelectorSyntaxException e) { - exThrown = e; - } - } - - if (ourMethod == null || arg == null) { - if (exThrown != null) { - throw exThrown; - } else { - throw new UiSelectorSyntaxException("Could not apply argument " + argument + " to UiSelector method"); - } - } - - try { - return (UiSelector)ourMethod.invoke(selector, arg); - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } catch (InvocationTargetException e) { - e.printStackTrace(); - throw new UiSelectorSyntaxException("problem using reflection to call this method"); - } - - } - - private Object coerceArgToType(Type type, String argument) throws UiSelectorSyntaxException { - Logger.debug("UiSelector coerce type: " + type + " arg: " + argument); - if (type == boolean.class) { - if (argument.equals("true")) { - return true; - } - if (argument.equals("false")) { - return false; - } - throw new UiSelectorSyntaxException(argument + " is not a boolean"); - } - - if (type == String.class) { - if (argument.charAt(0) != '"' || argument.charAt(argument.length()-1) != '"') { - throw new UiSelectorSyntaxException(argument + " is not a string"); - } - return argument.substring(1, argument.length()-1); - } - - if (type == int.class) { - return Integer.parseInt(argument); - } - - if (type.toString().equals("java.lang.Class")) { - try { - return Class.forName(argument); - } catch (ClassNotFoundException e) { - throw new UiSelectorSyntaxException(argument + " class could not be found"); - } - } - - if (type == UiSelector.class) { - UiSelectorParser parser = new UiSelectorParser(); - return parser.parse(argument); - } - - throw new UiSelectorSyntaxException("Could not coerce " + argument + " to any sort of Type"); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UnicodeEncoder.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UnicodeEncoder.java deleted file mode 100644 index 700e8491..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/UnicodeEncoder.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import java.nio.charset.Charset; - -import io.appium.android.bootstrap.Logger; - - -public class UnicodeEncoder { - private static final Charset M_UTF7 = Charset.forName("x-IMAP-mailbox-name"); - private static final Charset ASCII = Charset.forName("US-ASCII"); - - - public static String encode(final String text) { - byte[] encoded = text.getBytes(M_UTF7); - String ret = new String(encoded, ASCII); - if (ret.charAt(ret.length()-1) != text.charAt(text.length()-1) && !ret.endsWith("-")) { - // in some cases there is a problem and the closing tag is not added - // to the encoded text (for instance, with `ü`) - Logger.debug("Closing tag missing. Adding."); - ret = ret + "-"; - } - return ret; - } - - public static boolean needsEncoding(final String text) { - char[] chars = text.toCharArray(); - for (int i = 0; i < chars.length; i++) { - int cp = Character.codePointAt(chars, i); - if (cp > 0x7F || cp == '&') { - // Selenium uses a Unicode PUA to cover certain special characters - // see https://code.google.com/p/selenium/source/browse/java/client/src/org/openqa/selenium/Keys.java - // these should juse be passed through as is. - return !(cp >= 0xE000 && cp <= 0xE040); - } - } - return false; - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/XMLHierarchy.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/XMLHierarchy.java deleted file mode 100644 index e6d8c128..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/utils/XMLHierarchy.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import android.graphics.Point; -import android.os.Environment; -import android.view.Display; -import android.view.accessibility.AccessibilityNodeInfo; -import io.appium.android.bootstrap.exceptions.ElementNotFoundException; -import io.appium.android.bootstrap.exceptions.InvalidSelectorException; -import io.appium.android.bootstrap.exceptions.PairCreationException; -import io.appium.uiautomator.core.AccessibilityNodeInfoDumper; -import io.appium.uiautomator.core.UiAutomatorBridge; -import org.w3c.dom.Document; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Created by jonahss on 8/12/14. - */ -public abstract class XMLHierarchy { - - public static ArrayList getClassInstancePairs(String xpathExpression) - throws ElementNotFoundException, InvalidSelectorException, ParserConfigurationException { - XPath xpath = XPathFactory.newInstance().newXPath(); - XPathExpression exp = null; - try { - exp = xpath.compile(xpathExpression); - } catch (XPathExpressionException e) { - throw new InvalidSelectorException(e.getMessage()); - } - - Node formattedXmlRoot; - - formattedXmlRoot = getFormattedXMLDoc(); - - return getClassInstancePairs(exp, formattedXmlRoot); - } - - public static ArrayList getClassInstancePairs(XPathExpression xpathExpression, Node root) throws ElementNotFoundException { - - NodeList nodes; - try { - nodes = (NodeList) xpathExpression.evaluate(root, XPathConstants.NODESET); - } catch (XPathExpressionException e) { - e.printStackTrace(); - throw new ElementNotFoundException("XMLWindowHierarchy could not be parsed: " + e.getMessage()); - } - - ArrayList pairs = new ArrayList(); - for (int i = 0; i < nodes.getLength(); i++) { - if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) { - try { - pairs.add(getPairFromNode(nodes.item(i))); - } catch (PairCreationException e) { } - } - } - - return pairs; - } - - public static InputSource getRawXMLHierarchy() { - AccessibilityNodeInfo root = getRootAccessibilityNode(); - return serializeAccessibilityNode(root); - } - - private static AccessibilityNodeInfo getRootAccessibilityNode() { - while(true){ - AccessibilityNodeInfo root = UiAutomatorBridge.getInstance().getQueryController().getAccessibilityRootNode(); - if (root != null) { - return root; - } - } - } - - private static InputSource serializeAccessibilityNode(AccessibilityNodeInfo root) { - try { - - final File dumpFolder = new File(Environment.getDataDirectory(), "local/tmp"); - final File dumpFile = new File(dumpFolder, "dump.xml"); - - dumpFolder.mkdirs(); - dumpFile.delete(); - - AccessibilityNodeInfoDumper.dumpWindowToFile(root, dumpFile); - - return new InputSource(new FileReader(dumpFile)); - } catch (Exception e) { - throw new RuntimeException("Failed to Dump Window Hierarchy", e); - } - } - - public static Node getFormattedXMLDoc() { - return formatXMLInput(getRawXMLHierarchy()); - } - - public static Node formatXMLInput(InputSource input) { - XPath xpath = XPathFactory.newInstance().newXPath(); - - Node root = null; - try { - root = (Node) xpath.evaluate("/", input, XPathConstants.NODE); - } catch (XPathExpressionException e) { - throw new RuntimeException("Could not read xml hierarchy: " + e.getMessage()); - } - - HashMap instances = new HashMap(); - - // rename all the nodes with their "class" attribute - // add an instance attribute - annotateNodes(root, instances); - - return root; - } - - private static ClassInstancePair getPairFromNode(Node node) throws PairCreationException { - - NamedNodeMap attrElements = node.getAttributes(); - String androidClass; - String instance; - - try { - androidClass = attrElements.getNamedItem("class").getNodeValue(); - instance = attrElements.getNamedItem("instance").getNodeValue(); - } catch (Exception e) { - throw new PairCreationException("Could not create ClassInstancePair object: " + e.getMessage()); - } - - return new ClassInstancePair(androidClass, instance); - } - - private static void annotateNodes(Node node, HashMap instances) { - NodeList children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (children.item(i).getNodeType() == Node.ELEMENT_NODE) { - visitNode(children.item(i), instances); - annotateNodes(children.item(i), instances); - } - } - } - - // set the node's tag name to the same as it's android class. - // also number all instances of each class with an "instance" number. It increments for each class separately. - // this allows use to use class and instance to identify a node. - // we also take this chance to clean class names that might have dollar signs in them (and other odd characters) - private static void visitNode(Node node, HashMap instances) { - - Document doc = node.getOwnerDocument(); - NamedNodeMap attributes = node.getAttributes(); - - String androidClass; - try { - androidClass = attributes.getNamedItem("class").getNodeValue(); - } catch (Exception e) { - return; - } - - androidClass = cleanTagName(androidClass); - - if (!instances.containsKey(androidClass)) { - instances.put(androidClass, 0); - } - Integer instance = instances.get(androidClass); - - Node attrNode = doc.createAttribute("instance"); - attrNode.setNodeValue(instance.toString()); - attributes.setNamedItem(attrNode); - - doc.renameNode(node, node.getNamespaceURI(), androidClass); - - instances.put(androidClass, instance + 1); - } - - private static String cleanTagName(String name) { - name = name.replaceAll("[$@#&]", "."); - return name.replaceAll("\\s", ""); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoDumper.java b/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoDumper.java deleted file mode 100644 index 61a9234f..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoDumper.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.appium.uiautomator.core; -import android.graphics.Point; -import android.os.SystemClock; -import android.util.Log; -import android.util.Xml; -import android.view.Display; -import android.view.accessibility.AccessibilityNodeInfo; -import io.appium.android.bootstrap.utils.API; -import org.xmlpull.v1.XmlSerializer; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.util.regex.Pattern; - - - -/** - * - * The AccessibilityNodeInfoDumper in Android Open Source Project contains a - * lot of bugs which will stay in old android versions forever. By coping the - * code of the latest version it is ensured that all patches become available on - * old android versions. - * - * down ported bugs are e.g. - * { @link https://code.google.com/p/android/issues/detail?id=62906 } - * { @link https://code.google.com/p/android/issues/detail?id=58733 } - * - */ -public class AccessibilityNodeInfoDumper { - private static final String LOGTAG = AccessibilityNodeInfoDumper.class.getSimpleName(); - private static final String[] NAF_EXCLUDED_CLASSES = new String[] { - android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(), - android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName() - }; - // XML 1.0 Legal Characters (http://stackoverflow.com/a/4237934/347155) - // #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] - private static Pattern XML10Pattern = Pattern.compile("[^" - + "\u0009\r\n" - + "\u0020-\uD7FF" - + "\uE000-\uFFFD" - + "\ud800\udc00-\udbff\udfff" - + "]"); - /** - * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy - * and generates an xml dump to the location specified by dumpFile - * @param root The root accessibility node. - * @param dumpFile The file to dump to. - */ - public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile) { - final long startTime = SystemClock.uptimeMillis(); - try { - FileWriter writer = new FileWriter(dumpFile); - XmlSerializer serializer = Xml.newSerializer(); - StringWriter stringWriter = new StringWriter(); - serializer.setOutput(stringWriter); - serializer.startDocument("UTF-8", true); - serializer.startTag("", "hierarchy"); - - if (root != null) { - int width = -1; - int height = -1; - if(API.API_18){ - // getDefaultDisplay method available since API level 18 - Display display = UiAutomatorBridge.getInstance().getDefaultDisplay(); - Point size = new android.graphics.Point(); - display.getSize(size); - width = size.x; - height = size.y; - - serializer.attribute("", "rotation", Integer.toString(display.getRotation())); - } - - dumpNodeRec(root, serializer, 0, width, height); - } - - serializer.endTag("", "hierarchy"); - serializer.endDocument(); - writer.write(stringWriter.toString()); - writer.close(); - } catch (IOException e) { - Log.e(LOGTAG, "failed to dump window to file", e); - } - final long endTime = SystemClock.uptimeMillis(); - Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms"); - } - private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,int index, - int width, int height) throws IOException { - serializer.startTag("", "node"); - if (!nafExcludedClass(node) && !nafCheck(node)) - serializer.attribute("", "NAF", Boolean.toString(true)); - serializer.attribute("", "index", Integer.toString(index)); - serializer.attribute("", "text", safeCharSeqToString(node.getText())); - serializer.attribute("", "class", safeCharSeqToString(node.getClassName())); - serializer.attribute("", "package", safeCharSeqToString(node.getPackageName())); - serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription())); - serializer.attribute("", "checkable", Boolean.toString(node.isCheckable())); - serializer.attribute("", "checked", Boolean.toString(node.isChecked())); - serializer.attribute("", "clickable", Boolean.toString(node.isClickable())); - serializer.attribute("", "enabled", Boolean.toString(node.isEnabled())); - serializer.attribute("", "focusable", Boolean.toString(node.isFocusable())); - serializer.attribute("", "focused", Boolean.toString(node.isFocused())); - serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable())); - serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable())); - serializer.attribute("", "password", Boolean.toString(node.isPassword())); - serializer.attribute("", "selected", Boolean.toString(node.isSelected())); - - if(API.API_18){ - serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen( - node, width, height).toShortString()); - - serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName())); - } - - int count = node.getChildCount(); - for (int i = 0; i < count; i++) { - AccessibilityNodeInfo child = node.getChild(i); - if (child != null) { - if (child.isVisibleToUser()) { - dumpNodeRec(child, serializer, i, width, height); - child.recycle(); - } else { - Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString())); - } - } else { - Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s", - i, count, node.toString())); - } - } - serializer.endTag("", "node"); - } - /** - * The list of classes to exclude my not be complete. We're attempting to - * only reduce noise from standard layout classes that may be falsely - * configured to accept clicks and are also enabled. - * - * @param node - * @return true if node is excluded. - */ - private static boolean nafExcludedClass(AccessibilityNodeInfo node) { - String className = safeCharSeqToString(node.getClassName()); - for(String excludedClassName : NAF_EXCLUDED_CLASSES) { - if(className.endsWith(excludedClassName)) - return true; - } - return false; - } - /** - * We're looking for UI controls that are enabled, clickable but have no - * text nor content-description. Such controls configuration indicate an - * interactive control is present in the UI and is most likely not - * accessibility friendly. We refer to such controls here as NAF controls - * (Not Accessibility Friendly) - * - * @param node - * @return false if a node fails the check, true if all is OK - */ - private static boolean nafCheck(AccessibilityNodeInfo node) { - boolean isNaf = node.isClickable() && node.isEnabled() - && safeCharSeqToString(node.getContentDescription()).isEmpty() - && safeCharSeqToString(node.getText()).isEmpty(); - if (!isNaf) - return true; - // check children since sometimes the containing element is clickable - // and NAF but a child's text or description is available. Will assume - // such layout as fine. - return childNafCheck(node); - } - /** - * This should be used when it's already determined that the node is NAF and - * a further check of its children is in order. A node maybe a container - * such as LinerLayout and may be set to be clickable but have no text or - * content description but it is counting on one of its children to fulfill - * the requirement for being accessibility friendly by having one or more of - * its children fill the text or content-description. Such a combination is - * considered by this dumper as acceptable for accessibility. - * - * @param node - * @return false if node fails the check. - */ - private static boolean childNafCheck(AccessibilityNodeInfo node) { - int childCount = node.getChildCount(); - for (int x = 0; x < childCount; x++) { - AccessibilityNodeInfo childNode = node.getChild(x); - if (childNode == null) { - Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s", - x, childCount, node.toString())); - continue; - } - if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty() - || !safeCharSeqToString(childNode.getText()).isEmpty()) - return true; - if (childNafCheck(childNode)) - return true; - } - return false; - } - private static String safeCharSeqToString(CharSequence cs) { - if (cs == null) - return ""; - else { - return stripInvalidXMLChars(cs); - } - } - // Original Google code here broke UTF characters - private static String stripInvalidXMLChars(CharSequence charSequence) { - final StringBuilder sb = new StringBuilder(charSequence.length()); - sb.append(charSequence); - return XML10Pattern.matcher(sb.toString()).replaceAll("?"); - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoHelper.java b/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoHelper.java deleted file mode 100644 index 98c1b998..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/AccessibilityNodeInfoHelper.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.appium.uiautomator.core; -import android.graphics.Rect; -import android.view.accessibility.AccessibilityNodeInfo; -/** - * This class contains static helper methods to work with - * {@link AccessibilityNodeInfo} - */ -class AccessibilityNodeInfoHelper { - /** - * Returns the node's bounds clipped to the size of the display - * - * @param node - * @param width pixel width of the display - * @param height pixel height of the display - * @return null if node is null, else a Rect containing visible bounds - */ - static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) { - if (node == null) { - return null; - } - // targeted node's bounds - Rect nodeRect = new Rect(); - node.getBoundsInScreen(nodeRect); - Rect displayRect = new Rect(); - displayRect.top = 0; - displayRect.left = 0; - displayRect.right = width; - displayRect.bottom = height; - nodeRect.intersect(displayRect); - return nodeRect; - } -} \ No newline at end of file diff --git a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/InteractionController.java b/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/InteractionController.java deleted file mode 100644 index 89daece5..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/InteractionController.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.appium.uiautomator.core; - -import android.view.InputEvent; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; - -import static io.appium.android.bootstrap.utils.ReflectionUtils.invoke; -import static io.appium.android.bootstrap.utils.ReflectionUtils.method; - -public class InteractionController { - - private static final String CLASS_INTERACTION_CONTROLLER = "com.android.uiautomator.core.InteractionController"; - private static final String METHOD_SEND_KEY = "sendKey"; - private static final String METHOD_INJECT_EVENT_SYNC = "injectEventSync"; - private static final String METHOD_TOUCH_DOWN = "touchDown"; - private static final String METHOD_TOUCH_UP = "touchUp"; - private static final String METHOD_TOUCH_MOVE = "touchMove"; - public static final String METHOD_PERFORM_MULTI_POINTER_GESTURE = "performMultiPointerGesture"; - - private final Object interactionController; - - public InteractionController(Object interactionController) { - this.interactionController = interactionController; - } - - public boolean sendKey(int keyCode, int metaState){ - return (Boolean) invoke(method(CLASS_INTERACTION_CONTROLLER, METHOD_SEND_KEY, int.class, int.class), interactionController, keyCode, metaState); - } - - public boolean injectEventSync(InputEvent event) { - return (Boolean) invoke(method(CLASS_INTERACTION_CONTROLLER, METHOD_INJECT_EVENT_SYNC, InputEvent.class), interactionController, event); - } - - public boolean touchDown(int x, int y) { - return (Boolean) invoke(method(CLASS_INTERACTION_CONTROLLER, METHOD_TOUCH_DOWN, int.class, int.class), interactionController, x, y); - } - - public boolean touchUp(int x, int y) { - return (Boolean) invoke(method(CLASS_INTERACTION_CONTROLLER, METHOD_TOUCH_UP, int.class, int.class), interactionController, x, y); - } - - public boolean touchMove(int x, int y) { - return (Boolean) invoke(method(CLASS_INTERACTION_CONTROLLER, METHOD_TOUCH_MOVE, int.class, int.class), interactionController, x, y); - } - - public Boolean performMultiPointerGesture(MotionEvent.PointerCoords[][] pcs) { - return (Boolean) invoke(method(CLASS_INTERACTION_CONTROLLER, METHOD_PERFORM_MULTI_POINTER_GESTURE, PointerCoords[][].class), interactionController, (Object) pcs); - } -} diff --git a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/QueryController.java b/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/QueryController.java deleted file mode 100644 index 17539071..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/QueryController.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.appium.uiautomator.core; - -import android.view.accessibility.AccessibilityNodeInfo; - -import static io.appium.android.bootstrap.utils.ReflectionUtils.invoke; -import static io.appium.android.bootstrap.utils.ReflectionUtils.method; - -public class QueryController { - - private static final String CLASS_QUERY_CONTROLLER = "com.android.uiautomator.core.QueryController"; - private static final String METHOD_GET_ACCESSIBILITY_ROOT_NODE = "getAccessibilityRootNode"; - - private final Object queryController; - - public QueryController(Object queryController) { - this.queryController = queryController; - } - - public AccessibilityNodeInfo getAccessibilityRootNode() { - return (AccessibilityNodeInfo) invoke(method(CLASS_QUERY_CONTROLLER, METHOD_GET_ACCESSIBILITY_ROOT_NODE), queryController); - } - -} diff --git a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/UiAutomatorBridge.java b/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/UiAutomatorBridge.java deleted file mode 100644 index 6af710c9..00000000 --- a/lib/devices/android/bootstrap/src/io/appium/uiautomator/core/UiAutomatorBridge.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.appium.uiautomator.core; - -import android.util.Log; -import android.view.Display; -import android.view.InputEvent; -import com.android.uiautomator.core.UiDevice; -import io.appium.android.bootstrap.utils.ReflectionUtils; - -import static io.appium.android.bootstrap.utils.ReflectionUtils.getField; -import static io.appium.android.bootstrap.utils.ReflectionUtils.invoke; -import static io.appium.android.bootstrap.utils.ReflectionUtils.method; - -public class UiAutomatorBridge { - - private static final String CLASS_UI_AUTOMATOR_BRIDGE = "com.android.uiautomator.core.UiAutomatorBridge"; - - private static final String FIELD_UI_AUTOMATOR_BRIDGE = "mUiAutomationBridge"; - private static final String FIELD_QUERY_CONTROLLER = "mQueryController"; - private static final String FIELD_INTERACTION_CONTROLLER = "mInteractionController"; - - private static final String METHOD_GET_DEFAULT_DISPLAY = "getDefaultDisplay"; - private static final String METHOD_INJECT_INPUT_EVENT = "injectInputEvent"; - - private static UiAutomatorBridge INSTANCE = new UiAutomatorBridge(); - - private final Object uiAutomatorBridge; - - public UiAutomatorBridge() { - try { - final UiDevice device = UiDevice.getInstance(); - - this.uiAutomatorBridge = getField(UiDevice.class, FIELD_UI_AUTOMATOR_BRIDGE, device); - } catch ( Error error){ - Log.e("ERROR", "error", error); - throw error; - } - } - - public InteractionController getInteractionController() { - return new InteractionController(getField(CLASS_UI_AUTOMATOR_BRIDGE, FIELD_INTERACTION_CONTROLLER, uiAutomatorBridge)); - } - - public QueryController getQueryController() { - return new QueryController(getField(CLASS_UI_AUTOMATOR_BRIDGE, FIELD_QUERY_CONTROLLER, uiAutomatorBridge)); - } - - public Display getDefaultDisplay() { - return (Display) invoke(method(CLASS_UI_AUTOMATOR_BRIDGE, METHOD_GET_DEFAULT_DISPLAY), uiAutomatorBridge); - } - - public boolean injectInputEvent(InputEvent event, boolean sync) { - return (Boolean) invoke(method(CLASS_UI_AUTOMATOR_BRIDGE, METHOD_INJECT_INPUT_EVENT, InputEvent.class, boolean.class), - uiAutomatorBridge, event, sync); - } - - public static UiAutomatorBridge getInstance() { - return INSTANCE; - } -} diff --git a/lib/devices/android/bootstrap/test/io/appium/android/bootstrap/utils/XMLHierarchyTest.java b/lib/devices/android/bootstrap/test/io/appium/android/bootstrap/utils/XMLHierarchyTest.java deleted file mode 100644 index 547ff4b5..00000000 --- a/lib/devices/android/bootstrap/test/io/appium/android/bootstrap/utils/XMLHierarchyTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.appium.android.bootstrap.utils; - -import junit.framework.TestCase; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.xml.sax.InputSource; - -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathFactory; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; - -public class XMLHierarchyTest extends TestCase { - - private static XPath xpath = XPathFactory.newInstance().newXPath(); - - public void testGetClassInstancePairs() throws Exception { - - String xmlString = "\n"; - InputSource testInput = new InputSource(new StringReader(xmlString)); - - Node root = (Node) xpath.evaluate("/", testInput, XPathConstants.NODE); - - - XPathExpression expression = xpath.compile("//android.widget.GridView/android.widget.RelativeLayout"); - ArrayList pairs = XMLHierarchy.getClassInstancePairs(expression, root); - assertEquals(4, pairs.size()); - assertEquals("android.widget.RelativeLayout", pairs.get(0).getAndroidClass()); - assertEquals("0", pairs.get(0).getInstance()); - assertEquals("2", pairs.get(2).getInstance()); - - } - - public void testFormatXMLInput() throws Exception { - String xmlString = ""; - InputSource testInput = new InputSource(new StringReader(xmlString)); - - Node formatted = XMLHierarchy.formatXMLInput(testInput); - - Node childNode = formatted.getFirstChild().getFirstChild(); - - assertEquals("class0", childNode.getNodeName()); - assertEquals("0", childNode.getAttributes().getNamedItem("instance").getNodeValue()); - - childNode = formatted.getFirstChild().getLastChild(); - - assertEquals("class1", childNode.getNodeName()); - assertEquals("2", childNode.getAttributes().getNamedItem("instance").getNodeValue()); - - } - - public void testCleaningTags() throws Exception { - String[] samples = {"foo $ bar", "foo$bar", "foo.bar", "foo. bar"}; - String expected = "foo.bar"; - for (String sample : samples) { - String test = "teeeeeext"; - InputSource testInput = new InputSource(new StringReader(test)); - Node formatted = XMLHierarchy.formatXMLInput(testInput); - assertEquals(expected, formatted.getFirstChild().getNodeName()); - } - } - - public void testOutput() throws Exception { - String xmlString = "\n"; - InputSource testInput = new InputSource(new StringReader(xmlString)); - - Node node = XMLHierarchy.formatXMLInput(testInput); - Document doc = (Document) node; - - TransformerFactory tf = TransformerFactory.newInstance(); - StringWriter writer = new StringWriter(); - Transformer transformer; - String out; - - transformer = tf.newTransformer(); - transformer.transform(new DOMSource(doc), new StreamResult(writer)); - out = writer.getBuffer().toString(); - - assertEquals(xmlString, out); //close enough - - - } -} \ No newline at end of file diff --git a/lib/devices/android/chrome.js b/lib/devices/android/chrome.js deleted file mode 100644 index a5bc940d..00000000 --- a/lib/devices/android/chrome.js +++ /dev/null @@ -1,230 +0,0 @@ -"use strict"; - -var Android = require('./android.js') - , Chromedriver = require('appium-chromedriver') - , _ = require('underscore') - , logger = require('../../server/logger.js').get('appium') - , status = require('../../server/status.js') - , deviceCommon = require('../common.js') - , jwpSuccess = deviceCommon.jwpSuccess - , async = require('async') - , UiAutomator = require('./uiautomator.js'); - -var NATIVE_WIN = "NATIVE_APP"; -var WEBVIEW_WIN = "WEBVIEW"; -var WEBVIEW_BASE = WEBVIEW_WIN + "_"; - -var ChromeAndroid = function () { - this.init(); -}; - -_.extend(ChromeAndroid.prototype, Android.prototype); -ChromeAndroid.prototype._androidInit = Android.prototype.init; -ChromeAndroid.prototype.init = function () { - this._androidInit(); - this.adb = null; - this.onDieCb = null; - this.setChromedriverMode(); -}; - -ChromeAndroid.prototype.setChromedriverMode = function () { - logger.info("Set mode: Proxying straight through to Chromedriver"); - this.isProxy = true; - // when proxying to chromedriver, we need to make sure the context endpoints - // are trapped by appium for its own purposes - this.avoidProxy = [ - ['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/[^/]+/orientation')], - ['GET', new RegExp('^/wd/hub/session/[^/]+/orientation')] - ]; -}; - -ChromeAndroid.prototype.setNativeMode = function () { - logger.info("Set mode: Proxying to Appium Bootstrap"); - this.isProxy = false; -}; - -ChromeAndroid.prototype.configure = function (args, caps, cb) { - logger.debug("Looks like we want chrome on android"); - this._deviceConfigure(args, caps); - var bName = this.args.browserName || ""; - var app = this.appString().toLowerCase() || - bName.toString().toLowerCase(); - if (app === "chromium") { - this.args.androidPackage = "org.chromium.chrome.shell"; - this.args.androidActivity = ".ChromeShellActivity"; - } else if (app === "chromebeta") { - this.args.androidPackage = "com.chrome.beta"; - this.args.androidActivity = "com.google.android.apps.chrome.Main"; - } else if (app === "browser") { - this.args.androidPackage = "com.android.browser"; - this.args.androidActivity = "com.android.browser.BrowserActivity"; - } else { - this.args.androidPackage = "com.android.chrome"; - this.args.androidActivity = "com.google.android.apps.chrome.Main"; - } - // don't allow setAndroidArgs to reclobber our androidPackage/activity - delete this.capabilities.appPackage; - delete this.capabilities.appActivity; - this.setAndroidArgs(); - this.args.app = null; - this.args.proxyPort = this.args.chromeDriverPort; - cb(); -}; - -ChromeAndroid.prototype.startAutomation = function (cb) { - // this wrapper is required because uiautomator is not instantiated - // at the beginning of the async#series call - this.uiautomator.start(cb); -}; - -ChromeAndroid.prototype.start = function (cb, onDie) { - this.onDieCb = onDie; - - async.series([ - this.initAdb.bind(this), - this.prepareUiAutomator.bind(this), - this.initUiautomator.bind(this), - this.prepareDevice.bind(this), - this.prepareChromedriver.bind(this), - this.pushAndUnlock.bind(this), - this.forwardPort.bind(this), - this.pushAppium.bind(this), - this.startAutomation.bind(this), - this.getDataDir.bind(this), - this.createSession.bind(this) - ], function (err, results) { - if (err) return cb(err); - this.didLaunch = true; - var sessionId = results[results.length - 1]; - cb(null, sessionId); - }.bind(this)); -}; - -ChromeAndroid.prototype.prepareChromedriver = function (cb) { - var chromeArgs = { - port: this.args.proxyPort, - executable: this.args.chromedriverExecutable - }; - this.chromedriver = new Chromedriver(chromeArgs); - this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver); - 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); - this.unlock(cb); - }.bind(this)); -}; - -ChromeAndroid.prototype.createSession = function (cb) { - var caps = { - chromeOptions: { - androidPackage: this.args.appPackage - } - }; - if (this.args.enablePerformanceLogging) { - caps.loggingPrefs = {performance: 'ALL'}; - } - - var knownPackages = ["org.chromium.chrome.shell", - "com.android.chrome", - "com.chrome.beta"]; - - if (!_.contains(knownPackages, this.args.appPackage)) { - caps.chromeOptions.androidActivity = this.args.appActivity; - } - - caps = this.decorateChromeOptions(caps); - this.chromedriver.on(Chromedriver.EVENT_CHANGED, function (msg) { - if (msg.state === Chromedriver.STATE_STOPPED) { - logger.info("Chromedriver stopped unexpectedly on us, shutting down " + - "then calling back up with the on-die callback"); - this.onChromedriverStop(this.onDieCb); - } - }.bind(this)); - this.chromedriver.start(caps).nodeify(function (err) { - if (err) return cb(err); - cb(null, this.chromedriver.sessionId()); - }.bind(this)); -}; - -ChromeAndroid.prototype.stop = function (cb) { - // stop listening for the stopped state event - this.chromedriver.removeAllListeners(Chromedriver.EVENT_CHANGED); - // now we can handle the stop on our own - this.chromedriver.stop().nodeify(function (err) { - if (err) logger.warn("Error stopping Chromedriver: " + err.message); - this.onChromedriverStop(cb); - }.bind(this)); -}; - -ChromeAndroid.prototype.onChromedriverStop = function (cb) { - if (this.adb) { - this.uiautomator.shutdown(function () { - this.adb.forceStop(this.args.appPackage, function (err) { - if (err) return cb(err); - this.adb.stopLogcat(cb); - }.bind(this)); - }.bind(this)); - } else { - cb(); - } -}; - -// since we're in chrome, our default context is not the native mode, but web -ChromeAndroid.prototype.defaultContext = function () { - return WEBVIEW_BASE + "1"; -}; - -// write a new getContexts function that hard-codes the two available contexts -ChromeAndroid.prototype.getContexts = function (cb) { - this.contexts = [NATIVE_WIN, this.defaultContext()]; - logger.info("Available contexts: " + this.contexts); - cb(null, { - status: status.codes.Success.code - , value: this.contexts - }); -}; - -// write a new setContext function that handles starting and stopping of -// chrome mode; the default android context controller method won't work here -// because here we don't need to worry about starting/stopping chromedriver -// itself; it's already on -ChromeAndroid.prototype.setContext = function (name, cb) { - if (name === null) { - name = this.defaultContext(); - } else if (name === WEBVIEW_WIN) { - name = this.defaultContext(); - } - this.getContexts(function () { - if (!_.contains(this.contexts, name)) { - return cb(null, { - status: status.codes.NoSuchContext.code - , value: "Context '" + name + "' does not exist" - }); - } - if (name === this.curContext) { - return jwpSuccess(cb); - } - if (name.indexOf(WEBVIEW_WIN) !== -1) { - this.setChromedriverMode(); - } else { - this.setNativeMode(); - } - this.curContext = name; - jwpSuccess(cb); - }.bind(this)); -}; - -module.exports = ChromeAndroid; diff --git a/lib/devices/android/selendroid.js b/lib/devices/android/selendroid.js deleted file mode 100644 index a71037a4..00000000 --- a/lib/devices/android/selendroid.js +++ /dev/null @@ -1,582 +0,0 @@ -"use strict"; - -var Device = require('../device.js') - , mkdirp = require('mkdirp') - , _ = require('underscore') - , deviceCommon = require('../common.js') - , androidController = require('./android-controller.js') - , androidContextController = require('./android-context-controller.js') - , proxyTo = deviceCommon.proxyTo - , doRequest = deviceCommon.doRequest - , logger = require('../../server/logger.js').get('appium') - , status = require("../../server/status.js") - , fs = require('fs') - , async = require('async') - , androidCommon = require('./android-common.js') - , androidHybrid = require('./android-hybrid.js') - , path = require('path') - , md5 = require('md5calculator') - , utf7 = require('utf7').imap; - -var Selendroid = function () { - this.init(); -}; - -_.extend(Selendroid.prototype, Device.prototype); -Selendroid.prototype._deviceInit = Device.prototype.init; -Selendroid.prototype.init = function () { - this._deviceInit(); - this.selendroidHost = 'localhost'; - this.selendroidPort = 8080; - this.selendroidSessionId = null; - this.appExt = ".apk"; - this.args.devicePort = this.selendroidPort; - this.serverApk = null; - this.onStop = function () {}; - this.adb = null; - this.isProxy = true; - this.mobileMethodsSupported = [ - 'setLocation' - , 'setCommandTimeout' - , 'reset' - , 'lock' - , 'background' - , 'keyevent' - , 'currentActivity' - , 'installApp' - , 'uninstallApp' - , 'removeApp' - , 'closeApp' - , 'isAppInstalled' - , 'launchApp' - , 'toggleData' - , 'toggleFlightMode' - , 'toggleWiFi' - , 'toggleLocationServices' - , 'getStrings' - ]; - this.proxyHost = this.selendroidHost; - this.avoidProxy = [ - ['GET', new RegExp('^/wd/hub/session/[^/]+/log/types$')] - , ['POST', new RegExp('^/wd/hub/session/[^/]+/log')] - , ['POST', new RegExp('^/wd/hub/session/[^/]+/location')] - , ['POST', new RegExp('^/wd/hub/session/[^/]+/appium')] - , ['GET', new RegExp('^/wd/hub/session/[^/]+/appium')] - , ['POST', new RegExp('^/wd/hub/session/[^/]+/context')] - , ['GET', new RegExp('^/wd/hub/session/[^/]+/context')] - , ['GET', new RegExp('^/wd/hub/session/[^/]+/contexts')] - , ['POST', new RegExp('^/wd/hub/session/[^/]+/element/[^/]+/value')] - , ['GET', new RegExp('^/wd/hub/session/[^/]+/network_connection')] - , ['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(); -}; - -Selendroid.prototype.getSettings = deviceCommon.getSettings; -Selendroid.prototype.updateSettings = deviceCommon.updateSettings; - -Selendroid.prototype.pushUnlock = androidController.pushUnlock; -Selendroid.prototype.unlock = androidController.unlock; - -Selendroid.prototype.installApp = androidController.installApp; -Selendroid.prototype.isAppInstalled = androidController.isAppInstalled; -Selendroid.prototype.removeApp = androidController.removeApp; - -_.extend(Selendroid.prototype, androidCommon); -Selendroid.prototype._deviceConfigure = Device.prototype.configure; -Selendroid.prototype._setAndroidArgs = androidCommon.setAndroidArgs; -Selendroid.prototype.setAndroidArgs = function () { - this._setAndroidArgs(); - this.args.systemPort = this.args.selendroidPort; - this.proxyPort = this.args.systemPort; -}; - -Selendroid.prototype.start = function (cb) { - logger.debug("Starting selendroid server"); - - var modServerExists = false - , modAppPkg = null - , resignedServerMd5Hash = null; - - var checkModServerExists = function (cb) { - this.selendroidServerPath = path.resolve(this.args.tmpDir, - 'selendroid.' + this.args.appPackage + '.apk'); - modAppPkg = this.args.appPackage + '.selendroid'; - fs.exists(this.selendroidServerPath, function (exists) { - modServerExists = exists; - cb(); - }); - }.bind(this); - - var checkServerResigned = function (cb) { - if (modServerExists) { - md5(this.selendroidServerPath, function (err, md5Hash) { - if (err) return cb(err); - logger.debug("MD5 for selendroid server is " + md5Hash); - if (resignedServerMd5Hash !== md5Hash) { - resignedServerMd5Hash = md5Hash; - modServerExists = false; - } - }.bind(this)); - } - cb(); - }.bind(this); - - var conditionalUninstallSelendroid = function (cb) { - if (!modServerExists) { - logger.debug("Rebuilt selendroid apk does not exist, uninstalling " + - "any instances of it on device to make way for new one"); - this.adb.uninstallApk(modAppPkg, cb); - } else { - logger.debug("Rebuilt selendroid apk exists, doing nothing"); - cb(); - } - }.bind(this); - - var conditionalInsertManifest = function (cb) { - if (!modServerExists) { - logger.debug("Rebuilt selendroid server does not exist, inserting " + - "modified manifest"); - this.insertSelendroidManifest(this.serverApk, cb); - } else { - logger.debug("Rebuilt selendroid server already exists, no need to " + - "rebuild it with a new manifest"); - cb(); - } - }.bind(this); - - var conditionalInstallSelendroid = function (cb) { - this.adb.isAppInstalled(modAppPkg, function (e, installed) { - if (!installed) { - logger.debug("Rebuilt selendroid is not installed, installing it"); - this.adb.install(this.selendroidServerPath, cb); - } else { - logger.debug("Rebuilt selendroid is already installed"); - cb(); - } - }.bind(this)); - }.bind(this); - - async.series([ - this.initJavaVersion.bind(this), - this.initAdb.bind(this), - this.ensureServerExists.bind(this), - this.prepareDevice.bind(this), - this.checkInternetPermissionForApp.bind(this), - this.packageAndLaunchActivityFromManifest.bind(this), - checkModServerExists, - conditionalInsertManifest, - this.checkSelendroidCerts.bind(this), - checkServerResigned, - conditionalUninstallSelendroid, - conditionalInstallSelendroid, - this.extractStringsSelendroid.bind(this), - this.uninstallApp.bind(this), - this.installAppForTest.bind(this), - this.forwardPort.bind(this), - this.initUnicode.bind(this), - this.pushSettingsApp.bind(this), - this.pushUnlock.bind(this), - this.unlock.bind(this), - this.pushSelendroid.bind(this), - this.waitForServer.bind(this) - ], function (err) { - if (err) return cb(err); - async.series([ - this.createSession.bind(this), - this.initAutoWebview.bind(this) - ], function (err, res) { - if (err) return cb(err); - // `createSession` returns session id, so send that along - cb(null, res[0]); - }); - }.bind(this)); -}; - -Selendroid.prototype.pushSelendroid = function (cb) { - var instrumentWith = this.args.appPackage + ".selendroid/" + - "io.selendroid.server.ServerInstrumentation"; - this.adb.instrument(this.args.appPackage, this.args.appActivity, instrumentWith, cb); -}; - -Selendroid.prototype.checkInternetPermissionForApp = function (cb) { - var apk = this.args.app; - this.adb.hasInternetPermissionFromManifest(apk, function (err, hasInternetPermission) { - if (err) return cb(err); - if (hasInternetPermission) { - return cb(); - } - else { - var errorMessg = "apk does not have INTERNET permissions. Selendroid needs internet " + - "permission to proceed, please check if you have in your " + - "AndroidManifest.xml"; - cb(new Error(errorMessg)); - } - }); -}; - -Selendroid.prototype.checkSelendroidCerts = function (cb) { - var alreadyReturned = false - , checks = 0; - - var onDoneSigning = function () { - checks++; - if (checks === 2 && !alreadyReturned) { - cb(); - } - }; - - // these run in parallel - var apks = [this.selendroidServerPath, this.args.app]; - _.each(apks, function (apk) { - logger.debug("Checking signed status of " + apk); - this.adb.checkApkCert(apk, this.args.appPackage, function (err, isSigned) { - if (err) return cb(err); - if (isSigned) return onDoneSigning(); - this.adb.sign(apk, function (err) { - if (err && !alreadyReturned) { - alreadyReturned = true; - return cb(err); - } - onDoneSigning(); - }); - }.bind(this)); - }.bind(this)); -}; - -Selendroid.prototype.stop = function (ocb) { - var completeShutdown = function (cb) { - if (this.args.unicodeKeyboard && this.args.resetKeyboard && this.defaultIME) { - logger.debug('Resetting IME to \'' + this.defaultIME + '\''); - this.adb.setIME(this.defaultIME, function (err) { - if (err) { - // simply warn on error here, because we don't want to stop the shutdown - // process - logger.warn(err); - } - logger.debug("Stopping selendroid server"); - this.deleteSession(cb); - }.bind(this)); - } else { - logger.debug("Stopping selendroid server"); - this.deleteSession(cb); - } - }.bind(this); - - completeShutdown(function (err) { - if (err) return ocb(err); - - // Remove the app _after_ stopping Selendroid, or Selendroid will fail - if (this.args.fullReset) { - logger.debug("Removing app from device"); - this.uninstallApp(function (err) { - if (err) { - // simply warn on error here, because we don't want to stop the shutdown - // process - logger.warn(err); - } - ocb(); - }); - } else { - ocb(); - } - }.bind(this)); -}; - -Selendroid.prototype.keyevent = function (keycode, metastate, cb) { - this.adb.keyevent(keycode, function () { - cb(null, { - status: status.codes.Success.code - , value: null - }); - }); -}; - -/* - * Execute an arbitrary function and handle potential ADB disconnection before - * proceeding - */ -Selendroid.prototype.wrapActionAndHandleADBDisconnect = function (action, ocb) { - async.series([ - function (cb) { - action(cb); - }.bind(this) - , this.adb.restart.bind(this.adb) - , this.forwardPort.bind(this) - ], function (err) { - ocb(err); - }.bind(this)); -}; - -Selendroid.prototype.ensureServerExists = function (cb) { - logger.debug("Checking whether selendroid is built yet"); - var selBin = path.resolve(__dirname, "..", "..", "..", "build", "selendroid", - "selendroid.apk"); - fs.stat(selBin, function (err) { - if (err) { - logger.debug("Selendroid needs to be built; please run ./reset.sh " + - "--selendroid"); - return cb(err); - } - logger.debug("Selendroid server exists!"); - this.serverApk = selBin; - cb(null); - }.bind(this)); -}; - -Selendroid.prototype.waitForServer = function (cb) { - var waitMs = 20000 - , intMs = 800 - , start = Date.now(); - - var pingServer = function () { - this.proxyTo('/wd/hub/status', 'GET', null, function (err, res, body) { - if (body === null || typeof body === "undefined" || !body.trim()) { - if (Date.now() - start < waitMs) { - setTimeout(pingServer, intMs); - } else { - cb(new Error("Waited " + (waitMs / 1000) + " secs for " + - "selendroid server and it never showed up")); - } - } else { - logger.debug("Selendroid server is alive!"); - cb(null); - } - }); - }.bind(this); - - pingServer(); -}; - -Selendroid.prototype.createSession = function (cb) { - logger.debug("Listening for Selendroid logs"); - this.adb.logcat.on('log', function (logObj) { - if (/System/.test(logObj.message)) { - var type = ""; - if (/System\.err/.test(logObj.message)) { - type = " ERR"; - } - var msg = logObj.message.replace(/^.+: /, ''); - logger.debug("[SELENDROID" + type + "] " + msg); - } - }.bind(this)); - logger.debug("Creating Selendroid session"); - var data = {desiredCapabilities: this.capabilities}; - this.proxyTo('/wd/hub/session', 'POST', data, function (err, res, body) { - if (err) return cb(err); - - if (res.statusCode === 200 && body.sessionId) { - logger.debug("Successfully started selendroid session"); - this.selendroidSessionId = body.sessionId; - this.proxySessionId = this.selendroidSessionId; - this.adb.waitForActivity(this.args.appWaitPackage, this.args.appWaitActivity, 1800, - function (err) { - if (err) { - logger.debug("Selendroid hasn't started app yet, let's do it " + - "manually with adb.startApp"); - var onStart = function (err) { - if (err) return cb(err); - return cb(null, body.sessionId); - }.bind(this); - - return this.adb.startApp({ - pkg: this.args.appPackage, - activity: this.args.appActivity, - action: this.args.intentAction, - category: this.args.intentCategory, - flags: this.args.intentFlags, - waitPkg: this.args.appWaitPackage, - waitActivity: this.args.appWaitActivity, - optionalIntentArguments: this.args.optionalIntentArguments, - stopApp: !this.args.dontStopAppOnReset, - retry: false - }, onStart); - } - return cb(null, body.sessionId); - }.bind(this), 1800); - } else { - logger.error("Selendroid create session did not work. Status was " + - res.statusCode + " and body was " + body); - cb(new Error("Selendroid session creation did not work.")); - } - }.bind(this)); -}; - -Selendroid.prototype.deleteSession = function (cb) { - var url = '/wd/hub/session/' + this.selendroidSessionId; - this.proxyTo(url, 'DELETE', null, function (err, res) { - if (err) return cb(err); - if (res.statusCode !== 200) return cb(new Error("Status was not 200")); - this.adb.forceStop(this.args.appPackage, function (err) { - if (err) return cb(err); - this.adb.stopLogcat(cb); - }.bind(this)); - }.bind(this)); -}; - -Selendroid.prototype.proxyTo = proxyTo; - -Selendroid.prototype.insertSelendroidManifest = function (serverPath, cb) { - logger.debug("Inserting selendroid manifest"); - var newServerPath = this.selendroidServerPath - , newPackage = this.args.appPackage + '.selendroid' - , srcManifest = path.resolve(__dirname, '..', '..', '..', 'build', - 'selendroid', 'AndroidManifest.xml') - , dstDir = path.resolve(this.args.tmpDir, this.args.appPackage) - , dstManifest = path.resolve(dstDir, 'AndroidManifest.xml'); - - try { - fs.mkdirSync(dstDir); - } catch (e) { - if (e.message.indexOf("EEXIST") === -1) { - throw e; - } - } - fs.writeFileSync(dstManifest, fs.readFileSync(srcManifest, "utf8"), "utf8"); - async.series([ - function (cb) { mkdirp(dstDir, cb); }.bind(this), - function (cb) { this.adb.checkSdkBinaryPresent("aapt", cb); }.bind(this), - function (cb) { - this.adb.compileManifest(dstManifest, newPackage, - this.args.appPackage, cb); - }.bind(this), - function (cb) { - this.adb.insertManifest(dstManifest, serverPath, - newServerPath, cb); - }.bind(this) - ], cb); -}; - -Selendroid.prototype.setLocation = androidController.setLocation; -Selendroid.prototype.removeApp = androidController.removeApp; -Selendroid.prototype.unpackApp = androidController.unpackApp; - -Selendroid.prototype.translatePath = function (req) { - var path = req.originalUrl; - if (path.indexOf("contexts") !== -1) { - logger.debug("Temporarily translating 'contexts' to 'window_handles"); - path = path.replace("contexts", "window_handles"); - } else if (path.indexOf("context") !== -1) { - logger.debug("Temporarily translating 'context' to 'window'"); - path = path.replace("context", "window"); - } - req.originalUrl = path; -}; - -Selendroid.prototype.extractStringsSelendroid = function (cb) { - this.extractStrings(function () { - cb(); - }); -}; - -Selendroid.prototype.getStrings = function (language, stringFile, cb) { - if (this.language && this.language === language) { - // Return last strings - return cb(null, { - status: status.codes.Success.code, - value: this.apkStrings - }); - } - - // Extract and return strings - return this.extractStrings(function () { - cb(null, { - status: status.codes.Success.code, - value: this.apkStrings - }); - }.bind(this), language); -}; - - -_.extend(Selendroid.prototype, androidHybrid); -_.extend(Selendroid.prototype, androidContextController); - - -Selendroid.prototype.isChromedriverContext = function (windowName) { - return windowName === this.CHROMIUM_WIN; -}; - -Selendroid.prototype.getContexts = function (cb) { - var chromiumViews = []; - this.listWebviews(function (err, webviews) { - if (err) return cb(err); - if (_.contains(webviews, this.CHROMIUM_WIN)) { - chromiumViews = [this.CHROMIUM_WIN]; - } else { - chromiumViews = []; - } - - var selendroidViews = []; - var reqUrl = this.selendroidHost + ':' + this.args.selendroidPort + '/wd/hub/session/' + this.selendroidSessionId; - doRequest(reqUrl + '/window_handles', 'GET', {}, null, function (err, res) { - if (err) return cb(err); - selendroidViews = JSON.parse(res.body).value; - this.contexts = _.union(selendroidViews, chromiumViews); - logger.debug("Available contexts: " + JSON.stringify(this.contexts)); - cb(null, {sessionId: this.selendroidSessionId, status: status.codes.Success.code, value: this.contexts}); - }.bind(this)); - }.bind(this)); -}; - -Selendroid.prototype.defaultWebviewName = function () { - return this.WEBVIEW_WIN + "_0"; -}; - -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 (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 - // see https://code.google.com/p/selenium/source/browse/java/client/src/org/openqa/selenium/Keys.java - } else { - // encode the text - value = utf7.encode(value); - break; - } - } - } - 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'; - doRequest(reqUrl, 'POST', { value: [value] }, null, function (err) { - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code, - value: '' - }); - }); -}; - -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); -}; - -module.exports = Selendroid; diff --git a/lib/devices/android/uiautomator.js b/lib/devices/android/uiautomator.js deleted file mode 100644 index 0dd2330f..00000000 --- a/lib/devices/android/uiautomator.js +++ /dev/null @@ -1,223 +0,0 @@ -"use strict"; - -var _ = require('underscore') - , net = require('net') - , status = require("../../server/status.js") - , logger = require('../../server/logger.js').get('appium'); - -var noop = function () {}; - -var UiAutomator = function (adb, opts) { - this.adb = adb; - this.proc = null; - this.cmdCb = null; - this.socketClient = null; - this.restartBootstrap = false; - this.onSocketReady = noop; - this.alreadyExited = false; - this.onExit = noop; - this.shuttingDown = false; - this.webSocket = opts.webSocket; - this.systemPort = opts.systemPort; - this.resendLastCommand = function () {}; - this.appPackage = opts.appPackage; - this.disableAndroidWatchers = !!opts.disableAndroidWatchers; -}; - -UiAutomator.prototype.start = function (readyCb) { - logger.info("Starting App"); - this.adb.killProcessesByName('uiautomator', function (err) { - if (err) return readyCb(err); - logger.debug("Running bootstrap"); - var args = ["shell", "uiautomator", "runtest", "AppiumBootstrap.jar", "-c", - "io.appium.android.bootstrap.Bootstrap", "-e", "pkg", this.appPackage, "-e", "disableAndroidWatchers", this.disableAndroidWatchers]; - - this.alreadyExited = false; - this.onSocketReady = readyCb; - - this.proc = this.adb.spawn(args); - this.proc.on("error", function (err) { - logger.error("Unable to spawn adb: " + err.message); - if (!this.alreadyExited) { - this.alreadyExited = true; - readyCb(new Error("Unable to start Android Debug Bridge: " + - err.message)); - } - }.bind(this)); - this.proc.stdout.on('data', this.outputStreamHandler.bind(this)); - this.proc.stderr.on('data', this.errorStreamHandler.bind(this)); - this.proc.on('exit', this.exitHandler.bind(this)); - }.bind(this)); -}; - -UiAutomator.prototype.setExitHandler = function (onExit) { - this.onExit = onExit; -}; - -UiAutomator.prototype.exitHandler = function (code) { - this.cmdCb = null; - if (this.socketClient) { - this.socketClient.end(); - this.socketClient.destroy(); - this.socketClient = null; - } - - if (!this.alreadyExited) { - this.alreadyExited = true; - if (this.shuttingDown) { - logger.debug("UiAutomator shut down normally"); - } - this.onExit(code); - } -}; - -UiAutomator.prototype.checkForSocketReady = function (output) { - if (/Appium Socket Server Ready/.test(output)) { - this.socketClient = net.connect(this.systemPort, function () { - this.debug("Connected!"); - this.onSocketReady(null); - }.bind(this)); - this.socketClient.setEncoding('utf8'); - var oldData = ''; - this.socketClient.on('data', function (data) { - this.debug("Received command result from bootstrap"); - try { - data = JSON.parse(oldData + data); - oldData = ''; - } catch (e) { - logger.debug("Stream still not complete, waiting"); - oldData += data; - return; - } - if (this.cmdCb) { - var next = this.cmdCb; - this.cmdCb = null; - next(data); - } else { - this.debug("Got data when we weren't expecting it, ignoring:"); - this.debug(JSON.stringify(data)); - } - }.bind(this)); - } -}; - -UiAutomator.prototype.sendAction = function (action, params, cb) { - if (typeof params === "function") { - cb = params; - params = {}; - } - var extra = {action: action, params: params}; - this.sendCommand('action', extra, cb); -}; - -UiAutomator.prototype.sendCommand = function (type, extra, cb) { - if (this.cmdCb !== null) { - logger.warn("Trying to run a command when one is already in progress. " + - "Will spin a bit and try again"); - var start = Date.now(); - var timeoutMs = 10000; - var intMs = 200; - var waitForCmdCbNull = function () { - if (this.cmdCb === null) { - this.sendCommand(type, extra, cb); - } else if ((Date.now() - start) < timeoutMs) { - setTimeout(waitForCmdCbNull, intMs); - } else { - cb(new Error("Never became able to push strings since a command " + - "was in process")); - } - }.bind(this); - waitForCmdCbNull(); - } else if (this.socketClient) { - this.resendLastCommand = function () { - this.sendCommand(type, extra, cb); - }.bind(this); - if (typeof extra === "undefined" || extra === null) { - extra = {}; - } - var cmd = {cmd: type}; - cmd = _.extend(cmd, extra); - var cmdJson = JSON.stringify(cmd) + "\n"; - this.cmdCb = cb; - var logCmd = cmdJson.trim(); - if (logCmd.length > 1000) { - logCmd = logCmd.substr(0, 1000) + "..."; - } - this.debug("Sending command to android: " + logCmd); - this.socketClient.write(cmdJson); - } else { - cb({ - status: status.codes.UnknownError.code - , value: "Tried to send command to non-existent Android socket, " + - "maybe it's shutting down?" - }); - } -}; - -UiAutomator.prototype.shutdown = function (cb) { - this.onExit = cb; - this.shuttingDown = true; - this.sendCommand('shutdown', null, function () { - logger.debug("Sent shutdown command, waiting for UiAutomator to stop..."); - setTimeout(function () { - if (!this.alreadyExited) { - logger.warn("UiAutomator did not shut down fast enough, calling it gone"); - this.alreadyExited = true; - cb(); - } - }.bind(this), 7000); - }.bind(this)); -}; - -UiAutomator.prototype.outputStreamHandler = function (output) { - this.checkForSocketReady(output); - this.handleBootstrapOutput(output); -}; - -UiAutomator.prototype.handleBootstrapOutput = function (output) { - // for now, assume all intentional logging takes place on one line - // and that we don't get half-lines from the stream. - // probably bad assumptions - output = output.toString().trim(); - var lines = output.split("\n"); - var re = /^\[APPIUM-UIAUTO\] (.+)\[\/APPIUM-UIAUTO\]$/; - var match; - _.each(lines, function (line) { - line = line.trim(); - if (line !== '') { - match = re.exec(line); - if (match) { - logger.debug("[BOOTSTRAP] " + match[1]); - - var alertRe = /Emitting system alert message/; - if (alertRe.test(line)) { - logger.debug("Emitting alert message..."); - this.webSocket.sockets.emit('alert', {message: line}); - } - } else { - // The dump command will always disconnect UiAutomation. - // Detect the crash then restart UiAutomation. - if (line.indexOf("UiAutomationService not connected") !== -1) { - this.restartBootstrap = true; - } - var log = "[UIAUTOMATOR STDOUT] " + line; - logger.debug(log.grey); - } - } - }.bind(this)); -}; - -UiAutomator.prototype.errorStreamHandler = function (output) { - var lines = output.toString().split("\n"); - _.each(lines, function (line) { - logger.debug(("[UIAUTOMATOR STDERR] " + line).yellow); - }); -}; - -UiAutomator.prototype.debug = function (msg) { - if (this.debugMode) { - logger.debug("[UIAUTOMATOR] " + msg); - } -}; - -module.exports = UiAutomator; diff --git a/lib/devices/common.js b/lib/devices/common.js deleted file mode 100644 index 0de3283a..00000000 --- a/lib/devices/common.js +++ /dev/null @@ -1,382 +0,0 @@ -"use strict"; - -var errors = require('../server/errors.js') - , request = require('request') - , _ = require('underscore') - , exec = require('child_process').exec - , status = require("../server/status.js") - , logger = require('../server/logger.js').get('appium') - , logDeprecationWarning = require('../helpers.js').logDeprecationWarning; - -var UnknownError = errors.UnknownError - , ProtocolError = errors.ProtocolError; - -exports.respond = function (response, cb) { - if (typeof response === 'undefined') { - cb(null, ''); - } else { - if (typeof(response) !== "object") { - cb(new UnknownError(), response); - } else if (!('status' in response)) { - cb(new ProtocolError('Status missing in response from device'), response); - } else { - var status = parseInt(response.status, 10); - if (isNaN(status)) { - cb(new ProtocolError('Invalid status in response from device'), response); - } else { - response.status = status; - cb(null, response); - } - } - } -}; - -exports.proxy = function (command, cb) { - logger.debug('Pushing command to appium work queue: ' + JSON.stringify(command)); - this.push([command, cb]); -}; - -exports.proxyWithMinTime = function (command, ms, cb) { - var start = Date.now(); - logger.debug('Pushing command to appium work queue: ' + JSON.stringify(command)); - this.push([command, function () { - var args = Array.prototype.slice.call(arguments, 0); - var waitNeeded = ms - (Date.now() - start); - if (waitNeeded > 0) { - setTimeout(function () { - cb.apply(null, args); - }, waitNeeded); - } else { - cb.apply(null, args); - } - }]); -}; - -exports.resetTimeout = function () { - if (this.onResetTimeout) this.onResetTimeout(); -}; - -exports.waitForCondition = function (waitMs, condFn, cb, intervalMs) { - if (typeof intervalMs === "undefined") { - intervalMs = 500; - } - var begunAt = Date.now(); - var endAt = begunAt + waitMs; - var spin = function () { - condFn(function (condMet) { - var now = Date.now(); - var waited = now - begunAt; - var args = Array.prototype.slice.call(arguments); - if (condMet) { - cb.apply(this, args.slice(1)); - } else if (now < endAt) { - logger.debug("Waited for " + waited + "ms so far"); - setTimeout(spin, intervalMs); - } else { - logger.debug("Condition unmet after " + waited + "ms. Timing out."); - cb.apply(this, args.slice(1)); - } - }.bind(this)); - }.bind(this); - spin(); -}; - -exports.implicitWaitForCondition = function (condFn, cb) { - logger.debug("Waiting up to " + this.implicitWaitMs + "ms for condition"); - var _condFn = condFn; - condFn = function () { - var args = Array.prototype.slice.call(arguments, 0); - this.resetTimeout(); - _condFn.apply(this, args); - }.bind(this); - this.waitForCondition(this.implicitWaitMs, condFn, cb); -}; - -exports.doRequest = function (url, method, body, contentType, cb) { - if (typeof cb === "undefined" && typeof contentType === "function") { - cb = contentType; - contentType = null; - } - if (typeof contentType === "undefined" || contentType === null) { - contentType = "application/json;charset=UTF-8"; - } - if (!(/^https?:\/\//.exec(url))) { - url = 'http://' + url; - } - var opts = { - url: url - , method: method - }; - if (_.contains(['put', 'post', 'patch'], method.toLowerCase())) { - if (typeof body === "object") { - opts.json = body; - } else { - opts.body = body || ""; - } - } - // explicitly set these headers with correct capitalization to work around - // an issue in node/requests - logger.debug("Making http request with opts: " + JSON.stringify(opts)); - request(opts, function (err, res, body) { - cb(err, res, body); - }); -}; - -exports.isAppInstalled = function (isInstalledCommand, cb) { - logger.debug("Checking app install status using: " + isInstalledCommand); - exec(isInstalledCommand, function (error, stdout) { - cb(error, stdout); - }); -}; - -exports.removeApp = function (removeCommand, udid, bundleId, cb) { - logger.debug("Removing app using cmd: " + removeCommand); - exec(removeCommand, function (error) { - if (error !== null) { - cb(new Error('Unable to un-install [' + bundleId + '] from device with id [' + udid + ']. Error [' + error + ']')); - } else { - cb(error, 'Successfully un-installed [' + bundleId + '] from device with id [' + udid + ']'); - } - }); -}; - -exports.installApp = function (installationCommand, udid, unzippedAppPath, cb) { - logger.debug("Installing app using cmd: " + installationCommand); - exec(installationCommand, { maxBuffer: 524288 }, function (error) { - if (error !== null) { - cb(new Error('Unable to install [' + unzippedAppPath + '] to device with id [' + udid + ']. Error [' + error + ']')); - } else { - cb(error, 'Successfully unzipped and installed [' + unzippedAppPath + '] to device with id [' + udid + ']'); - } - }); -}; - -exports.unpackApp = function (req, packageExtension, cb) { - var reqAppPath = req.body.appPath; - if (reqAppPath.toLowerCase().substring(0, 4) === "http") { - req.device.downloadAndUnzipApp(reqAppPath, function (err, appPath) { - cb(appPath); - }); - } else if (reqAppPath.toLowerCase().substring(reqAppPath.length - 4) === ".zip") { - req.device.unzipLocalApp(reqAppPath, function (err, appPath) { - cb(appPath); - }); - } else if (reqAppPath.toLowerCase().substring(reqAppPath.length - 4) === packageExtension) { - cb(reqAppPath.toString()); - } else { - cb(null); - } -}; - -exports.proxyTo = function (endpoint, method, data, cb) { - if (endpoint[0] !== '/') { - endpoint = '/' + endpoint; - } - var url = 'http://' + this.proxyHost + ':' + this.proxyPort + endpoint; - exports.doRequest(url, method, data ? data : '', cb); -}; - -exports.parseExecuteResponse = function (response, cb) { - if ((response.value !== null) && (typeof response.value !== "undefined")) { - var wdElement = null; - if (!_.isArray(response.value)) { - if (typeof response.value.ELEMENT !== "undefined") { - wdElement = this.parseElementResponse(response.value); - if (wdElement === null) { - cb(null, { - status: status.codes.UnknownError.code - , value: "Error converting element ID atom for using in WD: " + response.value.ELEMENT - }); - } - response.value = wdElement; - } - } else { - var args = response.value; - for (var i = 0; i < args.length; i++) { - wdElement = args[i]; - if ((args[i] !== null) && (typeof args[i].ELEMENT !== "undefined")) { - wdElement = this.parseElementResponse(args[i]); - if (wdElement === null) { - cb(null, { - status: status.codes.UnknownError.code - , value: "Error converting element ID atom for using in WD: " + args[i].ELEMENT - }); - return; - } - args[i] = wdElement; - } - } - response.value = args; - } - } - return response; -}; - -exports.checkValidLocStrat = function (strat, includeWeb, cb) { - if (typeof includeWeb === "undefined") { - includeWeb = false; - } - var validStrats = [ - 'xpath', - 'id', - 'name', - 'class name' - ]; - var nativeStrats = [ - '-ios uiautomation', - 'accessibility id', - '-android uiautomator' - ]; - var webStrats = [ - 'link text', - 'css selector', - 'tag name', - 'partial link text' - ]; - var nativeDeprecations = {}; - var webDeprecations = {}; - var deprecations = {name: 'accessibility id'}; - - if (includeWeb) { - validStrats = validStrats.concat(webStrats); - deprecations = _.extend(deprecations, webDeprecations); - } else { - validStrats = validStrats.concat(nativeStrats); - deprecations = _.extend(deprecations, nativeDeprecations); - } - if (!_.contains(validStrats, strat)) { - logger.debug("Invalid locator strategy: " + strat); - cb(null, { - status: status.codes.UnknownCommand.code, - value: {message: "Invalid locator strategy: " + strat} - }); - return false; - } else { - if (_.has(deprecations, strat)) { - logDeprecationWarning('locator strategy', strat, deprecations[strat]); - } - return true; - } -}; - -exports.parseElementResponse = function (element) { - var objId = element.ELEMENT - , clientId = (5000 + this.webElementIds.length).toString(); - this.webElementIds.push(objId); - return {ELEMENT: clientId}; -}; - -exports.getAtomsElement = function (wdId) { - var atomsId; - try { - atomsId = this.webElementIds[parseInt(wdId, 10) - 5000]; - } catch (e) { - return null; - } - if (typeof atomsId === "undefined") { - return null; - } - return {'ELEMENT': atomsId}; -}; - -exports.useAtomsElement = function (elementId, failCb, cb) { - if (parseInt(elementId, 10) < 5000) { - logger.debug("Element with id " + elementId + " passed in for use with " + - "atoms, but it's out of our internal scope. Adding 5000"); - elementId = (parseInt(elementId, 10) + 5000).toString(); - } - var atomsElement = this.getAtomsElement(elementId); - if (atomsElement === null) { - failCb(null, { - status: status.codes.UnknownError.code - , value: "Error converting element ID for using in WD atoms: " + elementId - }); - } else { - cb(atomsElement); - } -}; - -exports.convertElementForAtoms = function (args, cb) { - for (var i = 0; i < args.length; i++) { - if (args[i] !== null && typeof args[i].ELEMENT !== "undefined") { - var atomsElement = this.getAtomsElement(args[i].ELEMENT); - if (atomsElement === null) { - cb(true, { - status: status.codes.UnknownError.code - , value: "Error converting element ID for using in WD atoms: " + args[i].ELEMENT - }); - return; - } - args[i] = atomsElement; - } - } - cb(null, args); -}; - -exports.jwpError = function (err, code, cb) { - if (typeof code === "function") { - cb = code; - code = null; - } - if (code) { - return cb(null, { - status: code - , value: err.message - }); - } - return cb(err); -}; - -exports.jwpSuccess = function (val, cb) { - if (typeof val === "function") { - cb = val; - val = null; - } - return cb(null, { - status: status.codes.Success.code - , value: val - }); -}; - -exports.jwpResponse = function (err, val, cb) { - if (typeof err === "function") { - return exports.jwpSuccess(err); - } - if (typeof val === "function") { - cb = val; - val = null; - } - if (err) { - return exports.jwpError(err, status.codes.UnknownError.code, cb); - } - return exports.jwpSuccess(val, cb); -}; - -// methods for appium session Settings -// These should really be on a Session object instead of the Device, but Sessions don't exist yet -exports.getSettings = function (cb) { - if (!this.settings) { - return cb(new UnknownError('No settings object for session')); - } - - return cb(null, { - status: status.codes.Success.code - , value: this.settings._settings - }); -}; - -// settings passed in update matching keys in existing settings object, create if not present. -exports.updateSettings = function (newSettings, cb) { - if (!this.settings) { - return cb(new UnknownError('No settings object for session')); - } - this.settings.update(newSettings, function (err) { - if (err) { - return cb(new UnknownError('err updating setting: ' + err)); - } - return cb(null, { - status: status.codes.Success.code - , value: null - }); - }); -}; diff --git a/lib/devices/device-settings.js b/lib/devices/device-settings.js deleted file mode 100644 index f7d98f77..00000000 --- a/lib/devices/device-settings.js +++ /dev/null @@ -1,64 +0,0 @@ -"use strict"; - -var EventEmitter = require('events').EventEmitter - , CamelBackPromise = require('camel-back-promise') // The straw that breaks the camels back. Instantiate by passing in a deferred promise and a number n. After the CamelBackPromise is called n times, the deferred promise is resolved. - , _ = require('underscore') - , Q = require('q') - ; - -// EventEmitter which emits an 'update' event every time a setting is changed. One call to "updateSettings" could trigger multiple update events. -// The value of the event is a tuple: {key, value, oldValue, callback}. -// Any listeners *MUST* call the function stored in 'callback' to tell the appium server that it it can continue to take commands. This is to prevent race conditions between enabling a setting and calling the next command. -// `callback` is a node-style callback, so passing an argument will cause the updateSettings command to throw an error. Note that the setting still gets changed, even if the subscriber doesn't affect the change. It's up to the client to resolve this case. -var DeviceSettings = function () { - this.init(); -}; - -_.extend(DeviceSettings.prototype, EventEmitter.prototype); - -DeviceSettings.prototype.init = function () { - - this._settings = {}; - - // this is where default settings can be declared - this._settings.ignoreUnimportantViews = false; -}; - -DeviceSettings.prototype.update = function (newSettings, cb) { - // if this code looks familiar, it's because it's modified from the underscore.js implementation of `extend` - if (!_.isObject(newSettings)) { - cb(); - } - var numListeners = this.listeners("update").length; - - var prop; - var pendingUpdates = []; - - for (prop in newSettings) { - if (hasOwnProperty.call(newSettings, prop)) { - var deferred = Q.defer(); - pendingUpdates.push(deferred.promise); - - var updatePayload = { - key: prop, - value: newSettings[prop], - oldValue: this._settings[prop], - callback: new CamelBackPromise(deferred, numListeners) - }; - - this._settings[prop] = newSettings[prop]; - this.emit("update", updatePayload); - } - } - - Q.all(pendingUpdates).then( - function () { - return cb(); - }, - function (err) { - return cb(err); - } - ); -}; - -module.exports = DeviceSettings; diff --git a/lib/devices/device.js b/lib/devices/device.js deleted file mode 100644 index 75c1c984..00000000 --- a/lib/devices/device.js +++ /dev/null @@ -1,189 +0,0 @@ -"use strict"; - -var fs = require('fs') - , path = require('path') - , _ = require('underscore') - , logger = require('../server/logger.js').get('appium') - , helpers = require('../helpers.js') - , isWindows = require('appium-support').system.isWindows - , copyLocalZip = helpers.copyLocalZip - , unzipApp = helpers.unzipApp - , downloadFile = helpers.downloadFile - , capConversion = require('../server/capabilities.js').capabilityConversions - , DeviceSettings = require('./device-settings.js') - , url = require('url'); - -var Device = function () { - throw new Error("Cannot instantiate Device directly"); -}; - -Device.prototype.init = function () { - this.appExt = null; - this.tempFiles = []; - this.args = {}; - this.capabilities = {}; - this.settings = new DeviceSettings(); -}; - -Device.prototype.configure = function (args, caps) { - _.extend(this.args, args); - _.extend(this.capabilities, caps); - _.each(caps, function (val, cap) { - this.setArgFromCap(cap, cap); - }.bind(this)); - if (this.args.tmpDir === null) { - // use a custom tmp dir to avoid loosing data and app - // when computer is restarted - this.args.tmpDir = process.env.APPIUM_TMP_DIR || - (isWindows() ? process.env.TEMP : "/tmp"); - } - if (this.args.traceDir === null) { - this.args.traceDir = path.resolve(this.args.tmpDir , 'appium-instruments'); - } -}; - -Device.prototype.setArgFromCap = function (arg, cap) { - if (typeof this.capabilities[cap] !== "undefined") { - if (_.has(capConversion, cap)) { - var key = capConversion[cap]; - if (!_.has(this.capabilities, key)) { - // if we have both 'version' and 'platformVersion' caps being sent, - // make sure 'platformVersion' isn't overwritten by 'version' - this.args[key] = this.capabilities[cap]; - } - } else { - this.args[arg] = this.capabilities[cap]; - } - } -}; - -Device.prototype.appString = function () { - return this.args.app ? this.args.app.toString() : ''; -}; - -Device.prototype.configureApp = function (cb) { - if (this.args.app.substring(0, 4).toLowerCase() === "http") { - this.configureDownloadedApp(cb); - } else { - this.configureLocalApp(cb); - } -}; - -Device.prototype.configureLocalApp = function (cb) { - this.args.app = path.resolve(this.args.app); - var appPath = this.args.app; - var origin = this.capabilities.app ? "desired caps" : "command line"; - var ext = appPath.substring(appPath.length - 4).toLowerCase(); - if (ext === this.appExt) { - this.args.app = appPath; - logger.debug("Using local app from " + origin + ": " + appPath); - fs.stat(appPath, function (err) { - if (err) { - return cb(new Error("Error locating the app: " + err.message)); - } - cb(); - }); - } else if (ext === ".zip" || ext === ".ipa") { - logger.debug("Using local " + ext + " from " + origin + ": " + appPath); - this.unzipLocalApp(appPath, function (zipErr, newAppPath) { - if (zipErr) return cb(zipErr); - if (ext === ".ipa") { - this.args.ipa = this.args.app; - } - this.args.app = newAppPath; - logger.debug("Using locally extracted app: " + this.args.app); - cb(); - }.bind(this)); - } else { - var msg = "Using local app, but didn't end in .zip, .ipa or " + this.appExt; - logger.error(msg); - cb(new Error(msg)); - } -}; - -Device.prototype.appIsPackageOrBundle = function (app) { - return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app); -}; - -Device.prototype.configureDownloadedApp = function (cb) { - var origin = this.capabilities.app ? "desired caps" : "command line"; - var appUrl; - try { - appUrl = url.parse(this.args.app); - } catch (err) { - cb("Invalid App URL (" + this.args.app + ")"); - } - var ext = path.extname(appUrl.pathname); - if (ext === ".apk") { - try { - downloadFile(url.format(appUrl), ".apk", function (err, appPath) { - if (err) return cb(err); - this.tempFiles.push(appPath); - this.args.app = appPath; - cb(); - }.bind(this)); - } catch (e) { - var err = e.toString(); - logger.error("Failed downloading app from appUrl " + appUrl.href); - cb(err); - } - } else if (ext === ".zip" || ext === ".ipa") { - try { - this.downloadAndUnzipApp(url.format(appUrl), function (zipErr, appPath) { - if (zipErr) { - cb(zipErr); - } else { - this.args.app = appPath; - logger.debug("Using extracted app: " + this.args.app); - cb(); - } - }.bind(this)); - logger.debug("Using downloadable app from " + origin + ": " + appUrl.href); - } catch (e) { - var err = e.toString(); - logger.error("Failed downloading app from appUrl " + appUrl.href); - cb(err); - } - } else { - cb("App URL (" + this.args.app + ") didn't seem to point to a .zip, " + - ".apk, or .ipa file"); - } -}; - -Device.prototype.unzipLocalApp = function (localZipPath, cb) { - try { - copyLocalZip(localZipPath, function (err, zipPath) { - if (err) return cb(err); - this.unzipApp(zipPath, cb); - }.bind(this)); - } catch (e) { - logger.error("Failed copying and unzipping local app: " + localZipPath); - cb(e); - } -}; - -Device.prototype.unzipApp = function (zipPath, cb) { - this.tempFiles.push(zipPath); - unzipApp(zipPath, this.appExt, function (err, appPath) { - if (err) { - cb(err, null); - } else { - this.tempFiles.push(appPath); - cb(null, appPath); - } - }.bind(this)); -}; - -Device.prototype.downloadAndUnzipApp = function (appUrl, cb) { - downloadFile(appUrl, ".zip", function (err, zipPath) { - if (err) return cb(err); - this.unzipApp(zipPath, cb); - }.bind(this)); -}; - -// get a specific setting -Device.prototype.getSetting = function (str) { - return this.settings._settings[str]; -}; - -module.exports = Device; diff --git a/lib/devices/firefoxos/atoms/gaia_apps.js b/lib/devices/firefoxos/atoms/gaia_apps.js deleted file mode 100644 index 2c71cc1b..00000000 --- a/lib/devices/firefoxos/atoms/gaia_apps.js +++ /dev/null @@ -1,239 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -var GaiaApps = { - - normalizeName: function(name) { - return name.replace(/[- ]+/g, '').toLowerCase(); - }, - - getRunningApps: function() { - let runningApps = window.wrappedJSObject.WindowManager.getRunningApps(); - // Return a simplified version of the runningApps object which can be - // JSON-serialized. - let apps = {}; - for (let app in runningApps) { - let anApp = {}; - for (let key in runningApps[app]) { - if (["name", "origin", "manifest"].indexOf(key) > -1) { - anApp[key] = runningApps[app][key]; - } - } - apps[app] = anApp; - } - return apps; - }, - - getRunningAppOrigin: function(name) { - let runningApps = window.wrappedJSObject.WindowManager.getRunningApps(); - let origin; - - for (let property in runningApps) { - if (runningApps[property].name == name) { - origin = property; - } - } - - return origin; - }, - - getPermission: function(appName, permissionName) { - GaiaApps.locateWithName(appName, function(app) { - console.log("Getting permission '" + permissionName + "' for " + appName); - var mozPerms = navigator.mozPermissionSettings; - var result = mozPerms.get( - permissionName, app.manifestURL, app.origin, false - ); - marionetteScriptFinished(result); - }); - }, - - setPermission: function(appName, permissionName, value) { - GaiaApps.locateWithName(appName, function(app) { - console.log("Setting permission '" + permissionName + "' for " + - appName + "to '" + value + "'"); - var mozPerms = navigator.mozPermissionSettings; - mozPerms.set( - permissionName, value, app.manifestURL, app.origin, false - ); - marionetteScriptFinished(); - }); - }, - - locateWithName: function(name, aCallback) { - var callback = aCallback || marionetteScriptFinished; - function sendResponse(app, appName, entryPoint) { - if (callback === marionetteScriptFinished) { - if (typeof(app) === 'object') { - var result = { - name: app.manifest.name, - origin: app.origin, - entryPoint: entryPoint || null, - normalizedName: appName - }; - callback(result); - } else { - callback(false); - } - } else { - callback(app, appName, entryPoint); - } - } - - let appsReq = navigator.mozApps.mgmt.getAll(); - appsReq.onsuccess = function() { - let apps = appsReq.result; - let normalizedSearchName = GaiaApps.normalizeName(name); - - for (let i = 0; i < apps.length; i++) { - let app = apps[i]; - let origin = null; - let entryPoints = app.manifest.entry_points; - if (entryPoints) { - for (let ep in entryPoints) { - let currentEntryPoint = entryPoints[ep]; - let appName = currentEntryPoint.name; - - if (normalizedSearchName === GaiaApps.normalizeName(appName)) { - return sendResponse(app, appName, ep); - } - } - } else { - let appName = app.manifest.name; - - if (normalizedSearchName === GaiaApps.normalizeName(appName)) { - return sendResponse(app, appName); - } - } - } - callback(false); - } - }, - - // Returns the number of running apps. - numRunningApps: function() { - let count = 0; - let runningApps = window.wrappedJSObject.WindowManager.getRunningApps(); - for (let origin in runningApps) { - count++; - } - return count; - }, - - // Kills the specified app. - kill: function(aOrigin, aCallback) { - var callback = aCallback || marionetteScriptFinished; - let runningApps = window.wrappedJSObject.WindowManager.getRunningApps(); - if (!runningApps.hasOwnProperty(aOrigin)) { - callback(false); - } - else { - window.addEventListener('appterminated', function gt_onAppTerminated() { - window.removeEventListener('appterminated', gt_onAppTerminated); - waitFor( - function() { - console.log("app with origin '" + aOrigin + "' has terminated"); - callback(true); - }, - function() { - let runningApps = - window.wrappedJSObject.WindowManager.getRunningApps(); - return !runningApps.hasOwnProperty(aOrigin); - } - ); - }); - console.log("terminating app with origin '" + aOrigin + "'"); - window.wrappedJSObject.WindowManager.kill(aOrigin); - } - }, - - // Kills all running apps, except the homescreen. - killAll: function() { - let originsToClose = []; - let that = this; - - let runningApps = window.wrappedJSObject.WindowManager.getRunningApps(); - for (let origin in runningApps) { - if (origin.indexOf('homescreen') == -1) { - originsToClose.push(origin); - } - } - - if (!originsToClose.length) { - marionetteScriptFinished(true); - return; - } - - originsToClose.slice(0).forEach(function(origin) { - GaiaApps.kill(origin, function() {}); - }); - - // Even after the 'appterminated' event has been fired for an app, - // it can still exist in the apps list, so wait until 1 or fewer - // apps are running (since we don't close the homescreen app). - waitFor( - function() { marionetteScriptFinished(true); }, - function() { return that.numRunningApps() <= 1; } - ); - }, - - // Launches app with the specified name (e.g., 'Calculator'); returns the - // app frame's id if successful, false if the app can't be found, or times - // out if the app frame can't be found after launching the app. - launchWithName: function(name) { - GaiaApps.locateWithName(name, function(app, appName, entryPoint) { - if (app) { - let windowManager = window.wrappedJSObject.WindowManager; - let runningApps = windowManager.getRunningApps(); - let origin = GaiaApps.getRunningAppOrigin(appName); - - let sendResponse = function() { - let app = runningApps[origin]; - let result = {frame: app.frame.firstChild, - src: app.iframe.src, - name: app.name, - origin: origin}; - marionetteScriptFinished(result); - }; - - if (windowManager.getDisplayedApp() == origin) { - console.log("app with origin '" + origin + "' is already running"); - sendResponse(); - } - else { - window.addEventListener('apploadtime', function apploadtime() { - window.removeEventListener('apploadtime', apploadtime); - waitFor( - function() { - console.log("app with origin '" + origin + "' has launched"); - sendResponse(); - }, - function() { - origin = GaiaApps.getRunningAppOrigin(appName); - return !!origin; - } - ); - }); - console.log("launching app with name '" + appName + "'"); - app.launch(entryPoint || null); - } - } else { - marionetteScriptFinished(false); - } - }); - }, - - /** - * Uninstalls the app with the specified name. - */ - uninstallWithName: function(name) { - GaiaApps.locateWithName(name, function uninstall(app) { - navigator.mozApps.mgmt.uninstall(app); - marionetteScriptFinished(false); - }); - } - -}; diff --git a/lib/devices/firefoxos/firefoxos-atoms.js b/lib/devices/firefoxos/firefoxos-atoms.js deleted file mode 100644 index e7b5419b..00000000 --- a/lib/devices/firefoxos/firefoxos-atoms.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; - -var fs = require("fs") - , path = require("path") - , atomsCache = {}; - -exports.get = function (atomName) { - var atomFileName = path.resolve(__dirname, "atoms/" + atomName + ".js"); - - // Check if we have already loaded an cached this Atom - if (!atomsCache.hasOwnProperty(atomName)) { - try { - atomsCache[atomName] = fs.readFileSync(atomFileName); - } catch (e) { - throw "Unable to load Atom '" + atomName + - "' from file '" + atomFileName + "'"; - } - } - - return atomsCache[atomName]; -}; - diff --git a/lib/devices/firefoxos/firefoxos.js b/lib/devices/firefoxos/firefoxos.js deleted file mode 100644 index 03bf31d5..00000000 --- a/lib/devices/firefoxos/firefoxos.js +++ /dev/null @@ -1,347 +0,0 @@ -"use strict"; - -var errors = require('../../server/errors.js') - , _ = require('underscore') - , Device = require('../device.js') - , logger = require('../../server/logger.js').get('appium') - , net = require('net') - , deviceCommon = require('../common.js') - , status = require("../../server/status.js") - , getAtomSrc = require('./firefoxos-atoms.js').get - , async = require('async') - , NotYetImplementedError = errors.NotYetImplementedError; - -var Firefox = function () { - this.init(); -}; - -_.extend(Firefox.prototype, Device.prototype); -Firefox.prototype._deviceInit = Device.prototype.init; -Firefox.prototype.init = function () { - this._deviceInit(); - this.capabilities = { - platform: 'LINUX' - , browserName: 'FirefoxOS' - , platformVersion: '18.0' - , webStorageEnabled: false - , takesScreenshot: true - , javascriptEnabled: true - , databaseEnabled: false - }; - this.queue = []; - this.progress = 0; - this.onStop = function () {}; - this.implicitWaitMs = 0; - this.commandTimeoutMs = 60 * 1000; - this.origCommandTimeoutMs = this.commandTimeoutMs; - this.commandTimeout = null; - this.asyncWaitMs = 0; - this.onConnect = null; - this.hasConnected = false; - this.fromActor = null; - this.socket = null; - this.receiveStream = null; - this.expectedRcvBytes = null; - this.lastCmd = null; - this.initCommandMap(); -}; - -Firefox.prototype._deviceConfigure = Device.prototype.configure; -Firefox.prototype.configure = function (args, caps, cb) { - this._deviceConfigure(args, caps); - this.args.systemPort = 2828; - cb(); -}; - -Firefox.prototype.start = function (cb, onDie) { - this.socket = new net.Socket(); - this.socket.on('close', function () { - onDie(0); - }); - this.socket.on('data', this.receive.bind(this)); - - this.socket.connect(this.args.systemPort, 'localhost', function () { }); - - - this.onConnect = function () { - logger.debug("Firefox OS socket connected"); - var mainCb = cb; - async.waterfall([ - function (cb) { this.getMarionetteId(cb); }.bind(this), - function (cb) { this.createSession(cb); }.bind(this), - function (cb) { this.launchAppByName(cb); }.bind(this), - function (frameId, cb) { this.frame(frameId, cb); }.bind(this) - ], function () { mainCb(null, this.sessionId); }.bind(this)); - }; - -}; - -Firefox.prototype.stop = function (cb) { - logger.debug("Stopping firefoxOs connection"); - this.proxy({type: 'deleteSession'}, function (err) { - if (err) return cb(err); - this.socket.destroy(); - cb(0); - }.bind(this)); -}; - -Firefox.prototype.getMarionetteId = function (cb) { - logger.debug("Getting marionette id"); - this.proxy({type: 'getMarionetteID'}, function (err, res) { - if (err) return cb(err); - this.fromActor = res.id; - cb(null); - }.bind(this)); -}; - -Firefox.prototype.createSession = function (cb) { - logger.debug("Creating firefox os session"); - this.proxy({type: 'newSession'}, function (err, res) { - if (err) return cb(err); - this.sessionId = res.value; - cb(null); - }.bind(this)); -}; - -Firefox.prototype.launchAppByName = function (cb) { - logger.debug("Launching our app by its name"); - var atomSrc = getAtomSrc('gaia_apps'); - var wrappedScript = atomSrc + - ";GaiaApps.launchWithName('" + this.args.app + "');"; - var cmd = { - type: 'executeAsyncScript' - , args: [] - , newSandbox: true - , specialPowers: false - , value: wrappedScript - }; - this.proxy(cmd, function (err, res) { - if (err) return cb(err); - cb(null, res.value.frame.ELEMENT); - }); -}; - -Firefox.prototype.receive = function (data) { - var parts, bytes, jsonData; - if (this.receiveStream) { - this.receiveStream += data.toString(); - logger.debug(data.length + " b more data received, adding to stream (" + this.receiveStream.length + " b)"); - try { - data = JSON.parse(this.receiveStream); - this.receiveStream = null; - } catch (e) { - logger.debug("Stream still not complete, waiting"); - return; - } - } else { - parts = data.toString().split(":"); - bytes = parseInt(parts[0], 10); - logger.debug("Data received, looking for " + bytes + " bytes"); - jsonData = parts.slice(1).join(":"); - try { - data = JSON.parse(jsonData); - } catch (e) { - logger.debug("Data did not parse, waiting for more"); - this.receiveStream = jsonData; - return; - } - } - logger.debug(JSON.stringify(data)); - if (!this.hasConnected) { - this.hasConnected = true; - this.fromActor = data.from; - this.onConnect(); - } else if (this.cbForCurrentCmd) { - var cb = this.cbForCurrentCmd; - this.progress--; - this.cbForCurrentCmd = null; - if (typeof cb === 'function') { - this.respond(data, cb); - } - this.executeNextCommand(); - } -}; - -Firefox.prototype.proxy = deviceCommon.proxy; -Firefox.prototype.getSettings = deviceCommon.getSettings; -Firefox.prototype.updateSettings = deviceCommon.updateSettings; - -Firefox.prototype.push = function (elem) { - this.queue.push(elem); - this.executeNextCommand(); -}; - - -Firefox.prototype.respond = function (data, cb) { - if (typeof data.error !== "undefined") { - cb(new Error(data.error.message)); - } else if (typeof data.id !== "undefined") { - cb(null, data); - } else { - cb(null, { - status: status.codes.Success.code - , value: data.value - }); - } -}; - - -Firefox.prototype.executeNextCommand = function () { - if (this.queue.length <= 0 || this.progress > 0) { - return; - } - - var target = this.queue.shift() - , command = target[0] - , cb = target[1]; - - this.cbForCurrentCmd = cb; - - this.progress++; - command.to = this.fromActor; - var cmdStr = JSON.stringify(command); - cmdStr = cmdStr.length + ':' + cmdStr; - - logger.debug("Sending command to firefoxOs: " + cmdStr); - this.socket.write(cmdStr); -}; - -Firefox.prototype.cmdMap = function () { - var elFn = function (elId) { return {element: elId }; }; - return { - implicitWait: ['setSearchTimeout'] - , getUrl: ['getUrl'] - , findElement: ['findElement', function (strategy, selector) { - return { - using: strategy, - value: selector - }; - }, function (err, res, cb) { - if (err) return cb(err); - res.value = {ELEMENT: res.value}; - cb(null, res); - }] - , click: ['clickElement', elFn] - , setValue: ['sendKeysToElement', function (elId, val) { - return { - element: elId - , value: val.split("") - }; - }] - , getText: ['getElementText', elFn] - , getPageSource: ['getPageSource'] - , execute: ['executeScript', function (script, params) { - return { - value: script - , args: params - }; - }] - , frame: ['switchToFrame', elFn] - }; -}; - -Firefox.prototype.notImplementedCmds = function () { - return [ - 'equalsWebElement' - , 'findElements' - , 'findElementFromElement' - , 'findElementsFromElement' - , 'complexTap' - , 'flick' - , 'touchLongClick' - , 'touchDown' - , 'touchUp' - , 'touchMove' - , 'swipe' - , 'hideKeyboard' - , 'clear' - , 'getName' - , 'getAttribute' - , 'getCssProperty' - , 'getLocation' - , 'getSize' - , 'getWindowSize' - , 'getPageIndex' - , 'pressKeyCode' - , 'longPressKeyCode' - , 'keyevent' - , 'back' - , 'forward' - , 'refresh' - , 'keys' - , 'leaveWebView' - , 'elementDisplayed' - , 'elementEnabled' - , 'elementSelected' - , 'getAlertText' - , 'setAlertText' - , 'postAcceptAlert' - , 'postDismissAlert' - , 'asyncScriptTimeout' - , 'setOrientation' - , 'getOrientation' - , 'moveTo' - , 'clickCurrent' - , 'fakeFlickElement' - , 'executeAsync' - , 'title' - , 'submit' - , 'url' - , 'active' - , 'getWindowHandle' - , 'setWindow' - , 'closeWindow' - , 'getWindowHandles' - , 'receiveAsyncResponse' - , 'setValueImmediate' - , 'getCookies' - , 'setCookie' - , 'deleteCookie' - , 'deleteCookies' - , 'getCurrentActivity' - ]; -}; - -Firefox.prototype.initCommandMap = function () { - var nyiCmds = this.notImplementedCmds(); - // create controller functions dynamically for implemented commands - _.each(this.cmdMap(), function (cmdInfo, controller) { - if (_.contains(nyiCmds, controller)) { - throw new Error("Controller " + controller + " is listed in both " + - "implemented and not-yet-implemented lists. Fix this " + - "before moving on!"); - } - this[controller] = function () { - var args = Array.prototype.slice.call(arguments, 0); - var cb; - var outerCb = args[args.length - 1]; - if (typeof cmdInfo[2] === 'function') { - cb = function (err, res) { - cmdInfo[2](err, res, outerCb); - }; - } else { - cb = outerCb; - } - args = args.slice(0, args.length - 1); - var cmd = { - type: cmdInfo[0] - }; - if (typeof cmdInfo[1] === 'function') { - cmd = _.extend(cmd, cmdInfo[1].apply(this, args)); - } else if (typeof cmdInfo[1] === "undefined" && args.length > 0) { - cmd = _.extend(cmd, {value: args[0]}); - } - this.proxy(cmd, cb); - }.bind(this); - }.bind(this)); - // throw not yet implemented for any command in nyi list - _.each(nyiCmds, function (controller) { - this[controller] = function () { - var args = Array.prototype.slice.call(arguments, 0); - var cb = args[args.length - 1]; - cb(new NotYetImplementedError(), null); - }.bind(this); - }.bind(this)); -}; - -module.exports = Firefox; diff --git a/lib/devices/ios/instruments.js b/lib/devices/ios/instruments.js deleted file mode 100644 index d01c359a..00000000 --- a/lib/devices/ios/instruments.js +++ /dev/null @@ -1,9 +0,0 @@ -// Gets instrument from npm package and setup logger -"use strict"; - -var logger = require('appium-instruments').logger, - Instruments = require('appium-instruments').Instruments; - -logger.init(require('../../server/logger.js').get('appium')); - -module.exports = Instruments; diff --git a/lib/devices/ios/ios-controller.js b/lib/devices/ios/ios-controller.js deleted file mode 100644 index 7b402f71..00000000 --- a/lib/devices/ios/ios-controller.js +++ /dev/null @@ -1,2411 +0,0 @@ -"use strict"; -var uuid = require('uuid-js') - , path = require('path') - , fs = require('fs') - , async = require('async') - , path = require('path') - , _ = require('underscore') - , exec = require('child_process').exec - , status = require("../../server/status.js") - , logger = require('../../server/logger.js').get('appium') - , helpers = require('../../helpers.js') - , escapeSpecialChars = helpers.escapeSpecialChars - , CookieMan = require('../../cookies') - , rotateImage = helpers.rotateImage - , request = require('request') - , mkdirp = require('mkdirp') - , AdmZip = require('adm-zip') - , deviceCommon = require('../common.js') - , js2xml = require("js2xmlparser2") - , xpath = require("xpath") - , XMLDom = require("xmldom") - , 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 - , Parser = require('../../server/parser.js') - , url = require('url'); - -var iOSController = {}; -var FLICK_MS = 3000; - -var NATIVE_WIN = "NATIVE_APP"; -var WEBVIEW_WIN = "WEBVIEW"; -var WEBVIEW_BASE = WEBVIEW_WIN + "_"; - -iOSController.NATIVE_WIN = NATIVE_WIN; -iOSController.WEBVIEW_BASE = WEBVIEW_BASE; - -var logTypesSupported = { - 'syslog': 'Logs for iOS applications on real devices and simulators', - 'crashlog': 'Crash logs for iOS applications on real devices and simulators', - '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 - return ext; -}; - -iOSController.createGetElementCommand = function (strategy, selector, ctx, - many) { - var ext = many ? 's' : ''; - var command = ""; - ctx = !ctx ? ctx : ", '" + ctx + "'" ; - switch (strategy) { - case "name": - command = ["au.getElement", ext, "ByName('", selector, "'", ctx, - ")"].join(''); - break; - case "accessibility id": - command = ["au.getElement", ext, "ByAccessibilityId('", selector, "'", ctx, - ")"].join(''); - break; - case "id": - command = ["au.getElement", ext, "ById('", selector, "')"].join(''); - break; - case "-ios uiautomation": - command = ["au.getElement", ext, "ByUIAutomation('", selector, "'", ctx, - ")"].join(''); - break; - default: - command = ["au.getElement", ext, "ByType('", selector, "'", ctx, - ")"].join(''); - } - - return command; -}; - -iOSController.findUIElementOrElements = function (strategy, selector, ctx, many, cb) { - if (strategy !== "xpath") { - selector = escapeSpecialChars(selector, "'"); - } - if (typeof ctx === "undefined" || !ctx) { - ctx = ''; - } else if (typeof ctx === "string") { - ctx = escapeSpecialChars(ctx, "'"); - } - - try { - selector = this.getSelectorForStrategy(strategy, selector); - } catch (e) { - return cb(null, { - status: status.codes.UnknownError.code - , value: e - }); - } - - if (!selector) return; - var doFind = function (findCb) { - if (strategy === "xpath") { - this.findUIElementsByXpath(selector, ctx, many, function (err, res) { - this.handleFindCb(err, res, many, findCb); - }.bind(this)); - } else if (strategy === "id") { - // For the ID strategy, we first want to handle the selector as an - // accessibility id. If no element is found by that strategy, we fall - // back to searching for the string. - var findByAxIdCmd = this.createGetElementCommand("accessibility id", selector, ctx, many); - this.proxy(findByAxIdCmd, function (err, res) { - this.handleFindCb(err, res, many, function (found, err, res) { - if (found) { - this.handleFindCb(err, res, many, findCb); - } else { - // Since no element was found using the accessibility id, we fall - // back to search by string. - var findByIdCmd = this.createGetElementCommand("id", - this.getLocalizedStringForSelector(selector), - ctx, many); - this.proxy(findByIdCmd, function (err, res) { - this.handleFindCb(err, res, many, findCb); - }.bind(this)); - } - }.bind(this)); - }.bind(this)); - } else { - var command = this.createGetElementCommand(strategy, selector, ctx, many); - this.proxy(command, function (err, res) { - this.handleFindCb(err, res, many, findCb); - }.bind(this)); - } - }.bind(this); - - if (_.contains(this.supportedStrategies, strategy)) { - this.implicitWaitForCondition(doFind, cb); - } else { - cb(null, { - status: status.codes.UnknownError.code - , value: "Sorry, we don't support the '" + strategy + "' locator " + - "strategy for iOS." - }); - } -}; - -var _pathFromDomNode = function (node) { - var path = null; - _.each(node.attributes, function (attrObj) { - if (attrObj.name === "path") { - path = attrObj.value; - } - }); - return path; -}; - -var _xmlSourceFromJson = function (jsonSource) { - if (typeof jsonSource === "string") { - jsonSource = JSON.parse(jsonSource); - } - return js2xml("AppiumAUT", jsonSource, { - wrapArray: {enabled: false, elementName: "element"}, - declaration: {include: true}, - prettyPrinting: {indentString: " "} - }); -}; - -var _performXpathQueryOnJson = function (selector, jsonSource) { - var xmlSource = _xmlSourceFromJson(jsonSource); - var dom = new XMLDom.DOMParser().parseFromString(xmlSource); - return xpath.select(selector, dom); -}; - -iOSController.findUIElementsByXpath = function (selector, ctx, many, curRetry, - cb) { - if (typeof curRetry === "function") { - cb = curRetry; - curRetry = 0; - } - this.getSourceForElementForXML(ctx, function (err, res) { - var selectedNodes; - if (err || res.status !== 0) { - logger.error("Error getting source, can't continue finding element " + - "by XPath"); - return cb(err, res); - } - try { - selectedNodes = _performXpathQueryOnJson(selector, res.value); - } catch (e) { - return cb(e); - } - if (!many) selectedNodes = selectedNodes.slice(0, 1); - var indexPaths = []; - // filter out elements without 'path' attribute - _.each(selectedNodes, function (node) { - var ip = _pathFromDomNode(node); - if (ip !== null) { - indexPaths.push(ip); - } - }); - - if (!many && indexPaths.length < 1) { - // if we don't have any matching nodes, and we wanted at least one, fail - return cb(null, { - status: status.codes.NoSuchElement.code, - value: null - }); - } else if (indexPaths.length < 1) { - // and if we don't have any matching nodes, return the empty array - return cb(null, { - status: status.codes.Success.code, - value: [] - }); - } - - // otherwise look up the actual element by its index path - var proxyCmd; - if (!many) { - proxyCmd = "au.getElementByIndexPath('" + indexPaths[0] + "')"; - } else { - var ipArrString = JSON.stringify(indexPaths); - proxyCmd = "au.getElementsByIndexPaths(" + ipArrString + ")"; - } - // having index paths means we think elements should be there. Sometimes - // uiauto lags in enabling us to get elements, so we retry a few times if - // it can't find elements we know should be there. see uiauto code - // for more logic - this.proxy(proxyCmd, function (err, res) { - if (err) return cb(err); - // we get a StaleElementReference if uiauto can't find an element - // by the path we mentioned - if (res.status !== status.codes.Success.code && - curRetry < 3) { - logger.debug("Got a warning from uiauto that some index paths " + - "could not be resolved, trying again"); - return setTimeout(function () { - this.findUIElementsByXpath(selector, ctx, many, curRetry + 1, cb); - }.bind(this), 300); - } - cb(err, res); - }.bind(this)); - }.bind(this)); -}; - -iOSController.getSourceForElementForXML = function (ctx, cb) { - var _cb = cb; - cb = function (err, res) { - if (err) return _cb(err); - - // TODO: all this json/xml logic is very expensive, we need - // to use a SAX parser instead. - if (res) { - if (res.value) res.value = JSON.stringify(res.value); - _cb(err, res); - } else { - // this should never happen but we've received bug reports; this will help us track down - // what's wrong in getTreeForXML - _cb(new Error("Bad response from getTreeForXML. Err was " + err + " and res was " + JSON.stringify(res))); - } - }; - if (!ctx) { - this.proxy("au.mainApp().getTreeForXML()", cb); - } else { - this.proxy("au.getElement('" + ctx + "').getTreeForXML()", cb); - } -}; - -iOSController.getLocalizedStringForSelector = function (selector) { - var newSelector = selector; - - var strings = this.localizableStrings; - if (strings) { - var localizedSelector = strings[selector]; - if (localizedSelector) { - newSelector = localizedSelector; - } else { - logger.debug("Id selector, '" + selector + "', not found in " + - "Localizable.strings."); - } - } - - return newSelector; -}; - -iOSController.getSelectorForStrategy = function (strategy, selector) { - var newSelector = selector; - if (strategy === 'class name') { - if (selector.indexOf('UIA') !== 0) { - throw new TypeError("The class name selector must use full UIA class " + - "names. Try 'UIA" + selector + "' instead."); - } - } - return newSelector; -}; - -iOSController.handleFindCb = function (err, res, many, findCb) { - if (!res) res = {}; - if (!res.value) { - res.status = status.codes.NoSuchElement.code; - } - if (!err && !many && res.status === 0) { - findCb(true, err, res); - } else if (!err && many && res.value && res.value.length > 0) { - findCb(true, err, res); - } else { - findCb(false, err, res); - } -}; - -iOSController.findWebElementOrElements = function (strategy, selector, ctx, many, cb) { - var ext = many ? 's' : ''; - var atomsElement = this.getAtomsElement(ctx); - var doFind = function (findCb) { - this.executeAtom('find_element' + ext, [strategy, selector, atomsElement], function (err, res) { - this.handleFindCb(err, res, many, findCb); - }.bind(this)); - }.bind(this); - this.implicitWaitForCondition(doFind, cb); -}; - -iOSController.findElementOrElements = function (strategy, selector, ctx, many, cb) { - if (deviceCommon.checkValidLocStrat(strategy, this.curContext, cb)) { - if (this.curContext) { - this.findWebElementOrElements(strategy, selector, ctx, many, cb); - } else { - this.findUIElementOrElements(strategy, selector, ctx, many, cb); - } - } -}; - -iOSController.findElement = function (strategy, selector, cb) { - this.findElementOrElements(strategy, selector, null, false, cb); -}; - -iOSController.findElements = function (strategy, selector, cb) { - this.findElementOrElements(strategy, selector, null, true, cb); -}; - -iOSController.findElementFromElement = function (element, strategy, selector, cb) { - this.findElementOrElements(strategy, selector, element, false, cb); -}; - -iOSController.findElementsFromElement = function (element, strategy, selector, cb) { - this.findElementOrElements(strategy, selector, element, true, cb); -}; - -iOSController.setValueImmediate = function (elementId, value, cb) { - value = escapeSpecialChars(value, "'"); - var command = ["au.getElement('", elementId, "').setValue('", value, "')"].join(''); - this.proxy(command, cb); -}; - -iOSController.setValue = function (elementId, value, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('click', [atomsElement], function (err, res) { - if (err) { - cb(err, res); - } else { - this.executeAtom('type', [atomsElement, value], cb); - } - }.bind(this)); - }.bind(this)); - } else { - value = escapeSpecialChars(value, "'"); - // de-escape \n so it can be used specially - value = value.replace(/\\\\n/g, "\\n"); - if (this.useRobot) { - var keysUrl = this.args.robotUrl + "/sendKeys"; - request.post({url: keysUrl, form: {keys: value}}, cb); - } else { - var command = ["au.getElement('", elementId, "').setValueByType('", value, "')"].join(''); - this.proxy(command, cb); - } - } -}; - -iOSController.replaceValue = function (elementId, value, cb) { - logger.debug("Not implemented for iOS, please use setValue as you would normally."); - return cb(new NotYetImplementedError(), null); -}; - -iOSController.useAtomsElement = deviceCommon.useAtomsElement; - -iOSController.click = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('click', [atomsElement], cb); - }.bind(this)); - } else { - if (this.useRobot) { - var locCmd = "au.getElement('" + elementId + "').rect()"; - this.proxy(locCmd, function (err, res) { - if (err) return cb(err, res); - var rect = res.value; - var pos = {x: rect.origin.x, y: rect.origin.y}; - var size = {w: rect.size.width, h: rect.size.height}; - var tapPoint = { x: pos.x + (size.w / 2), y: pos.y + (size.h / 2) }; - var tapUrl = this.args.robotUrl + "/tap"; - request.post({url:tapUrl, form: {x:tapPoint.x, y:tapPoint.y}}, cb); - }.bind(this)); - } else { - this.nativeTap(elementId, cb); - } - } -}; - -iOSController.nativeTap = function (elementId, cb) { - var command = ["au.tapById('", elementId, "')"].join(''); - this.proxy(command, cb); -}; - - -iOSController.nativeWebTap = function (elementId, cb) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('get_top_left_coordinates', [atomsElement], function (err, res) { - if (err || res.status !== 0) return cb(err, res); - var x = res.value.x, y = res.value.y; - this.executeAtom('get_size', [atomsElement], function (err, res) { - if (err || res.status !== 0) return cb(err, res); - var w = res.value.width, h = res.value.height; - var clickX = x + (w / 2); - var clickY = y + (h / 2); - this.curWebCoords = {x: clickX, y: clickY}; - this.clickWebCoords(function (err, res) { - // make sure real tap actually has time to register - setTimeout(function () { - cb(err, res); - }, 500); - }); - }.bind(this)); - }.bind(this)); - }.bind(this)); -}; - -iOSController.pushFile = function (base64Data, remotePath, cb) { - if (this.realDevice) { - logger.debug("Unsupported: cannot write files to physical device"); - return cb(new NotYetImplementedError(), null); - } - - logger.debug("Pushing " + remotePath + " to iOS simulator"); - - var writeFile = function (err, fullPath) { - if (err) return cb(err); - logger.debug("Attempting to write " + fullPath + "..."); - - async.series([ - function (cb) { - try { - if (fs.existsSync(fullPath)) { - logger.debug(fullPath + " already exists, deleting..."); - fs.unlinkSync(fullPath); - } - - mkdirp.sync(path.dirname(fullPath)); - - var content = new Buffer(base64Data, 'base64'); - var fd = fs.openSync(fullPath, 'w'); - fs.writeSync(fd, content, 0, content.length, 0); - fs.closeSync(fd); - logger.debug("Wrote " + content.length + "bytes to " + fullPath); - cb(null); - } catch (e) { - cb(e); - } - }.bind(this) - ], function (err) { - logger.debug("Returning response"); - if (err) return cb(err); - cb(null, { - status: status.codes.Success.code - }); - }); - }; - - this._getFullPath(remotePath, writeFile); -}; - -/* - * Get the full path to an iOS simulator file. - * Calls cb(err, fullFilePath) - * /Some/Path fetches a file relative to the root of the device's filesystem. - * /Applications/AppName.app/Some/Path fetches a file relative to the root of that Application's .app directory, adding in the GUID. - * So it looks something like: /Applications/GUID-GUID-GUID-GUID/Some/Path - */ -iOSController._getFullPath = function (remotePath, cb) { - var fullPath = ""; - var v = (this.args.platformVersion || this.iOSSDKVersion); - var simRoots = this.sim.getDirs(); - if (simRoots.length < 1) { - return cb(new Error("Could not find simulator root for SDK version " + - v + "; have you launched this sim before?")); - } else if (simRoots.length > 1) { - logger.warn("There were multiple simulator roots for version " + v + ". " + - "We may be pulling the file from the one you're not using!"); - } - - if (simRoots.length > 1) { - var filteredSimRoots = simRoots.filter(function (root) { - return fs.existsSync(root + "/Applications") || // ios7 - fs.existsSync(root + "/Containers/Data/Application"); // ios8 - }); - - if (filteredSimRoots.length > 0) { - simRoots = filteredSimRoots; - } - } - - var basePath = simRoots[0]; - - var appName = null; - - if (this.args.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) { - remotePath = remotePath.slice(4); - } - } else { - if (remotePath.indexOf("/") === 0) { - remotePath = remotePath.slice(1); - } - } - - if (remotePath.indexOf(appName) === 0) { - logger.debug("We want an app-relative file"); - - var findPath = basePath; - if (this.args.platformVersion >= 8) { - // the .app file appears in /Containers/Data and /Containers/Bundle both. We only want /Bundle - findPath = path.resolve(basePath, "Containers", "Bundle"); - } - findPath = findPath.replace(/\s/g, '\\ '); - - var findCmd = 'find ' + findPath + ' -name "' + appName + '"'; - exec(findCmd, function (err, stdout) { - if (err) return cb(err); - var appRoot = stdout.replace(/\n$/, ''); - var subPath = remotePath.substring(appName.length + 1); - fullPath = path.resolve(appRoot, subPath); - cb(null, fullPath); - }.bind(this)); - } else { - logger.debug("We want a sim-relative file"); - fullPath = path.resolve(basePath, remotePath); - cb(null, fullPath); - } -}; - - -iOSController.pullFile = function (remotePath, cb) { - logger.debug("Pulling " + remotePath + " from sim"); - if (this.realDevice) { - return cb(new NotYetImplementedError(), null); - } - - var readAndReturnFile = function (err, fullPath) { - if (err) return cb(err); - logger.debug("Attempting to read " + fullPath); - fs.readFile(fullPath, {encoding: 'base64'}, function (err, data) { - if (err) return cb(err); - cb(null, {status: status.codes.Success.code, value: data}); - }); - }; - - this._getFullPath(remotePath, readAndReturnFile); -}; - -iOSController.pullFolder = function (remotePath, cb) { - logger.debug("Pulling " + remotePath + " from sim"); - if (this.realDevice) { - return cb(new NotYetImplementedError(), null); - } - - var bufferOnSuccess = function (buffer) { - logger.debug("Converting in-memory zip file to base64 encoded string"); - var data = buffer.toString('base64'); - logger.debug("Returning in-memory zip file as base54 encoded string"); - cb(null, {status: status.codes.Success.code, value: data}); - }; - - var bufferOnFail = function (err) { - cb(new Error(err)); - }; - - var zipAndReturnFolder = function (err, fullPath) { - if (err) return cb(err); - logger.debug("Adding " + fullPath + " to an in-memory zip archive"); - var zip = new AdmZip(); - zip.addLocalFolder(fullPath); - zip.toBuffer(bufferOnSuccess, bufferOnFail); - }; - - this._getFullPath(remotePath, zipAndReturnFolder); - -}; - -iOSController.touchLongClick = function (elementId, cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.touchDown = function (elId, x, y, cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.touchUp = function (elementId, cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.touchMove = function (elId, startX, startY, cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.toggleData = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.toggleFlightMode = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.toggleWiFi = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.toggleLocationServices = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.getStrings = function (language, stringFile, cb) { - this.parseLocalizableStrings(language, stringFile, function () { - var strings = this.localizableStrings; - if (strings && strings.length >= 1) strings = strings[0]; - - cb(null, { - status: status.codes.Success.code - , value: strings - }); - }.bind(this)); -}; - -iOSController.executeAtom = function (atom, args, cb, alwaysDefaultFrame) { - var counter = this.executedAtomsCounter++; - var frames = alwaysDefaultFrame === true ? [] : this.curWebFrames; - this.returnedFromExecuteAtom[counter] = false; - this.processingRemoteCmd = true; - this.remote.executeAtom(atom, args, frames, function (err, res) { - this.processingRemoteCmd = false; - if (!this.returnedFromExecuteAtom[counter]) { - this.returnedFromExecuteAtom[counter] = true; - res = this.parseExecuteResponse(res); - cb(err, res); - } - }.bind(this)); - this.lookForAlert(cb, counter, 0, 5000); -}; - -iOSController.executeAtomAsync = function (atom, args, responseUrl, cb) { - var counter = this.executedAtomsCounter++; - this.returnedFromExecuteAtom[counter] = false; - this.processingRemoteCmd = true; - this.asyncResponseCb = cb; - this.remote.executeAtomAsync(atom, args, this.curWebFrames, responseUrl, function (err, res) { - this.processingRemoteCmd = false; - if (!this.returnedFromExecuteAtom[counter]) { - this.returnedFromExecuteAtom[counter] = true; - res = this.parseExecuteResponse(res); - cb(err, res); - } - }.bind(this)); - this.lookForAlert(cb, counter, 0, 5000); -}; - -iOSController.receiveAsyncResponse = function (asyncResponse) { - var asyncCb = this.asyncResponseCb; - //mark returned as true to stop looking for alerts; the js is done. - this.returnedFromExecuteAtom = true; - - if (asyncCb !== null) { - this.parseExecuteResponse(asyncResponse, asyncCb); - asyncCb(null, asyncResponse); - this.asyncResponseCb = null; - } else { - logger.warn("Received async response when we weren't expecting one! " + - "Response was: " + JSON.stringify(asyncResponse)); - } -}; - -iOSController.parseExecuteResponse = deviceCommon.parseExecuteResponse; -iOSController.parseElementResponse = deviceCommon.parseElementResponse; - -iOSController.lookForAlert = function (cb, counter, looks, timeout) { - setTimeout(function () { - if (typeof looks === 'undefined') { - looks = 0; - } - if (this.instruments !== null) { - if (!this.returnedFromExecuteAtom[counter] && looks < 11 && !this.selectingNewPage) { - logger.debug("atom did not return yet, checking to see if " + - "we are blocked by an alert"); - // temporarily act like we're not processing a remote command - // so we can proxy the alert detection functionality - this.alertCounter++; - this.proxy("au.alertIsPresent()", function (err, res) { - if (res !== null) { - if (res.value === true) { - logger.debug("Found an alert, returning control to client"); - this.returnedFromExecuteAtom[counter] = true; - cb(null, { - status: status.codes.Success.code - , value: '' - }); - } else { - // say we're processing remote cmd again - looks++; - setTimeout(this.lookForAlert(cb, counter, looks), 1000); - } - } - }.bind(this)); - } - } - }.bind(this), timeout); -}; - -iOSController.clickCurrent = function (button, cb) { - var noMoveToErr = { - status: status.codes.UnknownError.code - , value: "Cannot call click() before calling moveTo() to set coords" - }; - - if (this.isWebContext()) { - if (this.curWebCoords === null) { - return cb(null, noMoveToErr); - } - this.clickWebCoords(cb); - } else { - if (this.curCoords === null) { - return cb(null, noMoveToErr); - } - this.clickCoords(this.curCoords, cb); - } -}; - -iOSController.clickCoords = function (coords, cb) { - if (this.useRobot) { - var tapUrl = this.args.robotUrl + "/tap"; - request.post({url:tapUrl, form: {x:coords.x, y:coords.y}}, cb); - } else { - var opts = coords; - opts.tapCount = 1; - opts.duration = 0.3; - opts.touchCount = 1; - var command = ["au.complexTap(" + JSON.stringify(opts) + ")"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.translateWebCoords = function (coords, cb) { - var wvCmd = "au.getElementsByType('webview')" - , webviewIndex = this.webContextIndex(); - - // add static offset for safari in landscape mode - var yOffset = this.curOrientation === "LANDSCAPE" ? - this.landscapeWebCoordsOffset : - 0; - - // absolutize web coords - this.proxy(wvCmd, function (err, res) { - if (err) return cb(err, res); - if (res.value.length < 1) { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Could not find any webviews to click inside!" - }); - } - if (typeof res.value[webviewIndex] === "undefined") { - logger.warn("Could not find webview at index " + webviewIndex + ", " + - "taking last available one for clicking purposes"); - webviewIndex = res.value.length - 1; - } - var realDims, wvDims, wvPos; - var step1 = function () { - var wvId = res.value[webviewIndex].ELEMENT; - var locCmd = "au.getElement('" + wvId + "').rect()"; - this.proxy(locCmd, function (err, res) { - if (err) return cb(err, res); - var rect = res.value; - wvPos = {x: rect.origin.x, y: rect.origin.y}; - realDims = {w: rect.size.width, h: rect.size.height}; - next(); - }); - }.bind(this); - var step2 = function () { - var cmd = "(function () { return {w: document.width, h: document.height}; })()"; - this.remote.execute(cmd, function (err, res) { - wvDims = {w: res.result.value.w, h: res.result.value.h}; - next(); - }); - }.bind(this); - var next = function () { - if (wvDims && realDims && wvPos) { - var xRatio = realDims.w / wvDims.w; - var yRatio = realDims.h / wvDims.h; - var serviceBarHeight = 20; - if (parseFloat(this.args.platformVersion) >= 8) { - // ios8 includes the service bar height in the app - serviceBarHeight = 0; - } - var newCoords = { - x: wvPos.x + Math.round(xRatio * coords.x) - , y: wvPos.y + yOffset + Math.round(yRatio * coords.y) - serviceBarHeight - }; - logger.debug("Converted web coords " + JSON.stringify(coords) + - "into real coords " + JSON.stringify(newCoords)); - cb(newCoords); - } - }.bind(this); - step1(); - step2(); - }.bind(this)); -}; - -iOSController.clickWebCoords = function (cb) { - this.translateWebCoords(this.curWebCoords, function (coords) { - this.clickCoords(coords, cb); - }.bind(this)); -}; - -iOSController.submit = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('submit', [atomsElement], cb); - }.bind(this)); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.pressKeyCode = function (keycode, metastate, cb) { - cb(new NotImplementedError(), null); -}; - -iOSController.longPressKeyCode = function (keycode, metastate, cb) { - cb(new NotImplementedError(), null); -}; - -iOSController.keyevent = function (keycode, metastate, cb) { - cb(new NotImplementedError(), null); -}; - -iOSController.complexTap = function (tapCount, touchCount, duration, x, y, elementId, cb) { - var command; - var options = { - tapCount: tapCount - , touchCount: touchCount - , duration: duration - , x: x - , y: y - }; - if (this.useRobot) { - var tapUrl = this.args.robotUrl + "/tap"; - request.post({url:tapUrl, form: options}, cb); - } else { - 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); - } -}; - -iOSController.clear = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('clear', [atomsElement], cb); - }.bind(this)); - } else { - var command = ["au.getElement('", elementId, "').setValue('')"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.getText = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('get_text', [atomsElement], cb); - }.bind(this)); - } else { - var command = ["au.getElement('", elementId, "').text()"].join(''); - this.proxy(command, function (err, res) { - // in some cases instruments returns in integer. we only want a string - res.value = res.value ? res.value.toString() : ''; - cb(err, res); - }); - } -}; - -iOSController.getName = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - var script = "return arguments[0].tagName.toLowerCase()"; - this.executeAtom('execute_script', [script, [atomsElement]], cb); - }.bind(this)); - } else { - var command = ["au.getElement('", elementId, "').type()"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.getAttribute = function (elementId, attributeName, cb) { - if (this.isWebContext()) { - var atomsElement = this.getAtomsElement(elementId); - if (atomsElement === null) { - cb(null, { - status: status.codes.UnknownError.code - , value: "Error converting element ID for using in WD atoms: " + elementId - }); - } else { - this.executeAtom('get_attribute_value', [atomsElement, attributeName], cb); - } - } else { - if (_.contains(['label', 'name', 'value', 'values', 'hint'], attributeName)) { - var command = ["au.getElement('", elementId, "').", attributeName, "()"].join(''); - this.proxy(command, cb); - } else { - cb(null, { - status: status.codes.UnknownCommand.code - , value: "UIAElements don't have the attribute '" + attributeName + "'" - }); - } - } -}; - -iOSController.getLocation = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('get_top_left_coordinates', [atomsElement], cb); - }.bind(this)); - } else { - var command = ["au.getElement('", elementId, - "').getElementLocation()"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.getSize = function (elementId, cb) { - if (this.isWebContext()) { - var atomsElement = this.getAtomsElement(elementId); - if (atomsElement === null) { - cb(null, { - status: status.codes.UnknownError.code - , value: "Error converting element ID for using in WD atoms: " + elementId - }); - } else { - this.executeAtom('get_size', [atomsElement], cb); - } - } else { - var command = ["au.getElement('", elementId, "').getElementSize()"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.getWindowSize = function (windowHandle, cb) { - if (windowHandle !== "current") { - cb(null, { - status: status.codes.NoSuchWindow.code - , value: "Currently only getting current window size is supported." - }); - } - - if (this.isWebContext()) { - this.executeAtom('get_window_size', [], function (err, res) { - cb(null, { - status: status.codes.Success.code - , value: res - }); - }); - } else { - this.proxy("au.getWindowSize()", cb); - } -}; - -iOSController.mobileWebNav = function (navType, cb) { - this.remote.willNavigateWithoutReload = true; - this.executeAtom('execute_script', ['history.' + navType + '();', null], cb); -}; - -iOSController.back = function (cb) { - if (this.isWebContext()) { - this.mobileWebNav("back", cb); - } else { - var command = "au.back();"; - this.proxy(command, cb); - } -}; - -iOSController.forward = function (cb) { - if (this.isWebContext()) { - this.mobileWebNav("forward", cb); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.refresh = function (cb) { - if (this.isWebContext()) { - this.executeAtom('refresh', [], cb); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.getPageIndex = function (elementId, cb) { - if (this.isWebContext()) { - cb(new NotImplementedError(), null); - } else { - var command = ["au.getElement('", elementId, "').pageIndex()"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.keys = function (keys, cb) { - keys = escapeSpecialChars(keys, "'"); - if (this.isWebContext()) { - this.active(function (err, res) { - if (err || typeof res.value.ELEMENT === "undefined") { - return cb(err, res); - } - this.setValue(res.value.ELEMENT, keys, cb); - }.bind(this)); - } else { - var command = ["au.sendKeysToActiveElement('", keys, "')"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.frame = function (frame, cb) { - if (this.isWebContext()) { - var atom; - if (frame === null) { - this.curWebFrames = []; - logger.debug("Leaving web frame and going back to default content"); - cb(null, { - status: status.codes.Success.code - , value: '' - }); - } else { - if (typeof frame.ELEMENT !== "undefined") { - this.useAtomsElement(frame.ELEMENT, cb, function (atomsElement) { - this.executeAtom('get_frame_window', [atomsElement], function (err, res) { - if (this.checkSuccess(err, res, cb)) { - logger.debug("Entering new web frame: " + res.value.WINDOW); - this.curWebFrames.unshift(res.value.WINDOW); - cb(err, res); - } - }.bind(this)); - }.bind(this)); - } else { - atom = "frame_by_id_or_name"; - if (typeof frame === "number") { - atom = "frame_by_index"; - } - this.executeAtom(atom, [frame], function (err, res) { - if (this.checkSuccess(err, res, cb)) { - if (res.value === null || typeof res.value.WINDOW === "undefined") { - cb(null, { - status: status.codes.NoSuchFrame.code - , value: '' - }); - } else { - logger.debug("Entering new web frame: " + res.value.WINDOW); - this.curWebFrames.unshift(res.value.WINDOW); - cb(err, res); - } - } - }.bind(this)); - } - } - } else { - frame = frame ? frame : 'target.frontMostApp()'; - var command = ["wd_frame = ", frame].join(''); - this.proxy(command, cb); - } -}; - -iOSController.implicitWait = function (ms, cb) { - this.implicitWaitMs = parseInt(ms, 10); - logger.debug("Set iOS implicit wait to " + ms + "ms"); - cb(null, { - status: status.codes.Success.code - , value: null - }); -}; - -iOSController.asyncScriptTimeout = function (ms, cb) { - this.asyncWaitMs = parseInt(ms, 10); - logger.debug("Set iOS async script timeout to " + ms + "ms"); - cb(null, { - status: status.codes.Success.code - , value: null - }); -}; - -iOSController.pageLoadTimeout = function (ms, cb) { - this.pageLoadMs = parseInt(ms, 10); - if (this.remote) this.remote.pageLoadMs = this.pageLoadMs; - logger.debug("Set iOS page load timeout to " + ms + "ms"); - cb(null, { - status: status.codes.Success.code - , value: null - }); -}; - - -iOSController.elementDisplayed = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('is_displayed', [atomsElement], cb); - }.bind(this)); - } else { - var command = ["au.getElement('", elementId, "').isDisplayed()"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.elementEnabled = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('is_enabled', [atomsElement], cb); - }.bind(this)); - } else { - var command = ["au.getElement('", elementId, "').isEnabled() === 1"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.elementSelected = function (elementId, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('is_selected', [atomsElement], cb); - }.bind(this)); - } else { - var command = ["au.getElement('", elementId, "').isSelected()"].join(''); - this.proxy(command, cb); - } -}; - -iOSController.getCssProperty = function (elementId, propertyName, cb) { - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('get_value_of_css_property', [atomsElement, - propertyName], cb); - }.bind(this)); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.getPageSource = function (cb) { - if (this.isWebContext()) { - this.processingRemoteCmd = true; - var cmd = 'document.getElementsByTagName("html")[0].outerHTML'; - this.remote.execute(cmd, function (err, res) { - if (err) { - cb("Remote debugger error", { - status: status.codes.UnknownError.code - , value: res - }); - } else { - cb(null, { - status: status.codes.Success.code - , value: res.result.value - }); - } - this.processingRemoteCmd = false; - }.bind(this)); - } else { - this.getSourceForElementForXML(null, function (err, res) { - var xmlSource; - if (err || res.status !== 0) return cb(err, res); - try { - xmlSource = _xmlSourceFromJson(res.value); - } catch (e) { - return cb(e); - } - return cb(null, { - status: status.codes.Success.code - , value: xmlSource - }); - }.bind(this)); - } -}; - -iOSController.getAlertText = function (cb) { - this.proxy("au.getAlertText()", cb); -}; - -iOSController.setAlertText = function (text, cb) { - text = escapeSpecialChars(text, "'"); - this.proxy("au.setAlertText('" + text + "')", cb); -}; - -iOSController.postAcceptAlert = function (cb) { - this.proxy("au.acceptAlert()", cb); -}; - -iOSController.postDismissAlert = function (cb) { - this.proxy("au.dismissAlert()", cb); -}; - -iOSController.lock = function (secs, cb) { - this.proxy(["au.lock(", secs, ")"].join(''), cb); -}; - -iOSController.isLocked = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.background = function (secs, cb) { - this.proxy(["au.background(", secs, ")"].join(''), cb); -}; - -iOSController.getOrientation = function (cb) { - this.proxy("au.getScreenOrientation()", function (err, res) { - if (res && res.status === status.codes.Success.code) { - // keep track of orientation for our own purposes - logger.debug("Setting internal orientation to " + res.value); - this.curOrientation = res.value; - } - cb(err, res); - }); -}; - -iOSController.setOrientation = function (orientation, cb) { - var command = ["au.setScreenOrientation('", orientation, "')"].join(''); - this.proxy(command, function (err, res) { - if (res && res.status === 0) { - this.curOrientation = orientation; - } - cb(err, res); - }.bind(this)); -}; - -iOSController.getScreenshot = function (cb) { - var guid = uuid.create(); - var command = ["au.capture('screenshot", guid, "')"].join(''); - - var shotFolder = path.resolve(this.args.tmpDir, - "appium-instruments/Run 1/"); - if (!fs.existsSync(shotFolder)) { - mkdirp.sync(shotFolder); - } - - var shotPath = path.resolve(shotFolder, 'screenshot' + guid + '.png'); - // Retrying the whole screenshot process for three times. - async.retry(3, - function (cb) { - async.waterfall([ - function (cb) { this.getOrientation(function () { cb(); }); }.bind(this), - function (cb) { this.proxy(command, cb); }.bind(this), - function (response, cb) { - var data; - var screenshotWaitTimeout = (this.args.screenshotWaitTimeout || 10) * 1000; - logger.debug('Waiting ' + screenshotWaitTimeout + ' ms for screenshot to ge generated.'); - var startMs = Date.now(); - var lastErr; - async.until( - function () { return data || Date.now() - startMs > screenshotWaitTimeout; }, - function (cb) { - setTimeout(function () { - fs.readFile(shotPath, function (err, _data) { - lastErr = err; - if (!err) { data = _data; } - cb(); - }); - }, 300); - }, - function (err) { - if (!data) { - return cb(new Error("Timed out waiting for screenshot file. " + (lastErr || '').toString())); - } - cb(err, response, data); - } - ); - }.bind(this), - function (response, data, cb) { - // rotate if necessary - if (this.curOrientation === "LANDSCAPE") { - // need to rotate 90 deg CC - logger.debug("Rotating landscape screenshot"); - rotateImage(shotPath, -90, function (err) { - if (err) return cb(new Error("Could not rotate screenshot appropriately"), null); - fs.readFile(shotPath, function read(err, _data) { - if (err) return cb(new Error("Could not retrieve screenshot file following rotate. " + err.toString())); - cb(null, response, _data); - }); - }); - } else cb(null, response, data); - }.bind(this), - function (response, data, cb) { - var b64data = new Buffer(data).toString('base64'); - response.value = b64data; - cb(null, response); - } - ], cb); - }.bind(this), cb); -}; - -iOSController.fakeFlick = function (xSpeed, ySpeed, swipe, cb) { - var command = ""; - if (swipe) { - command = ["au.touchSwipeFromSpeed(", xSpeed, ",", ySpeed, ")"].join(''); - this.proxy(command, cb); - } else { - command = ["au.touchFlickFromSpeed(", xSpeed, ",", ySpeed, ")"].join(''); - this.proxyWithMinTime(command, FLICK_MS, cb); - } - -}; - -iOSController.fakeFlickElement = function (elementId, xoffset, yoffset, speed, cb) { - var command = ""; - if (this.isWebContext()) { - this.useAtomsElement(elementId, cb, function (atomsElement) { - this.executeAtom('get_top_left_coordinates', [atomsElement], function (err, res) { - if (err || res.status !== 0) return cb(err, res); - var x = res.value.x, y = res.value.y; - this.executeAtom('get_size', [atomsElement], function (err, res) { - if (err || res.status !== 0) return cb(err, res); - var w = res.value.width, h = res.value.height; - var clickX = x + (w / 2); - var clickY = y + (h / 2); - - this.translateWebCoords({x: clickX, y: clickY}, function (from) { - this.translateWebCoords({x: clickX + xoffset, y: clickY + yoffset}, function (to) { - // speed is not used because underlying UIATarget.flickFromTo doesn't support it - command = ["au.flick(", JSON.stringify({from: from, to: to}), ")"].join(''); - this.proxy(command, cb); - }.bind(this)); - }.bind(this)); - }.bind(this)); - }.bind(this)); - }.bind(this)); - } else { - command = ["au.getElement('", elementId, "').touchFlick(", xoffset, ",", - yoffset, ",", speed, ")"].join(''); - this.proxyWithMinTime(command, FLICK_MS, cb); - } -}; - -iOSController.drag = function (startX, startY, endX, endY, duration, touchCount, elId, destElId, cb) { - var command; - if (elId) { - if (this.isWebContext()) { - return cb(new NotYetImplementedError(), null); - } - command = ["au.getElement('", elId, "').drag(", startX, ',', startY, ',', - endX, ',', endY, ',', duration, ',', touchCount, ")"].join(''); - } else { - command = ["au.dragApp(", startX, ',', startY, ',', endX, ',', endY, ',', - duration, ")"].join(''); - } - // wait for device to complete swipe - this.proxy(command, function (err, res) { - setTimeout(function () { - cb(err, res); - }, duration * 1000); - }); -}; - -iOSController.swipe = function (startX, startY, endX, endY, duration, touchCount, elId, cb) { - this.drag(startX, startY, endX, endY, duration, touchCount, elId, null, cb); -}; - -iOSController.rotate = function (x, y, radius, rotation, duration, touchCount, elId, cb) { - var command; - var location = {'x' : x, 'y' : y}; - var options = {'duration' : duration, 'radius' : radius, 'rotation' : rotation, 'touchCount' : touchCount}; - if (elId) { - if (this.isWebContext()) { - return cb(new NotYetImplementedError(), null); - } - command = "au.getElement('" + elId + "').rotateWithOptions(" + JSON.stringify(location) + - "," + JSON.stringify(options) + ")"; - this.proxy(command, cb); - } else { - this.proxy("target.rotateWithOptions(" + JSON.stringify(location) + "," + JSON.stringify(options) + ")", cb); - } -}; - -iOSController.pinchClose = function (startX, startY, endX, endY, duration, - percent, steps, elId, cb) { - var command; - var fromPointObject = {'x' : startX, 'y' : startY}; - var toPointObject = {'x' : endX, 'y' : endY}; - if (elId) { - command = ["au.getElement('", elId, "').pinchCloseFromToForDuration(", - JSON.stringify(fromPointObject), ",", JSON.stringify(toPointObject), - ",", duration, ")"].join(''); - this.proxy(command, cb); - } else { - this.proxy("target.pinchCloseFromToForDuration(" + JSON.stringify(fromPointObject) + - "," + JSON.stringify(toPointObject) + "," + duration + ")", cb); - } -}; - -iOSController.pinchOpen = function (startX, startY, endX, endY, duration, - percent, steps, elId, cb) { - var command; - var fromPointObject = {'x' : startX, 'y' : startY}; - var toPointObject = {'x' : endX, 'y' : endY}; - if (elId) { - if (this.isWebContext()) { - return cb(new NotYetImplementedError(), null); - } - command = ["au.getElement('", elId, "').pinchOpenFromToForDuration(", - JSON.stringify(fromPointObject), ",", JSON.stringify(toPointObject), ",", - duration + ")"].join(''); - this.proxy(command, cb); - } else { - this.proxy("target.pinchOpenFromToForDuration(" + JSON.stringify(fromPointObject) + - "," + JSON.stringify(toPointObject) + "," + duration + ")", cb); - } -}; - -iOSController.flick = function (startX, startY, endX, endY, touchCount, elId, - cb) { - var command; - if (elId) { - if (this.isWebContext()) { - return cb(new NotYetImplementedError(), null); - } - command = ["au.getElement('", elId, "').flick(", startX, ',', startY, ',', - endX, ',', endY, ',', touchCount, ")"].join(''); - } else { - command = ["au.flickApp(", startX, ',', startY, ',', endX, ',', endY, - ")"].join(''); - } - this.proxyWithMinTime(command, FLICK_MS, cb); -}; - -iOSController.scrollTo = function (elementId, text, direction, cb) { - if (this.isWebContext()) { - return cb(new NotYetImplementedError(), null); - } - // we ignore text for iOS, as the element is the one being scrolled too - var command = ["au.getElement('", elementId, "').scrollToVisible()"].join(''); - this.proxy(command, cb); -}; - -iOSController.scroll = function (elementId, direction, cb) { - direction = direction.charAt(0).toUpperCase() + direction.slice(1); - // By default, scroll the first scrollview. - var command = "au.scrollFirstView('" + direction + "')"; - if (elementId) { - if (this.isWebContext()) { - return cb(new NotYetImplementedError(), null); - } - // if elementId is defined, call scrollLeft, scrollRight, scrollUp, and scrollDown on the element. - command = ["au.getElement('", elementId, "').scroll", direction, "()"].join(''); - } - this.proxy(command, cb); -}; - -iOSController.shake = function (cb) { - this.proxy("au.shake()", cb); -}; - -iOSController.setLocation = function (latitude, longitude, altitude, horizontalAccuracy, verticalAccuracy, course, speed, cb) { - var coordinates = {'latitude' : latitude, 'longitude' : longitude}; - var hasOptions = altitude !== null || horizontalAccuracy !== null || verticalAccuracy !== null || course !== null || speed !== null; - if (hasOptions) { - var options = {}; - if (altitude !== null) { - options.altitude = altitude; - } - if (horizontalAccuracy !== null) { - options.horizontalAccuracy = horizontalAccuracy; - } - if (verticalAccuracy !== null) { - options.verticalAccuracy = verticalAccuracy; - } - if (course !== null) { - options.course = course; - } - if (speed !== null) { - options.speed = speed; - } - this.proxy("target.setLocationWithOptions(" + JSON.stringify(coordinates) + "," + - JSON.stringify(options) + ")", cb); - } else { - this.proxy("target.setLocation(" + JSON.stringify(coordinates) + ")", cb); - } -}; - -iOSController.hideKeyboard = function (strategy, key, cb) { - this.proxy("au.hideKeyboard(" + - "'" + strategy + "'" + - (key ? ",'" + key + "'" : "") + - ")", - cb); -}; - -iOSController.url = function (url, cb) { - if (this.isWebContext()) { - // make sure to clear out any leftover web frames - this.curWebFrames = []; - this.processingRemoteCmd = true; - this.remote.navToUrl(url, function () { - cb(null, { - status: status.codes.Success.code - , value: '' - }); - this.processingRemoteCmd = false; - }.bind(this)); - } else { - // in the future, detect whether we have a UIWebView that we can use to - // make sense of this command. For now, and otherwise, it's a no-op - cb(null, {status: status.codes.Success.code, value: ''}); - } -}; - -iOSController.getUrl = function (cb) { - if (this.isWebContext()) { - this.processingRemoteCmd = true; - this.remote.execute('window.location.href', function (err, res) { - if (err) { - cb("Remote debugger error", { - status: status.codes.JavaScriptError.code - , value: res - }); - } else { - cb(null, { - status: status.codes.Success.code - , value: res.result.value - }); - } - this.processingRemoteCmd = false; - }.bind(this)); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.active = function (cb) { - if (this.isWebContext()) { - this.executeAtom('active_element', [], function (err, res) { - cb(err, res); - }); - } else { - this.proxy("au.getActiveElement()", cb); - } -}; - -iOSController.isWebContext = function () { - return this.curContext !== null && this.curContext !== NATIVE_WIN; -}; - -iOSController.webContextIndex = function () { - return this.curContext.replace(WEBVIEW_BASE, "") - 1; -}; - -iOSController.getCurrentContext = function (cb) { - var err = null, response = null; - if (this.curContext) { - response = { - status: status.codes.Success.code - , value: WEBVIEW_BASE + this.curContext - }; - } else { - response = { - status: status.codes.Success.code - , value: 'NATIVE_APP' - }; - } - cb(err, response); -}; - -iOSController.getContexts = function (cb) { - this.getContextsAndViews(function (err, richCtxs) { - if (err) return cb(err); - var ctxs = _(richCtxs).map(function (richCtx) { - return richCtx.id; - }); - cb(null, { - status: status.codes.Success.code - , value: ctxs - }); - }); -}; - -iOSController.setContext = function (name, callback, skipReadyCheck) { - var cb = function (err, res) { - if (!err && res.status === status.codes.Success.code && this.perfLogEnabled) { - logger.debug('Starting performance log on ' + this.curContext); - this.logs.performance = new IOSPerfLog(this.remote); - this.logs.performance.startCapture(function () { - callback(err, res); - }); - } else { - callback(err, res); - } - }.bind(this); - - logger.debug("Attempting to set context to '" + name + "'"); - if (name === this.curContext) { - cb(null, { - status: status.codes.Success.code - , value: "" - }); - } else if (name === NATIVE_WIN || name === null) { - if (this.curContext === null || this.curContext === NATIVE_WIN) { - cb(null, { - status: status.codes.Success.code - , value: "" - }); - } else { - this.curContext = null; - if (this.args.udid) { - this.remote.disconnect(); - } - cb(null, { - status: status.codes.Success.code - , value: '' - }); - } - } else { - var idx = name.replace(WEBVIEW_BASE, ''); - if (idx === WEBVIEW_WIN) { - // allow user to pass in "WEBVIEW" without an index - idx = '1'; - } - var pickContext = function () { - if (_.contains(this.contexts, idx)) { - var pageIdKey = parseInt(idx, 10); - var next = function () { - this.processingRemoteCmd = true; - if (this.args.udid === null) { - this.remote.selectPage(pageIdKey, function () { - this.curContext = idx; - this.processingRemoteCmd = false; - cb(null, { - status: status.codes.Success.code - , value: '' - }); - }.bind(this), skipReadyCheck); - } else { - if (name === this.curContext) { - logger.debug("Remote debugger is already connected to window [" + name + "]"); - this.processingRemoteCmd = false; - cb(null, { - status: status.codes.Success.code - , value: name - }); - } else { - this.remote.disconnect(function () { - this.curContext = idx; - this.remote.connect(idx, function () { - this.processingRemoteCmd = false; - cb(null, { - status: status.codes.Success.code - , value: name - }); - }.bind(this)); - }.bind(this)); - } - } - }.bind(this); - next(); - } else { - cb(null, { - status: status.codes.NoSuchContext.code - , value: "Context '" + name + "' does not exist" - }); - } - }.bind(this); - - // only get contexts if they haven't already been gotten - if (typeof this.contexts === 'undefined') { - this.getContexts(function () { - pickContext(); - }.bind(this)); - } else { - pickContext(); - } - } -}; - -iOSController.getWindowHandle = function (cb) { - if (this.isWebContext()) { - var windowHandle = this.curContext; - var response = { - status: status.codes.Success.code - , value: windowHandle - }; - cb(null, response); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.massagePage = function (page) { - page.id = page.id.toString(); - return page; -}; - -iOSController.getWindowHandles = function (cb) { - if (!this.isWebContext()) { - return cb(new NotImplementedError(), null); - } - - this.listWebFrames(function (err, pageArray) { - if (err) { - return cb(err); - } - this.windowHandleCache = _.map(pageArray, this.massagePage); - var idArray = _.pluck(this.windowHandleCache, 'id'); - // since we use this.contexts to manage selecting debugger pages, make - // sure it gets populated even if someone did not use the - // getContexts method - if (!this.contexts) { - this.contexts = idArray; - } - cb(null, { - status: status.codes.Success.code - , value: idArray - }); - }.bind(this)); -}; - -iOSController.setWindow = function (name, cb, skipReadyCheck) { - if (!this.isWebContext()) { - return cb(new NotImplementedError(), null); - } - - if (_.contains(_.pluck(this.windowHandleCache, 'id'), name)) { - var pageIdKey = parseInt(name, 10); - var next = function () { - this.processingRemoteCmd = true; - if (this.args.udid === null) { - this.remote.selectPage(pageIdKey, function () { - this.curContext = pageIdKey.toString(); - this.curWindowHandle = pageIdKey.toString(); - cb(null, { - status: status.codes.Success.code - , value: '' - }); - this.processingRemoteCmd = false; - }.bind(this), skipReadyCheck); - } else { - if (name === this.curWindowHandle) { - logger.debug("Remote debugger is already connected to window [" + name + "]"); - cb(null, { - status: status.codes.Success.code - , value: name - }); - } else if (_.contains(_.pluck(this.windowHandleCache, 'id'), name)) { - this.remote.disconnect(function () { - this.curContext = name; - this.curWindowHandle = name; - this.remote.connect(name, function () { - cb(null, { - status: status.codes.Success.code - , value: name - }); - }); - }.bind(this)); - } else { - cb(null, { - status: status.codes.NoSuchWindow.code - , value: null - }); - } - } - }.bind(this); - next(); - } else { - cb(null, { - status: status.codes.NoSuchWindow.code - , value: null - }); - } -}; - -iOSController.closeWindow = function (cb) { - if (this.isWebContext()) { - var script = "return window.open('','_self').close();"; - this.executeAtom('execute_script', [script, []], function (err, res) { - setTimeout(function () { - cb(err, res); - }, 500); - }, true); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.setSafariWindow = function (windowId, cb) { - var checkPages = function (_cb) { - this.findElement('name', 'Pages', function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - this.getAttribute(res.value.ELEMENT, 'value', function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - if (res.value === "") { - _cb(err, res); - } else { - _cb(); - } - } - }.bind(this)); - } - }.bind(this)); - }.bind(this); - - var tapPages = function (_cb) { - this.findElement('name', 'Pages', function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - this.nativeTap(res.value.ELEMENT, function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - _cb(); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this); - - var selectPage = function (_cb) { - this.findElement('class name', 'UIAPageIndicator', function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - this.setValue(res.value.ELEMENT, windowId, function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - _cb(); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this); - - var doneWithPages = function (_cb) { - this.findElement('name', 'Done', function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - this.nativeTap(res.value.ELEMENT, function (err, res) { - if (this.checkSuccess(err, res, _cb)) { - _cb(); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this); - - async.series([checkPages, tapPages, selectPage, doneWithPages], - function (err) { cb(err); }); -}; - -iOSController.checkSuccess = function (err, res, cb) { - if (typeof res === "undefined") { - cb(err, { - status: status.codes.UnknownError.code - , value: "Did not get valid response from execution. Expected res to " + - "be an object and was " + JSON.stringify(res) - }); - return false; - } else if (err || res.status !== status.codes.Success.code) { - cb(err, res); - return false; - } - return true; -}; - -iOSController.execute = function (script, args, cb) { - if (this.isWebContext()) { - this.convertElementForAtoms(args, function (err, res) { - if (err) { - cb(null, res); - } else { - this.executeAtom('execute_script', [script, res], cb); - } - }.bind(this)); - } else { - this.proxy(script, 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) { - cb(null, res); - } else { - this.executeAtomAsync('execute_async_script', [script, args, this.asyncWaitMs], responseUrl, cb); - } - }.bind(this)); - } else { - this.proxy(script, cb); - } -}; - -iOSController.convertElementForAtoms = deviceCommon.convertElementForAtoms; - -iOSController.title = function (cb) { - if (this.isWebContext()) { - this.executeAtom('title', [], cb, true); - } else { - cb(new NotImplementedError(), null); - } -}; - -iOSController.moveTo = function (element, xoffset, yoffset, cb) { - this.getLocation(element, function (err, res) { - if (err) return cb(err, res); - var coords = { - x: res.value.x + xoffset - , y: res.value.y + yoffset - }; - if (this.isWebContext()) { - this.curWebCoords = coords; - this.useAtomsElement(element, cb, function (atomsElement) { - var relCoords = {x: xoffset, y: yoffset}; - this.executeAtom('move_mouse', [atomsElement, relCoords], cb); - }.bind(this)); - } else { - this.curCoords = coords; - cb(null, { - status: status.codes.Success.code - , value: null - }); - } - }.bind(this)); -}; - -iOSController.equalsWebElement = function (element, other, cb) { - var ctxElem = this.getAtomsElement(element); - var otherElem = this.getAtomsElement(other); - var retStatus = status.codes.Success.code - , retValue = false; - - // We assume if it's referencing the same element id, then it's equal - if (ctxElem.ELEMENT === otherElem.ELEMENT) { - retValue = true; - cb(null, { - status: retStatus - , value: retValue - }); - } else { - // ...otherwise let the browser tell us. - this.executeAtom('element_equals_element', [ctxElem.ELEMENT, otherElem.ELEMENT], cb); - } -}; - -iOSController.getCookies = function (cb) { - if (!this.isWebContext()) { - return cb(new NotImplementedError(), null); - } - var script = "return document.cookie"; - this.executeAtom('execute_script', [script, []], function (err, res) { - if (this.checkSuccess(err, res, cb)) { - var cookieMan = new CookieMan(res.value); - var cookies = []; - try { - cookies = _(cookieMan.cookies()).map(function (v,k) { - return {name: k, value: v}; - }); - } catch (e) { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Error parsing cookies from result, which was " + res.value - }); - } - cb(null, { - status: status.codes.Success.code - , value: cookies - }); - } - }.bind(this), true); -}; - -iOSController.setCookie = function (cookie, cb) { - if (!this.isWebContext()) { - return cb(new NotImplementedError(), null); - } - - cookie = _.clone(cookie); - // if 'Path' field is not specified, Safari will not update cookies as expected; eg issue #1708 - if (!cookie.path) { - cookie.path = "/"; - } - var webCookie = (new CookieMan()).cookie(cookie.name, cookie.value, { - expires: _.isNumber(cookie.expiry) ? (new Date(cookie.expiry * 1000)).toUTCString() : - cookie.expiry, path: cookie.path, domain: cookie.domain, - httpOnly: cookie.httpOnly, secure: cookie.secure}); - var script = "document.cookie = " + JSON.stringify(webCookie); - this.executeAtom('execute_script', [script, []], function (err, res) { - if (this.checkSuccess(err, res, cb)) { - cb(null, { - status: status.codes.Success.code - , value: true - }); - } - }.bind(this), true); -}; - -iOSController._deleteCookie = function (cookieName, cb) { - var webCookie = (new CookieMan()).removeCookie(cookieName , {path: "/"}); - var script = "document.cookie = " + JSON.stringify(webCookie); - this.executeAtom('execute_script', [script, []], function (err, res) { - if (this.checkSuccess(err, res, cb)) { - cb(null, { - status: status.codes.Success.code - , value: true - }); - } - }.bind(this), true); -}; - - -iOSController.deleteCookie = function (cookieName, cb) { - if (!this.isWebContext()) { - return cb(new NotImplementedError(), null); - } - // check cookie existence - var script = "return document.cookie"; - this.executeAtom('execute_script', [script, []], function (err, res) { - if (this.checkSuccess(err, res, cb)) { - var cookieMan = new CookieMan(res.value); - if (_.isUndefined(cookieMan.cookie(cookieName))) { - // nothing to delete - return cb(null, { - status: status.codes.Success.code - , value: true - }); - } - // delete cookie - this._deleteCookie(cookieName, cb); - } - }.bind(this), true); -}; - -iOSController.deleteCookies = function (cb) { - if (!this.isWebContext()) { - return cb(new NotImplementedError(), null); - } - this.getCookies(function (err, res) { - if (this.checkSuccess(err, res, cb)) { - var numCookies = res.value.length; - var cookies = res.value; - if (numCookies) { - var returned = false; - var deleteNextCookie = function (cookieIndex) { - if (!returned) { - var cookie = cookies[cookieIndex]; - this._deleteCookie(cookie.name, function (err, res) { - if (err || res.status !== status.codes.Success.code) { - returned = true; - cb(err, res); - } else if (cookieIndex < cookies.length - 1) { - deleteNextCookie(cookieIndex + 1); - } else { - returned = true; - cb(null, { - status: status.codes.Success.code - , value: true - }); - } - }); - } - }.bind(this); - deleteNextCookie(0); - } else { - cb(null, { - status: status.codes.Success.code - , value: false - }); - } - } - }.bind(this)); -}; - -iOSController.endCoverage = function (intentToBroadcast, ecOnDevicePath, cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.getCurrentActivity = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.getLogTypes = function (cb) { - return cb(null, { - status: status.codes.Success.code - , value: _.keys(logTypesSupported) - }); -}; - -iOSController.getLog = function (logType, cb) { - // Check if passed logType is supported - if (!_.has(logTypesSupported, logType)) { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Unsupported log type '" + logType + "' for this device, supported types : " + JSON.stringify(logTypesSupported) - }); - } - var logs; - try { - logs = this.logs[logType].getLogs(); - } catch (e) { - return cb(e); - } - // If logs captured successfully send response with data, else send error - if (logs) { - return cb(null, { - status: status.codes.Success.code - , value: logs - }); - } else { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Unknown error while getting logs" - }); - } -}; - -iOSController.handleTap = function (gesture, cb) { - var options = gesture.options; - var cmdBase; - if (options.element) { - cmdBase = ['au.getElement(\'', options.element, '\')']; - } else { - cmdBase = ['UIATarget.localTarget().frontMostApp()']; - } - - // start by getting the size and position of the element we are tapping - var cmd = cmdBase.concat('.rect()').join(''); - this.proxy(cmd, function (err, res) { - if (err) return cb(err); - - // default to center - var offsetX = 0.5; - var offsetY = 0.5; - - // there are times when there is no error but no value is returned - if (res.value) { - var rect = res.value; - var size = {w: rect.size.width, h: rect.size.height}; - - // default options x/y to center, no matter the container - options.x = (options.x || (size.w / 2)); - options.y = (options.y || (size.h / 2)); - - offsetX = options.x / size.w; - offsetY = options.y / size.h; - } - - var opts = { - tapOffset: { - x: offsetX, - y: offsetY - }, - tapCount: options.count || 1, - touchCount: 1 - }; - cmd = cmdBase.concat(['.tapWithOptions(', JSON.stringify(opts), ')']).join(''); - return this.proxy(cmd, cb); - }.bind(this)); -}; - -iOSController.performTouch = function (gestures, cb) { - if (gestures.length === 1 && gestures[0].action === 'tap') { - return this.handleTap(gestures[0], cb); - } - this.parseTouch(gestures, function (err, touchStateObjects) { - if (err !== null) return cb(err); - this.proxy("target.touch(" + JSON.stringify(touchStateObjects) + ")", cb); - }.bind(this)); -}; - -iOSController.parseTouch = function (gestures, cb) { - // `release` is automatic in iOS - if (_.last(gestures).action === 'release') { - gestures.pop(); - } - - var touchStateObjects = []; - var finishParsing = function () { - var prevPos = null; - - // we need to change the time (which is now an offset) - // and the position (which may be an offset) - var time = 0; - _.each(touchStateObjects, function (state) { - if (state.touch[0] === false) { - // if we have no position (this happens with `wait`) we need the previous one - state.touch[0] = prevPos; - } else if (state.touch[0].offset && prevPos) { - // the current position is an offset - state.touch[0].x += prevPos.x; - state.touch[0].y += prevPos.y; - } - // prevent wait => press => moveto crash - if (state.touch[0]) { - delete state.touch[0].offset; - prevPos = state.touch[0]; - } - - - var timeOffset = state.timeOffset; - time += timeOffset; - state.time = time; - - delete state.timeOffset; - }); - - cb(null, touchStateObjects); - }.bind(this); - - var needsPoint = function (action) { - return _.contains(['press', 'moveTo', 'tap', 'longPress'], action); - }; - - var cycleThroughGestures = function () { - var gesture = gestures.shift(); - if (typeof gesture === "undefined") { - return finishParsing(); - } - var tapPoint = false; - - if (needsPoint(gesture.action)) { // press, longPress, moveTo and tap all need a position - var elementId = gesture.options.element; - if (elementId) { - var command = ["au.getElement('", elementId, "').rect()"].join(''); - this.proxy(command, function (err, res) { - if (err) return cb(err); // short circuit and quit - - var rect = res.value; - var pos = {x: rect.origin.x, y: rect.origin.y}; - var size = {w: rect.size.width, h: rect.size.height}; - - if (gesture.options.x || gesture.options.y) { - tapPoint = { - offset: false, - x: pos.x + (gesture.options.x || 0), - y: pos.y + (gesture.options.y || 0) - }; - } else { - tapPoint = { - offset: false, - x: pos.x + (size.w / 2), - y: pos.y + (size.h / 2) - }; - } - - var touchStateObject = { - timeOffset: 0.2, - touch: [ - tapPoint - ] - }; - touchStateObjects.push(touchStateObject); - cycleThroughGestures(); - }.bind(this)); - } else { - // iOS expects absolute coordinates, so we need to save these as offsets - // and then translate when everything is done - tapPoint = { - offset: true, - x: (gesture.options.x || 0), - y: (gesture.options.y || 0) - }; - touchStateObject = { - timeOffset: 0.2, - touch: [ - tapPoint - ] - }; - touchStateObjects.push(touchStateObject); - cycleThroughGestures(); - } - } else { - // in this case we need the previous entry's tap point - tapPoint = false; // temporary marker - var offset = 0.2; - if (gesture.action === 'wait') { - if (typeof gesture.options.ms !== 'undefined' || gesture.options.ms !== null) { - offset = (parseInt(gesture.options.ms) / 1000); - } - } - var touchStateObject = { - timeOffset: offset, - touch: [ - tapPoint - ] - }; - touchStateObjects.push(touchStateObject); - cycleThroughGestures(); - } - }.bind(this); - - cycleThroughGestures(); -}; - -var mergeStates = function (states) { - var getSlice = function (states, index) { - var array = []; - for (var i = 0; i < states.length; i++) { - array.push(states[i][index]); - } - - return array; - }; - - var timeSequence = function (states) { - var seq = []; - _.each(states, function (state) { - var times = _.pluck(state, "time"); - seq = _.union(seq, times); - }); - - return seq.sort(); - }; - - // for now we will just assume that the times line up - var merged = []; - _.each(timeSequence(states), function (time, index) { - var slice = getSlice(states, index); - var obj = { - time: time, - touch: [] - }; - _.each(slice, function (action) { - obj.touch.push(action.touch[0]); - }); - merged.push(obj); - }); - return merged; -}; - -iOSController.performMultiAction = function (elementId, actions, cb) { - var states = []; - var cycleThroughActions = function () { - var action = actions.shift(); - if (typeof action === "undefined") { - var mergedStates = mergeStates(states); - return this.proxy("target.touch(" + JSON.stringify(mergedStates) + ")", cb); - } - - this.parseTouch(action, function (err, val) { - if (err) return cb(err); // short-circuit the loop and send the error up - states.push(val); - - cycleThroughActions(); - }.bind(this)); - }.bind(this); - cycleThroughActions(); -}; - -iOSController.openNotifications = function (cb) { - cb(new NotImplementedError(), null); -}; - -iOSController.isIMEActivated = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.availableIMEEngines = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.getActiveIMEEngine = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.activateIMEEngine = function (imeId, cb) { - cb(new NotYetImplementedError(), null); -}; - -iOSController.deactivateIMEEngine = function (cb) { - cb(new NotYetImplementedError(), null); -}; - -module.exports = iOSController; diff --git a/lib/devices/ios/ios-crash-log.js b/lib/devices/ios/ios-crash-log.js deleted file mode 100644 index 3408a4c9..00000000 --- a/lib/devices/ios/ios-crash-log.js +++ /dev/null @@ -1,72 +0,0 @@ -"use strict"; - -var path = require('path') - , fs = require('fs') - , _ = require('underscore') - , glob = require('glob') - , logger = require('../../server/logger.js').get('appium'); - -// Date-Utils: Polyfills for the Date object -require('date-utils'); - -var IosCrashLog = function () { - this.prevLogs = []; - - this.logsSinceLastRequest = []; - this.crashDir = path.resolve(process.env.HOME, "Library", "Logs", "DiagnosticReports"); -}; - -IosCrashLog.prototype.getCrashes = function (cb) { - glob(this.crashDir + "/*.crash", function (err, crashFiles) { - if (err) { - var msg = "There was a problem getting the crash list"; - logger.error(msg); - cb(new Error(msg)); - } - - cb(null, crashFiles); - - }.bind(this)); -}; - -IosCrashLog.prototype.filesToJSON = function (arr) { - var logs = []; - for (var i = 0; i < arr.length; i++) { - var filename = path.resolve(this.crashDir, arr[i]); - var stat = fs.statSync(filename); - var logObj = { - timestamp: stat.ctime, - level: 'ALL', - message: fs.readFileSync(filename, 'utf8') - }; - logs.push(logObj); - } - return logs; -}; - -IosCrashLog.prototype.startCapture = function (cb) { - this.getCrashes(function (err, fileList) { - if (err) cb(err); - - this.prevLogs = fileList; - cb(null); - }.bind(this)); -}; - -IosCrashLog.prototype.getLogs = function () { - var crashFiles = glob.sync(this.crashDir + "/*.crash"); - var diff = _.difference(crashFiles, this.prevLogs, this.logsSinceLastRequest); - this.logsSinceLastRequest = _.union(this.logsSinceLastRequest, diff); - - return this.filesToJSON(diff); -}; - -IosCrashLog.prototype.getAllLogs = function () { - var crashFiles = glob.sync(this.crashDir + "/*.crash"); - var logFiles = _.difference(crashFiles, this.prevLogs); - return this.filesToJSON(logFiles); -}; - -module.exports = function () { - return new IosCrashLog(); -}; diff --git a/lib/devices/ios/ios-hybrid.js b/lib/devices/ios/ios-hybrid.js deleted file mode 100644 index 3617d06a..00000000 --- a/lib/devices/ios/ios-hybrid.js +++ /dev/null @@ -1,237 +0,0 @@ -"use strict"; - -var logger = require('../../server/logger.js').get('appium') - , _ = require('underscore') - , deviceCommon = require('../common.js') - , rd = require('./remote-debugger.js') - , wkrd = require('./webkit-remote-debugger.js'); - -var iOSHybrid = {}; - -iOSHybrid.closeAlertBeforeTest = function (cb) { - this.proxy("au.alertIsPresent()", function (err, res) { - if (!err && res !== null && typeof res !== "undefined" && res.value === true) { - logger.debug("Alert present before starting test, let's banish it"); - this.proxy("au.dismissAlert()", function () { - logger.debug("Alert banished!"); - cb(null, true); - }); - } else { - cb(null, false); - } - }.bind(this)); -}; - -iOSHybrid.getDebuggerAppKey = function (appDict, bundleId) { - var appKey = null; - if (parseFloat(this.args.platformVersion) >= 8) { - _.each(appDict, function (data, key) { - if (data.bundleId === bundleId) { - appKey = key; - } - }); - // now we need to determine if we should pick a proxy for this instead - if (appKey) { - _.each(appDict, function (data, key) { - if (data.isProxy && data.hostId === appKey) { - logger.debug("Found separate bundleId " + data.bundleId + " " + - "acting as proxy for " + bundleId + ". Going to use its " + - "app ID key of " + key + " instead"); - appKey = key; - } - }); - } - } else { - if (_.has(appDict, bundleId)) { - appKey = bundleId; - } - } - if (appKey === null) { - throw new Error("Couldn't get app key from app dict"); - } - return appKey; -}; - -iOSHybrid.listWebFrames = function (cb, exitCb) { - var isDone = false; - if (!this.args.bundleId) { - logger.error("Can't enter web frame without a bundle ID"); - return cb(new Error("Tried to enter web frame without a bundle ID")); - } - var onDone = function (err, res) { - if (err) return cb(err); - this.processingRemoteCmd = false; - isDone = true; - if (Array.isArray(res) && res.length === 0) { - // we have no web frames, so disconnect from the remote debugger - this.stopRemote(); - } - cb(null, res); - }.bind(this); - - this.processingRemoteCmd = true; - if (this.remote !== null && this.args.bundleId !== null) { - if (this.args.udid !== null) { - this.remote.pageArrayFromJson(onDone); - } else { - if (this.remoteAppKey === null) { - return cb(new Error("remote debug app key must be set")); - } - this.remote.selectApp(this.remoteAppKey, function (res) { - onDone(null, res); - }); - } - } else { - if (this.args.udid !== null) { - this.remote = wkrd.init(exitCb); - this.remote.pageArrayFromJson(onDone); - } else { - this.remote = rd.init(exitCb); - this.remote.configureExtraOpts({ - useNewSafari: this.useNewSafari(), - pageLoadMs: this.pageLoadMs - }); - this.remote.connect(function (appDict) { - var appKey; - try { - appKey = this.getDebuggerAppKey(appDict, this.args.bundleId); - } catch (e) { - logger.warn("Remote debugger did not list " + this.args.bundleId + - " among its available apps"); - try { - appKey = this.getDebuggerAppKey(appDict, "com.apple.mobilesafari"); - logger.debug("Using mobile safari instead"); - } catch (e) { - return onDone(null, []); - } - } - logger.debug("Using remote debugger app key: " + appKey); - this.remoteAppKey = appKey; - this.remote.selectApp(appKey, function (res) { - onDone(null, res); - }); - }.bind(this), this.onPageChange.bind(this)); - var loopCloseRuns = 0; - var loopClose = function () { - loopCloseRuns++; - if (!isDone && loopCloseRuns < 3) { - this.closeAlertBeforeTest(function (err, didDismiss) { - if (!didDismiss) { - setTimeout(loopClose, 1000); - } - }); - } - }.bind(this); - setTimeout(loopClose, 4000); - } - } -}; - -iOSHybrid.onPageChange = function (pageArray) { - logger.debug("Remote debugger notified us of a new page listing"); - if (this.selectingNewPage) { - logger.debug("We're in the middle of selecting a page, ignoring"); - return; - } - var newIds = [] - , keyId = null; - _.each(pageArray, function (page) { - newIds.push(page.id.toString()); - if (page.isKey) { - keyId = page.id.toString(); - } - }); - var newPages = []; - _.each(newIds, function (id) { - if (!_.contains(this.contexts, id)) { - newPages.push(id); - this.contexts.push(id); - } - }.bind(this)); - var newPage = null; - if (this.curContext === null) { - logger.debug("We don't appear to have window set yet, ignoring"); - } else if (newPages.length) { - logger.debug("We have new pages, going to select page " + newPages[0]); - newPage = newPages[0]; - } else if (!_.contains(newIds, this.curContext.toString())) { - logger.debug("New page listing from remote debugger doesn't contain " + - "current window, let's assume it's closed"); - if (keyId !== null) { - logger.debug("Debugger already selected page " + keyId + ", " + - "confirming that choice."); - } else { - logger.error("Don't have our current window anymore, and there " + - "aren't any more to load! Doing nothing..."); - } - this.curContext = keyId; - this.remote.pageIdKey = parseInt(keyId, 10); - } else { - var dirty = function () { - var item = function (arr) { - return _.filter(arr, function (obj) { - return obj.id === this.curContext; - }, this)[0]; - }.bind(this); - - return !_.isEqual(item(this.contexts), item(pageArray)); - }.bind(this); - - // If a window gets navigated to an anchor it doesn't always fire a page callback event - // Let's check if we wound up in such a situation. - if (dirty()) { - this.remote.pageLoad(); - } - - logger.debug("New page listing is same as old, doing nothing"); - } - - if (newPage !== null) { - this.selectingNewPage = true; - this.remote.selectPage(parseInt(newPage, 10), function () { - this.selectingNewPage = false; - this.curContext = newPage; - if (this.onPageChangeCb !== null) { - this.onPageChangeCb(); - this.onPageChangeCb = null; - } - }.bind(this)); - } else if (this.onPageChangeCb !== null) { - this.onPageChangeCb(); - this.onPageChangeCb = null; - } - this.windowHandleCache = _.map(pageArray, this.massagePage); -}; - -iOSHybrid.getAtomsElement = deviceCommon.getAtomsElement; - -iOSHybrid.stopRemote = function (closeWindowBeforeDisconnecting) { - if (typeof closeWindowBeforeDisconnecting === "undefined") { - closeWindowBeforeDisconnecting = false; - } - if (!this.remote) { - logger.error("We don't appear to be in a web frame"); - throw new Error("Tried to leave a web frame but weren't in one"); - } else { - var disconnect = function () { - if (this.args.udid) { - this.remote.disconnect(function () {}); - } else { - this.remote.disconnect(); - } - this.curContext = null; - this.remoteAppKey = null; - this.curWebFrames = []; - this.curWebCoords = null; - this.remote = null; - }.bind(this); - - if (closeWindowBeforeDisconnecting) { - this.closeWindow(disconnect); - } else { - disconnect(); - } - } -}; - -module.exports = iOSHybrid; diff --git a/lib/devices/ios/ios-log.js b/lib/devices/ios/ios-log.js deleted file mode 100644 index 9c6c7657..00000000 --- a/lib/devices/ios/ios-log.js +++ /dev/null @@ -1,270 +0,0 @@ -"use strict"; - -var spawn = require('win-spawn') - , through = require('through') - , path = require('path') - , fs = require('fs') - , _ = require('underscore') - , which = require('which') - , glob = require('glob') - , logger = require('../../server/logger.js').get('appium') - , async = require('async') - , mkdirp = require('mkdirp') - , touch = require('touch') - , xcode = require('../../future.js').xcode; - -// Date-Utils: Polyfills for the Date object -require('date-utils'); - -var START_TIMEOUT = 10000; - -var IosLog = function (opts) { - this.udid = opts.udid; - this.simUdid = opts.simUdid; - this.showLogs = opts.showLogs; - this.proc = null; - this.onIosLogStart = null; - this.iosLogStarted = false; - this.iosLogStartTime = null; - this.calledBack = false; - this.loggingModeOn = true; - this.logs = []; - this.logRow = ""; - this.logsSinceLastRequest = []; - this.startTimer = null; - -}; - -IosLog.prototype.startCapture = function (cb) { - cb = _.once(cb); // only respond once - this.onIosLogStart = cb; - // Select cmd for log capture - if (this.udid) { - this.loggingModeOn = false; - var spawnEnv = _.clone(process.env); - - logger.debug("Attempting iOS device log capture via libimobiledevice idevicesyslog"); - which("idevicesyslog", function (err) { - if (!err) { - try { - this.proc = spawn("idevicesyslog", {env: spawnEnv}); - } catch (e) { - cb(e); - } - } else { - logger.warn("Could not capture device log using libimobiledevice idevicesyslog. Libimobiledevice probably isn't installed"); - logger.debug("Attempting iOS device log capture via deviceconsole"); - var limdDir = path.resolve(__dirname, - "../../../build/deviceconsole"); - spawnEnv.PATH = process.env.PATH + ":" + limdDir; - spawnEnv.DYLD_LIBRARY_PATH = limdDir + ":" + process.env.DYLD_LIBRARY_PATH; - - // deviceconsole retrieves many old device log lines that came before it was - // started so filter those out until we encounter new log lines. - try { - this.proc = spawn("deviceconsole", ["-u", this.udid], {env: spawnEnv}); - } catch (e) { - cb(e); - } - } - - this.finishStartingLogCapture(cb); - }.bind(this)); - - } else { - xcode.getVersion(function (err, xcodeVersion) { - if (err) return cb(err); - - var ver = parseInt(xcodeVersion.split(".")[0], 10); - if (ver >= 5) { - var logsPath; - if (ver >= 6) { - logger.debug("Starting iOS 8.* simulator log capture"); - if (_.isUndefined(this.simUdid)) { - return cb(new Error("iOS8 log capture requires a sim udid")); - } - logsPath = path.resolve(process.env.HOME, "Library", "Logs", - "CoreSimulator", this.simUdid); - } else { - logger.debug("Starting iOS 7.* simulator log capture"); - logsPath = path.resolve(process.env.HOME, "Library", "Logs", - "iOS Simulator", "7.*"); - } - var errOnlyCb = function (cb) { return function (err) { cb(err); }; }; // makes waterfall safer - - var touchLog = function (cb) { - if (logsPath.indexOf('*') >= 0) { - return cb(); - } - async.series([ - function (cb) { mkdirp(logsPath, errOnlyCb(cb)); }, - function (cb) { touch(path.resolve(logsPath, "system.log"), errOnlyCb(cb)); }, - function (cb) { - fs.appendFile( - path.resolve(logsPath, "system.log"), - 'A new simulator is about to start!', - errOnlyCb(cb)); - } - ], errOnlyCb(cb)); - }; - - var getLogFiles = function (cb) { - glob(path.resolve(logsPath, "system.log"), function (err, files) { - if (err || files.length < 1) { - logger.error("Could not start log capture because no iOS " + - "simulator logs could be found at " + logsPath + "/system.log" + - "Logging will not be functional for this run"); - return cb(new Error('Could not start log capture')); - } - cb(null, files); - }); - }; - - var tailLogs = function (files, cb) { - var lastModifiedLogPath = files[0] - , lastModifiedLogTime = fs.statSync(files[0]).mtime; - _.each(files, function (file) { - var mtime = fs.statSync(file).mtime; - if (mtime > lastModifiedLogTime) { - lastModifiedLogPath = file; - lastModifiedLogTime = mtime; - } - }); - this.proc = spawn("tail", ["-f", "-n", "1", lastModifiedLogPath]); - // -n 1 is used so that tail returns a line that lets this process know that the - // first line returned wasn't from stderr so it can tell the tailing was successful - this.finishStartingLogCapture(cb); - }.bind(this); - - async.waterfall([touchLog, getLogFiles, tailLogs], function (err) { - if (err) { - logger.warn('System log capture failed.'); - } - cb(); - }); - } else { - logger.debug("Starting iOS 6.* simulator log capture"); - this.proc = spawn("tail", ["-f", "-n", "1", "/var/log/system.log"]); - this.finishStartingLogCapture(cb); - } - }.bind(this)); - } -}; - -IosLog.prototype.finishStartingLogCapture = function (cb) { - if (!this.proc) { - var msg = "Could not capture device log"; - logger.warn(msg); - return cb(new Error(msg)); - } - - this.startTimer = setTimeout(function () { - var msg = "Log capture did not start in a reasonable amount of time"; - logger.error(msg); - if (!this.calledBack) { - this.calledBack = true; - cb(new Error(msg)); - } - }, START_TIMEOUT); - this.proc.stdout.setEncoding('utf8'); - this.proc.stderr.setEncoding('utf8'); - this.proc.on('error', function (err) { - clearTimeout(this.startTimer); - logger.error("iOS log capture failed: " + err.message); - if (!this.calledBack) { - this.calledBack = true; - cb(err); - } - }.bind(this)); - this.proc.stdout.pipe(through(this.onStdout.bind(this))); - this.proc.stderr.pipe(through(this.onStderr.bind(this))); -}; - -IosLog.prototype.stopCapture = function () { - logger.debug("Stopping iOS log capture"); - if (this.proc) { - this.proc.kill(); - } - this.proc = null; -}; - -IosLog.prototype.onStdout = function (data) { - clearTimeout(this.startTimer); - if (!this.calledBack) { - // don't store the first line of the log because it came before the sim or device was launched - this.onOutput(data); - } else { - this.logRow += data; - if (data.substr(-1, 1) === "\n") { - this.onOutput(data, ""); - this.logRow = ""; - } - } -}; - -IosLog.prototype.onStderr = function (data) { - clearTimeout(this.startTimer); - if (/execvp\(\)/.test(data)) { - logger.error("iOS log capture process failed to start"); - if (!this.calledBack) { - this.calledBack = true; - this.onIosLogStart(new Error("iOS log capture process failed to start")); - return; - } - } - this.onOutput(data, ' STDERR'); -}; - -IosLog.prototype.onOutput = function (data, prefix) { - if (!this.iosLogStarted) { - this.iosLogStarted = true; - this.iosLogStartTime = new Date(); - if (!this.calledBack) { - this.calledBack = true; - this.onIosLogStart(); - } - } - var logs = this.logRow.split("\n"); - _.each(logs, function (log) { - log = log.trim(); - if (log) { - // Turn logging on when the 1st row that has a new date comes through. - // - Some system.log lines do not have a leading date and therefore never pass the - // logRowDate.isAfter(this.iosLogStartTime) check so we require a state flag here, - // otherwise all the lines without dates get filtered out even if they are new. - if (!this.loggingModeOn) { - var logRowParts = log.split(/\s+/); - var logRowDate = new Date( - Date.parse(this.iosLogStartTime.getFullYear() + " " + logRowParts[0] + " " + logRowParts[1] + " " + logRowParts[2]) - ); - if (logRowDate.isAfter(this.iosLogStartTime)) { - this.loggingModeOn = true; - } - } - if (this.loggingModeOn) { - var logObj = { - timestamp: Date.now() - , level: 'ALL' - , message: log - }; - this.logs.push(logObj); - this.logsSinceLastRequest.push(logObj); - if (this.showLogs) logger.info('[IOS_SYSLOG_ROW ' + prefix + '] ' + log); - } - } - }.bind(this)); -}; - -IosLog.prototype.getLogs = function () { - var ret = this.logsSinceLastRequest; - this.logsSinceLastRequest = []; - return ret; -}; - -IosLog.prototype.getAllLogs = function () { - return this.logs; -}; - -module.exports = function (opts) { - return new IosLog(opts); -}; diff --git a/lib/devices/ios/ios-perf-log.js b/lib/devices/ios/ios-perf-log.js deleted file mode 100644 index e9a34717..00000000 --- a/lib/devices/ios/ios-perf-log.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict"; - -var logger = require('../../server/logger.js').get('appium'); - -// Date-Utils: Polyfills for the Date object -require('date-utils'); - -var MAX_EVENTS = 5000; - -var IosPerfLog = function (remote) { - this.remote = remote; - this.timelineEvents = []; - this.flushed = true; -}; - -IosPerfLog.prototype.startCapture = function (cb) { - logger.debug('Starting to capture timeline logs'); - this.timelineEvents = []; - return this.remote.startTimeline(this.onTimelineEvent.bind(this), cb); -}; - -IosPerfLog.prototype.stopCapture = function (cb) { - this.timelineEvents = null; - return this.remote.stopTimeline(cb); -}; - -IosPerfLog.prototype.onTimelineEvent = function (event) { - if (this.flushed) { - logger.debug('Flushing Timeline events'); - this.timelineEvents = []; - this.flushed = false; - } - this.timelineEvents.push(event); - if (this.timelineEvents.length > MAX_EVENTS) { - this.timelineEvents.shift(); - } -}; - -IosPerfLog.prototype.getLogs = function () { - this.flushed = true; - return this.timelineEvents; -}; - -IosPerfLog.prototype.getAllLogs = function () {}; - -module.exports = function (opts) { - return new IosPerfLog(opts); -}; diff --git a/lib/devices/ios/ios.js b/lib/devices/ios/ios.js deleted file mode 100644 index ee0089ab..00000000 --- a/lib/devices/ios/ios.js +++ /dev/null @@ -1,1707 +0,0 @@ -"use strict"; -var path = require('path') - , rimraf = require('rimraf') - , ncp = require('ncp').ncp - , fs = require('fs') - , _ = require('underscore') - , which = require('which') - , logger = require('../../server/logger.js').get('appium') - , exec = require('child_process').exec - , spawn = require('child_process').spawn - , bplistCreate = require('bplist-creator') - , bplistParse = require('bplist-parser') - , xmlplist = require('plist') - , Device = require('../device.js') - , Instruments = require('./instruments.js') - , xcode = require('../../future.js').xcode - , errors = require('../../server/errors.js') - , deviceCommon = require('../common.js') - , iOSLog = require('./ios-log.js') - , iOSCrashLog = require('./ios-crash-log.js') - , status = require("../../server/status.js") - , iDevice = require('node-idevice') - , async = require('async') - , iOSController = require('./ios-controller.js') - , iOSHybrid = require('./ios-hybrid.js') - , settings = require('./settings.js') - , Simulator = require('./simulator.js') - , prepareBootstrap = require('./uiauto').prepareBootstrap - , CommandProxy = require('./uiauto').CommandProxy - , UnknownError = errors.UnknownError - , binaryPlist = true - , Args = require("vargs").Constructor - , logCustomDeprecationWarning = require('../../helpers.js').logCustomDeprecationWarning; - -// XML Plist library helper -var parseXmlPlistFile = function (plistFilename, cb) { - try { - var xmlContent = fs.readFileSync(plistFilename, 'utf8'); - var result = xmlplist.parse(xmlContent); - return cb(null, result); - } catch (ex) { - return cb(ex); - } -}; - -var parsePlistFile = function (plist, cb) { - bplistParse.parseFile(plist, function (err, obj) { - if (err) { - logger.debug("Could not parse plist file (as binary) at " + plist); - logger.info("Will try to parse the plist file as XML"); - parseXmlPlistFile(plist, function (err, obj) { - if (err) { - logger.debug("Could not parse plist file (as XML) at " + plist); - return cb(err, null); - } else { - logger.debug("Parsed app Info.plist (as XML)"); - binaryPlist = false; - cb(null, obj); - } - }); - } else { - binaryPlist = true; - if (obj.length) { - logger.debug("Parsed app Info.plist (as binary)"); - cb(null, obj[0]); - } else { - cb(new Error("Binary Info.plist appears to be empty")); - } - } - }); -}; - -var IOS = function () { - this.init(); -}; - -_.extend(IOS.prototype, Device.prototype); - -IOS.prototype._deviceInit = Device.prototype.init; -IOS.prototype.init = function () { - this._deviceInit(); - this.appExt = ".app"; - this.capabilities = { - webStorageEnabled: false - , locationContextEnabled: false - , browserName: 'iOS' - , platform: 'MAC' - , javascriptEnabled: true - , databaseEnabled: false - , takesScreenshot: true - , networkConnectionEnabled: false - }; - this.xcodeVersion = null; - this.iOSSDKVersion = null; - this.iosSimProcess = null; - this.iOSSimUdid = null; - this.logs = {}; - this.instruments = null; - this.commandProxy = null; - this.initQueue(); - this.onInstrumentsDie = function () {}; - this.stopping = false; - this.cbForCurrentCmd = null; - this.remote = null; - this.curContext = null; - this.curWebFrames = []; - this.selectingNewPage = false; - this.processingRemoteCmd = false; - this.remoteAppKey = null; - this.windowHandleCache = []; - this.webElementIds = []; - this.implicitWaitMs = 0; - this.asyncWaitMs = 0; - this.pageLoadMs = 60000; - this.asyncResponseCb = null; - this.returnedFromExecuteAtom = {}; - this.executedAtomsCounter = 0; - this.curCoords = null; - this.curWebCoords = null; - this.onPageChangeCb = null; - this.supportedStrategies = ["name", "xpath", "id", "-ios uiautomation", - "class name", "accessibility id"]; - this.landscapeWebCoordsOffset = 0; - this.localizableStrings = {}; - this.keepAppToRetainPrefs = false; - this.isShuttingDown = false; -}; - -IOS.prototype._deviceConfigure = Device.prototype.configure; -IOS.prototype.configure = function (args, caps, cb) { - var msg; - this._deviceConfigure(args, caps); - this.setIOSArgs(); - - if (this.args.locationServicesAuthorized && !this.args.bundleId) { - msg = "You must set the bundleId cap if using locationServicesEnabled"; - logger.error(msg); - return cb(new Error(msg)); - } - - // on iOS8 we can use a bundleId to launch an app on the simulator, but - // on previous versions we can only do so on a real device, so we need - // to do a check of which situation we're in - var ios8 = caps.platformVersion && - parseFloat(caps.platformVersion) >= 8; - - if (!this.args.app && - !((ios8 || this.args.udid) && this.args.bundleId)) { - msg = "Please provide the 'app' or 'browserName' capability or start " + - "appium with the --app or --browser-name argument. Alternatively, " + - "you may provide the 'bundleId' and 'udid' capabilities for an app " + - "under test on a real device."; - logger.error(msg); - - 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); -}; - -IOS.prototype.setIOSArgs = function () { - this.args.withoutDelay = !this.args.nativeInstrumentsLib; - this.args.reset = !this.args.noReset; - this.args.initialOrientation = this.capabilities.deviceOrientation || - this.args.orientation || - "PORTRAIT"; - this.useRobot = this.args.robotPort > 0; - this.args.robotUrl = this.useRobot ? - "http://" + this.args.robotAddress + ":" + this.args.robotPort + "" : - null; - this.curOrientation = this.args.initialOrientation; - this.sock = path.resolve(this.args.tmpDir || '/tmp', 'instruments_sock'); - - this.perfLogEnabled = !!(typeof this.args.loggingPrefs === 'object' && this.args.loggingPrefs.performance); -}; - -IOS.prototype.configureApp = function (cb) { - var _cb = cb; - cb = function (err) { - if (err) { - err = new Error("Bad app: " + this.args.app + ". App paths need to " + - "be absolute, or relative to the appium server " + - "install dir, or a URL to compressed file, or a " + - "special app name. cause: " + err); - } - _cb(err); - }.bind(this); - - var app = this.appString(); - - // if the app name is a bundleId assign it to the bundleId property - if (!this.args.bundleId && this.appIsPackageOrBundle(app)) { - this.args.bundleId = app; - } - - if (app !== "" && app.toLowerCase() === "settings") { - if (parseFloat(this.args.platformVersion) >= 8) { - logger.debug("We're on iOS8+ so not copying preferences app"); - this.args.bundleId = "com.apple.Preferences"; - this.args.app = null; - } - cb(); - } else if (this.args.bundleId && - this.appIsPackageOrBundle(this.args.bundleId) && - (app === "" || this.appIsPackageOrBundle(app))) { - // we have a bundle ID, but no app, or app is also a bundle - logger.debug("App is an iOS bundle, will attempt to run as pre-existing"); - cb(); - } else { - Device.prototype.configureApp.call(this, cb); - } -}; - -IOS.prototype.removeInstrumentsSocket = function (cb) { - var removeSocket = function (innerCb) { - logger.debug("Removing any remaining instruments sockets"); - rimraf(this.sock, function (err) { - if (err) return innerCb(err); - logger.debug("Cleaned up instruments socket " + this.sock); - innerCb(); - }.bind(this)); - }.bind(this); - removeSocket(cb); -}; - -IOS.prototype.getNumericVersion = function () { - return parseFloat(this.args.platformVersion); -}; - -IOS.prototype.startRealDevice = function (cb) { - async.series([ - this.removeInstrumentsSocket.bind(this), - this.detectUdid.bind(this), - this.parseLocalizableStrings.bind(this), - this.setBundleIdFromApp.bind(this), - this.createInstruments.bind(this), - this.startLogCapture.bind(this), - this.installToRealDevice.bind(this), - this.startInstruments.bind(this), - this.onInstrumentsLaunch.bind(this), - this.configureBootstrap.bind(this), - this.setBundleId.bind(this), - this.setInitialOrientation.bind(this), - this.initAutoWebview.bind(this), - this.waitForAppLaunched.bind(this), - ], function (err) { - cb(err); - }); -}; - -IOS.prototype.startSimulator = function (cb) { - async.series([ - this.removeInstrumentsSocket.bind(this), - this.setXcodeVersion.bind(this), - this.setiOSSDKVersion.bind(this), - this.checkSimAvailable.bind(this), - this.createSimulator.bind(this), - this.moveBuiltInApp.bind(this), - this.detectUdid.bind(this), - this.parseLocalizableStrings.bind(this), - this.setBundleIdFromApp.bind(this), - this.createInstruments.bind(this), - this.setDeviceInfo.bind(this), - this.checkPreferences.bind(this), - this.runSimReset.bind(this), - this.isolateSimDevice.bind(this), - this.setLocale.bind(this), - this.setPreferences.bind(this), - this.startLogCapture.bind(this), - this.prelaunchSimulator.bind(this), - this.startInstruments.bind(this), - this.onInstrumentsLaunch.bind(this), - this.configureBootstrap.bind(this), - this.setBundleId.bind(this), - this.setInitialOrientation.bind(this), - this.initAutoWebview.bind(this), - this.waitForAppLaunched.bind(this), - ], function (err) { - cb(err); - }); -}; - -IOS.prototype.start = function (cb, onDie) { - if (this.instruments !== null) { - var msg = "Trying to start a session but instruments is still around"; - logger.error(msg); - return cb(new Error(msg)); - } - - if (typeof onDie === "function") { - this.onInstrumentsDie = onDie; - } - - if (this.args.udid) { - this.startRealDevice(cb); - } else { - this.startSimulator(cb); - } -}; - -IOS.prototype.createInstruments = function (cb) { - logger.debug("Creating instruments"); - this.commandProxy = new CommandProxy({ sock: this.sock }); - this.makeInstruments(function (err, instruments) { - if (err) return cb(err); - this.instruments = instruments; - cb(); - }.bind(this)); -}; - -IOS.prototype.startInstruments = function (cb) { - cb = _.once(cb); - - var treatError = function (err, cb) { - if (!_.isEmpty(this.logs)) { - this.logs.syslog.stopCapture(); - this.logs = {}; - } - this.postCleanup(function () { - cb(err); - }); - }.bind(this); - - 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) - ); -}; - -IOS.prototype.makeInstruments = function (cb) { - - // at the moment all the logging in uiauto is at debug level - // TODO: be able to use info in appium-uiauto - var bootstrap = prepareBootstrap({ - sock: this.sock, - interKeyDelay: this.args.interKeyDelay, - 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') - }); - - bootstrap.then(function (bootstrapPath) { - var instruments = new Instruments({ - // on real devices bundleId is always used - app: (!this.args.udid ? this.args.app : null) || this.args.bundleId - , udid: this.args.udid - , processArguments: this.args.processArguments - , ignoreStartupExit: this.shouldIgnoreInstrumentsExit() - , bootstrap: bootstrapPath - , template: this.args.automationTraceTemplatePath - , instrumentsPath: this.args.instrumentsPath - , withoutDelay: this.args.withoutDelay - , platformVersion: this.args.platformVersion - , webSocket: this.args.webSocket - , launchTimeout: this.args.launchTimeout - , flakeyRetries: this.args.backendRetries - , simulatorSdkAndDevice: this.iOSSDKVersion >= 7.1 ? this.getDeviceString() : null - , tmpDir: path.resolve(this.args.tmpDir , 'appium-instruments') - , traceDir: this.args.traceDir - }); - cb(null, instruments); - }.bind(this), cb).fail(cb); -}; - -IOS.prototype.shouldIgnoreInstrumentsExit = function () { - return false; -}; - -IOS.prototype.onInstrumentsLaunch = function (cb) { - logger.debug('Instruments launched. Starting poll loop for new commands.'); - this.instruments.setDebug(true); - if (this.args.origAppPath) { - logger.debug("Copying app back to its original place"); - return ncp(this.args.app, this.args.origAppPath, cb); - } - - cb(); -}; - -IOS.prototype.setBundleId = function (cb) { - if (this.args.bundleId) { - // We already have a bundle Id - cb(); - } else { - this.proxy('au.bundleId()', function (err, bId) { - if (err) return cb(err); - logger.debug('Bundle ID for open app is ' + bId.value); - this.args.bundleId = bId.value; - cb(); - }.bind(this)); - } -}; - -IOS.prototype.setInitialOrientation = function (cb) { - if (typeof this.args.initialOrientation === "string" && - _.contains(["LANDSCAPE", "PORTRAIT"], - this.args.initialOrientation.toUpperCase()) - ) { - logger.debug("Setting initial orientation to " + this.args.initialOrientation); - var command = ["au.setScreenOrientation('", - this.args.initialOrientation.toUpperCase(), "')"].join(''); - this.proxy(command, function (err, res) { - if (err || res.status !== status.codes.Success.code) { - logger.warn("Setting initial orientation did not work!"); - } else { - this.curOrientation = this.args.initialOrientation; - } - cb(); - }.bind(this)); - } else { - cb(); - } -}; - -IOS.isSpringBoard = function (uiAppObj) { -// Test for iOS homescreen (SpringBoard). AUT occassionally start the sim, but fails to load -// the app. If that occurs, getSourceForElementFoXML will return a doc object that meets our -// app-check conditions, resulting in a false positive. This function tests the UiApplication -// property's meta data to ensure that the Appium doesn't confuse SpringBoard with the app -// under test. - return _.propertyOf(uiAppObj['@'])('name') === 'SpringBoard'; -}; - -IOS.prototype.waitForAppLaunched = function (cb) { - // on iOS8 in particular, we can get a working session before the app - // is ready to respond to commands; in that case the source will be empty - // so we just spin until it's not - var condFn; - if (this.args.waitForAppScript) { - // the default getSourceForElementForXML does not fit some use case, so making this customizable. - // TODO: collect script from customer and propose several options, please comment in issue #4190. - logger.debug("Using custom script to wait for app start:" + this.args.waitForAppScript); - condFn = function (cb) { - this.proxy('try{\n' + this.args.waitForAppScript + - '\n} catch(err) { $.log("waitForAppScript err: " + error); false; };', - function (err, res) { - cb(!!res.value, err); - }); - }.bind(this); - } else { - logger.debug("Waiting for app source to contain elements"); - condFn = function (cb) { - this.getSourceForElementForXML(null, function (err, res) { - if (err || !res || res.status !== status.codes.Success.code) { - return cb(false, err); - } - var sourceObj, appEls; - try { - sourceObj = JSON.parse(res.value); - appEls = sourceObj.UIAApplication['>']; - - if (appEls.length > 0 && !IOS.isSpringBoard(sourceObj.UIAApplication)) { - return cb(true); - } else { - return cb(false, new Error("App did not have elements")); - } - } catch (e) { - return cb(false, new Error("Couldn't parse JSON source")); - } - return cb(true, err); - }); - }.bind(this); - } - this.waitForCondition(10000, condFn, cb, 500); -}; - -IOS.prototype.configureBootstrap = function (cb) { - logger.debug("Setting bootstrap config keys/values"); - var isVerbose = logger.transports.console.level === 'debug'; - var cmd = ''; - cmd += 'target = $.target();\n'; - cmd += 'au = $;\n'; - cmd += '$.isVerbose = ' + isVerbose + ';\n'; - // Not using uiauto grace period because of bug. - // cmd += '$.target().setTimeout(1);\n'; - this.proxy(cmd, cb); -}; - -IOS.prototype.onUnexpectedInstrumentsExit = function (code) { - logger.debug("Instruments exited unexpectedly"); - this.isShuttingDown = true; - var postShutdown = function () { - if (typeof this.cbForCurrentCmd === "function") { - logger.debug("We were in the middle of processing a command when " + - "instruments died; responding with a generic error"); - var error = new UnknownError("Instruments died while responding to " + - "command, please check appium logs"); - this.onInstrumentsDie(error, this.cbForCurrentCmd); - } else { - this.onInstrumentsDie(); - } - }.bind(this); - if (this.commandProxy) { - this.commandProxy.safeShutdown(function () { - this.shutdown(code, postShutdown); - }.bind(this)); - } else { - this.shutdown(code, postShutdown); - } -}; - -IOS.prototype.setXcodeVersion = function (cb) { - logger.debug("Setting Xcode version"); - 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)); -}; - -IOS.prototype.setiOSSDKVersion = function (cb) { - logger.debug("Setting iOS SDK Version"); - xcode.getMaxIOSSDK(function (err, versionNumber) { - if (err) { - logger.error("Could not determine iOS SDK version"); - return cb(err); - } - this.iOSSDKVersion = versionNumber; - logger.debug("iOS SDK Version set to " + this.iOSSDKVersion); - cb(); - }.bind(this)); -}; - -IOS.prototype.setLocale = function (cb) { - var msg; - var setLoc = function (err) { - logger.debug("Setting locale information"); - if (err) return cb(err); - var needSimRestart = false; - this.localeConfig = this.localeConfig || {}; - _(['language', 'locale', 'calendarFormat']).each(function (key) { - needSimRestart = needSimRestart || - (this.args[key] && - this.args[key] !== this.localeConfig[key]); - }, this); - this.localeConfig = { - language: this.args.language, - locale: this.args.locale, - calendarFormat: this.args.calendarFormat - }; - var simRoots = this.sim.getDirs(); - if (simRoots.length < 1) { - msg = "Cannot set locale information because the iOS Simulator directory could not be determined."; - logger.error(msg); - return cb(new Error(msg)); - } - - try { - this.sim.setLocale(this.args.language, this.args.locale, this.args.calendarFormat); - } catch (e) { - msg = "Appium was unable to set locale info: " + e; - logger.error(msg); - return cb(new Error(msg)); - } - - logger.debug("Locale was set"); - if (needSimRestart) { - logger.debug("First time setting locale, or locale changed, killing existing Instruments and Sim procs."); - Instruments.killAllSim(); - Instruments.killAll(); - setTimeout(cb, 250); - } else { - cb(); - } - }.bind(this); - - if ((this.args.language || this.args.locale || this.args.calendarFormat) && this.args.udid === null) { - - if (this.args.fullReset && this.args.platformVersion <= 6.1) { - msg = "Cannot set locale information because a full-reset was requested. full-reset interferes with the language/locale caps on iOS 6.1 and older"; - logger.error(msg); - return cb(new Error(msg)); - } - - if (!this.sim.dirsExist()) { - this.instantLaunchAndQuit(false, setLoc); - } else { - setLoc(); - } - } else if (this.args.udid) { - logger.debug("Not setting locale because we're using a real device"); - cb(); - } else { - logger.debug("Not setting locale"); - cb(); - } -}; - -IOS.prototype.checkPreferences = function (cb) { - logger.debug("Checking whether we need to set app preferences"); - if (this.args.udid !== null) { - logger.debug("Not setting iOS and app preferences since we're on a real " + - "device"); - return cb(); - } - - var settingsCaps = [ - 'locationServicesEnabled', - 'locationServicesAuthorized', - 'safariAllowPopups', - 'safariIgnoreFraudWarning', - 'safariOpenLinksInBackground' - ]; - var safariSettingsCaps = settingsCaps.slice(2, 5); - this.needToSetPrefs = false; - this.needToSetSafariPrefs = false; - _.each(settingsCaps, function (cap) { - if (_.has(this.capabilities, cap)) { - this.needToSetPrefs = true; - if (_.contains(safariSettingsCaps, cap)) { - this.needToSetSafariPrefs = true; - } - } - }.bind(this)); - - this.keepAppToRetainPrefs = this.needToSetPrefs; - - cb(); - -}; - -IOS.prototype.setPreferences = function (cb) { - if (!this.needToSetPrefs) { - logger.debug("No iOS / app preferences to set"); - return cb(); - } else if (this.args.fullReset) { - var msg = "Cannot set preferences because a full-reset was requested"; - logger.debug(msg); - logger.error(msg); - return cb(new Error(msg)); - } - - var setPrefs = function (err) { - if (err) return cb(err); - try { - this.setLocServicesPrefs(); - } catch (e) { - logger.error("Error setting location services preferences, prefs will not work"); - logger.error(e); - logger.error(e.stack); - } - try { - this.setSafariPrefs(); - } catch (e) { - logger.error("Error setting safari preferences, prefs will not work"); - logger.error(e); - logger.error(e.stack); - } - cb(); - }.bind(this); - - logger.debug("Setting iOS and app preferences"); - if (!this.sim.dirsExist() || - !settings.locServicesDirsExist(this.sim) || - (this.needToSetSafariPrefs && !this.sim.safariDirsExist())) { - this.instantLaunchAndQuit(this.needToSetSafariPrefs, setPrefs); - } else { - setPrefs(); - } -}; - -IOS.prototype.instantLaunchAndQuit = function (needSafariDirs, cb) { - logger.debug("Sim files for the " + this.iOSSDKVersion + " SDK do not yet exist, launching the sim " + - "to populate the applications and preference dirs"); - - var condition = function () { - var simDirsExist = this.sim.dirsExist(); - var locServicesExist = settings.locServicesDirsExist(this.sim); - var safariDirsExist = this.args.platformVersion < 7.0 || - (this.sim.safariDirsExist() && - (this.args.platformVersion < 8.0 || - this.sim.userSettingsPlistExists()) - ); - var okToGo = simDirsExist && locServicesExist && - (!needSafariDirs || safariDirsExist); - if (!okToGo) { - logger.debug("We launched the simulator but the required dirs don't " + - "yet exist. Waiting some more..."); - } - return okToGo; - }.bind(this); - - this.prelaunchSimulator(function (err) { - if (err) return cb(err); - this.makeInstruments(function (err, instruments) { - instruments.launchAndKill(condition, function (err) { - if (err) return cb(err); - this.endSimulator(cb); - }.bind(this)); - }.bind(this)); - }.bind(this)); -}; - -IOS.prototype.setLocServicesPrefs = function () { - if (typeof this.capabilities.locationServicesEnabled !== "undefined" || - this.capabilities.locationServicesAuthorized) { - var locServ = this.capabilities.locationServicesEnabled; - locServ = locServ || this.capabilities.locationServicesAuthorized; - locServ = locServ ? 1 : 0; - logger.debug("Setting location services to " + locServ); - settings.updateSettings(this.sim, 'locationServices', { - LocationServicesEnabled: locServ, - 'LocationServicesEnabledIn7.0': locServ, - 'LocationServicesEnabledIn8.0': locServ - } - ); - } - if (typeof this.capabilities.locationServicesAuthorized !== "undefined") { - if (!this.args.bundleId) { - var msg = "Can't set location services for app without bundle ID"; - logger.error(msg); - throw new Error(msg); - } - var locAuth = !!this.capabilities.locationServicesAuthorized; - if (locAuth) { - logger.debug("Authorizing location services for app"); - } else { - logger.debug("De-authorizing location services for app"); - } - settings.updateLocationSettings(this.sim, this.args.bundleId, locAuth); - } -}; - - -IOS.prototype.setSafariPrefs = function () { - var safariSettings = {}; - var val; - if (_.has(this.capabilities, 'safariAllowPopups')) { - val = !!this.capabilities.safariAllowPopups; - logger.debug("Setting javascript window opening to " + val); - safariSettings.WebKitJavaScriptCanOpenWindowsAutomatically = val; - safariSettings.JavaScriptCanOpenWindowsAutomatically = val; - } - if (_.has(this.capabilities, 'safariIgnoreFraudWarning')) { - val = !this.capabilities.safariIgnoreFraudWarning; - logger.debug("Setting fraudulent website warning to " + val); - safariSettings.WarnAboutFraudulentWebsites = val; - } - if (_.has(this.capabilities, 'safariOpenLinksInBackground')) { - val = this.capabilities.safariOpenLinksInBackground ? 1 : 0; - logger.debug("Setting opening links in background to " + !!val); - safariSettings.OpenLinksInBackground = val; - } - if (_.size(safariSettings) > 0) { - settings.updateSafariSettings(this.sim, safariSettings); - } -}; - -IOS.prototype.detectUdid = function (cb) { - var msg; - logger.debug("Auto-detecting iOS udid..."); - if (this.args.udid !== null && this.args.udid === "auto") { - which('idevice_id', function (notFound, cmdPath) { - - var udidetectPath; - if (notFound) { - udidetectPath = require.resolve('udidetect'); - } else { - udidetectPath = cmdPath + " -l"; - } - - exec(udidetectPath, { maxBuffer: 524288, timeout: 3000 }, function (err, stdout) { - if (err) { - msg = "Error detecting udid: " + err.message; - logger.error(msg); - cb(err); - } - - if (stdout && stdout.length > 2) { - this.args.udid = stdout.split("\n")[0]; - logger.debug("Detected udid as " + this.args.udid); - cb(); - } else { - msg = "Could not detect udid."; - logger.error(msg); - cb(new Error(msg)); - } - }.bind(this)); - - }.bind(this)); - } else { - logger.debug("Not auto-detecting udid, running on sim"); - cb(); - } -}; - -IOS.prototype.setBundleIdFromApp = function (cb) { - // This method will try to extract the bundleId from the app - if (this.args.bundleId) { - // We aleady have a bundle Id - cb(); - } else { - this.getBundleIdFromApp(function (err, bundleId) { - if (err) { - logger.error("Could not set the bundleId from app."); - return cb(err); - } - this.args.bundleId = bundleId; - cb(); - }.bind(this)); - } -}; - -IOS.prototype.installToRealDevice = function (cb) { - // if user has passed in desiredCaps.autoLaunch = false - // meaning they will manage app install / launching - if (this.args.autoLaunch === false) { - cb(); - } else { - if (this.args.udid) { - try { - this.realDevice = this.getIDeviceObj(); - } catch (e) { - return cb(e); - } - this.isAppInstalled(this.args.bundleId, function (err, installed) { - if (err || !installed) { - logger.debug("App is not installed. Will try to install the app."); - } else { - logger.debug("App is installed."); - if (this.args.fullReset) { - logger.debug("fullReset requested. Forcing app install."); - } else { - logger.debug("fullReset not requested. No need to install."); - return cb(); - } - } - if (this.args.ipa && this.args.bundleId) { - this.installIpa(cb); - } else if (this.args.ipa) { - var msg = "You specified a UDID and ipa but did not include the bundle " + - "id"; - logger.error(msg); - cb(new Error(msg)); - } else if (this.args.app) { - this.installApp(this.args.app, cb); - } else { - logger.debug("Real device specified but no ipa or app path, assuming bundle ID is " + - "on device"); - cb(); - } - }.bind(this)); - } else { - logger.debug("No device id or app, not installing to real device."); - cb(); - } - } -}; - -IOS.prototype.getIDeviceObj = function () { - var idiPath = path.resolve(__dirname, "../../../build/", - "libimobiledevice-macosx/ideviceinstaller"); - logger.debug("Creating iDevice object with udid " + this.args.udid); - try { - return iDevice(this.args.udid); - } catch (e1) { - logger.debug("Couldn't find ideviceinstaller, trying built-in at " + - idiPath); - try { - return iDevice(this.args.udid, {cmd: idiPath}); - } catch (e2) { - var msg = "Could not initialize ideviceinstaller; make sure it is " + - "installed and works on your system"; - logger.error(msg); - throw new Error(msg); - } - } -}; - -IOS.prototype.installIpa = function (cb) { - logger.debug("Installing ipa found at " + this.args.ipa); - if (!this.realDevice) { - this.realDevice = this.getIDeviceObj(); - } - var d = this.realDevice; - async.waterfall([ - function (cb) { d.isInstalled(this.args.bundleId, cb); }.bind(this), - function (installed, cb) { - if (installed) { - logger.debug("Bundle found on device, removing before reinstalling."); - d.remove(this.args.bundleId, cb); - } else { - logger.debug("Nothing found on device, going ahead and installing."); - cb(); - } - }.bind(this), - function (cb) { d.installAndWait(this.args.ipa, this.args.bundleId, cb); }.bind(this) - ], cb); -}; - -IOS.getDeviceStringFromOpts = function (opts) { - logger.debug("Getting device string from opts: " + JSON.stringify({ - forceIphone: opts.forceIphone, - forceIpad: opts.forceIpad, - xcodeVersion: opts.xcodeVersion, - iOSSDKVersion: opts.iOSSDKVersion, - deviceName: opts.deviceName, - platformVersion: opts.platformVersion - })); - var isiPhone = opts.forceIphone || opts.forceIpad === null || (opts.forceIpad !== null && !opts.forceIpad); - var isTall = isiPhone; - var isRetina = opts.xcodeVersion[0] !== '4'; - var is64bit = false; - var deviceName = opts.deviceName; - var fixDevice = true; - if (deviceName && deviceName[0] === '=') { - return deviceName.substring(1); - } - logger.debug("fixDevice is " + (fixDevice ? "on" : "off")); - if (deviceName) { - var device = deviceName.toLowerCase(); - if (device.indexOf("iphone") !== -1) { - isiPhone = true; - } else if (device.indexOf("ipad") !== -1) { - isiPhone = false; - } - if (deviceName !== opts.platformName) { - isTall = isiPhone && (device.indexOf("4-inch") !== -1); - isRetina = (device.indexOf("retina") !== -1); - is64bit = (device.indexOf("64-bit") !== -1); - } - } - - var iosDeviceString = isiPhone ? "iPhone" : "iPad"; - if (opts.xcodeVersion[0] === '4') { - if (isiPhone && isRetina) { - iosDeviceString += isTall ? " (Retina 4-inch)" : " (Retina 3.5-inch)"; - } else { - iosDeviceString += isRetina ? " (Retina)" : ""; - } - } else if (opts.xcodeVersion[0] === '5') { - iosDeviceString += isRetina ? " Retina" : ""; - if (isiPhone) { - if (isRetina && isTall) { - iosDeviceString += is64bit ? " (4-inch 64-bit)" : " (4-inch)"; - } else if (deviceName.toLowerCase().indexOf("3.5") !== -1) { - iosDeviceString += " (3.5-inch)"; - } - } else { - iosDeviceString += is64bit ? " (64-bit)" : ""; - } - } else if (opts.xcodeVersion[0] === '6') { - iosDeviceString = opts.deviceName || - (isiPhone ? "iPhone Simulator" : "iPad Simulator"); - } - var reqVersion = opts.platformVersion || opts.iOSSDKVersion; - if (opts.iOSSDKVersion >= 8) { - iosDeviceString += " (" + reqVersion + " Simulator)"; - } else if (opts.iOSSDKVersion >= 7.1) { - iosDeviceString += " - Simulator - iOS " + reqVersion; - } - if (fixDevice) { - // Some device config are broken in 5.1 - var CONFIG_FIX = { - 'iPhone - Simulator - iOS 7.1': 'iPhone Retina (4-inch 64-bit) - ' + - 'Simulator - iOS 7.1', - 'iPad - Simulator - iOS 7.1': 'iPad Retina (64-bit) - Simulator - ' + - 'iOS 7.1', - 'iPad Simulator (8.0 Simulator)': 'iPad 2 (8.0 Simulator)', - 'iPad Simulator (8.1 Simulator)': 'iPad 2 (8.1 Simulator)', - 'iPad Simulator (8.2 Simulator)': 'iPad 2 (8.2 Simulator)', - 'iPad Simulator (8.3 Simulator)': 'iPad 2 (8.3 Simulator)', - 'iPad Simulator (8.4 Simulator)': 'iPad 2 (8.4 Simulator)', - 'iPad Simulator (7.1 Simulator)': 'iPad 2 (7.1 Simulator)', - 'iPhone Simulator (8.4 Simulator)': 'iPhone 6 (8.4 Simulator)', - 'iPhone Simulator (8.3 Simulator)': 'iPhone 6 (8.3 Simulator)', - 'iPhone Simulator (8.2 Simulator)': 'iPhone 6 (8.2 Simulator)', - 'iPhone Simulator (8.1 Simulator)': 'iPhone 6 (8.1 Simulator)', - 'iPhone Simulator (8.0 Simulator)': 'iPhone 6 (8.0 Simulator)', - 'iPhone Simulator (7.1 Simulator)': 'iPhone 5s (7.1 Simulator)' - }; - if (CONFIG_FIX[iosDeviceString]) { - var oldDeviceString = iosDeviceString; - iosDeviceString = CONFIG_FIX[iosDeviceString]; - logger.debug("Fixing device. Changed from: \"" + oldDeviceString + - "\" to: \"" + iosDeviceString + "\""); - } - } - logger.debug("Final device string is: '" + iosDeviceString + "'"); - return iosDeviceString; -}; - -IOS.prototype.getDeviceString = function () { - var opts = _.clone(this.args); - _.extend(opts, { - xcodeVersion: this.xcodeVersion, - iOSSDKVersion: this.iOSSDKVersion - }); - return IOS.getDeviceStringFromOpts(opts); -}; - -IOS.prototype.setDeviceTypeInInfoPlist = function (cb) { - var plist = path.resolve(this.args.app, "Info.plist"); - var dString = this.getDeviceString(); - var isiPhone = dString.toLowerCase().indexOf("ipad") === -1; - var deviceTypeCode = isiPhone ? 1 : 2; - parsePlistFile(plist, function (err, obj) { - if (err) { - logger.error("Could not set the device type in Info.plist"); - return cb(err, null); - } else { - var newPlist; - obj.UIDeviceFamily = [deviceTypeCode]; - if (binaryPlist) { - newPlist = bplistCreate(obj); - } else { - newPlist = xmlplist.build(obj); - } - fs.writeFile(plist, newPlist, function (err) { - if (err) { - logger.error("Could not save new Info.plist"); - cb(err); - } else { - logger.debug("Wrote new app Info.plist with device type"); - cb(); - } - }.bind(this)); - } - }.bind(this)); -}; - -IOS.prototype.getBundleIdFromApp = function (cb) { - logger.debug("Getting bundle ID from app"); - var plist = path.resolve(this.args.app, "Info.plist"); - parsePlistFile(plist, function (err, obj) { - if (err) { - logger.error("Could not get the bundleId from app."); - cb(err, null); - } else { - cb(null, obj.CFBundleIdentifier); - } - }.bind(this)); -}; - -IOS.getSimForDeviceString = function (dString, availDevices) { - var matchedDevice = null; - var matchedUdid = null; - _.each(availDevices, function (device) { - if (device.indexOf(dString) !== -1) { - matchedDevice = device; - try { - matchedUdid = /.+\[([^\]]+)\]/.exec(device)[1]; - } catch (e) { - matchedUdid = null; - } - } - }); - return [matchedDevice, matchedUdid]; -}; - -IOS.prototype.checkSimAvailable = function (cb) { - if (this.args.udid) { - logger.debug("Not checking whether simulator is available since we're on " + - "a real device"); - return cb(); - } - - if (this.iOSSDKVersion < 7.1) { - logger.debug("Instruments v < 7.1, not checking device string support"); - return cb(); - } - - logger.debug("Checking whether instruments supports our device string"); - Instruments.getAvailableDevicesWithRetry(3, function (err, availDevices) { - if (err) return cb(err); - var noDevicesError = function () { - var msg = "Could not find a device to launch. You requested '" + - dString + "', but the available devices were: " + - JSON.stringify(availDevices); - logger.error(msg); - cb(new Error(msg)); - }; - var dString = this.getDeviceString(); - if (this.iOSSDKVersion >= 8) { - var sim = IOS.getSimForDeviceString(dString, availDevices); - if (sim[0] === null || sim[1] === null) { - return noDevicesError(); - } - this.iOSSimUdid = sim[1]; - logger.debug("iOS sim UDID is " + this.iOSSimUdid); - return cb(); - } else if (!_.contains(availDevices, dString)) { - return noDevicesError(); - } - cb(); - }.bind(this)); -}; - -IOS.prototype.setDeviceInfo = function (cb) { - this.shouldPrelaunchSimulator = false; - if (this.args.udid) { - logger.debug("Not setting device type since we're on a real device"); - return cb(); - } - - if (!this.args.app && this.args.bundleId) { - logger.debug("Not setting device type since we're using bundle ID and " + - "assuming app is already installed"); - return cb(); - } - - if (!this.args.deviceName && - this.args.forceIphone === null && - this.args.forceIpad === null) { - logger.debug("No device specified, current device in the iOS " + - "simulator will be used."); - return cb(); - } - - if (this.args.defaultDevice || this.iOSSDKVersion >= 7.1) { - if (this.iOSSDKVersion >= 7.1) { - logger.debug("We're on iOS7.1+ so forcing defaultDevice on"); - } else { - logger.debug("User specified default device, letting instruments launch it"); - } - } else { - this.shouldPrelaunchSimulator = true; - } - this.setDeviceTypeInInfoPlist(cb); -}; - -IOS.prototype.createSimulator = function (cb) { - this.sim = new Simulator({ - platformVer: this.args.platformVersion, - sdkVer: this.iOSSDKVersion, - udid: this.iOSSimUdid - }); - cb(); -}; - -IOS.prototype.moveBuiltInApp = function (cb) { - if (this.appString().toLowerCase() === "settings") { - logger.debug("Trying to use settings app, version " + - this.args.platformVersion); - this.sim.preparePreferencesApp(this.args.tmpDir, function (err, attemptedApp, origApp) { - if (err) { - logger.error("Could not prepare settings app: " + err); - return cb(err); - } - logger.debug("Using settings app at " + attemptedApp); - this.args.app = attemptedApp; - this.args.origAppPath = origApp; - cb(); - }.bind(this)); - } else { - cb(); - } -}; - -IOS.prototype.prelaunchSimulator = function (cb) { - var msg; - if (!this.shouldPrelaunchSimulator) { - logger.debug("Not pre-launching simulator"); - return cb(); - } - - xcode.getPath(function (err, xcodePath) { - if (err) { - return cb(new Error('Could not find xcode folder. Needed to start simulator. ' + err.message)); - } - - logger.debug("Pre-launching simulator"); - var iosSimPath = path.resolve(xcodePath, - "Platforms/iPhoneSimulator.platform/Developer/Applications" + - "/iPhone Simulator.app/Contents/MacOS/iPhone Simulator"); - if (!fs.existsSync(iosSimPath)) { - msg = "Could not find ios simulator binary at " + iosSimPath; - logger.error(msg); - return cb(new Error(msg)); - } - this.endSimulator(function (err) { - if (err) return cb(err); - logger.debug("Launching device: " + this.getDeviceString()); - var iosSimArgs = ["-SimulateDevice", this.getDeviceString()]; - this.iosSimProcess = spawn(iosSimPath, iosSimArgs); - var waitForSimulatorLogs = function (countdown) { - if (countdown <= 0 || - (this.logs.syslog && (this.logs.syslog.getAllLogs().length > 0 || - (this.logs.crashlog && this.logs.crashlog.getAllLogs().length > 0)))) { - logger.debug(countdown > 0 ? "Simulator is now ready." : - "Waited 10 seconds for simulator to start."); - cb(); - } else { - setTimeout(function () { - waitForSimulatorLogs(countdown - 1); - }, 1000); - } - }.bind(this); - waitForSimulatorLogs(10); - }.bind(this)); - - }.bind(this)); -}; - -IOS.prototype.parseLocalizableStrings = function (/* language, stringFile, cb */) { - var args = new Args(arguments); - var cb = args.callback; - if (this.args.app === null) { - logger.debug("Localizable.strings is not currently supported when using real devices."); - return cb(); - } - var language = args.all[0] || this.args.language - , stringFile = args.all[1] || "Localizable.strings" - , strings = null; - - if (language) { - strings = path.resolve(this.args.app, language + ".lproj", stringFile); - } - if (!fs.existsSync(strings)) { - if (language) { - logger.debug("No strings file '" + stringFile + "' for language '" + language + "', getting default strings"); - } - strings = path.resolve(this.args.app, stringFile); - } - if (!fs.existsSync(strings)) { - strings = path.resolve(this.args.app, this.args.localizableStringsDir, stringFile); - } - - parsePlistFile(strings, function (err, obj) { - if (err) { - logger.warn("Could not parse app " + stringFile +" assuming it " + - "doesn't exist"); - } else { - logger.debug("Parsed app " + stringFile); - this.localizableStrings = obj; - } - cb(); - }.bind(this)); -}; - - -IOS.prototype.deleteSim = function (cb) { - this.sim.deleteSim(cb); -}; - -IOS.prototype.clearAppData = function (cb) { - if (!this.keepAppToRetainPrefs && this.args.app && this.args.bundleId) { - this.sim.cleanCustomApp(path.basename(this.args.app), this.args.bundleId); - } - cb(); -}; - -IOS.prototype.cleanupSimState = function (cb) { - if (this.realDevice && this.args.bundleId && this.args.fullReset) { - logger.debug("fullReset requested. Will try to uninstall the app."); - var bundleId = this.args.bundleId; - this.realDevice.remove(bundleId, function (err) { - if (err) { - this.removeApp(bundleId, function (err) { - if (err) { - logger.error("Could not remove " + bundleId + " from device"); - cb(err); - } else { - logger.debug("Removed " + bundleId); - cb(); - } - }.bind(this)); - } else { - logger.debug("Removed " + bundleId); - cb(); - } - }.bind(this)); - } else if (!this.args.udid) { - this.sim.cleanSim(this.args.keepKeyChains, this.args.tmpDir, function (err) { - if (err) { - logger.error("Could not reset simulator. Leaving as is. Error: " + err.message); - } - this.clearAppData(cb); - }.bind(this)); - } else { - logger.debug("On a real device; cannot clean device state"); - cb(); - } -}; - -IOS.prototype.runSimReset = function (cb) { - if (this.args.reset || this.args.fullReset) { - logger.debug("Running ios sim reset flow"); - // The simulator process must be ended before we delete applications. - async.series([ - this.endSimulator.bind(this), - function (cb) { - if (this.args.reset) { - this.cleanupSimState(cb); - } else { - cb(); - } - }.bind(this), - function (cb) { - if (this.args.fullReset && !this.args.udid) { - this.deleteSim(cb); - } else { - cb(); - } - }.bind(this) - ], cb); - } else { - logger.debug("Reset not set, not ending sim or cleaning up app state"); - cb(); - } -}; - -IOS.prototype.isolateSimDevice = function (cb) { - if (!this.args.udid && this.args.isolateSimDevice && - this.iOSSDKVersion >= 8) { - this.sim.deleteOtherSims(cb); - } else { - cb(); - } -}; - -IOS.prototype.postCleanup = function (cb) { - this.curCoords = null; - this.curOrientation = null; - - if (!_.isEmpty(this.logs)) { - this.logs.syslog.stopCapture(); - this.logs = {}; - } - - if (this.remote) { - this.stopRemote(); - } - - this.runSimReset(function () { - // ignore any errors during reset and continue shutting down - this.isShuttingDown = false; - cb(); - }.bind(this)); - - -}; - -IOS.prototype.endSimulator = function (cb) { - logger.debug("Killing the simulator process"); - if (this.iosSimProcess) { - this.iosSimProcess.kill("SIGHUP"); - this.iosSimProcess = null; - } else { - Instruments.killAllSim(); - } - this.endSimulatorDaemons(cb); -}; - -IOS.prototype.endSimulatorDaemons = function (cb) { - logger.debug("Killing any other simulator daemons"); - var stopCmd = 'launchctl list | grep com.apple.iphonesimulator | cut -f 3 | xargs -n 1 launchctl stop'; - exec(stopCmd, { maxBuffer: 524288 }, function () { - var removeCmd = 'launchctl list | grep com.apple.iphonesimulator | cut -f 3 | xargs -n 1 launchctl remove'; - exec(removeCmd, { maxBuffer: 524288 }, function () { - cb(); - }); - }); -}; - -IOS.prototype.stop = function (cb) { - logger.debug("Stopping ios"); - if (this.instruments === null) { - logger.debug("Trying to stop instruments but it already exited"); - this.postCleanup(cb); - } else { - this.commandProxy.shutdown(function (err) { - if (err) logger.warn("Got warning when trying to close command proxy:", err); - this.instruments.shutdown(function (code) { - this.shutdown(code, cb); - }.bind(this)); - }.bind(this)); - } -}; - -IOS.prototype.shutdown = function (code, cb) { - this.commandProxy = null; - this.instruments = null; - this.postCleanup(cb); -}; - -IOS.prototype.resetTimeout = deviceCommon.resetTimeout; -IOS.prototype.waitForCondition = deviceCommon.waitForCondition; -IOS.prototype.implicitWaitForCondition = deviceCommon.implicitWaitForCondition; -IOS.prototype.proxy = deviceCommon.proxy; -IOS.prototype.proxyWithMinTime = deviceCommon.proxyWithMinTime; -IOS.prototype.respond = deviceCommon.respond; -IOS.prototype.getSettings = deviceCommon.getSettings; -IOS.prototype.updateSettings = deviceCommon.updateSettings; - -IOS.prototype.initQueue = function () { - - this.queue = async.queue(function (command, cb) { - if (!this.commandProxy) return cb(); - async.series([ - function (cb) { - async.whilst( - function () { return this.selectingNewPage && this.curContext; }.bind(this), - function (cb) { - logger.debug("We're in the middle of selecting a new page, " + - "waiting to run next command until done"); - setTimeout(cb, 100); - }, - cb - ); - }.bind(this), - function (cb) { - var matched = false; - var matches = ["au.alertIsPresent", "au.getAlertText", "au.acceptAlert", - "au.dismissAlert", "au.setAlertText", - "au.waitForAlertToClose"]; - _.each(matches, function (match) { - if (command.indexOf(match) === 0) { - matched = true; - } - }); - async.whilst( - function () { return !matched && this.curContext && this.processingRemoteCmd; }.bind(this), - function (cb) { - logger.debug("We're in the middle of processing a remote debugger " + - "command, waiting to run next command until done"); - setTimeout(cb, 100); - }, - cb - ); - }.bind(this) - ], function (err) { - if (err) return cb(err); - this.cbForCurrentCmd = cb; - if (this.commandProxy) { - this.commandProxy.sendCommand(command, function (response) { - this.cbForCurrentCmd = null; - if (typeof cb === 'function') { - this.respond(response, cb); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this), 1); -}; - -IOS.prototype.push = function (elem) { - this.queue.push(elem[0], elem[1]); -}; - -IOS.prototype.isAppInstalled = function (bundleId, cb) { - if (this.args.udid) { - this.realDevice.isInstalled(bundleId, cb); - } else { - cb(new Error("You can not call isInstalled for the iOS simulator!")); - } -}; - -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!")); - } -}; - -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!")); - } -}; - -IOS.prototype.unpackApp = function (req, cb) { - deviceCommon.unpackApp(req, '.app', cb); -}; - -IOS.prototype.startLogCapture = function (cb) { - if (!_.isEmpty(this.logs)) { - cb(new Error("Trying to start iOS log capture but it's already started!")); - return; - } - this.logs.crashlog = new iOSCrashLog(); - this.logs.syslog = new iOSLog({ - udid: this.args.udid - , simUdid: this.iOSSimUdid - , showLogs: this.args.showSimulatorLog || this.args.showIOSLog - }); - this.logs.syslog.startCapture(function (err) { - if (err) { - logger.warn("Could not capture logs from device. Continuing without capturing logs."); - return cb(); - } - this.logs.crashlog.startCapture(cb); - }.bind(this)); -}; - -IOS.prototype.initAutoWebview = function (cb) { - if (this.args.autoWebview) { - logger.debug('Setting auto webview'); - this.navToInitialWebview(cb); - } else { - cb(); - } -}; - -IOS.prototype.getContextsAndViews = function (cb) { - this.listWebFrames(function (err, webviews) { - if (err) return cb(err); - var ctxs = [{id: this.NATIVE_WIN}]; - this.contexts = [this.NATIVE_WIN]; - _.each(webviews, function (view) { - ctxs.push({id: this.WEBVIEW_BASE + view.id, view: view}); - this.contexts.push(view.id.toString()); - }.bind(this)); - cb(null, ctxs); - }.bind(this)); -}; - -IOS.prototype.getLatestWebviewContextForTitle = function (titleRegex, cb) { - this.getContextsAndViews(function (err, contexts) { - if (err) return cb(err); - var matchingCtx; - _(contexts).each(function (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 -// and frame to load, which leads to race conditions and flakiness -// , let see if we can transition to something better -IOS.prototype.useNewSafari = function () { - return parseFloat(this.iOSSDKVersion) >= 8.1 && - parseFloat(this.args.platformVersion) >= 8.1 && - !this.args.udid && - this.capabilities.safari; -}; - -IOS.prototype.navToInitialWebview = function (cb) { - var timeout = 0; - 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); - } else if (parseInt(this.iOSSDKVersion, 10) >= 7 && !this.args.udid && this.capabilities.safari) { - this.navToViewThroughFavorites(cb); - } else { - this.navToViewWithTitle(/.*/, cb); - } - }.bind(this), timeout); -}; - -IOS.prototype.typeAndNavToUrl = function (cb) { - var initialUrl = this.args.safariInitialUrl || 'http://127.0.0.1:' + this.args.port + '/welcome'; - var oldImpWait = this.implicitWaitMs; - this.implicitWaitMs = 7000; - function noArgsCb(cb) { return function (err) { cb(err); }; } - async.waterfall([ - this.findElement.bind(this, 'name', 'URL'), - function (res, cb) { - this.implicitWaitMs = oldImpWait; - this.nativeTap(res.value.ELEMENT, noArgsCb(cb)); - }.bind(this), - this.findElements.bind(this, 'name', 'Address'), - function (res, cb) { - var addressEl = res.value[res.value.length -1].ELEMENT; - this.setValueImmediate(addressEl, initialUrl, noArgsCb(cb)); - }.bind(this), - this.findElement.bind(this, 'name', 'go'), - function (res, cb) { - this.nativeTap(res.value.ELEMENT, noArgsCb(cb)); - }.bind(this) - ], function () { - this.navToViewWithTitle(/.*/i, function (err) { - if (err) return cb(err); - // Waits for page to finish loading. - this.remote.pageUnload(cb); - }.bind(this)); - }.bind(this)); -}; - -IOS.prototype.navToViewThroughFavorites = function (cb) { - logger.debug("We're on iOS7+ simulator: clicking apple button to get into " + - "a webview"); - var oldImpWait = this.implicitWaitMs; - this.implicitWaitMs = 7000; // wait 7s for apple button to exist - this.findElement('xpath', '//UIAScrollView[1]/UIAButton[1]', function (err, res) { - this.implicitWaitMs = oldImpWait; - if (err || res.status !== status.codes.Success.code) { - var msg = "Could not find button to click to get into webview. " + - "Proceeding on the assumption we have a working one."; - logger.error(msg); - return this.navToViewWithTitle(/.*/i, cb); - } - this.nativeTap(res.value.ELEMENT, function (err, res) { - if (err || res.status !== status.codes.Success.code) { - var msg = "Could not click button to get into webview. " + - "Proceeding on the assumption we have a working one."; - logger.error(msg); - } - this.navToViewWithTitle(/apple/i, cb); - }.bind(this)); - }.bind(this)); -}; - -IOS.prototype.navToViewWithTitle = function (titleRegex, cb) { - logger.debug("Navigating to most recently opened webview"); - var start = Date.now(); - var spinTime = 500; - var spinHandles = function () { - this.getLatestWebviewContextForTitle(titleRegex, function (err, res) { - if (err) { - cb(new Error("Could not navigate to webview! Err: " + err)); - } else if (!res) { - if ((Date.now() - start) < 90000) { - logger.warn("Could not find any webviews yet, refreshing/retrying"); - if (this.args.udid || !this.capabilities.safari) { - return setTimeout(spinHandles, spinTime); - } - this.findUIElementOrElements('accessibility id', 'ReloadButton', - '', false, function (err, res) { - if (err || !res || !res.value || !res.value.ELEMENT) { - logger.warn("Could not find reload button, continuing"); - setTimeout(spinHandles, spinTime); - } else { - this.nativeTap(res.value.ELEMENT, function (err, res) { - if (err || !res) { - logger.warn("Could not click reload button, continuing"); - } - setTimeout(spinHandles, spinTime); - }.bind(this)); - } - }.bind(this)); - } else { - cb(new Error("Could not navigate to webview; there aren't any!")); - } - } else { - var latestWindow = res; - logger.debug("Picking webview " + latestWindow); - this.setContext(latestWindow, function (err) { - if (err) return cb(err); - this.remote.cancelPageLoad(); - cb(); - }.bind(this), true); - } - }.bind(this)); - }.bind(this); - spinHandles(); -}; - -_.extend(IOS.prototype, iOSHybrid); -_.extend(IOS.prototype, iOSController); - -module.exports = IOS; diff --git a/lib/devices/ios/remote-debugger.js b/lib/devices/ios/remote-debugger.js deleted file mode 100644 index 4b11bb84..00000000 --- a/lib/devices/ios/remote-debugger.js +++ /dev/null @@ -1,729 +0,0 @@ -"use strict"; -/* DEPENDENCIES */ - -var net = require('net') - , appLogger = require('../../server/logger.js').get('appium') - , _ = require('underscore') - , messages = require('./remote-messages.js') - , getAtom = require('appium-atoms').get - , status = require("../../server/status.js") - , bplistCreate = require('bplist-creator') - , bplistParse = require('bplist-parser') - , bufferpack = require('bufferpack') - , uuid = require('node-uuid') - , noop = function () {} - , assert = require('assert') - , Args = require("vargs").Constructor; -// ==================================== -// CONFIG -// ==================================== - - -var RemoteDebugger = function (onDisconnect) { - this.init(2, onDisconnect); -}; - -RemoteDebugger.prototype.init = function (debuggerType, onDisconnect) { - this.socket = null; - this.connId = uuid.v4(); - this.senderId = uuid.v4(); - this.appIdKey = null; - this.pageIdKey = null; - this.pageLoading = false; - this.curMsgId = 0; - this.dataCbs = {}; - this.willNavigateWithoutReload = false; - this.onAppDisconnect = onDisconnect || noop; - this.pageChangeCb = noop; - this.specialCbs = { - '_rpc_reportIdentifier:': noop - , '_rpc_forwardGetListing:': noop - , 'connect': noop - , 'connectedToBadApp': noop - }; - this.pageLoadedCbs = []; - this.frameNavigatedCbs = []; - this.setHandlers(); - this.received = new Buffer(0); - this.readPos = 0; - this.debuggerType = debuggerType; - this.socketGone = false; - - this.logger = { - info: function (msg) { - appLogger.info("[REMOTE] " + msg); - } - , debug: function (msg) { - msg = "[REMOTE] " + msg; - appLogger.debug(msg.grey); - } - , error: function (msg) { - appLogger.error("[REMOTE] " + msg); - } - , warn: function (msg) { - appLogger.warn("[REMOTE] " + msg); - } - }; - -}; - -// temporarily using separate method to avoid conflict with webkit remote -// debugger -RemoteDebugger.prototype.configureExtraOpts = function (opts) { - opts = opts || {}; - this.useNewSafari = opts.useNewSafari || false; - this.pageLoadMs = opts.pageLoadMs; - this.logger.debug('useNewSafari --> ' + this.useNewSafari); -}; - -// ==================================== -// API -// ==================================== - -RemoteDebugger.prototype.debuggerTypeEnum = { - "webkit": 1, - "webinspector": 2 -}; - -RemoteDebugger.prototype.connect = function (cb, pageChangeCb) { - this.pageChangeCb = pageChangeCb; - this.socket = new net.Socket({type: 'tcp6'}); - this.socket.on('close', function () { - this.logger.debug('Debugger socket disconnected'); - this.socketGone = true; - this.onAppDisconnect(); - }.bind(this)); - this.socket.on('data', this.receive.bind(this)); - - var port = process.env.REMOTE_DEBUGGER_PORT || 27753; - this.socket.connect(port, '::1', function () { - this.logger.debug("Debugger socket connected to " + - this.socket.remoteAddress + ':' + this.socket.remotePort); - this.setConnectionKey(cb); - }.bind(this)); -}; - -RemoteDebugger.prototype.disconnect = function () { - this.logger.debug("Disconnecting from remote debugger"); - this.socketGone = true; - this.socket.destroy(); -}; - -RemoteDebugger.prototype.setConnectionKey = function (cb) { - try { assert.ok(this.connId); } catch (err) { return cb(err); } - var setConnKey = messages.setConnectionKey(this.connId); - this.logger.debug("Sending connection key"); - this.send(setConnKey, function (simNameKey, simBuildKey) { - this.logger.debug("Sim name: " + simNameKey); - this.logger.debug("Sim build: " + simBuildKey); - }.bind(this), function (appDict) { - var newDict = {}; - _.each(appDict, function (dict) { - var idKey = dict.WIRApplicationIdentifierKey; - newDict[idKey] = { - name: dict.WIRApplicationNameKey, - bundleId: dict.WIRApplicationBundleIdentifierKey, - isProxy: dict.WIRIsApplicationProxyKey, - hostId: dict.WIRHostApplicationIdentifierKey - }; - }.bind(this)); - cb(newDict); - }); -}; - -RemoteDebugger.prototype.selectApp = function (appIdKey, cb, tries) { - if (_.isUndefined(tries)) tries = 1; - try { assert.ok(this.connId); } catch (err) { return cb(err); } - this.appIdKey = appIdKey; - var connectToApp = messages.connectToApp(this.connId, this.appIdKey); - this.logger.debug("Selecting app " + this.appIdKey + " (try #" + tries + ")"); - var responded = false; - - var onConnect = function (appIdKey, pageDict) { - // appIdKey comes in but we trust that it's the same here, otherwise - // we'll stall out and not call cb - - // in iOS 8.2 the connect logic happens, but with an empty dictionary - // which leads to the remote debugger getting disconnected, and into a loop - if (_.isEmpty(pageDict)) { - var msg = "Empty page dictionary received. Trying again."; - return onRequiresRetry(this.appIdKey, msg); - } - if (responded) return; - responded = true; - cb(this.pageArrayFromDict(pageDict)); - this.specialCbs['_rpc_forwardGetListing:'] = this.onPageChange.bind(this); - }.bind(this); - - var onRequiresRetry = function (correctAppIdKey, msg) { - if (responded) return; - responded = true; - if (tries < 4) { - if (!msg) { - msg = "We were notified we connected to possibly the wrong " + - "app. Using the id key suggested and trying again"; - } - this.logger.info(msg); - this.selectApp(correctAppIdKey, cb, tries + 1); - } else { - msg = "Could not connect to a valid app after " + tries + " tries."; - this.logger.error(msg); - cb(new Error(msg)); - } - }.bind(this); - - this.send(connectToApp, onConnect, onRequiresRetry); -}; - -RemoteDebugger.prototype.pageArrayFromDict = function (pageDict) { - var newPageArray = []; - _.each(pageDict, function (dict) { - // count only WIRTypeWeb pages and ignore all others (WIRTypeJavaScript etc) - if (_.isUndefined(dict.WIRTypeKey) || dict.WIRTypeKey === 'WIRTypeWeb') { - newPageArray.push({ - id: dict.WIRPageIdentifierKey - , title: dict.WIRTitleKey - , url: dict.WIRURLKey - , isKey: !_.isUndefined(dict.WIRConnectionIdentifierKey) - }); - } - }); - return newPageArray; -}; - -RemoteDebugger.prototype.selectPage = function (pageIdKey, cb, skipReadyCheck) { - if (typeof skipReadyCheck === "undefined") { - skipReadyCheck = false; - } - try { - assert.ok(this.connId); - assert.ok(this.appIdKey); - assert.ok(this.senderId); - } catch (err) { return cb(err); } - - this.pageIdKey = pageIdKey; - var setSenderKey = messages.setSenderKey(this.connId, this.appIdKey, - this.senderId, this.pageIdKey); - this.logger.debug("Selecting page " + pageIdKey + " and forwarding socket setup"); - this.send(setSenderKey, function () { - this.logger.debug("Set sender key"); - var enablePage = messages.enablePage(this.appIdKey, this.connId, - this.senderId, this.pageIdKey, this.debuggerType); - this.send(enablePage, function () { - this.logger.debug("Enabled activity on page"); - if (skipReadyCheck) { - cb(); - } else { - this.checkPageIsReady(function (err, isReady) { - if (!isReady) { - return this.pageUnload(cb); - } - cb(); - }.bind(this)); - } - }.bind(this)); - }.bind(this)); -}; - -RemoteDebugger.prototype.checkPageIsReady = function (cb) { - this.logger.debug("Checking document readyState"); - var readyCmd = '(function (){return document.readyState;})()'; - this.execute(readyCmd, function (err, res) { - if (err) { - this.logger.debug("readyState returned error, calling it not ready"); - return cb(null, false); - } else { - this.logger.debug("readyState was " + res.result.value); - return cb(null, res.result.value === "complete"); - } - }.bind(this), true); -}; - -RemoteDebugger.prototype.onPageChange = function (appIdKey, pageDict) { - if (this.appIdKey !== appIdKey) { - this.logger.debug("New page listing did not match the app id we " + - "are currently using, ignoring"); - return; - } - this.pageChangeCb(this.pageArrayFromDict(pageDict)); -}; - -RemoteDebugger.prototype.wrapScriptForFrame = function (script, frame) { - var elFromCache = getAtom('get_element_from_cache') - , wrapper = ""; - this.logger.debug("Wrapping script for frame " + frame); - frame = JSON.stringify(frame); - wrapper += "(function (window) { var document = window.document; "; - wrapper += "return (" + script + ");"; - wrapper += "})((" + elFromCache + ")(" + frame + "))"; - return wrapper; -}; - -RemoteDebugger.prototype.executeAtom = function (atom, args, frames, cb) { - var atomSrc, script = ""; - atomSrc = getAtom(atom); - args = _.map(args, JSON.stringify); - if (frames.length > 0) { - script = atomSrc; - for (var i = 0; i < frames.length; i++) { - script = this.wrapScriptForFrame(script, frames[i]); - } - script += "(" + args.join(',') + ")"; - } else { - this.logger.debug("Executing '" + atom + "' atom in default context"); - script += "(" + atomSrc + ")(" + args.join(',') + ")"; - } - this.execute(script, function (err, res) { - if (err) { - cb(err, { - status: status.codes.UnknownError.code - , value: res - }); - } else { - if (typeof res.result.value === "undefined") { - return cb(null, { - status: status.codes.UnknownError.code - , value: "Did not get OK result from execute(). Result was: " + - JSON.stringify(res.result) - }); - } - if (typeof res.result.value === 'string') { - res.result.value = JSON.parse(res.result.value); - } - cb(null, res.result.value); - } - }); -}; - -RemoteDebugger.prototype.executeAtomAsync = function (atom, args, frames, responseUrl, cb) { - var atomSrc, script = "" - , asyncCallBack = ""; - - asyncCallBack += "function (res) { xmlHttp = new XMLHttpRequest(); xmlHttp.open('POST', '" + responseUrl + "', true);"; - asyncCallBack += "xmlHttp.setRequestHeader('Content-type','application/json'); xmlHttp.send(res); }"; - - atomSrc = getAtom(atom); - args = _.map(args, JSON.stringify); - if (frames.length > 0) { - script = atomSrc; - for (var i = 0; i < frames.length; i++) { - script = this.wrapScriptForFrame(script, frames[i]); - } - script += "(" + args.join(',') + ", " + asyncCallBack + ", true )"; - } else { - this.logger.debug("Executing atom in default context"); - script += "(" + atomSrc + ")(" + args.join(',') + ", " + asyncCallBack + ", true )"; - } - this.execute(script, function (err, res) { - if (err) { - cb(err, { - status: status.codes.UnknownError.code - , value: res - }); - } - }); -}; - -RemoteDebugger.prototype.execute = function (command, cb, override) { - if (this.pageLoading && !override) { - this.logger.debug("Trying to execute but page is not loaded. Waiting for dom"); - this.waitForDom(function () { - this.execute(command, cb); - }.bind(this)); - } else { - if (this.debuggerType === this.debuggerTypeEnum.webinspector) { - try { - assert.ok(this.connId); - assert.ok(this.appIdKey); - assert.ok(this.senderId); - //assert.ok(this.pageIdKey); - } catch (err) { return cb(err); } - } - this.logger.debug("Sending javascript command"); - var sendJSCommand = messages.sendJSCommand(command, this.appIdKey, - this.connId, this.senderId, this.pageIdKey, this.debuggerType); - this.send(sendJSCommand, cb); - } -}; - -RemoteDebugger.prototype.callFunction = function (objId, fn, args, cb) { - try { - assert.ok(this.connId); - assert.ok(this.appIdKey); - assert.ok(this.senderId); - assert.ok(this.pageIdKey); - } catch (err) { return cb(err); } - - this.logger.debug("Calling javascript function"); - var callJSFunction = messages.callJSfunction(objId, fn, args, this.appIdKey, - this.connId, this.senderId, this.pageIdKey, this.debuggerType); - this.send(callJSFunction, cb); -}; - -RemoteDebugger.prototype.navToUrl = function (url, cb) { - if (this.debuggerType === this.debuggerTypeEnum.webinspector) { - try { - assert.ok(this.connId); - assert.ok(this.appIdKey); - assert.ok(this.senderId); - assert.ok(this.pageIdKey); - } catch (err) { return cb(err); } - } - this.logger.debug("Navigating to new URL: " + url); - var navToUrl = messages.setUrl(url, this.appIdKey, this.connId, - this.senderId, this.pageIdKey, this.debuggerType); - this.send(navToUrl, noop); - var pageLoadStartMs = Date.now(); - setTimeout(function () { - this.waitForFrameNavigated(function () { - this.waitForDom(pageLoadStartMs, cb); - }.bind(this)); - }.bind(this), this.useNewSafari ? 0 : 1000); -}; - -RemoteDebugger.prototype.pageLoad = function (startPageLoadMs) { - clearTimeout(this.loadingTimeout); - var cbs = this.pageLoadedCbs - , intMs = 500 - , start = startPageLoadMs || Date.now(); - this.logger.debug("Page loaded, verifying whether ready through readyState"); - var verify = function () { - this.checkPageIsReady(function (err, isReady) { - if (isReady || (this.pageLoadMs > 0 && (start + this.pageLoadMs) < Date.now())) { - this.logger.debug("Page is ready, calling onload cbs"); - this.pageLoadedCbs = []; - this.pageLoading = false; - _.each(cbs, function (cb) { - cb(); - }); - } else { - this.logger.debug("Page was not ready, retrying"); - this.loadingTimeout = setTimeout(verify, intMs); - } - }.bind(this)); - }.bind(this); - this.loadingTimeout = setTimeout(verify, intMs); -}; - -RemoteDebugger.prototype.startTimeline = function (fn, cb) { - this.logger.debug("Starting to record the timeline"); - this.timelineEventHandler = fn; - var sendJSCommand = messages.startTimeline(this.appIdKey, - this.connId, this.senderId, this.pageIdKey, this.debuggerType); - this.send(sendJSCommand, cb); -}; - -RemoteDebugger.prototype.stopTimeline = function (cb) { - this.logger.debug("Stopping to record the timeline"); - var sendJSCommand = messages.stopTimeline(this.appIdKey, - this.connId, this.senderId, this.pageIdKey, this.debuggerType); - this.send(sendJSCommand, cb); -}; - -RemoteDebugger.prototype.timelineEventRecorded = function (result) { - this.timelineEventHandler(result); -}; - -RemoteDebugger.prototype.cancelPageLoad = function () { - this.logger.debug("Unregistering from page readiness notifications"); - this.pageLoadedCbs = []; - this.pageLoading = false; - clearTimeout(this.loadingTimeout); -}; - -RemoteDebugger.prototype.frameNavigated = function () { - this.logger.debug("Frame navigated, calling cbs"); - clearTimeout(this.navigatingTimeout); - var cbs = this.frameNavigatedCbs; - this.frameNavigatedCbs = []; - _.each(cbs, function (cb) { - cb(); - }); -}; - -RemoteDebugger.prototype.pageUnload = function (cb) { - if (typeof cb === "undefined") cb = null; - this.logger.debug("Page loading"); - this.pageLoading = true; - this.waitForDom(cb); -}; - -RemoteDebugger.prototype.waitForDom = function () { - var args = new Args(arguments); - var startPageLoadMs = args.all[0]; - var cb = args.callback; - this.logger.debug("Waiting for dom..."); - if (typeof cb === "function") { - this.pageLoadedCbs.push(_.once(cb)); - } - this.pageLoad(startPageLoadMs); -}; - -RemoteDebugger.prototype.waitForFrameNavigated = function (cb) { - this.logger.debug("Waiting for frame navigated..."); - var startMs = Date.now(); - - // wrapping cb to be able to log some data - var _cb = cb; - cb = function () { - this.logger.debug('frame navigated in ' + ((Date.now() - startMs)/1000) + ' sec.' ); - var args = [].slice.call(arguments); - _cb.apply(null, args); - }.bind(this); - - this.frameNavigatedCbs.push(cb); - if (!this.useNewSafari || this.pageLoadMs >= 0) { - this.navigatingTimeout = setTimeout(function () { - this.logger.debug('frame navigated timeout triggered'); - this.frameNavigated(); - }.bind(this), this.useNewSafari ? this.pageLoadMs : 500); - } -}; - -// ==================================== -// HANDLERS -// ==================================== - -RemoteDebugger.prototype.handleMessage = function (plist) { - var handlerFor = plist.__selector; - if (!handlerFor) { - this.logger.debug("Got an invalid plist"); - return; - } - if (_.has(this.handlers, handlerFor)) { - this.handlers[handlerFor](plist); - } else { - this.logger.debug("Debugger got a message for '" + handlerFor + "' and have no " + - "handler, doing nothing."); - } -}; - -RemoteDebugger.prototype.handleSpecialMessage = function (specialCb) { - var fn = this.specialCbs[specialCb]; - if (fn) { - if (specialCb !== "_rpc_forwardGetListing:") { - this.specialCbs[specialCb] = null; - } - fn.apply(this, _.rest(arguments)); - } else { - this.logger.warn("Tried to access special callback '" + specialCb + "' " + - "but it wasn't in our dictionary"); - } -}; - -RemoteDebugger.prototype.setHandlers = function () { - this.handlers = { - '_rpc_reportSetup:': function (plist) { - this.handleSpecialMessage('_rpc_reportIdentifier:', - plist.__argument.WIRSimulatorNameKey, - plist.__argument.WIRSimulatorBuildKey); - }.bind(this), - '_rpc_reportConnectedApplicationList:': function (plist) { - this.handleSpecialMessage('connect', - plist.__argument.WIRApplicationDictionaryKey); - }.bind(this), - '_rpc_applicationSentListing:': function (plist) { - this.handleSpecialMessage('_rpc_forwardGetListing:', - plist.__argument.WIRApplicationIdentifierKey, - plist.__argument.WIRListingKey); - }.bind(this), - '_rpc_applicationConnected:': function (plist) { - this.handleSpecialMessage('connectedToBadApp', - plist.__argument.WIRApplicationIdentifierKey); - }.bind(this), - '_rpc_applicationSentData:': function (plist) { - var dataKey = JSON.parse(plist.__argument.WIRMessageDataKey.toString('utf8')) - , msgId = dataKey.id - , result = dataKey.result - , error = dataKey.error || null; - if (msgId !== null && typeof msgId !== "undefined") { - msgId = msgId.toString(); - } - if (dataKey.method === "Profiler.resetProfiles") { - this.logger.debug("Device is telling us to reset profiles. Should probably " + - "do some kind of callback here"); - } else if (dataKey.method === "Page.frameNavigated") { - if (!this.willNavigateWithoutReload && !this.pageLoading) { - this.logger.debug("Frame navigated, unloading page"); - this.frameNavigated(); - } else { - this.logger.debug("Frame navigated but we were warned about it, not " + - "considering page state unloaded"); - this.willNavigateWithoutReload = false; - } - } else if (dataKey.method === "Page.loadEventFired") { - this.pageLoad(); - } else if (dataKey.method === "Timeline.eventRecorded") { - this.timelineEventRecorded(dataKey.params.record); - } else if (typeof this.dataCbs[msgId] === "function") { - this.dataCbs[msgId](error, result); - this.dataCbs[msgId] = null; - } else if (this.dataCbs[msgId] === null) { - this.logger.error("Debugger returned data for message " + msgId + - "but we already ran that callback! WTF??"); - } else { - if (!msgId && !result && !error) { - this.logger.debug("Got a blank data response from debugger"); - } else { - this.logger.error("Debugger returned data for message " + msgId + - " but we weren't waiting for that message! " + - " result: " + JSON.stringify(result) + - " error: " + error); - } - } - }.bind(this), - '_rpc_applicationDisconnected:': this.onAppDisconnect - }; -}; - -// ==================================== -// SOCKET I/O -// ==================================== - -RemoteDebugger.prototype.send = function (data, cb, cb2) { - var immediateCb = false - , plist; - - cb = cb || noop; - cb2 = cb2 || noop; - - if (_.has(this.specialCbs, data.__selector)) { - this.specialCbs[data.__selector] = cb; - if (data.__selector === '_rpc_reportIdentifier:') { - this.specialCbs.connect = cb2; - } else if (data.__selector === '_rpc_forwardGetListing:') { - this.logger.debug(cb2); - this.specialCbs.connectedToBadApp = cb2; - } - } else if (data.__argument && data.__argument.WIRSocketDataKey) { - this.curMsgId += 1; - this.dataCbs[this.curMsgId.toString()] = cb; - data.__argument.WIRSocketDataKey.id = this.curMsgId; - data.__argument.WIRSocketDataKey = new - Buffer(JSON.stringify(data.__argument.WIRSocketDataKey)); - } else { - immediateCb = true; - } - - this.logger.debug("Sending " + data.__selector + " message to remote debugger"); - if (data.__selector !== "_rpc_forwardSocketData:") { - this.logger.debug(JSON.stringify(data)); - } - - try { - plist = bplistCreate(data); - } catch (e) { - this.logger.error("Could not create binary plist from data"); - return this.logger.debug(e); - } - - if (!this.socketGone) { - this.socket.write(bufferpack.pack('L', [plist.length])); - this.socket.write(plist, immediateCb ? cb : noop); - } else { - this.logger.error("Attempted to write data to socket after it was closed!"); - } -}; - -RemoteDebugger.prototype.receive = function (data) { - var dataLeftOver, oldReadPos, prefix, msgLength, body, plist, chunk, leftOver; - this.logger.debug('Receiving data from remote debugger'); - - // Append this new data to the existing Buffer - this.received = Buffer.concat([this.received, data]); - dataLeftOver = true; - - // Parse multiple messages in the same packet - while (dataLeftOver) { - - // Store a reference to where we were - oldReadPos = this.readPos; - - // Read the prefix (plist length) to see how far to read next - // It's always 4 bytes long - prefix = this.received.slice(this.readPos, this.readPos + 4); - - try { - msgLength = bufferpack.unpack('L', prefix)[0]; - } catch (e) { - this.logger.error("Butter could not unpack"); - return this.logger.debug(e); - } - - // Jump forward 4 bytes - this.readPos += 4; - - // Is there enough data here? - // If not, jump back to our original position and gtfo - if (this.received.length < msgLength + this.readPos) { - this.readPos = oldReadPos; - break; - } - - // Extract the main body of the message (where the plist should be) - body = this.received.slice(this.readPos, msgLength + this.readPos); - - // Extract the plist - try { - plist = bplistParse.parseBuffer(body); - } catch (e) { - this.logger.error("Error parsing binary plist"); - this.logger.debug(e); - } - - // bplistParse.parseBuffer returns an array - if (plist.length === 1) { - plist = plist[0]; - } - - var plistCopy = plist; - if (typeof plistCopy.WIRMessageDataKey !== "undefined") { - plistCopy.WIRMessageDataKey = plistCopy.WIRMessageDataKey.toString("utf8"); - } - if (typeof plistCopy.WIRDestinationKey !== "undefined") { - plistCopy.WIRDestinationKey = plistCopy.WIRDestinationKey.toString("utf8"); - } - if (typeof plistCopy.WIRSocketDataKey !== "undefined") { - plistCopy.WIRSocketDataKey = plistCopy.WIRSocketDataKey.toString("utf8"); - } - - if (plistCopy.__selector === "_rpc_applicationSentData:") { - this.logger.debug("got applicationSentData response"); - } else { - this.logger.debug(JSON.stringify(plistCopy)); - } - - // Jump forward the length of the plist - this.readPos += msgLength; - - // Calculate how much buffer is left - leftOver = this.received.length - this.readPos; - - // Is there some left over? - if (leftOver !== 0) { - // Copy what's left over into a new buffer, and save it for next time - chunk = new Buffer(leftOver); - this.received.copy(chunk, 0, this.readPos); - this.received = chunk; - } else { - // Otherwise, empty the buffer and get out of the loop - this.received = new Buffer(0); - dataLeftOver = false; - } - - // Reset the read position - this.readPos = 0; - - // Now do something with the plist - if (plist) { - this.handleMessage(plist); - } - - } -}; - -exports.init = function (onDisconnect) { - return new RemoteDebugger(onDisconnect); -}; - -exports.RemoteDebugger = RemoteDebugger; diff --git a/lib/devices/ios/remote-messages.js b/lib/devices/ios/remote-messages.js deleted file mode 100644 index b821de32..00000000 --- a/lib/devices/ios/remote-messages.js +++ /dev/null @@ -1,132 +0,0 @@ -"use strict"; - -var _ = require("underscore"); - -// Connection - -exports.setConnectionKey = function (connId) { - return { - __argument: { - WIRConnectionIdentifierKey: connId - }, - __selector : '_rpc_reportIdentifier:' - }; -}; - -exports.connectToApp = function (connId, appIdKey) { - return { - __argument: { - WIRConnectionIdentifierKey: connId, - WIRApplicationIdentifierKey: appIdKey - }, - __selector : '_rpc_forwardGetListing:' - }; -}; - -exports.setSenderKey = function (connId, appIdKey, senderId, pageIdKey) { - return { - __argument: { - WIRApplicationIdentifierKey: appIdKey, - WIRConnectionIdentifierKey: connId, - WIRSenderKey: senderId, - WIRPageIdentifierKey: pageIdKey - }, - __selector: '_rpc_forwardSocketSetup:' - }; -}; - -// Action - -exports.indicateWebView = function (connId, appIdKey, pageIdKey, enabled) { - return { - __argument: { - WIRApplicationIdentifierKey: appIdKey, - WIRIndicateEnabledKey: typeof enabled === "undefined" ? true : enabled, - WIRConnectionIdentifierKey: connId, - WIRPageIdentifierKey: pageIdKey - }, - __selector: '_rpc_forwardIndicateWebView:' - }; -}; - -exports.sendJSCommand = function (js, appIdKey, connId, senderId, pageIdKey, debuggerType) { - return exports.command("Runtime.evaluate", - {expression: js, returnByValue: true}, appIdKey, connId, senderId, pageIdKey, debuggerType); -}; - -exports.callJSFunction = function (objId, fn, args, appIdKey, connId, senderId, pageIdKey, debuggerType) { - return exports.command("Runtime.callFunctionOn", - {objectId: objId, functionDeclaration: fn, arguments: args, returnByValue: true}, - appIdKey, connId, senderId, pageIdKey, debuggerType); -}; - -exports.setUrl = function (url, appIdKey, connId, senderId, pageIdKey, debuggerType) { - return exports.command("Page.navigate", {url: url}, appIdKey, connId, - senderId, pageIdKey, debuggerType); -}; - -exports.enablePage = function (appIdKey, connId, senderId, pageIdKey, debuggerType) { - return exports.command("Page.enable", {}, appIdKey, connId, senderId, - pageIdKey, debuggerType); -}; - -exports.startTimeline = function (appIdKey, connId, senderId, pageIdKey, debuggerType) { - return exports.command("Timeline.start", {}, appIdKey, connId, senderId, - pageIdKey, debuggerType); -}; - -exports.stopTimeline = function (appIdKey, connId, senderId, pageIdKey, debuggerType) { - return exports.command("Timeline.stop", {}, appIdKey, connId, senderId, - pageIdKey, debuggerType); -}; - -exports.command = function (method, params, appIdKey, connId, senderId, pageIdKey, debuggerType) { - if (debuggerType !== null && debuggerType === 1) { - return exports.commandWebKit(method, params); - } else { - return exports.commandWebInspector(method, params, appIdKey, connId, senderId, pageIdKey); - } -}; - -exports.commandWebInspector = function (method, params, appIdKey, connId, senderId, pageIdKey) { - var plist = { - __argument: { - WIRApplicationIdentifierKey: appIdKey, - WIRSocketDataKey: { - method: method, - params: { - objectGroup: "console", - includeCommandLineAPI: true, - doNotPauseOnExceptionsAndMuteConsole: true, - } - }, - WIRConnectionIdentifierKey: connId, - WIRSenderKey: senderId, - WIRPageIdentifierKey: pageIdKey - }, - __selector: '_rpc_forwardSocketData:' - }; - if (params) { - plist.__argument.WIRSocketDataKey.params = _.extend( - plist.__argument.WIRSocketDataKey.params, params); - } - return plist; -}; - - -//generate a json request using the webkit protocol -exports.commandWebKit = function (method, params) { - var jsonRequest = { - method: method, - params: { - objectGroup: "console", - includeCommandLineAPI: true, - doNotPauseOnExceptionsAndMuteConsole: true - } - }; - if (params) { - //if there any parameters add them - jsonRequest.params = _.extend(jsonRequest.params, params); - } - return jsonRequest; -}; \ No newline at end of file diff --git a/lib/devices/ios/safari.js b/lib/devices/ios/safari.js deleted file mode 100644 index c8ecf915..00000000 --- a/lib/devices/ios/safari.js +++ /dev/null @@ -1,201 +0,0 @@ -"use strict"; - -var IOS = require('./ios.js') - , logger = require('../../server/logger.js').get('appium') - , path = require('path') - , _ = require('underscore'); - -var SAFARI_BUNDLE = 'com.apple.mobilesafari'; - -var Safari = function () { - this.init(); - this.landscapeWebCoordsOffset = 40; -}; - -_.extend(Safari.prototype, IOS.prototype); - -Safari.prototype.configure = function (args, caps, cb) { - logger.debug("Configuring Safari session"); - this._deviceConfigure(args, caps); - this.setIOSArgs(); - this.capabilities.safari = true; - if (this.args.udid) { - this.dontCleanupSession = true; - this.args.app = path.resolve(__dirname, - "../../../build/SafariLauncher/SafariLauncher.zip"); - this.configureLocalApp(cb); - } else { - if (parseFloat(this.args.platformVersion) >= 8) { - logger.debug("We're on iOS8+ so not copying mobile safari app"); - this.args.bundleId = SAFARI_BUNDLE; - this.args.app = null; - } else { - // make sure args.app has something in it so we get to the right spots - // in moveBuiltInApp() - this.args.app = "safari"; - } - cb(); - } -}; - -Safari.prototype.moveBuiltInApp = function (cb) { - if (!this.args.udid && this.args.app !== null) { - logger.debug("Trying to use mobile safari, version " + - this.args.platformVersion); - this.sim.prepareSafari(this.args.tmpDir, function (err, attemptedApp, origApp) { - if (err) { - logger.error("Could not prepare mobile safari: " + err); - return cb(err); - } - logger.debug("Using mobile safari app at " + attemptedApp); - this.args.app = attemptedApp; - this.args.origAppPath = origApp; - cb(); - }.bind(this)); - } else { - 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) { - if (this.args.udid) { - return; // if we're using SafariLauncher, don't report failure - } - onDie(err); - }.bind(this); - - this._start(function (err) { - if (err) return cb(err); - - 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; -}; - -Safari.prototype._click = IOS.prototype.click; -Safari.prototype.click = function (elementId, cb) { - if (this.capabilities.nativeWebTap && !this.args.udid) { - // atoms-based clicks don't always work in safari 7 - this.nativeWebTap(elementId, cb); - } else { - this._click(elementId, cb); - } -}; - -Safari.prototype.setBundleId = function (cb) { - this.args.bundleId = SAFARI_BUNDLE; - cb(); -}; - -Safari.prototype._setInitialOrientation = IOS.prototype.setInitialOrientation; -Safari.prototype.setInitialOrientation = function (cb) { - if (this.shouldIgnoreInstrumentsExit()) { - logger.debug("Not setting initial orientation because we're on " + - "SafariLauncher"); - return cb(); - } - this._setInitialOrientation(cb); -}; - -Safari.prototype.installToRealDevice = function (cb) { - if (this.args.udid) { - try { - if (!this.realDevice) { - this.realDevice = this.getIDeviceObj(); - } - } catch (e) { - return cb(e); - } - this.isAppInstalled("com.bytearc.SafariLauncher", function (err, installed) { - if (err || !installed) { - this.installApp(this.args.app, cb); - } else { - cb(); - } - }.bind(this)); - } else { - logger.debug("Not installing to real device since we're on sim"); - cb(); - } -}; - -Safari.prototype.clearAppData = function (cb) { - if (this.args.fullReset) { - // Even though we delete typical apps on a regular reset, we do a good - // job of getting safari back to its original state, so actually deleting - // it is overkill in most cases, and requires an instantLaunchAndQuit. - // So we only delete it if the user requests a full reset - try { - this.sim.deleteSafari(); - } catch (e) { - return cb(e); - } - cb(); - } else { - this.sim.cleanSafari(this.keepAppToRetainPrefs, cb); - } -}; - -Safari.prototype._stopRemote = IOS.prototype.stopRemote; -Safari.prototype.stopRemote = function () { - this._stopRemote(true); -}; - -Safari.prototype._stop = IOS.prototype.stop; -Safari.prototype.stop = function (cb) { - if (this.shouldIgnoreInstrumentsExit()) { - logger.debug("Stopping safariLauncher"); - this.shutdown(null, cb); - } else { - this._stop(cb); - } -}; - -module.exports = Safari; diff --git a/lib/devices/ios/settings.js b/lib/devices/ios/settings.js deleted file mode 100644 index 93ffdf8a..00000000 --- a/lib/devices/ios/settings.js +++ /dev/null @@ -1,271 +0,0 @@ -"use strict"; - -var logger = require('../../server/logger.js').get('appium') - , _ = require('underscore') - , fs = require('fs') - , path = require('path') - , bplistCreate = require('bplist-creator') - , mkdirp = require('mkdirp') - , Simulator = require('./simulator.js') - , multiResolve = require('../../helpers.js').multiResolve; - -var settings = {}; -var plists = { - locationServices: 'com.apple.locationd.plist', - webInspector: 'com.apple.webInspector.plist', - mobileSafari: 'com.apple.mobilesafari.plist', - webFoundation: 'com.apple.WebFoundation.plist', - preferences: 'com.apple.Preferences.plist', - locationClients: 'clients.plist', - locationCache: 'cache.plist', - userSettings: 'UserSettings.plist', - effUserSettings: 'EffectiveUserSettings.plist' -}; - -var prefs = { - mobileSafari: { - OpenLinksInBackground: [0, 1], - WebKitJavaScriptCanOpenWindowsAutomatically: [true, false], - JavaScriptCanOpenWindowsAutomatically: [true, false], - SearchEngineStringSetting: ['Google', 'Yahoo!', 'Bing'], - SafariDoNotTrackEnabled: [true, false], - SuppressSearchSuggestions: [true, false], - SpotlightSuggestionsEnabled: [true, false], - SpeculativeLoading: [true, false], - WarnAboutFraudulentWebsites: [true, false], - ReadingListCellularFetchingEnabled: [true, false], - WebKitJavaScriptEnabled: [true, false], - JavaScriptEnabled: [true, false], - DisableWebsiteSpecificSearch: [true, false] - }, - webFoundation: { - NSHTTPAcceptCookies: ['never', 'always', 'current page'] - }, - webInspector: { - RemoteInspectorEnabled: [true, false] - }, - locationServices: { - LocationServicesEnabled: [0, 1], - 'LocationServicesEnabledIn7.0': [0, 1], - 'LocationServicesEnabledIn8.0': [0, 1], - ObsoleteDataDeleted: [true, false] - }, - preferences: { - KeyboardCapsLock: [true, false], - KeyboardAutocapitalization: [true, false], - KeyboardAutocorrection: [true, false], - KeybordCheckSpelling: [true, false], - KeyboardPeriodShortcut: [true, false] - } -}; - -var getPlistPaths = function (plist, sim) { - var files; - var file = plists[plist]; - var bases = getPrefsDirs(sim); - if (plist === 'mobileSafari' && parseFloat(sim.platformVer) >= 7) { - bases = multiResolve(sim.getSafariDirs(), "Library", "Preferences"); - } else if (plist === 'locationClients') { - bases = multiResolve(sim.getDirs(), "Library", "Caches", "locationd"); - } else if (plist === 'locationCache') { - var bases2 = multiResolve(sim.getDirs(), "Library", "Caches", "locationd"); - files = multiResolve(bases, file); - files = files.concat(multiResolve(bases2, file)); - return files; - } else if (plist === 'userSettings') { - files = multiResolve(sim.getDirs(), "Library", "ConfigurationProfiles", - file); - files = files.concat(multiResolve(sim.getDirs(), "Library", - "ConfigurationProfiles", plists.effUserSettings)); - files = files.concat(multiResolve(sim.getDirs(), "Library", - "ConfigurationProfiles", "PublicInfo", - "PublicEffectiveUserSettings.plist")); - return files; - } - - return multiResolve(bases, file); -}; - -var getPrefsDirs = function (sim) { - return multiResolve(sim.getDirs(), "Library", "Preferences"); -}; - -var checkValidSettings = function (forPlist, prefSet) { - var e = null; - if (!_.has(plists, forPlist) || !_.has(prefs, forPlist)) { - e = new Error("plist type " + forPlist + " doesn't exist"); - } - - _.each(prefSet, function (prefValue, prefName) { - if (!_.has(prefs[forPlist], prefName)) { - e = new Error("plist type " + forPlist + " has no option " + - prefName); - } - if (!_.contains(prefs[forPlist][prefName], prefValue)) { - e = new Error("plist type " + forPlist + ", option " + prefName + - " has no possible value " + prefValue); - } - }); - - if (e !== null) { - logger.error(e.message); - throw e; - } -}; - -settings.writeSettings = function (forPlist, prefSetPerFile, bypassCheck, - makeDirs) { - if (typeof makeDirs === "undefined") { - makeDirs = false; - } - var filesWritten = 0; - _.each(prefSetPerFile, function (prefSet, plistPath) { - logger.debug("Writing settings for " + forPlist + " to " + plistPath + ":"); - logger.debug(JSON.stringify(prefSet)); - if (!bypassCheck) { - checkValidSettings(forPlist, prefSet); - } - var baseDir = path.dirname(plistPath); - if (!fs.existsSync(baseDir)) { - logger.warn("Base directory " + baseDir + " doesn't exist, creating it"); - mkdirp.sync(baseDir); - } - prefSet = [prefSet]; // need to wrap in an array to get written correctly - try { - fs.unlinkSync(plistPath); - } catch (e) {} - try { - fs.writeFileSync(plistPath, bplistCreate(prefSet)); - filesWritten++; - } catch (e) { - logger.warn("Could not write to " + plistPath); - } - }); - if (filesWritten === 0) { - logger.warn("Could not write any settings files; is the first time " + - "you've launched the sim? The directories might not exist yet"); - } -}; - -settings.updateSettings = function (sim, forPlist, prefSet) { - logger.debug("Updating settings for " + forPlist); - checkValidSettings(forPlist, prefSet); - var prefSetPerFile = {}; - var curSettings = settings.getSettings(sim, forPlist); - _.each(curSettings, function (settingSet, file) { - _.each(prefSet, function (prefValue, prefName) { - settingSet[prefName] = prefValue; - }); - prefSetPerFile[file] = settingSet; - }); - settings.writeSettings(forPlist, prefSetPerFile, true); -}; - -settings.updateLocationSettings = function (sim, bundleId, authorized) { - var weirdLocKey = "com.apple.locationd.bundle-/System/Library/" + - "PrivateFrameworks/AOSNotification.framework"; - var newPrefs = { - BundleId: bundleId, - Authorized: !!authorized, - Whitelisted: false, - }; - var newCachePrefs = { - LastFenceActivityTimestamp: 412122103.232983, - CleanShutdown: true - }; - var prefSetPerFile = {}; - var cachePrefSetPerFile = {}; - var curCacheSettings = settings.getSettings(sim, 'locationCache'); - _.each(curCacheSettings, function (settingSet, file) { - cachePrefSetPerFile[file] = _.extend(_.clone(newCachePrefs), settingSet); - }); - var curSettings = settings.getSettings(sim, 'locationClients'); - _.each(curSettings, function (settingSet, file) { - // 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; - }); - settings.writeSettings('locationClients', prefSetPerFile, true); - settings.writeSettings('locationCache', cachePrefSetPerFile, true); -}; - -settings.updateSafariSettings = function (sim, settingSet) { - settings.updateSafariUserSettings(sim, settingSet); - settings.updateSettings(sim, 'mobileSafari', settingSet); -}; - -settings.updateSafariUserSettings = function (sim, settingSet) { - // add extra stuff to UserSettings.plist and EffectiveUserSettings.plist - var newUserSettings = {}; - if (_.has(settingSet, 'WebKitJavaScriptEnabled')) { - newUserSettings.safariAllowJavaScript = settingSet.WebKitJavaScriptEnabled; - } - if (_.has(settingSet, 'WebKitJavaScriptCanOpenWindowsAutomatically')) { - newUserSettings.safariAllowPopups = settingSet.WebKitJavaScriptCanOpenWindowsAutomatically; - } - if (_.has(settingSet, 'WarnAboutFraudulentWebsites')) { - newUserSettings.safariForceFraudWarning = !settingSet.WarnAboutFraudulentWebsites; - } - if (_.size(newUserSettings) > 0) { - logger.debug("Updating UserSettings.plist and friends"); - var userSettingsPerFile = {}; - var curUserSettings = settings.getSettings(sim, 'userSettings'); - _.each(curUserSettings, function (userSettingSet, file) { - if (!_.has(userSettingSet, 'restrictedBool')) { - userSettingSet.restrictedBool = {}; - } - var useValueTypePlist = false; - if (userSettingSet.restrictedBool && userSettingSet.restrictedBool.safariAllowJavaScript && _.has(userSettingSet.restrictedBool.safariAllowJavaScript, 'value')) { - useValueTypePlist = true; - } - if (useValueTypePlist) { - _.each(newUserSettings, function (value, key) { - userSettingSet.restrictedBool[key].value = value; - }); - } else { - _.extend(userSettingSet.restrictedBool, newUserSettings); - } - userSettingsPerFile[file] = userSettingSet; - }); - settings.writeSettings('userSettings', userSettingsPerFile, true); - } -}; - -settings.getSettings = function (sim, forPlist) { - var files = getPlistPaths(forPlist, sim); - var bplists = {}; - _.each(files, function (file) { - logger.debug("Getting current settings for " + forPlist + " from " + file); - try { - bplists[file] = Simulator.getPlistData(file); - } catch (err) { - bplists[file] = {}; - logger.warn(err.message); - } - }); - return bplists; -}; - -settings.locServicesDirsExist = function (sim) { - return getPlistPaths('locationClients', sim).length > 0; -}; - -module.exports = settings; diff --git a/lib/devices/ios/simulator.js b/lib/devices/ios/simulator.js deleted file mode 100644 index 355cbd9f..00000000 --- a/lib/devices/ios/simulator.js +++ /dev/null @@ -1,651 +0,0 @@ -"use strict"; - -var logger = require('../../server/logger.js').get('appium') - , _ = require('underscore') - , fs = require('fs') - , async = require('async') - , exec = require('child_process').exec - , path = require('path') - , ncp = require('ncp') - , mkdirp = require('mkdirp') - , xcode = require('../../future.js').xcode - , simctl = require('../../future').simctl - , multiResolve = require('../../helpers.js').multiResolve - , rimraf = require('rimraf') - , xmlplist = require('plist') - , bplistCreate = require('bplist-creator') - , bplistParse = require('bplist-parser'); - -var Simulator = function (opts) { - var requiredOpts = ['sdkVer', 'platformVer', 'udid']; - _.each(requiredOpts, function (opt) { - if (!_.has(opts, opt)) { - throw new Error(opt + " is required"); - } - this[opt] = opts[opt]; - }.bind(this)); -}; - -var wrapInArray = function (res) { - if (res) { - return [res]; - } else { - return []; - } -}; - -var rmrf = function (delPath, cb) { - exec("rm -rf '" + delPath + "'", function (err) { - cb(err); - }); -}; - -var safeRimRafSync = function (delPath, tries) { - if (_.isUndefined(tries)) tries = 0; - try { - rimraf.sync(delPath); - } catch (e) { - if (tries < 20) { - if (e.message.indexOf("ENOTEMPTY") !== -1) { - logger.debug("Path " + delPath + " was not empty during delete; retrying"); - return safeRimRafSync(delPath, tries + 1); - } else if (e.message.indexOf("ENOENT") !== -1) { - logger.debug("Path " + delPath + " didn't exist when we tried to delete, ignoring"); - return safeRimRafSync(delPath, tries + 1); - } - } - throw e; - } -}; - -Simulator.prototype.getRootDir = function () { - var home = process.env.HOME; - if (parseFloat(this.sdkVer) >= 8) { - return path.resolve(home, "Library", "Developer", - "CoreSimulator", "Devices"); - } - - return path.resolve(home, "Library", "Application Support", - "iPhone Simulator"); -}; - -Simulator.prototype.getDirs = function () { - var base; - if (parseFloat(this.sdkVer) >= 8) { - base = path.resolve(this.getRootDir(), this.udid, "data"); - if (fs.existsSync(base)) { - return [base]; - } - return []; - } else { - var sdk = this.platformVer.toString(); - base = this.getRootDir(); - var list = []; - try { - list = fs.readdirSync(base); - } catch (e) { - } - var dirs = []; - _.each(list, function (sdkDir) { - if (sdkDir.indexOf(sdk) !== -1) { - dirs.push(path.resolve(base, sdkDir)); - } - }); - return dirs; - } -}; - -Simulator.getPlistData = function (file) { - var data; - if (fs.existsSync(file)) { - var fileData = fs.readFileSync(file); - try { - data = bplistParse.parseBuffer(fileData)[0]; - } catch (err) { - if (err.message.indexOf("Invalid binary plist") !== -1) { - logger.debug("Plist was not binary format, retrying with xml"); - data = xmlplist.parse(fileData.toString()); - } else { - throw err; - } - } - } else { - throw new Error("Settings file " + file + " did not exist"); - } - return data; -}; - -var findAppByCondition = function (dir, condition) { - var list; - try { - list = fs.readdirSync(dir); - } catch (e) { - if (/ENOENT/.test(e.message)) { - logger.warn("Applications directory " + dir + " doesn't exist. " + - "Have you run this simulator before?"); - return null; - } - throw e; - } - var appDir = null; - for (var i = 0; i < list.length; i++) { - if (condition(list[i])) { - appDir = path.resolve(dir, list[i]); - break; - } - } - return appDir; -}; - -Simulator.prototype.getSafariDirs = function () { - if (parseFloat(this.platformVer) >= 8) { - return wrapInArray(this.getSafari8Dir()); - } else { - return this.getSafari7Dirs(); - } -}; - -Simulator.prototype.getAppDirs = function (appFile, appBundleId) { - if (parseFloat(this.platformVer) >= 8) { - var dataDirs = wrapInArray(this.getApp8Dir(appBundleId, "Data")); - var bundleDirs = wrapInArray(this.getApp8Dir(appBundleId, "Bundle")); - return dataDirs.concat(bundleDirs); - } else { - return this.getApp7Dirs([appFile]); - } -}; - -Simulator.prototype.getSafari7Dirs = function () { - return this.getApp7Dirs(["MobileSafari.app", "Appium-MobileSafari-" + this.platformVer + ".app"]); -}; - -Simulator.prototype.getApp7Dirs = function (appFiles) { - var appsDirs = multiResolve(this.getDirs(), "Applications"); - var foundAppDirs = []; - _.each(appsDirs, function (appsDir) { - var cond = function (d) { - var found = false; - _.each(appFiles, function (appFile) { - found = found || fs.existsSync(path.resolve(appsDir, d, appFile)); - }); - return found; - }; - var appDir = findAppByCondition(appsDir, cond); - if (appDir !== null) { - foundAppDirs.push(appDir); - } - }); - return foundAppDirs; -}; - -Simulator.prototype.getSafari8Dir = function () { - return this.getApp8Dir("com.apple.mobilesafari", "Data"); -}; - -Simulator.prototype.getProfilesDir = function () { - if (parseFloat(this.platformVer) >= 8) { - var profileDir = this.getProfiles8Dir(); - if (profileDir && fs.existsSync(profileDir)) { - return profileDir; - } - return null; - } else { - throw new Error("iOS < 8 don't have a profiles dir"); - } -}; - -Simulator.prototype.getProfiles8Dir = function () { - var root = this.getDirs()[0]; - if (!root) { - return null; - } - return path.resolve(root, "Library", "ConfigurationProfiles"); -}; - -Simulator.prototype.getUserSettingsPlist = function () { - var profilesDir = this.getProfilesDir(); - if (!profilesDir) { - return null; - } - var plistPath = path.resolve(profilesDir, "UserSettings.plist"); - if (fs.existsSync(plistPath)) { - return plistPath; - } - return null; -}; - -Simulator.prototype.getApp8Dir = function (bundleId, dataOrBundle) { - var root = this.getDirs()[0]; - if (!root) { - return null; - } - var appsDir = path.resolve(root, "Containers", dataOrBundle, "Application"); - var magicFile = ".com.apple.mobile_container_manager.metadata.plist"; - var cond = function (d) { - var magicPlist = path.resolve(appsDir, d, magicFile); - if (!fs.existsSync(magicPlist)) { - return false; - } - var data = Simulator.getPlistData(magicPlist); - return data.MCMMetadataIdentifier === bundleId; - }; - return findAppByCondition(appsDir, cond); -}; - -Simulator.prototype.dirsExist = function () { - return this.getDirs().length > 0; -}; - -Simulator.prototype.safariDirsExist = function () { - try { - return this.getSafariDirs().length > 0; - } catch (e) { - return false; - } -}; - -Simulator.prototype.profilesDirExists = function () { - try { - return this.getProfilesDir() !== null; - } catch (e) { - return false; - } -}; - -Simulator.prototype.userSettingsPlistExists = function () { - try { - return this.getUserSettingsPlist() !== null; - } catch (e) { - return false; - } -}; - -Simulator.prototype.cleanSafari = function (keepPrefs, cb) { - // this method is async because we use glob paths for deletes and rimraf - // can't handle them, so we use our own async 'rmrf' model that calls out - // to shell to do it - logger.debug("Cleaning mobile safari data files"); - if (!this.dirsExist()) { - logger.info("Couldn't find Safari support directories to clean out old " + - "data. Probably there's nothing to clean out"); - return cb(); - } - - var libraryDirs = multiResolve(this.getDirs(), 'Library'); - var safariLibDirs = multiResolve(this.getSafariDirs(), 'Library'); - var toDeletes = [ - 'Caches/Snapshots/com.apple.mobilesafari' - , 'Caches/com.apple.mobilesafari/Cache.db*' - , 'Caches/com.apple.WebAppCache/*.db' - , 'Safari' - , 'WebKit/LocalStorage/*.*' - , 'WebKit/GeolocationSites.plist' - , 'Cookies/*.binarycookies' - ]; - var deleteActions = []; - - var buildLibraryDeletes = function (libDirs, toDeletes) { - _.each(libDirs, function (libraryDir) { - _.each(toDeletes, function (relRmPath) { - var rmPath = path.resolve(libraryDir, relRmPath); - deleteActions.push(function (cb) { - logger.debug("Deleting " + rmPath); - rmrf(rmPath, cb); - }); - }); - }); - }; - - var safariToDeletes = _.clone(toDeletes); - if (!keepPrefs) { - safariToDeletes.push('Preferences/*.plist'); - } - - buildLibraryDeletes(libraryDirs, toDeletes); - buildLibraryDeletes(safariLibDirs, safariToDeletes); - - async.parallel(deleteActions, cb); -}; - -Simulator.prototype.deleteSafari = function () { - logger.debug("Deleting Safari apps"); - var safariDirs = multiResolve(this.getSafariDirs()); - if (parseFloat(this.platformVer) >= 8) { - var safariBundleDir = this.getApp8Dir("com.apple.mobilesafari", "Bundle"); - safariDirs = safariDirs.concat(wrapInArray(safariBundleDir)); - } - _.each(safariDirs, function (safariDir) { - logger.debug("Deleting " + safariDir); - safeRimRafSync(safariDir); - }); -}; - -Simulator.prototype.cleanCustomApp = function (appFile, appBundleId) { - logger.debug("Cleaning app data files"); - var appDirs = this.getAppDirs(appFile, appBundleId); - if (appDirs.length === 0) { - logger.info("Couldn't find app directories to delete. Probably it's not " + - "installed"); - return; - } - _.each(appDirs, function (appDir) { - logger.debug("Deleting " + appDir); - safeRimRafSync(appDir); - }); - - if (parseFloat(this.platformVer) >= 8) { - var toDeletes = [ - 'Library/Preferences/' + appBundleId + '.plist' - ]; - _.each(this.getDirs(), function (simDir) { - _.each(toDeletes, function (relRmPath) { - var rmPath = path.resolve(simDir, relRmPath); - logger.debug("Deleting " + rmPath); - safeRimRafSync(rmPath); - }); - }); - } -}; - -Simulator.prototype.cleanSim = function (keepKeychains, tempDir, cb) { - logger.debug("Cleaning sim data files"); - - - var cleanSimVLessThanEight = function (keepKeychains, cb) { - cb = _.once(cb); - if (!this.dirsExist()) { - logger.info("Couldn't find support directories to clean out old " + - "data. Probably there's nothing to clean out"); - return cb(); - } - var toDeletes = [ - 'Library/TCC', - 'Library/Caches/locationd', - 'Library/BackBoard/applicationState.plist', - 'Media' - ]; - if (!keepKeychains) { - toDeletes.push('Library/Keychains'); - } - _.each(this.getDirs(), function (simDir) { - _.each(toDeletes, function (relRmPath) { - var rmPath = path.resolve(simDir, relRmPath); - logger.debug("Deleting " + rmPath); - try { - safeRimRafSync(rmPath); - } catch (e) { - cb(e); - } - }); - }); - - logger.debug("Cleaning sim preferences"); - var prefsPlist = Simulator.getSimAppPrefsFile(); - var data = Simulator.getPlistData(prefsPlist); - var keysToClear = [ - 'CurrentDeviceUDID', - 'SimulateDevice' - ]; - var changed = false; - _.each(keysToClear, function (key) { - if (_.has(data, key)) { - logger.debug("Clearing key: " + key); - delete data[key]; - changed = true; - } - }); - if (changed) { - logger.debug("Writing new preferences plist data"); - return fs.writeFile(prefsPlist, bplistCreate(data), cb); - } - return cb(); - }.bind(this); - - - var cleanSimVEight = function (keepKeychains, tempDir, cb) { - - var base = path.resolve(this.getRootDir(), this.udid, "data"); - var keychainPath = path.resolve(base, "Library", "Keychains"); - var tempKeychainPath = path.resolve(tempDir, "simkeychains_" + this.udid); - - var removeKeychains = _.partial(ncp, keychainPath, tempKeychainPath); - - var returnKeychains = function (err, cb) { - if (err) { - return cb(err); - } - mkdirp(keychainPath, function (err) { - if (err) { return cb(err); } - ncp(tempKeychainPath, keychainPath, cb); - }); - }; - - var performWhileSavingKeychains = function (enclosed, cb) { - async.waterfall([ - removeKeychains, - enclosed, - returnKeychains - ], cb); - }; - - var cleanSim = _.partial(simctl.eraseDevice, this.udid); - - if (keepKeychains) { - logger.debug("Resetting simulator and saving Keychains"); - performWhileSavingKeychains(cleanSim, cb); - } else { - cleanSim(cb); - } - - }.bind(this); - - - - if (this.sdkVer >= 8) { - cleanSimVEight(keepKeychains, tempDir, cb); - } else { - cleanSimVLessThanEight(keepKeychains, 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); - rimraf(appPath, function (err) { - if (err) { - if (err.message.indexOf("EACCES") !== -1) { - return cb(new Error("We don't have write access to " + appPath + - ", please re-run authorize as " + process.env.USER)); - } - return cb(err); - } - logger.debug("Temporarily deleted original app at " + appPath); - cb(null, newAppDir, appPath); - }); - }); -}; - -Simulator.prototype.getBuiltInApp = function (appName, cb) { - this.getBuiltInAppDir(function (err, appDir) { - if (err) return cb(err); - var appPath = path.resolve(appDir, appName + ".app"); - fs.stat(appPath, function (err, s) { - if (err && err.message.indexOf("ENOENT") !== -1) { - logger.debug("App is not at " + appPath); - return cb(err); - } - cb(null, s, appPath); - }); - }); -}; - -Simulator.prototype.prepareBuiltInApp = function (appName, tmpDir, cb) { - logger.debug("Looking for built in app " + appName); - var newAppDir = path.resolve(tmpDir, 'Appium-' + appName + '-' + this.platformVer + '.app'); - var checkApp = function (s, appPath, cb) { - if (!s.isDirectory()) { - cb(new Error("App package was not a directory"), appPath); - return false; - } - return true; - }; - this.getBuiltInApp(appName, function (err, s, appPath) { - if (err) { - 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 " + appName + " in its home " + - "or temp dir!")); - } - if (checkApp(s, appPath, cb)) { - logger.debug("Couldn't find original app, but found the temp " + - "Appium one so using that"); - cb(null, newAppDir, appPath); - } - }); - return; - } - if (checkApp(s, appPath, cb)) { - if (parseInt(this.platformVer, 10) < 7) { - logger.debug("Got app, trying to copy " + appPath + " to tmp dir"); - ncp(appPath, newAppDir, function (err) { - if (err) return cb(err); - logger.debug("Copied " + appName); - cb(null, newAppDir, null); - }); - } else { - logger.debug("Got app, trying to move " + appPath + " to tmp dir"); - this.moveBuiltInApp(appPath, appName, newAppDir, cb); - } - } - }.bind(this)); -}; - -Simulator.prototype.getBuiltInAppDir = function (cb) { - xcode.getPath(function (err, xcodeDir) { - if (err) return cb(err); - var appDir = path.resolve(xcodeDir, "Platforms/iPhoneSimulator.platform/" + - "Developer/SDKs/iPhoneSimulator" + - this.platformVer + ".sdk/Applications/"); - var testDir = function (dir, ncb) { - fs.stat(appDir, function (err, s) { - if (err) { - ncb(err); - } else if (!s.isDirectory()) { - ncb(new Error("Could not load built in applications directory")); - } else { - logger.debug('Found app dir: ' + appDir); - ncb(null, dir); - } - }); - }; - testDir(appDir, function (err, dir) { - if (err) { - // try out other location - logger.debug('Unable to find app dir ' + appDir); - appDir = path.resolve("/Library/Developer/CoreSimulator/" + - "Profiles/Runtimes/iOS " + this.platformVer + ".simruntime/" + - "Contents/Resources/RuntimeRoot/Applications/"); - logger.debug('Trying app dir ' + appDir); - testDir(appDir, cb); - } else { - cb(null, dir); - } - }.bind(this)); - }.bind(this)); -}; - -Simulator.prototype.prepareSafari = function (tmpDir, cb) { - this.prepareBuiltInApp("MobileSafari", tmpDir, cb); -}; - -Simulator.prototype.preparePreferencesApp = function (tmpDir, cb) { - this.prepareBuiltInApp("Preferences", tmpDir, cb); -}; - -Simulator.prototype.deleteSim = function (cb) { - - var deleteSimVEight = function (cb) { - logger.debug('Resetting Content and Settings for Simulator'); - simctl.eraseDevice(this.udid, cb); - }.bind(this); - - var deleteSimLessThanVEight = function (cb) { - var root = this.getRootDir(); - logger.debug("Deleting simulator folder: " + root); - try { - safeRimRafSync(root); - } catch (e) { - return cb(e); - } - cb(); - }.bind(this); - - - if (parseFloat(this.sdkVer) >= 8) { - deleteSimVEight(cb); - } else { - deleteSimLessThanVEight(cb); - } - -}; - -Simulator.prototype.setLocale = function (language, locale, calendar) { - var globalPrefs = multiResolve(this.getDirs(), "Library", "Preferences", - ".GlobalPreferences.plist"); - _.each(globalPrefs, function (plist) { - var data = Simulator.getPlistData(plist); - var curLocaleAndCal = data.AppleLocale || null; - var supportedLangs = data.AppleLanguages || []; - var newLangs = []; - if (language) { - logger.debug("New language: " + language); - newLangs.push(language); - newLangs = newLangs.concat(_.without(supportedLangs, language)); - data.AppleLanguages = newLangs; - } - if (locale || calendar) { - var calSplit = "@calendar="; - if (curLocaleAndCal === null) { - curLocaleAndCal = language || 'en'; - } - var curLoc = curLocaleAndCal.split(calSplit)[0]; - var newLocaleAndCal = locale ? locale : curLoc; - if (calendar) { - newLocaleAndCal += calSplit + calendar; - } - logger.debug("New locale: " + newLocaleAndCal); - data.AppleLocale = newLocaleAndCal; - } - - logger.debug("Writing new locale plist data"); - fs.writeFileSync(plist, bplistCreate(data)); - }); -}; - -Simulator.getSimAppPrefsFile = function () { - var home = process.env.HOME; - return path.resolve(home, "Library", "Preferences", - "com.apple.iphonesimulator.plist"); -}; - -Simulator.prototype.deleteOtherSims = function (cb) { - logger.debug("Isolating the requested simulator by deleting all others"); - simctl.getDevices(function (err, devices) { - if (err) return cb(err); - var udids = []; - var deleteCalls = []; - _.each(devices, function (sdkDevices) { - udids = udids.concat(_.pluck(sdkDevices, 'udid')); - }); - _.each(_.without(udids, this.udid), function (udid) { - deleteCalls.push(_.partial(simctl.deleteDevice, udid)); - }); - async.series(deleteCalls, cb); - }.bind(this)); -}; - -module.exports = Simulator; diff --git a/lib/devices/ios/uiauto.js b/lib/devices/ios/uiauto.js deleted file mode 100644 index 97285d8a..00000000 --- a/lib/devices/ios/uiauto.js +++ /dev/null @@ -1,9 +0,0 @@ -// Gets instrument from npm package and setup logger -"use strict"; - -var uiauto = require('appium-uiauto'), - logger = uiauto.logger; - -logger.init(require('../../server/logger.js').get('appium')); - -module.exports = uiauto; diff --git a/lib/devices/ios/webkit-remote-debugger.js b/lib/devices/ios/webkit-remote-debugger.js deleted file mode 100644 index b81ecf40..00000000 --- a/lib/devices/ios/webkit-remote-debugger.js +++ /dev/null @@ -1,187 +0,0 @@ -"use strict"; -/* DEPENDENCIES */ - -var _ = require('underscore') - , RemoteDebugger = require('./remote-debugger.js').RemoteDebugger - , http = require('http') - , WebSocket = require('ws'); - -// ==================================== -// CONFIG -// ==================================== - -var WebKitRemoteDebugger = function (onDisconnect) { - this.dataMethods = {}; - this.host = 'localhost'; - this.port = process.env.REMOTE_DEBUGGER_PORT || 27753; - this.socketDisconnectCb = null; - this.init(1, onDisconnect); -}; - -//extend the remote debugger -_.extend(WebKitRemoteDebugger.prototype, RemoteDebugger.prototype); - -// ==================================== -// API -// ==================================== - -WebKitRemoteDebugger.prototype.connect = function (pageId, cb, pageChangeCb) { - this.frameNavigatedCbs = []; - this.pageChangeCb = pageChangeCb; - var url = 'ws://' + this.host + ':' + this.port + '/devtools/page/' + pageId; - this.pageIdKey = pageId; - this.socket = new WebSocket(url); - this.socket.on('open', function () { - this.logger.debug('Debugger web socket connected to url [' + url + ']'); - cb(); - }.bind(this)); - this.socket.on('close', function () { - this.logger.debug("Disconnecting from remote debugger"); - this.socket = null; - if (this.socketDisconnectCb) { - this.socketDisconnectCb(); - this.socketDisconnectCb = null; - } - }.bind(this)); - this.socket.on('error', function (exception) { - this.logger.debug('Debugger web socket error: ' + exception.message); - }.bind(this)); - this.socket.on('message', function (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)); -}; - -WebKitRemoteDebugger.prototype.disconnect = function (cb) { - if (this.isConnected()) { - this.socket.close(1001); - this.socketDisconnectCb = cb; - } else { - cb(); - } -}; - -WebKitRemoteDebugger.prototype.isConnected = function () { - return (this.socket !== null); -}; - -WebKitRemoteDebugger.prototype.pageArrayFromJson = function (cb) { - this.logger.debug("Getting WebKitRemoteDebugger pageArray"); - this.getJSONFromUrl(this.host, this.port, '/json', function (err, returnValue) { - if (err) { - this.logger.error("Could not connect to WebKitRemoteDebugger server"); - return cb(err); - } - var pageElementJSON = returnValue; - var newPageArray = []; - //Add elements to the array - _.each(pageElementJSON, function (pageObject) { - var urlArray = pageObject.webSocketDebuggerUrl.split('/').reverse(); - var id = urlArray[0]; - newPageArray.push({ - id: id - , title: pageObject.title - , url: pageObject.url - , isKey: id - }); - }); - cb(null, newPageArray); - }.bind(this)); -}; - -//TODO: move to utility class and use requests -WebKitRemoteDebugger.prototype.getJSONFromUrl = function (host, port, path, cb) { - cb = _.once(cb); - http.get({ host: host, port: port, path: path }, function (response) { - var jsonResponse = ""; - response.on('data', function (chunk) { - jsonResponse += chunk; - }); - response.on('end', function () { - cb(null, JSON.parse(jsonResponse)); - }); - }).on('error', cb); -}; - -// ==================================== -// HANDLERS -// ==================================== - -WebKitRemoteDebugger.prototype.handleMessage = function (data, method) { - var handlerFor = null; - if (method !== null) { - handlerFor = method; - } else { - handlerFor = data.method; - } - if (!handlerFor) { - this.logger.debug("Got an invalid method"); - return; - } - if (_.has(this.handlers, handlerFor)) { - this.handlers[handlerFor](data); - } else { - this.logger.debug("Debugger got a message for '" + handlerFor + - "' and have no handler, doing nothing."); - } -}; - -WebKitRemoteDebugger.prototype.setHandlers = function () { - this.handlers = { - 'Runtime.evaluate': function (data) { - var msgId = data.id - , result = data.result - , error = data.error || null; - this.dataCbs[msgId](error, result); - this.dataCbs[msgId] = null; - }.bind(this), - 'Profiler.resetProfiles': function () { - this.logger.debug("Device is telling us to reset profiles. Should probably " + - "do some kind of callback here"); - }.bind(this), - 'Timeline.eventRecorded': function (data) { - this.timelineEventRecorded(data.result); - }.bind(this) - }; -}; - -// ==================================== -// SOCKET I/O -// ==================================== - -WebKitRemoteDebugger.prototype.send = function (data, cb) { - //increase the current id - this.curMsgId += 1; - //set the id in the message - data.id = this.curMsgId; - //store the call back and the data sent - this.dataCbs[this.curMsgId.toString()] = cb; - this.dataMethods[this.curMsgId.toString()] = data.method; - //send the data - var truncated = JSON.stringify(data).slice(0, 200) + "..."; - this.logger.debug('Remote debugger data sent [' + truncated + ']'); - data = JSON.stringify(data); - this.socket.send(data, function (error) { - if (error !== null && typeof error !== "undefined") { - this.logger.debug(error); - } - }.bind(this)); -}; - -WebKitRemoteDebugger.prototype.receive = function (data) { - var method = null; - data = JSON.parse(data); - //check if there was an id - if (data.id) { - method = this.dataMethods[data.id]; - } - this.handleMessage(data, method); -}; - -exports.init = function (onDisconnect) { - return new WebKitRemoteDebugger(onDisconnect); -}; diff --git a/lib/doctor/android.js b/lib/doctor/android.js deleted file mode 100644 index 88effbc3..00000000 --- a/lib/doctor/android.js +++ /dev/null @@ -1,68 +0,0 @@ -"use strict"; -var path = require('path') - , fs = require('fs') - , env = process.env - , isWindows = require("appium-support").system.isWindows() - , async = require('async'); - -require("./common.js"); - -function AndroidChecker(log) { - this.log = log; -} -exports.AndroidChecker = AndroidChecker; - -AndroidChecker.prototype.runAllChecks = function (cb) { - async.series([ - this.checkAndroidHomeExported.bind(this), - this.checkJavaHomeExported.bind(this), - this.checkADBExists.bind(this), - this.checkAndroidExists.bind(this), - this.checkEmulatorExists.bind(this) - ], cb); -}; - -AndroidChecker.prototype.checkAndroidHomeExported = function (cb) { - if (typeof env.ANDROID_HOME === "undefined") { - this.log.fail('ANDROID_HOME is not set', cb); - } else if (fs.existsSync(env.ANDROID_HOME)) { - this.log.pass('ANDROID_HOME is set to "' + env.ANDROID_HOME + '"', cb); - } else { - this.log.fail('ANDROID_HOME is set but does not exist on the file system at "' + env.ANDROID_HOME + '"', cb); - } -}; - -AndroidChecker.prototype.checkJavaHomeExported = function (cb) { - if (typeof env.JAVA_HOME === "undefined") { - this.log.fail('JAVA_HOME is not set', cb); - } else if (fs.existsSync(env.JAVA_HOME)) { - this.log.pass('JAVA_HOME is set to "' + env.JAVA_HOME + '."', cb); - } else { - this.log.fail('JAVA_HOME is set but does not exist on the file system at "' + env.JAVA_HOME + '"', cb); - } -}; - -AndroidChecker.prototype.checkADBExists = function (cb) { - this.checkAndroidSDKBinaryExists("ADB", path.join("platform-tools", (isWindows ? 'adb.exe' : 'adb')), cb); -}; - -AndroidChecker.prototype.checkAndroidExists = function (cb) { - this.checkAndroidSDKBinaryExists("Android", path.join("tools", (isWindows ? 'android.bat' : 'android')), cb); -}; - -AndroidChecker.prototype.checkEmulatorExists = function (cb) { - this.checkAndroidSDKBinaryExists("Emulator", path.join("tools", (isWindows ? 'emulator.exe' : 'emulator')), cb); -}; - -AndroidChecker.prototype.checkAndroidSDKBinaryExists = function (toolName, relativeToolPath, cb) { - if (typeof env.ANDROID_HOME !== "undefined") { - var adbPath = path.resolve(env.ANDROID_HOME, relativeToolPath); - if (fs.existsSync(adbPath)) { - this.log.pass(toolName + " exists at " + adbPath, cb); - } else { - this.log.fail(toolName + " could not be found at " + adbPath, cb); - } - } else { - this.log.fail(toolName + " could not be found because ANDROID_HOME is not set.", cb); - } -}; diff --git a/lib/doctor/common.js b/lib/doctor/common.js deleted file mode 100644 index 4c594b65..00000000 --- a/lib/doctor/common.js +++ /dev/null @@ -1,159 +0,0 @@ -"use strict"; -var prompt = require("prompt") - , eol = require('os').EOL - , socketio = require("socket.io"); - -require("colors"); - -prompt.message = ''; -prompt.delimiter = ''; - -function Log(port) { - this.port = port !== null ? parseInt(port) : null; - this.broadcast = port !== null; - this.socket = null; - this.responseCallbacks = []; -} -exports.Log = Log; - -Log.prototype.pass = function (msg, cb) { - this.logEntry('\u2714 '.green + msg.white); - if (cb) { - cb(null, msg); - } -}; - -Log.prototype.fail = function (msg, cb) { - this.logEntry('\u2716 '.red + msg.white); - if (cb) { - cb(msg, msg); - } -}; - -Log.prototype.warning = function (msg) { - this.logEntry(msg.yellow); -}; - -Log.prototype.error = function (msg) { - this.logEntry(msg.red); -}; - -Log.prototype.comment = function (msg) { - this.logEntry(msg.cyan); -}; - -Log.prototype.info = function (msg) { - this.logEntry(msg.white); -}; - -Log.prototype.verbose = function (msg) { - this.logEntry(msg.grey); -}; - -Log.prototype.debug = function (msg) { - this.logEntry(msg.darkgrey); -}; - -Log.prototype.logEntry = function (msg) { - console.log(msg); - if (this.broadcast && this.socket !== null) { - this.socket.emit('log', { message : msg}); - } -}; - -Log.prototype.startBroadcast = function (cb) { - if (this.broadcast) { - this.io = socketio.listen(this.port, { log: false }); - this.socket = null; - this.io.sockets.on('connection', function (socket) { - this.socket = socket; - socket.emit('welcome', { message: 'Welcome' }); - socket.on('answer', function (data) { - var responseCb = this.responseCallbacks[data.cbIndex]; - this.responseCallbacks[data.cbIndex] = null; - responseCb(data.selection); - }.bind(this)); - cb(); - }.bind(this)); - } -}; - -Log.prototype.stopBroadcast = function () { - if (this.broadcast) { - this.socket.emit('done'); - this.io.server.close(); - } -}; - -Log.prototype.exitDoctor = function () { - this.stopBroadcast(); - this.error("Appium-Doctor detected problems. Please fix and rerun Appium-Doctor."); - process.exit(-1); -}; - -Log.prototype.promptToFix = function (problemDescription, yesCb, noCb) { - - if (this.broadcast) { - var cbIndex = this.responseCallbacks.push(function (selection) { - if (selection === "Yes") { - yesCb(); - } else { - noCb(); - } - }) - 1; - this.socket.emit('alert', { - title : "Appium Doctor", - message : problemDescription + eol + eol + "Would you like Appium Doctor to attempt to fix it?", - choices : [ "Yes", "No" ], - cbIndex : cbIndex - }); - } else { - prompt.start(); - var promptSchema = { - properties: { - continue: { - description: ("Fix it? (y/n) ").white, - delimiter: '', - type: 'string', - pattern: /^(y|n)/, - message: 'Please enter y or n!', - required: true - } - } - }; - prompt.get(promptSchema, function (err, result) { - if (result.continue === 'y') { - yesCb(); - } else { - noCb(); - } - }); - } -}; - -Log.prototype.promptToConfirmFix = function (cb) { - if (this.broadcast) { - var cbIndex = this.responseCallbacks.push(function () { - cb(); - }) - 1; - this.socket.emit('alert', { - title : "Appium Doctor", - message : "Press \"OK\" to check again.", - choices : [ "OK" ], - cbIndex : cbIndex - }); - } else { - prompt.start(); - var promptSchema = { - properties: { - continue: { - description: 'Press any key to continue:'.white, - type: 'string' - } - } - }; - prompt.get(promptSchema, function () { - cb(); - }); - } -}; \ No newline at end of file diff --git a/lib/doctor/dev.js b/lib/doctor/dev.js deleted file mode 100644 index 5242a5ad..00000000 --- a/lib/doctor/dev.js +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; -var fs = require('fs') - , exec = require('child_process').exec - , isWindows = require("appium-support").system.isWindows() - , eol = require('os').EOL - , async = require('async') - , path = require('path') - , env = process.env; - -require("./common.js"); - -function DevChecker(log) { - this.log = log; -} -exports.DevChecker = DevChecker; - -DevChecker.prototype.runAllChecks = function (cb) { - async.series([ - this.checkMavenExistsInPath.bind(this) - , this.checkADBExistsInPath.bind(this) - , this.checkAntExistsInPath.bind(this) - , this.checkAndroidSDK16Exists.bind(this) - , this.checkAndroidSDK19Exists.bind(this) - ], cb); -}; - -DevChecker.prototype.checkMavenExistsInPath = function (cb) { - this.checkBinaryExistsInPath(isWindows ? "mvn.bat" : "mvn", cb); -}; - -DevChecker.prototype.checkADBExistsInPath = function (cb) { - this.checkBinaryExistsInPath(isWindows ? "adb.exe" : "adb", cb); -}; - -DevChecker.prototype.checkAntExistsInPath = function (cb) { - this.checkBinaryExistsInPath(isWindows ? "ant.bat" : "ant", cb); -}; - -DevChecker.prototype.checkAndroidSDK16Exists = function (cb) { - this.checkAndroidSDKExists("android-16", cb); -}; - -DevChecker.prototype.checkAndroidSDK19Exists = function (cb) { - this.checkAndroidSDKExists("android-19", cb); -}; - -DevChecker.prototype.checkBinaryExistsInPath = function (binaryName,cb) { - exec(isWindows ? "where " + binaryName : "which " + binaryName, { maxBuffer: 524288 }, function (err, stdout) { - if (!err) { - var binaryPath = isWindows ? stdout.split(eol)[0] : stdout.replace(eol, ""); - if (fs.existsSync(binaryPath)) { - this.log.pass(binaryName + " was found at " + binaryPath, cb); - } else { - this.log.fail(binaryName + " does not exist in path. Please add to the system path", cb); - } - } else { - this.log.fail("Could not find " + binaryName + " in path. Please add it to system path", cb); - } - }.bind(this)); -}; - -DevChecker.prototype.checkAndroidSDKExists = function (sdk,cb) { - if (typeof env.ANDROID_HOME !== "undefined") { - var sdkPath = path.resolve(env.ANDROID_HOME, path.join("platforms", sdk)); - if (fs.existsSync(sdkPath)) { - this.log.pass(sdk + " exists at " + sdkPath, cb); - } else { - this.log.fail(sdk + " could not be found at " + sdkPath, cb); - } - } else { - this.log.fail(sdk + " could not be found because ANDROID_HOME is not set.", cb); - } -}; diff --git a/lib/doctor/ios.js b/lib/doctor/ios.js deleted file mode 100644 index aba2d9e2..00000000 --- a/lib/doctor/ios.js +++ /dev/null @@ -1,313 +0,0 @@ -"use strict"; -var path = require('path') - , fs = require('fs') - , env = process.env - , exec = require('child_process').exec - , async = require('async'); - -require("./common.js"); - -function IOSChecker(log) { - this.log = log; - this.osVersion = null; -} -exports.IOSChecker = IOSChecker; - -IOSChecker.prototype.runAllChecks = function (cb) { - async.series([ - this.getMacOSXVersion.bind(this), - this.checkForXcode.bind(this), - this.checkForXcodeCommandLineTools.bind(this), - this.checkDevToolsSecurity.bind(this), - this.checkAuthorizationDB.bind(this), - this.checkForNodeBinary.bind(this) - ], cb); -}; - -IOSChecker.prototype.getMacOSXVersion = function (cb) { - exec("sw_vers -productVersion", function (err, stdout) { - if (err === null) { - if (stdout.match('10.8') !== null) { - this.osVersion = '10.8'; - cb(null, "Mac OS X 10.8 is installed."); - } else if (stdout.match('10.9') !== null) { - this.osVersion = '10.9'; - cb(null, "Mac OS X 10.9 is installed."); - } else if ((stdout.match('10.10') !== null) || (stdout.match('10.10.1') !== null)) { - this.osVersion = '10.10'; - cb(null, "Mac OS X 10.10 is installed."); - } else { - this.log.fail("Could not detect Mac OS X Version", cb); - } - } else { - this.log.fail("Unknown SW Version Command: " + err, cb); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkForXcode = function (cb) { - var msg; - exec("xcode-select --print-path", { maxBuffer: 524288}, function (err, stdout) { - if (err === null) { - var xcodePath = stdout.replace("\n", ""); - if (fs.existsSync(xcodePath)) { - this.log.pass("Xcode is installed at " + xcodePath, cb); - } else { - msg = "Xcode is not installed."; - this.log.fail(msg); - this.log.promptToFix("Xcode is not installed.", function () { - this.installXcode(cb); - }.bind(this), function () { - cb(msg, msg); - }); - } - } else { - msg = "Xcode is not installed: " + err; - this.log.fail(msg); - this.log.promptToFix("Xcode is not installed.", function () { - this.installXcode(cb); - }.bind(this), function () { - cb(msg, msg); - }); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkForXcodeCommandLineTools = function (cb) { - var msg; - var pkgName = this.osVersion === '10.8' ? "com.apple.pkg.DeveloperToolsCLI" : "com.apple.pkg.CLTools_Executables"; - exec("pkgutil --pkg-info=" + pkgName, { maxBuffer: 524288}, function (err, stdout) { - if (err === null) { - var match = stdout.match(/install-time/); - if (match !== null) { - this.log.pass("Xcode Command Line Tools are installed.", cb); - } else { - msg = "Xcode Command Line Tools are NOT installed."; - this.log.fail(msg); - this.log.promptToFix("Xcode's command line tools are NOT installed.", function () { - this.installXcodeCommandLineTools(cb); - }.bind(this), function () { - cb(msg, msg); - }); - } - } else { - msg = "Xcode Command Line Tools are NOT installed: " + err; - this.log.fail(msg); - this.log.promptToFix("Xcode's command line tools are NOT installed.", function () { - this.installXcodeCommandLineTools(cb); - }.bind(this), function () { - cb(msg, msg); - }); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkDevToolsSecurity = function (cb) { - var msg; - exec("DevToolsSecurity", { maxBuffer: 524288}, function (err, stdout) { - if (err === null && stdout.match(/enabled/) !== null) { - this.log.pass("DevToolsSecurity is enabled.", cb); - } else { - msg = 'DevToolsSecurity is not enabled.'; - this.log.fail(msg); - this.log.promptToFix(msg, function () { - this.authorizeIOS(this.checkDevToolsSecurity.bind(this), cb); - }.bind(this), function () { - cb(msg, msg); - }); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkAuthorizationDB = function (cb) { - var msg; - exec("security authorizationdb read system.privilege.taskport", { maxBuffer: 524288}, function (err, stdout) { - if (err === null && (stdout.match(/is-developer/) !== null || stdout.match(/allow/) !== null)) { - this.log.pass("The Authorization DB is set up properly.", cb); - } else if (this.osVersion === '10.8') { - fs.readFile('/etc/authorization', 'utf8', function (err, data) { - if (err) { - this.log.fail('The Authorization DB is NOT set up properly.', cb); - } else { - var match = data.match(/system.privilege.taskport<\/key>\s*\n\s*\n\s*allow-root<\/key>\n\s*()/); - if (match !== null) { - this.log.pass("The Authorization DB is set up properly.", cb); - } else { - this.log.fail('The Authorization DB is NOT set up properly.', cb); - } - } - }.bind(this)); - } else { - msg = 'The Authorization DB is NOT set up properly.'; - this.log.fail(msg); - this.log.promptToFix(msg, function () { - this.authorizeIOS(this.checkAuthorizationDB.bind(this), cb); - }.bind(this), function () { - cb(msg, msg); - }); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkForNodeBinary = function (cb) { - this.checkForNodeBinaryInCommonPlaces(function (err, msg) { - if (!err) { - cb(null, msg); - } else { - this.checkForNodeBinaryUsingWhichCommand(function (err, msg) { - if (!err) { - cb(null, msg); - } else { - this.checkForNodeBinaryUsingAppleScript(function (err, msg) { - if (!err) { - cb(null, msg); - } else { - this.checkForNodeBinaryUsingAppiumConfigFile(function (err, msg) { - if (!err) { - cb(null, msg); - } else { - msg = 'The node binary could not be found.'; - this.log.fail(msg); - this.log.promptToFix("The node binary could not be found.", function () { - this.setupNodeBinaryPath(cb); - }.bind(this), function () { - cb(msg, msg); - }); - } - }.bind(this)); - } - }.bind(this)); - } - }.bind(this)); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkForNodeBinaryInCommonPlaces = function (cb) { - if (typeof env.NODE_BIN !== "undefined" && fs.existsSync(env.NODE_BIN)) { - this.log.pass("Node binary found using NODE_BIN environment variable at " + env.NODE_BIN, cb); - } else if (fs.existsSync('/usr/local/bin/node')) { - this.log.pass("Node binary found at /usr/local/bin/node", cb); - } else if (fs.existsSync('/opt/local/bin/node')) { - this.log.pass("Node binary found at /opt/local/bin/node", cb); - } else { - var msg = 'Node binary could not be found in the usual places'; - cb(msg, msg); - } -}; - -IOSChecker.prototype.checkForNodeBinaryUsingWhichCommand = function (cb) { - var msg; - exec("which node", { maxBuffer: 524288}, function (err, stdout) { - if (err === null && fs.existsSync(stdout.replace("\n", ""))) { - this.log.pass("Node binary found using which command at " + stdout.replace("\n", ""), cb); - } else { - msg = 'Node binary not found using the which command.'; - cb(msg, msg); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkForNodeBinaryUsingAppleScript = function (cb) { - var msg; - var appScript = [ - 'try' - , ' set appiumIsRunning to false' - , ' tell application "System Events"' - , ' set appiumIsRunning to name of every process contains "Appium"' - , ' end tell' - , ' if appiumIsRunning then' - , ' tell application "Appium" to return node path' - , ' end if' - , 'end try' - , 'return "NULL"' - ].join("\n"); - exec("osascript -e '" + appScript + "'", { maxBuffer: 524288}, function (err, stdout) { - if (err === null && fs.existsSync(stdout.replace("\n", ""))) { - this.log.pass("Node binary found using AppleScript at " + stdout.replace("\n", ""), cb); - } else { - msg = 'Node binary not found using AppleScript.'; - cb(msg, msg); - } - }.bind(this)); -}; - -IOSChecker.prototype.checkForNodeBinaryUsingAppiumConfigFile = function (cb) { - var msg = 'Node binary not found in the .appiumconfig.json file.'; - var appiumConfigPath = path.resolve(__dirname, "../..", ".appiumconfig.json"); - if (fs.existsSync(appiumConfigPath)) { - fs.readFile(appiumConfigPath, 'utf8', function (err, data) { - if (err === null) { - try { - var jsonobj = JSON.parse(data); - if (typeof jsonobj.node_bin !== "undefined" && fs.existsSync(jsonobj.node_bin)) { - this.log.pass("Node binary found using .appiumconfig.json at " + jsonobj.node_bin, cb); - } else { - cb(msg, msg); - } - } catch (jsonErr) { - cb(msg, msg); - } - } else { - cb(msg, msg); - } - }.bind(this)); - } else { - cb(msg, msg); - } -}; - -IOSChecker.prototype.installXcode = function (cb) { - exec("xcode-select --install", { maxBuffer: 524288}, function () { - this.log.promptToConfirmFix(function () { - this.checkForXcode(cb); - }.bind(this)); - }.bind(this)); -}; - -IOSChecker.prototype.installXcodeCommandLineTools = function (cb) { - exec("xcode-select --install", { maxBuffer: 524288}, function () { - this.log.promptToConfirmFix(function () { - this.checkForXcodeCommandLineTools(cb); - }.bind(this)); - }.bind(this)); -}; - -IOSChecker.prototype.authorizeIOS = function (outerCb, innerCb) { - var authorizePath = path.resolve(__dirname, "../../bin", "authorize-ios.js"); - exec("'" + process.execPath + "' '" + authorizePath + "'", { maxBuffer: 524288}, function (err) { - if (err) { - this.log.error('Could not authorize iOS: ' + err); - } - }.bind(this)).on('exit', function () { - this.log.promptToConfirmFix(function () { - outerCb(innerCb); - }.bind(this)); - }.bind(this)); -}; - -IOSChecker.prototype.setupNodeBinaryPath = function (cb) { - var appiumConfigPath = path.resolve(__dirname, "../../", ".appiumconfig.json"); - if (fs.existsSync(appiumConfigPath)) { - fs.readFile(appiumConfigPath, 'utf8', function (err, data) { - if (!err) { - try { - var jsonobj = JSON.parse(data); - jsonobj.node_bin = process.execPath; - fs.writeFile(appiumConfigPath, JSON.stringify(jsonobj), function () { - this.checkForNodeBinary(cb); - }.bind(this)); - } catch (jsonErr) { - this.log.error("Could not setup node binary path in .appiumconfig.json. Error parsing JSON: " + jsonErr); - this.checkForNodeBinary(cb); - } - } else { - this.log.error("Could not setup node binary path in .appiumconfig.json. Error reading config: " + err); - this.checkForNodeBinary(cb); - } - }.bind(this)); - } else { - this.log.error('The .appiumconfig.json file was not found at ' + appiumConfigPath); - this.exitDoctor(); - } -}; diff --git a/lib/future.js b/lib/future.js deleted file mode 100644 index e4d2eabe..00000000 --- a/lib/future.js +++ /dev/null @@ -1,52 +0,0 @@ -/* global Promise:true */ -"use strict"; - -var _ = require('underscore') - , Q = require('q') - , simctl = require('node-simctl') - , xcode = require('appium-xcode'); - -var nodeify = function (obj) { - if (typeof obj !== "function") { - var nodeified = {}; - _.each(Object.getOwnPropertyNames(obj), function (name) { - if (name !== "__esModule") { - nodeified[name] = nodeify(obj[name]); - } - }); - return nodeified; - } - return nodeifyFn(obj); -}; - -var nodeifyFn = function (fn, bindObj) { - if (typeof bindObj === "undefined") { - bindObj = null; - } - var newFn = function () { - var args = Array.prototype.slice.call(arguments, 0); - var cb = args[args.length - 1]; - args = args.slice(0, -1); - return new Q(fn.apply(bindObj, args)).nodeify(cb); - }; - return newFn; -}; - -// 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") { - 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), - xcode: nodeify(xcode), - nodeify: nodeify -}; diff --git a/lib/helpers.js b/lib/helpers.js deleted file mode 100644 index c7874f2f..00000000 --- a/lib/helpers.js +++ /dev/null @@ -1,353 +0,0 @@ -"use strict"; - -var logger = require('./server/logger.js').get('appium') - , fs = require('fs') - , request = require('request') - , _ = require('underscore') - , path = require('path') - , exec = require('child_process').exec - , support = require('appium-support') - , isWindows = support.system.isWindows - , isMac = support.system.isMac - , tempDir = support.tempDir - , AdmZip = require('adm-zip') - , ESCAPE_SPACE_RE = '\\ ' - , SPACE = / /; - - -exports.downloadFile = function (fileUrl, suffix, cb) { - // We will be downloading the files to a directory, so make sure it's there - // This step is not required if you have manually created the directory - cb = _.once(cb); - tempDir.open({prefix: 'appium-app', suffix: suffix}).nodeify(function (err, info) { - fs.close(info.fd); - var file = fs.createWriteStream(info.path); - request(fileUrl) - .on('error', function (err) { - logger.error("Problem downloading file: " + err.message); - cb(err); - }) - .pipe(file) - .on('close', function () { - logger.debug(fileUrl + ' downloaded to ' + info.path); - cb(null, info.path); - }); - }); -}; - -exports.copyLocalZip = function (localZipPath, cb) { - logger.debug("Copying local zip to tmp dir"); - fs.stat(localZipPath, function (err) { - if (err) return cb(err); - tempDir.open({prefix: 'appium-app', suffix: '.zip'}).nodeify(function (err, info) { - var infile = fs.createReadStream(localZipPath); - var outfile = fs.createWriteStream(info.path); - infile.pipe(outfile).on('close', function () { - logger.debug(localZipPath + ' copied to ' + info.path); - cb(null, info.path); - }); - }); - }); -}; - -exports.unzipFile = function (zipPath, cb) { - logger.debug("Unzipping " + zipPath); - exports.testZipArchive(zipPath, function (err, valid) { - if (valid) { - if (isWindows()) { - var zip = new AdmZip(zipPath); - zip.extractAllTo(path.dirname(zipPath), true); - logger.debug("Unzip successful"); - cb(null, null); - } else { - var execEnv = _.clone(process.env); - delete execEnv.UNZIP; - var execOpts = {cwd: path.dirname(zipPath), maxBuffer: 524288, - env: execEnv}; - exec('unzip -o ' + zipPath, execOpts, function (err, stderr, stdout) { - if (!err) { - logger.debug("Unzip successful"); - cb(null, stderr); - } else { - logger.error("Unzip threw error " + err); - logger.error("Stderr: " + stderr); - logger.error("Stdout: " + stdout); - cb("Archive could not be unzipped, check appium logs.", null); - } - }); - } - } else { - cb(err, null); - } - }); -}; - -exports.testZipArchive = function (zipPath, cb) { - logger.debug("Testing zip archive: " + zipPath); - if (isWindows()) { - if (fs.existsSync(zipPath)) { - logger.debug("Zip archive tested clean"); - cb(null, true); - } else { - cb("Zip archive was not found.", false); - } - } else { - var execEnv = _.clone(process.env); - delete execEnv.UNZIP; - var execOpts = {cwd: path.dirname(zipPath), maxBuffer: 524288, - env: execEnv}; - exec("unzip -tq " + zipPath, execOpts, function (err, stderr, stdout) { - if (!err) { - if (/No errors detected/.exec(stderr)) { - logger.debug("Zip archive tested clean"); - cb(null, true); - } else { - logger.error("Zip file " + zipPath + " was not valid"); - logger.error("Stderr: " + stderr); - logger.error("Stdout: " + stdout); - cb("Zip archive did not test successfully, check appium server logs " + - "for output", false); - } - } else { - logger.error("Test zip archive threw error " + err); - logger.error("Stderr: " + stderr); - logger.error("Stdout: " + stdout); - cb("Error testing zip archive, are you sure this is a zip file? " + err, null); - } - }); - } -}; - -exports.unzipApp = function (zipPath, appExt, cb) { - exec("find " + path.dirname(zipPath) + " -type d -name '*" + appExt + "' | xargs rm -rf " + path.dirname(zipPath) + - "/Payload*", function (error /*, stdout, stderr*/) { - if (!error) { - exports.unzipFile(zipPath, function (err, output) { - if (!err) { - var relaxedRegStr = "(?:creating|inflating|extracting): (.+" + appExt + ")/?"; - // in the strict regex, we check for an entry which ends with the - // extension - var strictReg = new RegExp(relaxedRegStr + "$", 'm'); - // otherwise, we allow an entry which contains the extension, but we - // need to be careful, because it might be a false positive - var relaxedReg = new RegExp(relaxedRegStr, 'm'); - var strictMatch = strictReg.exec(output); - var relaxedMatch = relaxedReg.exec(output); - var getAppPath = function (match) { - return path.resolve(path.dirname(zipPath), match[1]); - }; - if (strictMatch) { - cb(null, getAppPath(strictMatch)); - } else if (relaxedMatch) { - logger.debug("Got a relaxed match for app in zip, be careful for app match errors"); - cb(null, getAppPath(relaxedMatch)); - } else { - cb("App zip unzipped OK, but we couldn't find a .app bundle in it. " + - "Make sure your archive contains the .app package and nothing else", - null); - } - } else { - cb(err, null); - } - }); - } else { - cb(error, null); - } - }); -}; - -exports.getUser = function (cb) { - logger.debug("Determining current user"); - exec("whoami", { maxBuffer: 524288 }, function (err, stdout) { - if (err) { - logger.error(err); - cb(err); - } else { - logger.debug("User is " + stdout.trim()); - cb(null, stdout.trim()); - } - }); -}; - -exports.multiResolve = function (roots) { - var args = Array.prototype.slice.call(arguments, 1); - var paths = []; - _.each(roots, function (root) { - var resolveArgs = [root].concat(args); - paths.push(path.resolve.apply(null, resolveArgs)); - }); - return paths; -}; - -exports.delay = function (secs) { - var date = new Date(); - var curDate = null; - do { curDate = new Date(); } - while (curDate - date < (secs * 1000.0)); -}; - -exports.escapeSpecialChars = function (str, quoteEscape) { - if (typeof str !== "string") { - return str; - } - if (typeof quoteEscape === "undefined") { - quoteEscape = false; - } - str = str - .replace(/[\\]/g, '\\\\') - .replace(/[\/]/g, '\\/') - .replace(/[\b]/g, '\\b') - .replace(/[\f]/g, '\\f') - .replace(/[\n]/g, '\\n') - .replace(/[\r]/g, '\\r') - .replace(/[\t]/g, '\\t') - .replace(/[\"]/g, '\\"') - .replace(/\\'/g, "\\'"); - if (quoteEscape) { - var re = new RegExp(quoteEscape, "g"); - str = str.replace(re, "\\" + quoteEscape); - } - return str; -}; - -var warningsEmitted = {}; -var deprecationWarnings = []; - -var warningText = _.template( - "[DEPRECATED] The <%= deprecated %> <%= kind %> has been deprecated and will " + - "be removed." -); - -var replacementText = _.template( - " Please use the <%= replacement %> <%= kind %> instead." -); - -exports.formatDeprecationWarning = function (kind, deprecated, replacement) { - var warning = warningText({kind: kind, deprecated: deprecated}); - if (typeof replacement !== 'undefined') { - warning += replacementText({kind: kind, replacement: replacement}); - } - return warning; -}; - -exports.logDeprecationWarning = function (kind, deprecated, replacement) { - if (!_.has(warningsEmitted, kind)) { - warningsEmitted[kind] = {}; - } - if (!_.has(warningsEmitted[kind], deprecated)) { - var warning = exports.formatDeprecationWarning(kind, deprecated, replacement); - deprecationWarnings.push(warning); - logger.warn(warning); - warningsEmitted[kind][deprecated] = true; - } -}; - -exports.logCustomDeprecationWarning = function (kind, deprecated, msg) { - if (!_.has(warningsEmitted, kind)) { - warningsEmitted[kind] = {}; - } - if (!_.has(warningsEmitted[kind], deprecated)) { - deprecationWarnings.push(msg); - logger.warn(msg); - warningsEmitted[kind][deprecated] = true; - } -}; - -exports.logFinalDeprecationWarning = function () { - var numWarnings = deprecationWarnings.length; - if (numWarnings > 0) { - logger.warn("[DEPRECATED] You used " + numWarnings + " deprecated" + - " capabilities during this session. Please check the logs" + - " as they will be removed in a future version of Appium."); - } -}; - -exports.getDeprecationWarnings = function () { - return deprecationWarnings; -}; - -exports.clearWarnings = function () { - warningsEmitted = {}; - deprecationWarnings = []; -}; - -exports.rotateImage = function (imgPath, deg, cb) { - logger.debug("Rotating image " + imgPath + " " + deg + " degrees"); - var scriptPath = require('appium-uiauto').rotate; - var cmd = "osascript " + scriptPath + " " + JSON.stringify(imgPath) + - " " + deg; - exec(cmd, { maxBuffer: 524288 }, function (err, stdout) { - if (err) return cb(err); - console.log(stdout); - cb(null); - }); -}; - -exports.macVersionArray = function (cb) { - var versions = []; - if (isMac()) { - exec("sw_vers -productVersion", function (err, stdout) { - if (err) return cb(err); - stdout = stdout.trim(); - if (/\d+\.\d+\.\d+/.test(stdout)) { - _.each(stdout.split("."), function (ver) { - versions.push(parseInt(ver, 10)); - }); - cb(null, versions); - } else { - cb(new Error("Could not parse version information from: " + stdout)); - } - }); - } else { - cb(null, versions); - } -}; - -exports.getGitRev = function (cb) { - var cwd = path.resolve(__dirname, ".."); - exec("git rev-parse HEAD", {cwd: cwd, maxBuffer: 524288}, function (err, stdout) { - if (err) return cb(err); - cb(null, stdout.trim()); - }); -}; - -exports.getAppiumConfig = function () { - var configPath = path.resolve(__dirname, "..", ".appiumconfig.json"); - var config - , msg; - try { - config = require(configPath); - } catch (e) { - if (e.code === "MODULE_NOT_FOUND") { - msg = "Could not find config file: " + configPath + - "; looks like config hasn't been run." + - " Please run reset.sh or appium configure."; - logger.error(msg); - throw new Error(msg); - } else { - msg = "Invalid config file at " + configPath + - " please re-run reset.sh or appium config"; - logger.error(msg); - throw new Error(msg); - } - } - - return config; -}; - -exports.iosConfigured = function () { - return typeof exports.getAppiumConfig().ios !== "undefined"; -}; - -exports.truncateDecimals = function (number, digits) { - var multiplier = Math.pow(10, digits), - adjustedNum = number * multiplier, - truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum); - - return truncatedNum / multiplier; -}; - -exports.escapeSpace = function (str) { - return str.split(SPACE).join(ESCAPE_SPACE_RE); -}; - diff --git a/lib/server/capabilities.js b/lib/server/capabilities.js deleted file mode 100644 index ca4921e1..00000000 --- a/lib/server/capabilities.js +++ /dev/null @@ -1,207 +0,0 @@ -"use strict"; -var _ = require('underscore') - , logger = require('./logger.js').get('appium') - , warnDeprecated = require('../helpers.js').logDeprecationWarning; - -var capsConversion = { - 'launch': 'autoLaunch' -}; - -var okObjects = [ - 'proxy' -, 'launchTimeout' -, 'specialChromedriverSessionArgs' -, 'chromeOptions' -, 'loggingPrefs' -]; - -var requiredCaps = [ - 'platformName' -, 'deviceName' -]; - -var strictRequiredCaps = [ - 'platformName' -, 'platformVersion' -, 'deviceName' -]; - -var generalCaps = strictRequiredCaps.concat([ - 'automationName' -, 'app' -, 'browserName' -, 'newCommandTimeout' -, 'autoLaunch' -, 'language' -, 'locale' -, 'udid' -, 'orientation' -, 'autoWebview' -, 'noReset' -, 'fullReset' -]); - -var androidCaps = [ - 'appActivity' -, 'appPackage' -, 'appWaitActivity' -, 'appWaitPackage' -, 'deviceReadyTimeout' -, 'androidCoverage' -, 'enablePerformanceLogging' -, 'avdLaunchTimeout' -, 'avdReadyTimeout' -, 'avd' -, 'avdArgs' -, 'useKeystore' -, 'keystorePath' -, 'keystorePassword' -, 'keyAlias' -, 'keyPassword' -, 'autoWebviewTimeout' -, 'intentAction' -, 'intentCategory' -, 'intentFlags' -, 'optionalIntentArguments' -, 'unicodeKeyboard' -, 'resetKeyboard' -, 'noSign' -, 'ignoreUnimportantViews' -, 'dontStopAppOnReset' -, 'disableAndroidWatchers' -]; - -var iosCaps = [ - 'calendarFormat' -, 'bundleId' -, 'launchTimeout' -, 'locationServicesEnabled' -, 'locationServicesAuthorized' -, 'autoAcceptAlerts' -, 'autoDismissAlerts' -, 'nativeInstrumentsLib' -, 'nativeWebTap' -, 'safariAllowPopups' -, 'safariInitialUrl' -, 'safariIgnoreFraudWarning' -, 'safariOpenLinksInBackground' -, 'keepKeyChains' -, 'localizableStringsDir' -, 'interKeyDelay' -, 'showIOSLog' -, 'loggingPrefs' -, 'sendKeyStrategy' -, 'waitForAppScript' -]; - -var Capabilities = function (capabilities) { - this.warnings = {}; - this.setDesired(capabilities); -}; - -Capabilities.prototype.setDesired = function (caps) { - caps = _.clone(caps); - _.each(caps, function (value, cap) { - if (!_.contains(okObjects, cap) && - typeof value === "object" && - value !== null) { - logger.warn("Converting cap " + cap + " to string, since it was an " + - "object. This might be a user error. Original value was: " + - JSON.stringify(value)); - caps[cap] = JSON.stringify(value); - } - if (typeof value === "string") { - // handle the case where users send in "true" or "false" as a string - // instead of a boolean - var tempValue = value.trim().toLowerCase(); - if (tempValue === "false" || tempValue === "true") { - logger.warn("Converting cap " + cap + " from string to boolean. " + - "This might cause unexpected behavior."); - caps[cap] = (tempValue === "true"); - } - } - }); - _.each(caps, function (value, cap) { - if (_.contains(_.keys(capsConversion), cap)) { - warnDeprecated('capability', cap, capsConversion[cap]); - caps[capsConversion[cap]] = value; - delete caps[cap]; - } - }); - this.desired = caps; - _.each(caps, function (value, cap) { - this[cap] = value; - }, this); -}; - -Capabilities.prototype.checkValidity = function (deviceType, strictMode) { - if (strictMode) { - this.checkStrictValidity(deviceType); - } else { - - var capsUsed = _.keys(this.desired); - var forgottenRequiredCaps = _.difference(requiredCaps, capsUsed); - if (forgottenRequiredCaps.length) { - throw new Error('The following desired capabilities are required, but were not provided: ' + - forgottenRequiredCaps.join(', ')); - } - - // log a message about caps which are passed in and not recognized by appium - var allValidCaps = [].concat(generalCaps, androidCaps, iosCaps); - var unknownCaps = _.difference(capsUsed, allValidCaps); - if (unknownCaps.length > 0) { - logger.debug('The following desired capabilities were provided, but not recognized by appium.' + - ' They will be passed on to any other services running on this server. : ' + - unknownCaps.join(', ')); - } - - } -}; - -Capabilities.prototype.checkStrictValidity = function (deviceType) { - if (_.contains(["firefoxos", "selendroid"], deviceType)) { - logger.debug("Not checking cap validity because we're proxying all caps " + - "to " + deviceType); - return; - } - - logger.debug("Checking caps according to strict mode"); - - var e = function (msg) { throw new Error(msg); }; - - var allValidCaps = [].concat(generalCaps, androidCaps, iosCaps); - var capsUsed = _.keys(this.desired); - var unknownCaps = _.difference(capsUsed, allValidCaps); - if (unknownCaps.length > 0) { - return e("Appium does not know about these desired capabilities: " + - JSON.stringify(unknownCaps) + ". Please remove unknown caps"); - } - - var forgottenRequiredCaps = _.difference(strictRequiredCaps, capsUsed); - if (forgottenRequiredCaps.length > 0) { - return e("Appium requires the following caps to be passed in: " + - JSON.stringify(forgottenRequiredCaps)); - } - - if (_.difference(['app', 'browserName'], capsUsed).length > 1) { - return e("You must pass in either the 'app' or 'browserName' cap"); - } - - var validDeviceCaps = _.clone(generalCaps); - if (_.contains(["safari", "ios"], deviceType)) { - validDeviceCaps = validDeviceCaps.concat(iosCaps); - } else if (_.contains(["chrome", "android"], deviceType)) { - validDeviceCaps = validDeviceCaps.concat(androidCaps); - } - - var unknownDeviceCaps = _.difference(capsUsed, validDeviceCaps); - if (unknownDeviceCaps.length > 0) { - return e("These capabilities are not valid for your device: " + - JSON.stringify(unknownDeviceCaps) + ". Please remove them"); - } - -}; - -Capabilities.capabilityConversions = capsConversion; - -module.exports = Capabilities; diff --git a/lib/server/controller.js b/lib/server/controller.js deleted file mode 100644 index b9d94bc9..00000000 --- a/lib/server/controller.js +++ /dev/null @@ -1,1288 +0,0 @@ -// Appium webserver controller methods -// https://github.com/hugs/appium/blob/master/appium/server.py -"use strict"; -var status = require('./status.js') - , logger = require('./logger.js').get('appium') - , _ = require('underscore') - , _s = require("underscore.string") - , swig = require('swig') - , path = require('path') - , version = require('../../package.json').version - , proxy = require('./proxy.js') - , responses = require('./responses.js') - , getResponseHandler = responses.getResponseHandler - , respondError = responses.respondError - , respondSuccess = responses.respondSuccess - , checkMissingParams = responses.checkMissingParams - , notYetImplemented = responses.notYetImplemented - , hasValue = require('appium-support').util.hasValue - , helpers = require('../helpers.js') - , logCustomDeprecationWarning = helpers.logCustomDeprecationWarning - , safely = require('./helpers.js').safely - , NotImplementedError = require('./errors').NotImplementedError; - -exports.getGlobalBeforeFilter = function (appium) { - return function (req, res, next) { - req.appium = appium; - req.device = appium.device; - if (proxy.shouldProxy(req)) { - if (req.appium.commandTimeout) { - // if we're proxying, we never get into the sessionBeforeFilter, - // so let's make sure to reset the timeout on every request still - req.appium.resetTimeout(); - } - if (typeof req.device.translatePath !== "undefined") { - req.device.translatePath(req); - } - proxy.doProxy(req, res, next); - } else { - next(); - } - }; -}; - -exports.sessionBeforeFilter = function (req, res, next) { - var match = new RegExp("([^/]+)").exec(req.params[0]); - var sessId = match ? match[1] : null; - if (req.appium.commandTimeout) { - req.appium.resetTimeout(); - } - // if we don't actually have a valid session, respond with an error - if (sessId && (!req.device || req.appium.sessionId !== sessId)) { - safely(req, function () { - res.status(404).send({sessionId: null, status: status.codes.NoSuchDriver.code, value: ''}); - }); - } else { - next(); - } -}; - -exports.getStatus = function (req, res) { - // Return a static JSON object to the client - var gitSha = req.appium.serverConfig['git-sha']; - var data = {build: {version: version}}; - if (typeof gitSha !== "undefined") { - data.build.revision = gitSha; - } - if (req.device && typeof req.device.getStatusExtensions === "function") { - data = _.extend(data, req.device.getStatusExtensions()); - } - respondSuccess(req, res, data); -}; - -exports.installApp = function (req, res) { - var install = function (appPath) { - req.device.installApp(appPath, function (error, response) { - if (error !== null) { - respondError(req, res, error); - } else { - respondSuccess(req, res, response); - } - }); - }; - if (typeof req.body.appPath !== "undefined") { - req.device.unpackApp(req, function (unpackedAppPath) { - if (unpackedAppPath === null) { - respondError(req, res, 'Only a (zipped) app/apk files can be installed using this endpoint'); - } else { - install(unpackedAppPath); - } - }); - } else if (typeof req.device.args.app !== "undefined") { - install(req.device.args.app); - } else { - respondError(req, res, "No app defined (either through desired capabilities or as an argument)"); - } -}; - -exports.removeApp = function (req, res) { - req.body.appId = req.body.appId || req.body.bundleId; - if (checkMissingParams(req, res, {appId: req.body.appId}, true)) { - req.device.removeApp(req.body.appId, function (error, response) { - if (error !== null) { - respondError(req, res, response); - } else { - respondSuccess(req, res, response); - } - }); - } -}; - -exports.isAppInstalled = function (req, res) { - if (checkMissingParams(req, res, {bundleId: req.body.bundleId}, true)) { - req.device.isAppInstalled(req.body.bundleId, function (error, stdout) { - if (error !== null) { - respondSuccess(req, res, false); - } else { - // We're examining the type of stdout because `isAppInstalled` uses - // node-idevice for real iOS devices. node-idevice passes a boolean - // value in the second parameter of the callback function. Other - // functions pass back an array. Changing the parameter type of the - // other functions is a deeper and more dangerous change than just - // type-checking here. - if ((req.appium.args.udid && req.appium.args.udid.length === 40) || - (typeof stdout === "boolean" && stdout) || - (typeof stdout[0] !== "undefined")) { - respondSuccess(req, res, true); - } else { - respondSuccess(req, res, false); - } - } - }); - } -}; - -exports.startActivity = function (req, res) { - var onErr = function (err) { - respondError(req, res, "Unable to launch the app: " + err); - }; - - // 8/21/14: currently not supported on all devices - if (!req.device.startApp) { - onErr(new NotImplementedError()); - } else { - req.device.startApp(req.body, function (err) { - if (err) return onErr(err); - respondSuccess(req, res, "Successfully launched the app."); - }); - } -}; - -exports.launchApp = function (req, res) { - var onErr = function (err) { - respondError(req, res, "Unable to launch the app: " + err); - }; - req.device.start(function (err) { - if (err) return onErr(err); - respondSuccess(req, res, "Successfully launched the app."); - }, function () { - onErr(new Error("UiAutomator died")); - }); -}; - -exports.closeApp = function (req, res) { - req.device.stop(function () { - respondSuccess(req, res, "Successfully closed the [" + req.device.args.app + "] app."); - }, function () { - respondError(req, res, "Something went wrong whilst closing the [" + req.device.args.app + "] app."); - }); -}; - -exports.createSession = function (req, res) { - if (typeof req.body === 'string') { - req.body = JSON.parse(req.body); - } - - logger.info('Client User-Agent string:', req.headers['user-agent']); - - var next = function (reqHost, sessionId) { - safely(req, function () { - res.set('Location', "http://" + reqHost + "/wd/hub/session/" + sessionId); - res.status(303).send("Appium session started with sessionId " + sessionId); - }); - }; - if (req.appium.preLaunched && req.appium.sessionId) { - req.appium.preLaunched = false; - next(req.headers.host, req.appium.sessionId, req.appium.device, true); - } else { - req.appium.start(req.body.desiredCapabilities, function (err, instance) { - if (err) { - logger.error("Failed to start an Appium session, err was: " + err); - logger.debug(err.stack); - delete err.stack; - respondError(req, res, status.codes.SessionNotCreatedException, err); - } else { - logger.debug("Appium session started with sessionId " + req.appium.sessionId); - next(req.headers.host, req.appium.sessionId, instance); - } - }); - } -}; - -exports.getSession = function (req, res) { - // Return a static JSON object to the client - respondSuccess(req, res, req.device.capabilities); -}; - -exports.getSessions = function (req, res) { - var sessions = []; - if (req.appium.sessionId !== null) { - sessions.push({ - id: req.appium.sessionId - , capabilities: req.device.capabilities - }); - } - - respondSuccess(req, res, sessions); -}; - -exports.reset = function (req, res) { - req.appium.reset(getResponseHandler(req, res)); -}; - -exports.lock = function (req, res) { - var seconds = req.body.seconds; - if (checkMissingParams(req, res, {seconds: seconds})) { - req.device.lock(seconds, getResponseHandler(req, res)); - } -}; - -exports.unlock = function (req, res) { - if (req.device.unlock) { - req.device.unlock(getResponseHandler(req, res)); - } else { - notYetImplemented(req, res); - } -}; - -exports.isLocked = function (req, res) { - req.device.isLocked(getResponseHandler(req, res)); -}; - -exports.background = function (req, res) { - var seconds = req.body.seconds; - if (checkMissingParams(req, res, {seconds: seconds})) { - req.device.background(seconds, getResponseHandler(req, res)); - } -}; - -exports.deleteSession = function (req, res) { - req.appium.stop(getResponseHandler(req, res)); -}; - -exports.equalsElement = function (req, res) { - var element = req.params.elementId - , other = req.params.otherId; - - req.device.equalsWebElement(element, other, getResponseHandler(req, res)); -}; - -exports.findElements = function (req, res) { - var strategy = req.body.using - , selector = req.body.value; - - if (checkMissingParams(req, res, {strategy: strategy, selector: selector}, true)) { - req.device.findElements(strategy, selector, getResponseHandler(req, res)); - } -}; - -exports.findElement = function (req, res) { - var strategy = req.body.using - , selector = req.body.value; - - if (checkMissingParams(req, res, {strategy: strategy, selector: selector}, true)) { - req.device.findElement(strategy, selector, getResponseHandler(req, res)); - } -}; - -exports.findElementFromElement = function (req, res) { - var element = req.params.elementId - , strategy = req.body.using - , selector = req.body.value; - - req.device.findElementFromElement(element, strategy, selector, getResponseHandler(req, res)); -}; - -exports.findElementsFromElement = function (req, res) { - var element = req.params.elementId - , strategy = req.body.using - , selector = req.body.value; - - req.device.findElementsFromElement(element, strategy, selector, getResponseHandler(req, res)); -}; - -exports.setValue = function (req, res) { - var elementId = req.params.elementId - // spec says value attribute is an array of strings; - // let's turn it into one string - , value = req.body.value.join(''); - - req.device.setValue(elementId, value, getResponseHandler(req, res)); -}; - -exports.replaceValue = function (req, res) { - var elementId = req.params.elementId - // spec says value attribute is an array of strings; - // let's turn it into one string - , value = req.body.value.join(''); - - req.device.replaceValue(elementId, value, getResponseHandler(req, res)); -}; - -exports.performTouch = function (req, res) { - // touch actions do not work in webview - if (req.device.isWebContext()) { - return notYetImplemented(req, res); - } - - // first, assume that we are getting and array of gestures - var gestures = req.body; - - // some clients, like Python, send an object in which there is an `actions` - // property that is the array of actions - // get the gestures from there if we don't already have an array - if (gestures && !Array.isArray(gestures)) { - gestures = gestures.actions; - } - - // press-wait-moveTo-release is `swipe`, so use native method - if (gestures.length === 4 && - gestures[0].action === 'press' && - gestures[1].action === 'wait' && - gestures[2].action === 'moveTo' && - gestures[3].action === 'release') { - return exports.mobileSwipe(req, res, gestures); - } - - req.device.performTouch(gestures, getResponseHandler(req, res)); -}; - -exports.performMultiAction = function (req, res) { - // touch actions do not work in webview - if (req.device.isWebContext()) { - return notYetImplemented(req, res); - } - - var elementId = req.body.elementId; - var actions = req.body.actions; - - if (actions.length === 0) { - return respondError(req, res, status.codes.UnknownError.code, - new Error("Unable to perform Multi Pointer Gesture with no actions.")); - } - - req.device.performMultiAction(elementId, actions, getResponseHandler(req, res)); -}; - -exports.doClick = function (req, res) { - var elementId = req.params.elementId || req.body.element; - req.device.click(elementId, getResponseHandler(req, res)); -}; - -exports.touchLongClick = function (req, res) { - var element = req.body.element; - var x = req.body.x; - var y = req.body.y; - var duration = req.body.duration; - - if (element && checkMissingParams(req, res, {element: element}, true)) { - req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res)); - } else if (checkMissingParams(req, res, {x: x, y: y}, true)) { - req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res)); - } -}; - -exports.touchDown = function (req, res) { - var element = req.body.element; - var x = req.body.x; - var y = req.body.y; - - if (element && checkMissingParams(req, res, {element: element}, true)) { - req.device.touchDown(element, x, y, getResponseHandler(req, res)); - } else if (checkMissingParams(req, res, {x: x, y: y}, true)) { - req.device.touchDown(element, x, y, getResponseHandler(req, res)); - } -}; - -exports.touchUp = function (req, res) { - var element = req.body.element; - var x = req.body.x; - var y = req.body.y; - - if (element && checkMissingParams(req, res, {element: element}, true)) { - req.device.touchUp(element, x, y, getResponseHandler(req, res)); - } else if (checkMissingParams(req, res, {x: x, y: y}, true)) { - req.device.touchUp(element, x, y, getResponseHandler(req, res)); - } -}; - -exports.touchMove = function (req, res) { - var element = req.body.element; - var x = req.body.x; - var y = req.body.y; - - if (element && checkMissingParams(req, res, {element: element}, true)) { - req.device.touchMove(element, x, y, getResponseHandler(req, res)); - } else if (checkMissingParams(req, res, {x: x, y: y}, true)) { - req.device.touchMove(element, x, y, getResponseHandler(req, res)); - } -}; - -exports.mobileTap = function (req, res) { - req.body = _.defaults(req.body, { - tapCount: 1 - , touchCount: 1 - , duration: 0.1 - , x: 0.5 - , y: 0.5 - , element: null - }); - var tapCount = req.body.tapCount - , touchCount = req.body.touchCount - , duration = req.body.duration - , element = req.body.element - , x = req.body.x - , y = req.body.y; - - req.device.complexTap(tapCount, touchCount, duration, x, y, element, getResponseHandler(req, res)); -}; - -exports.mobileFlick = function (req, res) { - req.body = _.defaults(req.body, { - touchCount: 1 - , startX: 0.5 - , startY: 0.5 - , endX: 0.5 - , endY: 0.5 - , element: null - }); - var touchCount = req.body.touchCount - , element = req.body.element - , startX = req.body.startX - , startY = req.body.startY - , endX = req.body.endX - , endY = req.body.endY; - - req.device.flick(startX, startY, endX, endY, touchCount, element, getResponseHandler(req, res)); -}; - -exports.mobileDrag = function (req, res) { - req.body = _.defaults(req.body, { - startX: 0.5 - , startY: 0.5 - , endX: 0.5 - , endY: 0.5 - , duration: 1 - , touchCount: 1 - , element: null - , destEl: null - }); - var touchCount = req.body.touchCount - , element = req.body.element - , destEl = req.body.destEl - , duration = req.body.duration - , startX = req.body.startX - , startY = req.body.startY - , endX = req.body.endX - , endY = req.body.endY; - - req.device.drag(startX, startY, endX, endY, duration, touchCount, element, destEl, getResponseHandler(req, res)); -}; - -var _getSwipeTouchDuration = function (waitGesture) { - // the touch action api uses ms, we want seconds - // 0.8 is the default time for the operation - var duration = 0.8; - - if (typeof waitGesture.options.ms !== 'undefined' && waitGesture.options.ms) { - duration = waitGesture.options.ms / 1000; - if (duration === 0) { - // set to a very low number, since they wanted it fast - // but below 0.1 becomes 0 steps, which causes errors - duration = 0.1; - } - } - return duration; -}; - -exports.mobileSwipe = function (req, res, gestures) { - - var getCoordDefault = function (val) { - // going the long way and checking for undefined and null since - // we can't be assured `elId` is a string and not an int. Same - // thing with destElement below. - return hasValue(val) ? val : 0.5; - }; - - var touchCount = req.body.touchCount || 1 - , startX = getCoordDefault(gestures[0].options.x) - , startY = getCoordDefault(gestures[0].options.y) - , endX = getCoordDefault(gestures[2].options.x) - , endY = getCoordDefault(gestures[2].options.y) - , duration = _getSwipeTouchDuration(gestures[1]) - , element = gestures[0].options.element - , destElement = gestures[2].options.element || gestures[0].options.element; - - // there's no destination element handling in bootstrap and since it applies to all platforms, we handle it here - if (hasValue(destElement)) { - req.device.getLocation(destElement, function (err, locResult) { - if (err) { - respondError(req, res, err); - } else { - req.device.getSize(destElement, function (er, sizeResult) { - if (er) { - respondError(req, res, er); - } else { - - var offsetX = (Math.abs(endX) < 1 && Math.abs(endX) > 0) ? - sizeResult.value.width * endX : - endX; - var offsetY = (Math.abs(endY) < 1 && Math.abs(endY) > 0) ? - sizeResult.value.height * endY : - endY; - - var destX = locResult.value.x + offsetX; - var destY = locResult.value.y + offsetY; - - // if the target element was provided, the coordinates for the destination need to be relative to it. - if (hasValue(element)) { - req.device.getLocation(element, function (e, firstElLocation) { - if (e) { - respondError(req, res, e); - } else { - destX -= firstElLocation.value.x; - destY -= firstElLocation.value.y; - - req.device.swipe(startX, startY, destX, destY, duration, touchCount, element, getResponseHandler(req, res)); - } - }); - } else { - req.device.swipe(startX, startY, destX, destY, duration, touchCount, element, getResponseHandler(req, res)); - } - } - }); - } - }); - } else { - req.device.swipe(startX, startY, endX, endY, duration, touchCount, element, getResponseHandler(req, res)); - } -}; - -exports.mobileRotation = function (req, res) { - req.body = _.defaults(req.body, { - x: 0.5 - , y: 0.5 - , radius: 0.5 - , rotation: 3.14159265359 - , touchCount: 2 - , duration: 1 - , element: null - }); - var element = req.body.element - , duration = req.body.duration - , x = req.body.x - , y = req.body.y - , radius = req.body.radius - , touchCount = req.body.touchCount - , rotation = req.body.rotation; - - req.device.rotate(x, y, radius, rotation, duration, touchCount, element, getResponseHandler(req, res)); -}; - -exports.mobilePinchClose = function (req, res) { - req.body = _.defaults(req.body, { - startX: 0.5 - , startY: 0.5 - , endX: 0.5 - , endY: 0.5 - , duration: 0.8 - , percent: 200 - , steps: 50 - , element: null - }); - var element = req.body.element - , duration = req.body.duration - , startX = req.body.startX - , startY = req.body.startY - , endX = req.body.endX - , endY = req.body.endY - , percent = req.body.percent - , steps = req.body.steps; - - req.device.pinchClose(startX, startY, endX, endY, duration, percent, steps, element, getResponseHandler(req, res)); -}; - -exports.mobilePinchOpen = function (req, res) { - req.body = _.defaults(req.body, { - startX: 0.5 - , startY: 0.5 - , endX: 0.5 - , endY: 0.5 - , duration: 0.8 - , percent: 200 - , steps: 50 - , element: null - }); - var element = req.body.element - , duration = req.body.duration - , startX = req.body.startX - , startY = req.body.startY - , endX = req.body.endX - , endY = req.body.endY - , percent = req.body.percent - , steps = req.body.steps; - - req.device.pinchOpen(startX, startY, endX, endY, duration, percent, steps, element, getResponseHandler(req, res)); -}; - -exports.mobileScrollTo = function (req, res) { - logCustomDeprecationWarning('mobile method', 'scrollTo', - "scrollTo will be removed in a future version of appium"); - req.body = _.defaults(req.body, { - element: null - , text: null - , direction: "vertical" - }); - var element = req.body.element - , text = req.body.text - , direction = req.body.direction; - - req.device.scrollTo(element, text, direction, getResponseHandler(req, res)); -}; - -exports.mobileScroll = function (req, res) { - req.body = _.defaults(req.body, { - element: null - , direction: "down" - }); - var direction = req.body.direction.toString().toLowerCase() - , element = req.body.element; - if (!_.contains(['up', 'left', 'right', 'down'], direction)) { - return respondError(req, res, status.codes.UnknownCommand.code, - new Error("Direction " + direction + " is not valid for scroll")); - } - - req.device.scroll(element, direction, getResponseHandler(req, res)); -}; - -exports.mobileShake = function (req, res) { - req.device.shake(getResponseHandler(req, res)); -}; - -exports.hideKeyboard = function (req, res) { - var key = req.body.key || req.body.keyName; - var strategy = req.body.strategy || - (key ? 'pressKey' : 'default'); - req.device.hideKeyboard(strategy, key, getResponseHandler(req, res)); -}; - -exports.clear = function (req, res) { - var elementId = req.params.elementId; - req.device.clear(elementId, getResponseHandler(req, res)); -}; - -exports.getText = function (req, res) { - var elementId = req.params.elementId; - - req.device.getText(elementId, getResponseHandler(req, res)); -}; - -exports.getName = function (req, res) { - var elementId = req.params.elementId; - - req.device.getName(elementId, getResponseHandler(req, res)); -}; - -exports.getAttribute = function (req, res) { - var elementId = req.params.elementId - , attributeName = req.params.name; - - req.device.getAttribute(elementId, attributeName, getResponseHandler(req, res)); -}; - -exports.getCssProperty = function (req, res) { - var elementId = req.params.elementId - , propertyName = req.params.propertyName; - - req.device.getCssProperty(elementId, propertyName, getResponseHandler(req, res)); -}; - -exports.getLocation = function (req, res) { - logCustomDeprecationWarning('location', 'getLocation', "location will be removed in a future version of Appium, please use location_in_view"); - exports.getLocationInView(req, res); -}; - -exports.getLocationInView = function (req, res) { - var elementId = req.params.elementId; - req.device.getLocation(elementId, getResponseHandler(req, res)); -}; - -exports.getSize = function (req, res) { - var elementId = req.params.elementId; - req.device.getSize(elementId, getResponseHandler(req, res)); -}; - -exports.getWindowSize = function (req, res) { - var windowHandle = req.params.windowhandle; - req.device.getWindowSize(windowHandle, getResponseHandler(req, res)); -}; - -exports.maximizeWindow = function (req, res) { - // noop - respondSuccess(req, res); -}; - -exports.getPageIndex = function (req, res) { - var elementId = req.params.elementId; - req.device.getPageIndex(elementId, getResponseHandler(req, res)); -}; - -exports.pressKeyCode = function (req, res) { - req.body = _.defaults(req.body, { - keycode: null - , metastate: null - }); - var keycode = req.body.keycode - , metastate = req.body.metastate; - req.device.pressKeyCode(keycode, metastate, getResponseHandler(req, res)); -}; - -exports.longPressKeyCode = function (req, res) { - req.body = _.defaults(req.body, { - keycode: null - , metastate: null - }); - var keycode = req.body.keycode - , metastate = req.body.metastate; - req.device.longPressKeyCode(keycode, metastate, getResponseHandler(req, res)); -}; - -exports.keyevent = function (req, res) { - req.body = _.defaults(req.body, { - keycode: null - , metastate: null - }); - var keycode = req.body.keycode - , metastate = req.body.metastate; - req.device.keyevent(keycode, metastate, getResponseHandler(req, res)); -}; - -exports.back = function (req, res) { - req.device.back(getResponseHandler(req, res)); -}; - -exports.forward = function (req, res) { - req.device.forward(getResponseHandler(req, res)); -}; - -exports.refresh = function (req, res) { - req.device.refresh(getResponseHandler(req, res)); -}; - -exports.keys = function (req, res) { - var keys = req.body.value.join(''); - - req.device.keys(keys, getResponseHandler(req, res)); -}; - -exports.frame = function (req, res) { - var frame = req.body.id; - - req.device.frame(frame, getResponseHandler(req, res)); -}; - -exports.elementDisplayed = function (req, res) { - var elementId = req.params.elementId; - req.device.elementDisplayed(elementId, getResponseHandler(req, res)); -}; - -exports.elementEnabled = function (req, res) { - var elementId = req.params.elementId; - - req.device.elementEnabled(elementId, getResponseHandler(req, res)); -}; - -exports.elementSelected = function (req, res) { - var elementId = req.params.elementId; - req.device.elementSelected(elementId, getResponseHandler(req, res)); -}; - -exports.getPageSource = function (req, res) { - req.device.getPageSource(getResponseHandler(req, res)); -}; - -exports.getAlertText = function (req, res) { - req.device.getAlertText(getResponseHandler(req, res)); -}; - -exports.setAlertText = function (req, res) { - var text = req.body.text; - req.device.setAlertText(text, getResponseHandler(req, res)); -}; - -exports.postAcceptAlert = function (req, res) { - req.device.postAcceptAlert(getResponseHandler(req, res)); -}; - -exports.postDismissAlert = function (req, res) { - req.device.postDismissAlert(getResponseHandler(req, res)); -}; - -exports.implicitWait = function (req, res) { - var ms = req.body.ms; - req.device.implicitWait(ms, getResponseHandler(req, res)); -}; - -exports.asyncScriptTimeout = function (req, res) { - var ms = req.body.ms; - req.device.asyncScriptTimeout(ms, getResponseHandler(req, res)); -}; - -exports.pageLoadTimeout = function (req, res) { - var ms = req.body.ms; - req.device.pageLoadTimeout(ms, getResponseHandler(req, res)); -}; - -exports.timeouts = function (req, res) { - var timeoutType = req.body.type - , ms = req.body.ms; - if (checkMissingParams(req, res, {type: timeoutType, ms: ms})) { - if (timeoutType === "implicit") { - exports.implicitWait(req, res); - } else if (timeoutType === "script") { - exports.asyncScriptTimeout(req, res); - } else if (timeoutType === "command") { - var secs = parseInt(ms, 10) / 1000; - req.appium.setCommandTimeout(secs, getResponseHandler(req, res)); - } else if (timeoutType === "page load") { - exports.pageLoadTimeout(req, res); - } else { - respondError(req, res, status.codes.UnknownCommand.code, - new Error("Invalid timeout '" + timeoutType + "'")); - } - } -}; - -exports.setOrientation = function (req, res) { - var orientation = req.body.orientation; - req.device.setOrientation(orientation, getResponseHandler(req, res)); -}; - -exports.setLocation = function (req, res) { - if (req.body.latitude || req.body.longitude) { - logCustomDeprecationWarning('geolocation', 'wrongbody', - "Use of the set location method with the latitude and longitude " + - "params as top-level JSON params is deprecated and will be removed. " + - "Please update your client library to use a method that conforms " + - "to the spec"); - } - - var location = req.body.location || {}; - var latitude = location.latitude || req.body.latitude - , longitude = location.longitude || req.body.longitude - , altitude = location.altitude || req.body.altitude || null; - req.device.setLocation(latitude, longitude, altitude, null, null, null, null, getResponseHandler(req, res)); -}; - -exports.getOrientation = function (req, res) { - req.device.getOrientation(getResponseHandler(req, res)); -}; - -exports.getScreenshot = function (req, res) { - req.device.getScreenshot(getResponseHandler(req, res)); -}; - -exports.moveTo = function (req, res) { - req.body = _.defaults(req.body, { - xoffset: 0.5 - , yoffset: 0.5 - }); - var xoffset = req.body.xoffset - , yoffset = req.body.yoffset - , element = req.body.element; - - req.device.moveTo(element, xoffset, yoffset, getResponseHandler(req, res)); -}; - -exports.clickCurrent = function (req, res) { - var button = req.body.button || 0; - req.device.clickCurrent(button, getResponseHandler(req, res)); -}; - -exports.pickAFlickMethod = function (req, res) { - if (typeof req.body.xSpeed !== "undefined" || typeof req.body.xspeed !== "undefined") { - exports.flick(req, res); - } else { - exports.flickElement(req, res); - } -}; - -exports.flick = function (req, res) { - var swipe = req.body.swipe - , xSpeed = req.body.xSpeed - , ySpeed = req.body.ySpeed - , element = req.body.element; - - if (typeof xSpeed === "undefined") { - xSpeed = req.body.xspeed; - } - if (typeof ySpeed === "undefined") { - ySpeed = req.body.yspeed; - } - - if (checkMissingParams(req, res, {xSpeed: xSpeed, ySpeed: ySpeed})) { - if (element) { - exports.flickElement(req, res); - } else { - req.device.fakeFlick(xSpeed, ySpeed, swipe, getResponseHandler(req, res)); - } - } -}; - -exports.flickElement = function (req, res) { - var element = req.body.element - , xoffset = req.body.xoffset - , yoffset = req.body.yoffset - , speed = req.body.speed; - - if (checkMissingParams(req, res, {element: element, xoffset: xoffset, yoffset: yoffset})) { - req.device.fakeFlickElement(element, xoffset, yoffset, speed, getResponseHandler(req, res)); - } -}; - -exports.execute = function (req, res) { - var script = req.body.script - , args = req.body.args; - - if (checkMissingParams(req, res, {script: script, args: args})) { - if (_s.startsWith(script, "mobile: ")) { - var realCmd = script.replace("mobile: ", ""); - exports.executeMobileMethod(req, res, realCmd); - } else { - req.device.execute(script, args, getResponseHandler(req, res)); - } - } -}; - -exports.executeAsync = function (req, res) { - var script = req.body.script - , args = req.body.args - , responseUrl = ''; - - responseUrl += 'http://' + req.appium.args.callbackAddress + ':' + req.appium.args.callbackPort; - responseUrl += '/wd/hub/session/' + req.appium.sessionId + '/receive_async_response'; - - if (checkMissingParams(req, res, {script: script, args: args})) { - req.device.executeAsync(script, args, responseUrl, getResponseHandler(req, res)); - } -}; - -exports.executeMobileMethod = function (req, res, cmd) { - var args = req.body.args - , params = {}; - - var suppMethods = req.device.mobileMethodsSupported; - if (suppMethods && !_.contains(suppMethods, cmd)) { - return respondError(req, res, status.codes.UnknownCommand.code, - new Error("That device doesn't know how to respond to 'mobile: '" + - cmd + "--it's probably not using Appium's API")); - } - - if (args.length) { - if (args.length !== 1) { - safely(req, function () { - res.status(400).send("Mobile methods only take one parameter, which is a " + - "hash of named parameters to send to the method"); - }); - } else { - params = args[0]; - } - } - - if (_.has(mobileCmdMap, cmd)) { - req.body = params; - mobileCmdMap[cmd](req, res); - } else { - logger.debug("Tried to execute non-existent mobile command '" + cmd + "'" + - ". Most mobile commands have been ported to official client " + - "library methods. Please check your Appium library for more " + - "information and documentation"); - notYetImplemented(req, res); - } -}; - -exports.title = function (req, res) { - req.device.title(getResponseHandler(req, res)); -}; - -exports.submit = function (req, res) { - var elementId = req.params.elementId; - req.device.submit(elementId, getResponseHandler(req, res)); -}; - -exports.postUrl = function (req, res) { - var url = req.body.url; - - if (checkMissingParams(req, res, {url: url})) { - req.device.url(url, getResponseHandler(req, res)); - } -}; - -exports.getUrl = function (req, res) { - req.device.getUrl(getResponseHandler(req, res)); -}; - -exports.active = function (req, res) { - req.device.active(getResponseHandler(req, res)); -}; - -exports.setContext = function (req, res) { - var name = req.body.name; - - if (checkMissingParams(req, res, {name: name})) { - req.device.setContext(name, getResponseHandler(req, res)); - } -}; - -exports.getCurrentContext = function (req, res) { - req.device.getCurrentContext(getResponseHandler(req, res)); -}; - -exports.getContexts = function (req, res) { - req.device.getContexts(getResponseHandler(req, res)); -}; - -exports.getWindowHandle = function (req, res) { - req.device.getWindowHandle(getResponseHandler(req, res)); -}; - -exports.setWindow = function (req, res) { - var name = req.body.name; - - if (checkMissingParams(req, res, {name: name})) { - req.device.setWindow(name, getResponseHandler(req, res)); - } -}; - -exports.closeWindow = function (req, res) { - req.device.closeWindow(getResponseHandler(req, res)); -}; - -exports.getWindowHandles = function (req, res) { - req.device.getWindowHandles(getResponseHandler(req, res)); -}; - -exports.setCommandTimeout = function (req, res) { - var timeout = req.body.timeout; - - if (checkMissingParams(req, res, {timeout: timeout})) { - timeout = parseInt(timeout, 10); - req.appium.setCommandTimeout(timeout, getResponseHandler(req, res)); - } -}; - -exports.receiveAsyncResponse = function (req, res) { - var asyncResponse = req.body; - req.device.receiveAsyncResponse(asyncResponse); - safely(req, function () { - res.sendStatus(200); - }); -}; - -exports.setValueImmediate = function (req, res) { - var element = req.params.elementId - , value = req.body.value; - if (checkMissingParams(req, res, {element: element, value: value})) { - req.device.setValueImmediate(element, value, getResponseHandler(req, res)); - } -}; - -exports.getCookies = function (req, res) { - req.device.getCookies(getResponseHandler(req, res)); -}; - -exports.setCookie = function (req, res) { - var cookie = req.body.cookie; - if (checkMissingParams(req, res, {cookie: cookie})) { - if (typeof cookie.name !== "string" || typeof cookie.value !== "string") { - return respondError(req, res, status.codes.UnknownError, - "setCookie requires cookie of form {name: 'xxx', value: 'yyy'}"); - } - req.device.setCookie(cookie, getResponseHandler(req, res)); - } -}; - -exports.deleteCookie = function (req, res) { - var cookie = req.params.name; - req.device.deleteCookie(cookie, getResponseHandler(req, res)); -}; - -exports.deleteCookies = function (req, res) { - req.device.deleteCookies(getResponseHandler(req, res)); -}; - -exports.getCurrentActivity = function (req, res) { - req.device.getCurrentActivity(getResponseHandler(req, res)); -}; - -exports.getLog = function (req, res) { - var logType = req.body.type; - - if (checkMissingParams(req, res, {logType: logType})) { - req.device.getLog(logType, getResponseHandler(req, res)); - } -}; - -exports.getLogTypes = function (req, res) { - req.device.getLogTypes(getResponseHandler(req, res)); -}; - -exports.getStrings = function (req, res) { - req.body = _.defaults(req.body, { - language: null, - stringFile: null - }); - var language = req.body.language, - stringFile = req.body.stringFile; - req.device.getStrings(language, stringFile, getResponseHandler(req, res)); -}; - -exports.unknownCommand = function (req, res) { - logger.debug("Responding to client that we did not find a valid resource"); - safely(req, function () { - res.set('Content-Type', 'text/plain'); - res.status(404).send("That URL did not map to a valid JSONWP resource"); - }); -}; - -exports.pushFile = function (req, res) { - var data = req.body.data; // base64 data - var path = req.body.path; // remote path - - if (checkMissingParams(req, res, {data: data, path: path})) { - req.device.pushFile(data, path, getResponseHandler(req, res)); - } -}; - -exports.pullFile = function (req, res) { - var path = req.body.path; // remote path - - if (checkMissingParams(req, res, {path: path})) { - req.device.pullFile(path, getResponseHandler(req, res)); - } -}; - -exports.pullFolder = function (req, res) { - var path = req.body.path; // remote path - - if (checkMissingParams(req, res, {path: path})) { - req.device.pullFolder(path, getResponseHandler(req, res)); - } -}; - -exports.endCoverage = function (req, res) { - var intent = req.body.intent; - var path = req.body.path; - - if (checkMissingParams(req, res, {intent: intent, path: path})) { - req.device.endCoverage(intent, path, getResponseHandler(req, res)); - } -}; - -exports.toggleData = function (req, res) { - req.device.toggleData(getResponseHandler(req, res)); -}; - -exports.toggleFlightMode = function (req, res) { - req.device.toggleFlightMode(getResponseHandler(req, res)); -}; - -exports.toggleWiFi = function (req, res) { - req.device.toggleWiFi(getResponseHandler(req, res)); -}; - -exports.toggleLocationServices = function (req, res) { - req.device.toggleLocationServices(getResponseHandler(req, res)); -}; - -exports.notYetImplemented = notYetImplemented; -var mobileCmdMap = { - 'tap': exports.mobileTap -, 'drag': exports.mobileDrag -, 'flick': exports.mobileFlick -, 'scrollTo': exports.mobileScrollTo -, 'scroll': exports.mobileScroll -, 'longClick' : exports.touchLongClick -, 'down' : exports.touchDown -, 'up' : exports.touchUp -, 'move' : exports.touchMove -, 'pinchClose': exports.mobilePinchClose -, 'pinchOpen': exports.mobilePinchOpen -}; - -exports.produceError = function (req, res) { - req.device.proxy("thisisnotvalidjs", getResponseHandler(req, res)); -}; - -exports.crash = function () { - throw new Error("We just tried to crash Appium!"); -}; - -exports.guineaPig = function (req, res) { - var delay = req.param('delay') ? parseInt(req.param('delay'), 10) : 0; - setTimeout(function () { - var params = { - serverTime: parseInt(new Date().getTime() / 1000, 10) - , userAgent: req.headers['user-agent'] - , comment: "None" - }; - if (req.method === "POST") { - params.comment = req.body.comments || params.comment; - } - safely(req, function () { - res.set('Content-Type', 'text/html'); - res.cookie('guineacookie1', 'i am a cookie value', {path: '/'}); - res.cookie('guineacookie2', 'cookié2', {path: '/'}); - res.cookie('guineacookie3', 'cant access this', { - domain: '.blargimarg.com', - path: '/' - }); - res.send(exports.getTemplate('guinea-pig')(params)); - }); - }, delay); -}; - -exports.welcome = function (req, res) { - var params = { message: 'Let\'s browse!' }; - res.send(exports.getTemplate('welcome')(params)); -}; - -exports.getTemplate = function (templateName) { - return swig.compileFile(path.resolve(__dirname, "templates", - templateName + ".html")); -}; - -exports.openNotifications = function (req, res) { - req.device.openNotifications(getResponseHandler(req, res)); -}; - -exports.availableIMEEngines = function (req, res) { - req.device.availableIMEEngines(getResponseHandler(req, res)); -}; - -exports.isIMEActivated = function (req, res) { - req.device.isIMEActivated(getResponseHandler(req, res)); -}; - -exports.getActiveIMEEngine = function (req, res) { - req.device.getActiveIMEEngine(getResponseHandler(req, res)); -}; - -exports.activateIMEEngine = function (req, res) { - var imeId = req.body.engine; - req.device.activateIMEEngine(imeId, getResponseHandler(req, res)); -}; - -exports.deactivateIMEEngine = function (req, res) { - req.device.deactivateIMEEngine(getResponseHandler(req, res)); -}; - -exports.getNetworkConnection = function (req, res) { - req.device.getNetworkConnection(getResponseHandler(req, res)); -}; - -exports.setNetworkConnection = function (req, res) { - var type = req.body.type || req.body.parameters.type; - req.device.setNetworkConnection(type, getResponseHandler(req, res)); -}; - -exports.getSettings = function (req, res) { - req.device.getSettings(getResponseHandler(req, res)); -}; - -exports.updateSettings = function (req, res) { - var settings = req.body.settings || req.body.parameters.settings; - if (checkMissingParams(req, res, {settings: settings})) { - req.device.updateSettings(settings, getResponseHandler(req, res)); - } -}; diff --git a/lib/server/errors.js b/lib/server/errors.js deleted file mode 100644 index 1dbd7aba..00000000 --- a/lib/server/errors.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -var NotImplementedError = function (message) { - this.message = message ? message : "Not implemented in this context, try " + - "switching into or out of a web view"; - this.name = "NotImplementedError"; -}; - -var NotYetImplementedError = function (message) { - this.message = message ? message : "This method is not yet implemented! " + - "Please help: http://appium.io/get-involved.html"; - this.name = "NotYetImplementedError"; -}; - -var UnknownError = function (message) { - this.message = message ? message : "Invalid response from device"; - this.name = "UnknownError"; -}; - -var ProtocolError = function (message) { - this.message = message; - this.name = "ProtocolError"; -}; - -module.exports = { - NotImplementedError: NotImplementedError -, NotYetImplementedError: NotYetImplementedError -, UnknownError: UnknownError -, ProtocolError: ProtocolError -}; diff --git a/lib/server/grid-register.js b/lib/server/grid-register.js deleted file mode 100644 index 36bec327..00000000 --- a/lib/server/grid-register.js +++ /dev/null @@ -1,111 +0,0 @@ -"use strict"; -var request = require('request') - , fs = require('fs') - , logger = require('./logger.js').get('appium'); - -exports.registerNode = function (configFile, addr, port) { - fs.readFile(configFile, 'utf-8', function (err, data) { - if (err) { - logger.error("Unable to load node configuration file to register with grid"); - return; - } - // Check presence of data before posting it to the selenium grid - if (data) { - postRequest(data, addr, port); - } else { - logger.error("No data found in the node configuration file to send to the grid"); - } - }); -}; - -function registerToGrid(options_post, jsonObject) { - request(options_post, function (error, response/*, body*/) { - if (error !== null || response.statusCode !== 200) { - logger.error("Request to register with grid was Unsuccessful..."); - } else { - var logMessage = "Appium successfully registered with the grid on " + jsonObject.configuration.hubHost + ":" + jsonObject.configuration.hubPort; - logger.debug(logMessage); - } - }); -} - -function postRequest(data, addr, port) { - //parse json to get hub host and port - var jsonObject; - try { - jsonObject = JSON.parse(data); - } catch (e) { - return logger.error("Syntax error in node configuration file: " + e.message); - } - - // if the node config doesnt have the appium/webdriver url, host, and port, - // automatically ad it based on how appium was init - // otherwise, we will take whatever the user setup - // because we will always set localhost/127.0.0.1. this won't work if your - // node and grid aren't in the same place - if (!jsonObject.configuration.url || !jsonObject.configuration.host || !jsonObject.configuration.port) { - jsonObject.configuration.url = 'http://' + addr + ':' + port + '/wd/hub'; - jsonObject.configuration.host = addr; - jsonObject.configuration.port = port; - - // re-serialize the configuration with the auto populated data - data = JSON.stringify(jsonObject); - } - - // prepare the header - var post_headers = { - 'Content-Type' : 'application/json' - , 'Content-Length': data.length - }; - // the post options - var options_post = { - url : 'http://' + jsonObject.configuration.hubHost + ':' + jsonObject.configuration.hubPort + '/grid/register' - , method : 'POST' - , body : data - , headers : post_headers - }; - - if (jsonObject.configuration.register !== true) { - logger.debug("no registration sent ( " + jsonObject.configuration.register + " = false )"); - } else { - var registerCycleTime = jsonObject.configuration.registerCycle; - if (registerCycleTime !== null && registerCycleTime > 0) { - //initiate a new Thread - var first = true; - logger.debug("starting auto register thread for grid. Will try to register every " + registerCycleTime + " ms."); - setInterval(function () { - if (first !== true) { - isAlreadyRegistered(jsonObject, function (isRegistered) { - if (isRegistered !== null && isRegistered !== true) { - // make the http POST to the grid for registration - registerToGrid(options_post, jsonObject); - } - }); - } else { - first = false; - registerToGrid(options_post, jsonObject); - } - }, registerCycleTime); - - } - } -} - -function isAlreadyRegistered(jsonObject, cb) { - //check if node is already registered - var id = "http://" + jsonObject.configuration.host + ":" + jsonObject.configuration.port; - request({ - uri : 'http://' + jsonObject.configuration.hubHost + ':' + jsonObject.configuration.hubPort + - '/grid/api/proxy' + "?id=" + id - , method : "GET" - , timeout : 10000 - }, function (error, response /*, body*/) { - if (error !== null || response === undefined || response.statusCode !== 200) { - logger.debug("hub down or not responding."); - return cb(null); - } - var responseData = JSON.parse(response.body); - return cb(responseData.success); - }); - -} diff --git a/lib/server/helpers.js b/lib/server/helpers.js deleted file mode 100644 index d44a2eb1..00000000 --- a/lib/server/helpers.js +++ /dev/null @@ -1,328 +0,0 @@ -"use strict"; - -var _ = require("underscore") - , gridRegister = require('./grid-register.js') - , logger = require('./logger.js').get('appium') - , status = require('./status.js') - , io = require('socket.io') - , mkdirp = require('mkdirp') - , bytes = require('bytes') - , domain = require('domain') - , format = require('util').format - , Args = require("vargs").Constructor; - -module.exports.allowCrossDomain = function (req, res, next) { - safely(req, function () { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,OPTIONS,DELETE'); - res.header('Access-Control-Allow-Headers', 'origin, content-type, accept'); - }); - - // need to respond 200 to OPTIONS - - if ('OPTIONS' === req.method) { - safely(req, function () { - res.sendStatus(200); - }); - } else { - next(); - } -}; - -module.exports.winstonStream = { - write: function (msg) { - msg = msg.replace(/$\s*$/m, ""); - msg = msg.replace(/\[[^\]]+\] /, ""); - logger.log('debug', msg); - } -}; - -module.exports.catchAllHandler = function (e, req, res, next) { - safely(req, function () { - res.status(500).send({ - status: status.codes.UnknownError.code - , value: "ERROR running Appium command: " + e.message - }); - }); - next(e); -}; - -module.exports.checkArgs = function (parser, args) { - var exclusives = [ - ['noReset', 'fullReset'] - , ['ipa', 'safari'] - , ['app', 'safari'] - , ['forceIphone', 'forceIpad'] - , ['deviceName', 'defaultDevice'] - ]; - - _.each(exclusives, function (exSet) { - var numFoundInArgs = 0; - _.each(exSet, function (opt) { - if (_.has(args, opt) && args[opt]) { - numFoundInArgs++; - } - }); - if (numFoundInArgs > 1) { - console.error(("You can't pass in more than one argument from the set " + - JSON.stringify(exSet) + ", since they are mutually exclusive").red); - process.exit(1); - } - }); - - var checkValidPort = function (port) { - if (port > 0 && port < 65536) return true; - console.error("Port must be greater than 0 and less than 65536"); - return false; - }; - - var validations = { - port: checkValidPort - , callbackPort: checkValidPort - , bootstrapPort: checkValidPort - , selendroidPort: checkValidPort - , chromedriverPort: checkValidPort - , robotPort: checkValidPort - , backendRetries: function (r) { return r >= 0; } - }; - - var nonDefaultArgs = getNonDefaultArgs(parser, args); - - _.each(validations, function (validator, arg) { - if (_.has(nonDefaultArgs, arg)) { - if (!validator(args[arg])) { - console.error("Invalid argument for param " + arg + ": " + args[arg]); - process.exit(1); - } - } - }); - -}; - -module.exports.noColorLogger = function (tokens, req, res) { - var len = parseInt(res.getHeader('Content-Length'), 10); - - len = isNaN(len) ? '' : ' - ' + bytes(len); - return req.method + ' ' + req.originalUrl + ' ' + - res.statusCode + ' ' + (new Date() - req._startTime) + 'ms' + len; -}; - -module.exports.configureServer = function (rawConfig, appiumVer, appiumServer, - cb) { - var appiumRev; - - if (!rawConfig) { - return cb(new Error('config data required')); - } - - var versionMismatches = {}; - var excludedKeys = ["git-sha", "node_bin", "built"]; - _.each(rawConfig, function (deviceConfig, key) { - if (deviceConfig.version !== appiumVer && !_.contains(excludedKeys, key)) { - versionMismatches[key] = deviceConfig.version; - } else if (key === "git-sha") { - appiumRev = rawConfig['git-sha']; - } - }); - if (_.keys(versionMismatches).length) { - logger.error("Got some configuration version mismatches. Appium is " + - "at " + appiumVer + "."); - _.each(versionMismatches, function (mismatchedVer, key) { - logger.error(key + " configured at " + mismatchedVer); - }); - logger.error("Please re-run reset.sh or config"); - return cb(new Error("Appium / config version mismatch")); - } else { - appiumServer.registerConfig(rawConfig); - cb(null, appiumRev); - } -}; - -module.exports.conditionallyPreLaunch = function (args, appiumServer, cb) { - if (args.launch) { - logger.debug("Starting Appium in pre-launch mode"); - appiumServer.preLaunch(function (err) { - if (err) { - logger.error("Could not pre-launch appium: " + err); - cb(err); - } else { - cb(null); - } - }); - } else { - cb(null); - } -}; - -module.exports.prepareTmpDir = function (args, cb) { - if (args.tmpDir === null) return cb(); - mkdirp(args.tmpDir, function (err) { - if (err) { - logger.error("Could not ensure tmp dir '" + args.tmpDir + "' exists"); - logger.error(err); - } - cb(err); - }); -}; - -var startAlertSocket = function (restServer, appiumServer) { - var alerts = io(restServer, { - 'flash policy port': -1, - 'logger': logger, - 'log level': 1, - 'polling duration': 10, - 'transports': ['websocket', 'flashsocket'] - }); - - alerts.sockets.on("connection", function (socket) { - logger.debug("Client connected: " + (socket.id).toString()); - - socket.on('disconnect', function (data) { - logger.debug("Client disconnected: " + data); - }); - }); - - // add web socket so we can emit events - appiumServer.attachSocket(alerts); -}; - -var getNonDefaultArgs = function (parser, args) { - var nonDefaults = {}; - _.each(parser.rawArgs, function (rawArg) { - var arg = rawArg[1].dest; - if (args[arg] !== rawArg[1].defaultValue) { - nonDefaults[arg] = args[arg]; - } - }); - return nonDefaults; -}; - -var getDeprecatedArgs = function (parser, args) { - var deprecated = {}; - _.each(parser.rawArgs, function (rawArg) { - var arg = rawArg[1].dest; - if (args[arg] && rawArg[1].deprecatedFor) { - deprecated[rawArg[0]] = "use instead: " + rawArg[1].deprecatedFor; - } - }); - return deprecated; -}; - -module.exports.startListening = function (server, args, parser, appiumVer, appiumRev, appiumServer, cb) { - var alreadyReturned = false; - server.listen(args.port, args.address, function () { - var welcome = "Welcome to Appium v" + appiumVer; - if (appiumRev) { - welcome += " (REV " + appiumRev + ")"; - } - logger.info(welcome); - var logMessage = "Appium REST http interface listener started on " + - args.address + ":" + args.port; - logger.info(logMessage); - startAlertSocket(server, appiumServer); - if (args.nodeconfig !== null) { - gridRegister.registerNode(args.nodeconfig, args.address, args.port); - } - var showArgs = getNonDefaultArgs(parser, args); - if (_.size(showArgs)) { - logger.debug("Non-default server args: " + JSON.stringify(showArgs)); - } - var deprecatedArgs = getDeprecatedArgs(parser, args); - if (_.size(deprecatedArgs)) { - logger.warn("Deprecated server args: " + JSON.stringify(deprecatedArgs)); - } - - logger.info('Console LogLevel: ' + logger.transports.console.level); - - if (logger.transports.file) { - logger.info('File LogLevel: ' + logger.transports.file.level); - } - }); - server.on('error', function (err) { - if (err.code === 'EADDRNOTAVAIL') { - logger.error("Couldn't start Appium REST http interface listener. Requested address is not available."); - } else { - logger.error("Couldn't start Appium REST http interface listener. Requested port is already in use. Please make sure there's no other instance of Appium running already."); - } - if (!alreadyReturned) { - alreadyReturned = true; - cb(err); - } - }); - server.on('connection', function (socket) { - socket.setTimeout(600 * 1000); // 10 minute timeout - }); - setTimeout(function () { - if (!alreadyReturned) { - alreadyReturned = true; - cb(null); - } - }, 1000); -}; - -// Copied the morgan compile function over so that cooler formats -// may be configured -function compile(fmt) { - fmt = fmt.replace(/"/g, '\\"'); - var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, - function (_, name, arg) { - return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "'; - }) + '";'; - // jshint evil:true - return new Function('tokens, req, res', js); -} -module.exports.requestStartLoggingFormat = compile('-->'.white + ' ' + ':method'.white + ' ' + - ':url'.white); -// Copied the morgan format.dev function, modified to use colors package -// and custom logging line -module.exports.requestEndLoggingFormat = function (tokens, req, res) { - var status = res.statusCode; - var statusStr = ':status'; - if (status >= 500) statusStr = statusStr.red; - else if (status >= 400) statusStr = statusStr.yellow; - else if (status >= 300) statusStr = statusStr.cyan; - else statusStr = statusStr.green; - var fn = compile('<-- :method :url '.white + statusStr + - ' :response-time ms - :res[content-length]'.grey); - return fn(tokens, req, res); -}; - -function getRequestContext(req) { - if (!req) return ''; - var data = ''; - try { - if (req.body) data = JSON.stringify(req.body).substring(0, 200); - } catch (ign) {} - return format('context: [%s %s %s]', req.method, req.url, data).replace(/ ]$/, ''); -} - -// Mainly used to wrap http response methods, or for cases where errors -// perdure the domain -var safely = function () { - var args = new (Args)(arguments); - var req = args.all[0]; - var fn = args.callback; - try { - fn(); - } catch (err) { - logger.error('Unexpected error:', err.stack, getRequestContext(req)); - } -}; -module.exports.safely = safely; - -module.exports.domainMiddleware = function () { - return function (req, res, next) { - var reqDomain = domain.create(); - reqDomain.add(req); - reqDomain.add(res); - res.on('close', function () { - setTimeout(function () { - reqDomain.dispose(); - }, 5000); - }); - reqDomain.on('error', function (err) { - logger.error('Unhandled error:', err.stack, getRequestContext(req)); - }); - reqDomain.run(next); - }; -}; diff --git a/lib/server/logger.js b/lib/server/logger.js deleted file mode 100644 index 194abf81..00000000 --- a/lib/server/logger.js +++ /dev/null @@ -1,239 +0,0 @@ -"use strict"; - -// set up distributed logging before everything else -var npmlog = global._global_npmlog = require('npmlog'); -// npmlog is used only for emitting, we use winston for output -npmlog.level = "silent"; - -var winston = require('winston') - , fs = require('fs') - , os = require('os') - , path = require('path') - , util = require('util'); -require('date-utils'); - - -var levels = { - debug: 1 -, info: 2 -, warn: 3 -, error: 4 -}; - -var colors = { - info: 'cyan' -, debug: 'grey' -, warn: 'yellow' -, error: 'red' -}; - -var npmToWinstonLevels = { - silly: 'debug' -, verbose: 'debug' -, info: 'info' -, http: 'info' -, warn: 'warn' -, error: 'error' -}; - -var logger = null; -var timeZone = null; -var stackTrace = null; - -// capture any logs emitted by other packages using our global distributed -// logger and pass them through winston -npmlog.on('log', function (logObj) { - var winstonLevel = npmToWinstonLevels[logObj.level] || 'info'; - var msg = logObj.message && logObj.prefix ? - (logObj.prefix + ": " + logObj.message) : - (logObj.prefix || logObj.message); - logger[winstonLevel](msg); -}); - -var timestamp = function () { - var date = new Date(); - if (!timeZone) { - date = new Date(date.valueOf() + date.getTimezoneOffset() * 60000); - } - return date.toFormat("YYYY-MM-DD HH24:MI:SS:LL"); -}; - -// Strip the color marking within messages. -// We need to patch the transports, because the stripColor functionality in -// Winston is wrongly implemented at the logger level, and we want to avoid -// having to create 2 loggers. -function applyStripColorPatch(transport) { - var _log = transport.log.bind(transport); - transport.log = function (level, msg, meta, callback) { - var code = /\u001b\[(\d+(;\d+)*)?m/g; - msg = ('' + msg).replace(code, ''); - _log(level, msg, meta, callback); - }; -} - -var _createConsoleTransport = function (args, logLvl) { - var transport = new (winston.transports.Console)({ - name: "console" - , timestamp: args.logTimestamp ? timestamp : undefined - , colorize: !args.logNoColors - , handleExceptions: true - , exitOnError: false - , json: false - , level: logLvl - }); - if (args.logNoColors) applyStripColorPatch(transport); - return transport; -}; - -var _createFileTransport = function (args, logLvl) { - var transport = new (winston.transports.File)({ - name: "file" - , timestamp: timestamp - , filename: args.log - , maxFiles: 1 - , handleExceptions: true - , exitOnError: false - , json: false - , level: logLvl - } - ); - applyStripColorPatch(transport); - return transport; -}; - -var _createWebhookTransport = function (args, logLvl) { - var host = null, - port = null; - - if (args.webhook.match(':')) { - var hostAndPort = args.webhook.split(':'); - host = hostAndPort[0]; - port = parseInt(hostAndPort[1], 10); - } - - var transport = new (winston.transports.Webhook)({ - name: "webhook" - , host: host || '127.0.0.1' - , port: port || 9003 - , path: '/' - , handleExceptions: true - , exitOnError: false - , json: false - , level: logLvl - }); - applyStripColorPatch(transport); - return transport; -}; - -var _createTransports = function (args) { - var transports = []; - var consoleLogLevel = null, - fileLogLevel = null; - - if (args.loglevel && args.loglevel.match(":")) { - // --log-level arg can optionally provide diff logging levels for console and file separated by a colon - var lvlPair = args.loglevel.split(':'); - consoleLogLevel = lvlPair[0] || consoleLogLevel; - fileLogLevel = lvlPair[1] || fileLogLevel; - } else { - consoleLogLevel = fileLogLevel = args.loglevel; - } - - transports.push(_createConsoleTransport(args, consoleLogLevel)); - - if (args.log) { - try { - // if we don't delete the log file, winston will always append and it will grow infinitely large; - // winston allows for limiting log file size, but as of 9.2.14 there's a serious bug when using - // maxFiles and maxSize together. https://github.com/flatiron/winston/issues/397 - if (fs.existsSync(args.log)) { - fs.unlinkSync(args.log); - } - - transports.push(_createFileTransport(args, fileLogLevel)); - } catch (e) { - console.log("Tried to attach logging to file " + args.log + - " but an error occurred: " + e.msg); - } - } - - if (args.webhook) { - try { - transports.push(_createWebhookTransport(args, fileLogLevel)); - } catch (e) { - console.log("Tried to attach logging to webhook at " + args.webhook + - " but an error occurred. " + e.msg); - } - } - - return transports; -}; - -var _appDir = path.dirname(require.main.filename); - -var _stackToString = function (stack) { - var str = os.EOL + " [------TRACE------]" + os.EOL; - var len = stack.length < 15 ? stack.length : 15; - - for (var i = 0; i < len; i++) { - var fileName = stack[i].getFileName(); - // ignore calls from this file - if (fileName === __filename) continue; - var substr = " at "; - try { - var typeName = stack[i].getTypeName(); - - substr += util.format("%s.%s (%s:%d:%d)" + os.EOL, typeName, stack[i].getFunctionName(), - path.relative(_appDir, stack[i].getFileName()), stack[i].getLineNumber(), - stack[i].getColumnNumber()); - str += substr; - - } catch (e) { } - } - - return str; -}; - -var _addStackTrace = function (fn, stackTrace) { - var _fn = fn; - return function (msg) { - _fn(msg + os.EOL + _stackToString(stackTrace.get()) + os.EOL); - }; -}; - -module.exports.init = function (args) { - // set de facto param passed to timestamp function - timeZone = args.localTimezone; - - // by not adding colors here and not setting 'colorize' in transports - // when logNoColors === true, console output is fully stripped of color. - if (!args.logNoColors) { - winston.addColors(colors); - } - - logger = new (winston.Logger)({ - transports: _createTransports(args) - }); - - logger.setLevels(levels); - - // 8/19/14 this is a hack to force Winston to print debug messages to stdout rather than stderr. - // TODO: remove this if winston provides an API for directing streams. - if (levels[logger.transports.console.level] === levels.debug) { - logger.debug = function (msg) { logger.info('[debug] ' + msg); }; - } - - if (args.asyncTrace) { - stackTrace = require('stack-trace'); - logger.info = _addStackTrace(logger.info, stackTrace); - logger.warn = _addStackTrace(logger.warn, stackTrace); - logger.error = _addStackTrace(logger.error, stackTrace); - } -}; - -module.exports.get = function () { - if (logger === null) { - exports.init({}); - } - return logger; -}; diff --git a/lib/server/main.js b/lib/server/main.js deleted file mode 100644 index 5da89379..00000000 --- a/lib/server/main.js +++ /dev/null @@ -1,180 +0,0 @@ -"use strict"; - -var parser = require('./parser.js')() - , logFactory = require('./logger.js') - , logger = null - , args = null - , fs = require('fs') - , path = require('path') - , noPermsCheck = false; - -require('colors'); - -process.chdir(path.resolve(__dirname, '../..')); - -if (require.main === module) { - args = parser.parseArgs(); - noPermsCheck = args.noPermsCheck; - logFactory.init(args); -} - -logger = logFactory.get('appium'); - -if (!noPermsCheck) { - var appiumPermStat = fs.statSync(path.resolve(__dirname, - '../../package.json')); - var launchCmd = (process.env.SUDO_COMMAND || "").toLowerCase(); - var isWindows = require('appium-support').system.isWindows(); - - if ( - !isWindows && - // Appium should be run by user who owns files in Appium installation directory - appiumPermStat.uid !== process.getuid() && - // authorize* commands could be run using sudo - !launchCmd.match(/authorize/) - ) { - logger.error("Appium will not work if used or installed with sudo. " + - "Please rerun/install as a non-root user. If you had to " + - "install Appium using `sudo npm install -g appium`, the " + - "solution is to reinstall Node using a method (Homebrew, " + - "for example) that doesn't require sudo to install global " + - "npm packages."); - process.exit(1); - } -} - -var http = require('http') - , express = require('express') - , favicon = require('serve-favicon') - , bodyParser = require('body-parser') - , methodOverride = require('method-override') - , morgan = require('morgan') // logger - , routing = require('./routing.js') - , path = require('path') - , appium = require('../appium.js') - , parserWrap = require('./middleware').parserWrap - , appiumVer = require('../../package.json').version - , appiumRev = null - , async = require('async') - , helpers = require('./helpers.js') - , logFinalWarning = require('../helpers.js').logFinalDeprecationWarning - , getConfig = require('../helpers.js').getAppiumConfig - , allowCrossDomain = helpers.allowCrossDomain - , catchAllHandler = helpers.catchAllHandler - , checkArgs = helpers.checkArgs - , configureServer = helpers.configureServer - , startListening = helpers.startListening - , conditionallyPreLaunch = helpers.conditionallyPreLaunch - , prepareTmpDir = helpers.prepareTmpDir - , requestStartLoggingFormat = require('./helpers.js').requestStartLoggingFormat - , requestEndLoggingFormat = require('./helpers.js').requestEndLoggingFormat - , domainMiddleware = require('./helpers.js').domainMiddleware; - - -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; - } - - if (args.showConfig) { - try { - console.log(JSON.stringify(getConfig())); - } catch (e) { - process.exit(1); - } - 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 () {}; - } - - var rest = express() - , server = http.createServer(rest); - - rest.use(domainMiddleware()); - rest.use(morgan(function (tokens, req, res) { - // morgan output is redirected straight to winston - logger.info(requestEndLoggingFormat(tokens, req, res), - (res.jsonResp || '').grey); - })); - rest.use(favicon(path.join(__dirname, 'static/favicon.ico'))); - rest.use(express.static(path.join(__dirname, 'static'))); - rest.use(allowCrossDomain); - rest.use(parserWrap); - rest.use(bodyParser.urlencoded({extended: true})); - // 8/18/14: body-parser requires that we supply the limit field to ensure the server can - // handle requests large enough for Appium's use cases. Neither Node nor HTTP spec defines a max - // request size, so any hard-coded request-size limit is arbitrary. Units are in bytes (ie "gb" == "GB", - // not "Gb"). Using 1GB because..., well because it's arbitrary and 1GB is sufficiently large for 99.99% - // of testing scenarios while still providing an upperbounds to reduce the odds of squirrelliness. - rest.use(bodyParser.json({limit: '1gb'})); - rest.use(morgan(function (tokens, req, res) { - // morgan output is redirected straight to winston - var data = ''; - try { - if (req.body) data = JSON.stringify(req.body).substring(0, 1000); - } catch (ign) {} - logger.info(requestStartLoggingFormat(tokens, req, res), data.grey); - }, {immediate: true})); - - rest.use(methodOverride()); - - // Instantiate the appium instance - var appiumServer = appium(args); - // Hook up REST http interface - appiumServer.attachTo(rest); - - routing(appiumServer); - rest.use(catchAllHandler); - - async.series([ - function (cb) { - configureServer(getConfig(), appiumVer, appiumServer, function (err, rev) { - if (err) return cb(err); - appiumRev = rev; - cb(); - }); - }, - function (cb) { - prepareTmpDir(args, cb); - }, - function (cb) { - conditionallyPreLaunch(args, appiumServer, cb); - }, - function (cb) { - startListening(server, args, parser, appiumVer, appiumRev, appiumServer, cb); - } - ], function (err) { - if (err) { - process.exit(1); - } else if (typeof readyCb === "function") { - readyCb(appiumServer); - } - }); - - server.on('close', function () { - logFinalWarning(); - doneCb(); - }); - -}; - -if (require.main === module) { - main(args); -} - -module.exports.run = main; diff --git a/lib/server/middleware.js b/lib/server/middleware.js deleted file mode 100644 index 14ea74f5..00000000 --- a/lib/server/middleware.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; - -var _s = require('underscore.string'); - -module.exports.parserWrap = function (req, res, next) { - // allow guineapig - if (!_s.startsWith(req.path, "/test")) { - // hack because python client library sux - if (req.headers['content-type'] === 'application/x-www-form-urlencoded') { - req.headers['content-type'] = 'application/json'; - } - } - - next(); -}; diff --git a/lib/server/parser.js b/lib/server/parser.js deleted file mode 100644 index 43edea52..00000000 --- a/lib/server/parser.js +++ /dev/null @@ -1,669 +0,0 @@ -"use strict"; -var ap = require('argparse').ArgumentParser - , pkgObj = require('../../package.json') - , path = require('path') - , _ = require('underscore'); - -var args = [ - [['--shell'], { - required: false - , defaultValue: null - , help: 'Enter REPL mode' - , nargs: 0 - }], - - [['--localizable-strings-dir'], { - required: false - , dest: 'localizableStringsDir' - , defaultValue: 'en.lproj' - , help: 'IOS only: the relative path of the dir where Localizable.strings file resides ' - , example: "en.lproj" - }], - - [['--app'], { - required: false - , defaultValue: null - , help: 'IOS: abs path to simulator-compiled .app file or the bundle_id of the desired target on device; Android: abs path to .apk file' - , example: "/abs/path/to/my.app" - }], - - [['--ipa'], { - required: false - , defaultValue: null - , help: '(IOS-only) abs path to compiled .ipa file' - , example: "/abs/path/to/my.ipa" - }], - - [['-U', '--udid'], { - required: false - , defaultValue: null - , example: "1adsf-sdfas-asdf-123sdf" - , help: 'Unique device identifier of the connected physical device' - }], - - [['-a', '--address'], { - defaultValue: '0.0.0.0' - , required: false - , example: "0.0.0.0" - , help: 'IP Address to listen on' - }], - - [['-p', '--port'], { - defaultValue: 4723 - , required: false - , type: 'int' - , example: "4723" - , help: 'port to listen on' - }], - - [['-ca', '--callback-address'], { - required: false - , dest: 'callbackAddress' - , defaultValue: null - , example: "127.0.0.1" - , help: 'callback IP Address (default: same as --address)' - }], - - [['-cp', '--callback-port'], { - required: false - , dest: 'callbackPort' - , defaultValue: null - , type: 'int' - , example: "4723" - , help: 'callback port (default: same as port)' - }], - - [['-bp', '--bootstrap-port'], { - defaultValue: 4724 - , dest: 'bootstrapPort' - , required: false - , type: 'int' - , example: "4724" - , help: '(Android-only) port to use on device to talk to Appium' - }], - - [['-k', '--keep-artifacts'], { - defaultValue: false - , dest: 'keepArtifacts' - , action: 'storeTrue' - , required: false - , help: '[DEPRECATED] no effect, trace is now in tmp dir by default and is ' + - ' cleared before each run. Please also refer to the --trace-dir flag.' - , nargs: 0 - }], - - [['-r', '--backend-retries'], { - defaultValue: 3 - , dest: 'backendRetries' - , required: false - , type: 'int' - , example: "3" - , help: '(iOS-only) How many times to retry launching Instruments ' + - 'before saying it crashed or timed out' - }], - - [['--session-override'], { - defaultValue: false - , dest: 'sessionOverride' - , action: 'storeTrue' - , required: false - , help: 'Enables session override (clobbering)' - , nargs: 0 - }], - - [['--full-reset'], { - defaultValue: false - , dest: 'fullReset' - , action: 'storeTrue' - , required: false - , help: '(iOS) Delete the entire simulator folder. (Android) Reset app ' + - 'state by uninstalling app instead of clearing app data. On ' + - 'Android, this will also remove the app after the session is complete.' - , nargs: 0 - }], - - [['--no-reset'], { - defaultValue: false - , dest: 'noReset' - , action: 'storeTrue' - , required: false - , help: "Don't reset app state between sessions (IOS: don't delete app " + - "plist files; Android: don't uninstall app before new session)" - , nargs: 0 - }], - - [['-l', '--pre-launch'], { - defaultValue: false - , dest: 'launch' - , action: 'storeTrue' - , required: false - , help: 'Pre-launch the application before allowing the first session ' + - '(Requires --app and, for Android, --app-pkg and --app-activity)' - , nargs: 0 - }], - - [['-lt', '--launch-timeout'], { - defaultValue: 90000 - , dest: 'launchTimeout' - , required: false - , help: '(iOS-only) how long in ms to wait for Instruments to launch' - }], - - [['-g', '--log'], { - defaultValue: null - , dest: 'log' - , required: false - , example: "/path/to/appium.log" - , help: 'Also send log output to this file' - }], - - [['--log-level'], { - choices: [ 'info', 'info:debug', 'info:info', 'info:warn', 'info:error' - , 'warn', 'warn:debug', 'warn:info', 'warn:warn', 'warn:error' - , 'error', 'error:debug', 'error:info', 'error:warn', 'error:error' - , 'debug', 'debug:debug', 'debug:info', 'debug:warn', 'debug:error'] - , defaultValue: 'debug' - , dest: 'loglevel' - , required: false - , example: "debug" - , help: 'log level; default (console[:file]): debug[:debug]' - }], - - [['--log-timestamp'], { - defaultValue: false - , required: false - , help: 'Show timestamps in console output' - , nargs: 0 - , action: 'storeTrue' - , dest: 'logTimestamp' - }], - - [['--local-timezone'], { - defaultValue: false - , required: false - , help: 'Use local timezone for timestamps' - , nargs: 0 - , action: 'storeTrue' - , dest: 'localTimezone' - }], - - [['--log-no-colors'], { - defaultValue: false - , required: false - , help: "Don't use colors in console output" - , nargs: 0 - , action: 'storeTrue' - , dest: 'logNoColors' - }], - - [['-G', '--webhook'], { - defaultValue: null - , required: false - , example: "localhost:9876" - , help: 'Also send log output to this HTTP listener' - }], - - [['--native-instruments-lib'], { - defaultValue: false - , dest: 'nativeInstrumentsLib' - , action: 'storeTrue' - , required: false - , help: '(IOS-only) IOS has a weird built-in unavoidable ' + - 'delay. We patch this in appium. If you do not want it patched, ' + - 'pass in this flag.' - , nargs: 0 - }], - - [['--app-pkg'], { - dest: 'androidPackage' - , defaultValue: null - , required: false - , example: "com.example.android.myApp" - , help: "(Android-only) Java package of the Android app you want to run " + - "(e.g., com.example.android.myApp)" - }], - - [['--app-activity'], { - dest: 'androidActivity' - , defaultValue: null - , required: false - , example: "MainActivity" - , help: "(Android-only) Activity name for the Android activity you want " + - "to launch from your package (e.g., MainActivity)" - }], - - [['--app-wait-package'], { - dest: 'androidWaitPackage' - , defaultValue: false - , required: false - , example: "com.example.android.myApp" - , help: "(Android-only) Package name for the Android activity you want " + - "to wait for (e.g., com.example.android.myApp)" - }], - - [['--app-wait-activity'], { - dest: 'androidWaitActivity' - , defaultValue: false - , required: false - , example: "SplashActivity" - , help: "(Android-only) Activity name for the Android activity you want " + - "to wait for (e.g., SplashActivity)" - }], - - [['--android-coverage'], { - dest: 'androidCoverage' - , defaultValue: false - , required: false - , example: 'com.my.Pkg/com.my.Pkg.instrumentation.MyInstrumentation' - , help: "(Android-only) Fully qualified instrumentation class. Passed to -w in " + - "adb shell am instrument -e coverage true -w " - }], - - [['--avd'], { - defaultValue: null - , required: false - , example: "@default" - , help: "(Android-only) Name of the avd to launch" - }], - - [['--avd-args'], { - dest: 'avdArgs' - , defaultValue: null - , required: false - , example: "-no-snapshot-load" - , help: "(Android-only) Additional emulator arguments to launch the avd" - }], - - [['--device-ready-timeout'], { - dest: 'androidDeviceReadyTimeout' - , defaultValue: '5' - , required: false - , example: "5" - , help: "(Android-only) Timeout in seconds while waiting for device to become ready" - }], - - [['--safari'], { - defaultValue: false - , action: 'storeTrue' - , required: false - , help: "(IOS-Only) Use the safari app" - , nargs: 0 - }], - - [['--device-name'], { - dest: 'deviceName' - , defaultValue: null - , required: false - , example: "iPhone Retina (4-inch), Android Emulator" - , help: "Name of the mobile device to use" - }], - - [['--platform-name'], { - dest: 'platformName' - , defaultValue: null - , required: false - , example: "iOS" - , help: "Name of the mobile platform: iOS, Android, or FirefoxOS" - }], - - [['--platform-version'], { - dest: 'platformVersion' - , defaultValue: null - , required: false - , example: "7.1" - , help: "Version of the mobile platform" - }], - - [['--automation-name'], { - dest: 'automationName' - , defaultValue: null - , required: false - , example: "Appium" - , help: "Name of the automation tool: Appium or Selendroid" - }], - - [['--browser-name'], { - dest: 'browserName' - , defaultValue: null - , required: false - , example: "Safari" - , help: "Name of the mobile browser: Safari or Chrome" - }], - - [['--default-device', '-dd'], { - dest: 'defaultDevice' - , defaultValue: false - , action: 'storeTrue' - , required: false - , help: "(IOS-Simulator-only) use the default simulator that instruments " + - "launches on its own" - }], - - [['--force-iphone'], { - defaultValue: false - , dest: 'forceIphone' - , action: 'storeTrue' - , required: false - , help: "(IOS-only) Use the iPhone Simulator no matter what the app wants" - , nargs: 0 - }], - - [['--force-ipad'], { - defaultValue: false - , dest: 'forceIpad' - , action: 'storeTrue' - , required: false - , help: "(IOS-only) Use the iPad Simulator no matter what the app wants" - , nargs: 0 - }], - - [['--language'], { - defaultValue: null - , dest: 'language' - , required: false - , example: "en" - , help: 'Language for the iOS simulator / Android Emulator' - }], - - [['--locale'], { - defaultValue: null - , dest: 'locale' - , required: false - , example: "en_US" - , help: 'Locale for the iOS simulator / Android Emulator' - }], - - [['--calendar-format'], { - defaultValue: null - , dest: 'calendarFormat' - , required: false - , example: "gregorian" - , help: '(IOS-only) calendar format for the iOS simulator' - }], - - [['--orientation'], { - defaultValue: null - , required: false - , example: "LANDSCAPE" - , help: "(IOS-only) use LANDSCAPE or PORTRAIT to initialize all requests " + - "to this orientation" - }], - - [['--tracetemplate'], { - defaultValue: null - , dest: 'automationTraceTemplatePath' - , required: false - , example: "/Users/me/Automation.tracetemplate" - , help: "(IOS-only) .tracetemplate file to use with Instruments" - }], - - [['--instruments'], { - defaultValue: null - , dest: 'instrumentsPath' - , require: false - , example: "/path/to/instruments" - , help: "(IOS-only) path to instruments binary" - }], - - [['--show-sim-log'], { - defaultValue: false - , dest: 'showSimulatorLog' - , action: 'storeTrue' - , required: false - , deprecatedFor: '--show-ios-log' - , help: "(IOS-only) if set, the iOS simulator log will be written to the console" - , nargs: 0 - }], - - [['--show-ios-log'], { - defaultValue: false - , dest: 'showIOSLog' - , action: 'storeTrue' - , required: false - , help: "(IOS-only) if set, the iOS system log will be written to the console" - , nargs: 0 - }], - - [['--nodeconfig'], { - required: false - , defaultValue: null - , help: 'Configuration JSON file to register appium with selenium grid' - , example: "/abs/path/to/nodeconfig.json" - }], - - [['-ra', '--robot-address'], { - defaultValue: '0.0.0.0' - , dest: 'robotAddress' - , required: false - , example: "0.0.0.0" - , help: 'IP Address of robot' - }], - - [['-rp', '--robot-port'], { - defaultValue: -1 - , dest: 'robotPort' - , required: false - , type: 'int' - , example: "4242" - , help: 'port for robot' - }], - - [['--selendroid-port'], { - defaultValue: 8080 - , dest: 'selendroidPort' - , required: false - , type: 'int' - , example: "8080" - , help: 'Local port used for communication with Selendroid' - }], - - [['--chromedriver-port'], { - defaultValue: 9515 - , dest: 'chromeDriverPort' - , required: false - , type: 'int' - , example: '9515' - , help: 'Port upon which ChromeDriver will run' - }], - - [['--chromedriver-executable'], { - defaultValue: null - , dest: 'chromedriverExecutable' - , required: false - , help: 'ChromeDriver executable full path' - }], - - [['--use-keystore'], { - defaultValue: false - , dest: 'useKeystore' - , action: 'storeTrue' - , required: false - , help: '(Android-only) When set the keystore will be used to sign apks.' - }], - - [['--keystore-path'], { - defaultValue: path.resolve(process.env.HOME || process.env.USERPROFILE || '', '.android', 'debug.keystore') - , dest: 'keystorePath' - , required: false - , help: '(Android-only) Path to keystore' - }], - - [['--keystore-password'], { - defaultValue: 'android' - , dest: 'keystorePassword' - , required: false - , help: '(Android-only) Password to keystore' - }], - - [['--key-alias'], { - defaultValue: 'androiddebugkey' - , dest: 'keyAlias' - , required: false - , help: '(Android-only) Key alias' - }], - - [['--key-password'], { - defaultValue: 'android' - , dest: 'keyPassword' - , required: false - , help: '(Android-only) Key password' - }], - - [['--show-config'], { - defaultValue: false - , dest: 'showConfig' - , action: 'storeTrue' - , required: false - , help: 'Show info about the appium server configuration and exit' - }], - - [['--no-perms-check'], { - defaultValue: false - , dest: 'noPermsCheck' - , action: 'storeTrue' - , required: false - , help: "Bypass Appium's checks to ensure we can read/write necessary files" - }], - - [['--command-timeout'], { - defaultValue: 60 - , dest: 'defaultCommandTimeout' - , type: 'int' - , required: false - , help: 'The default command timeout for the server to use for all ' + - 'sessions. Will still be overridden by newCommandTimeout cap' - }], - - [['--keep-keychains'], { - defaultValue: false - , dest: 'keepKeyChains' - , action: 'storeTrue' - , required: false - , help: "(iOS) Whether to keep keychains (Library/Keychains) when reset app between sessions" - , nargs: 0 - }], - - [['--strict-caps'], { - defaultValue: false - , dest: 'enforceStrictCaps' - , action: 'storeTrue' - , required: false - , help: "Cause sessions to fail if desired caps are sent in that Appium " + - "does not recognize as valid for the selected device" - , nargs: 0 - }], - - [['--isolate-sim-device'], { - defaultValue: false - , dest: 'isolateSimDevice' - , action: 'storeTrue' - , required: false - , help: "Xcode 6 has a bug on some platforms where a certain simulator " + - "can only be launched without error if all other simulator devices " + - "are first deleted. This option causes Appium to delete all " + - "devices other than the one being used by Appium. Note that this " + - "is a permanent deletion, and you are responsible for using simctl " + - "or xcode to manage the categories of devices used with Appium." - , nargs: 0 - }], - - [['--tmp'], { - defaultValue: null - , dest: 'tmpDir' - , required: false - , help: 'Absolute path to directory Appium can use to manage temporary ' + - 'files, like built-in iOS apps it needs to move around. On *nix/Mac ' + - 'defaults to /tmp, on Windows defaults to C:\\Windows\\Temp' - }], - - [['--trace-dir'], { - defaultValue: null - , dest: 'traceDir' - , required: false - , help: 'Absolute path to directory Appium use to save ios instruments ' + - 'traces, defaults to /appium-instruments' - }], - - [['--intent-action'], { - dest: 'intentAction' - , defaultValue: "android.intent.action.MAIN" - , required: false - , example: "android.intent.action.MAIN" - , help: "(Android-only) Intent action which will be used to start activity" - }], - - [['--intent-category'], { - dest: 'intentCategory' - , defaultValue: "android.intent.category.LAUNCHER" - , required: false - , example: "android.intent.category.APP_CONTACTS" - , help: "(Android-only) Intent category which will be used to start activity" - }], - - [['--intent-flags'], { - dest: 'intentFlags' - , defaultValue: "0x10200000" - , required: false - , example: "0x10200000" - , help: "(Android-only) Flags that will be used to start activity" - }], - - [['--intent-args'], { - dest: 'optionalIntentArguments' - , defaultValue: null - , required: false - , example: "0x10200000" - , help: "(Android-only) Additional intent arguments that will be used to " + - "start activity" - }], - - [['--dont-stop-app-on-reset'], { - dest: 'dontStopAppOnReset' - , defaultValue: false - , action: 'storeTrue' - , required: false - , help: "(Android-only) When included, refrains from stopping the app before restart" - }], - - [['--debug-log-spacing'], { - dest: 'debugLogSpacing' - , defaultValue: false - , action: 'storeTrue' - , required: false - , help: "Add exaggerated spacing in logs to help with visual inspection" - }], - - [['--suppress-adb-kill-server'], { - dest: 'suppressAdbKillServer' - , defaultValue: false - , action: 'storeTrue' - , required: false - , help: "(Android-only) If set, prevents Appium from killing the adb server instance" - , nargs: 0 - }], - - [['--async-trace'], { - dest: 'asyncTrace' - , defaultValue: false - , required: false - , action: 'storeTrue' - , help: 'Add long stack traces to log entries. Recommended for debugging only.' - }] -]; - -// Setup all the command line argument parsing -module.exports = function () { - var parser = new ap({ - version: pkgObj.version, - addHelp: true, - description: 'A webdriver-compatible server for use with native and hybrid iOS and Android applications.' - }); - - _.each(args, function (arg) { - parser.addArgument(arg[0], arg[1]); - }); - - parser.rawArgs = args; - - return parser; -}; diff --git a/lib/server/proxy.js b/lib/server/proxy.js deleted file mode 100644 index 234d4fb4..00000000 --- a/lib/server/proxy.js +++ /dev/null @@ -1,111 +0,0 @@ -"use strict"; - -var _s = require('underscore.string') - , logger = require('./logger.js').get('appium') - , status = require('./status.js') - , doRequest = require('../devices/common.js').doRequest - , respondError = require('./responses.js').respondError - , _ = require('underscore') - , safely = require('./helpers').safely; - - -module.exports.shouldProxy = function (req) { - if (req.device === null) return false; - if (!req.device.isProxy) return false; - - var deviceAvoids = req.device.avoidProxy || []; - - var avoid = [ - ['POST', new RegExp('^/wd/hub/session$')] - , ['DELETE', new RegExp('^/wd/hub/session/[^/]+$')] - ].concat(deviceAvoids); - var method = req.method.toUpperCase(); - var path = req.originalUrl; - var shouldAvoid = false; - - // check for POST /execute mobile: - if (method === 'POST' && - new RegExp('^/wd/hub/session/.+/execute$').test(path) && - _s.startsWith(req.body.script, "mobile: ")) { - shouldAvoid = true; - } - - _.each(avoid, function (pathToAvoid) { - if (method === pathToAvoid[0] && pathToAvoid[1].exec(path)) { - shouldAvoid = true; - } - }); - return !shouldAvoid; -}; - -module.exports.doProxy = function (req, res) { - if (req.device.proxyReqRes) { - // this section is triggered when we have defined the proxy device to use - // the appium-jsonwp-proxy method proxyReqRes. Ultimately we'll be moving - // everything to this paradigm and will delete the code after this block. - // proxyReqRes might be a promise or a callback-based function, so handle - // both - var handler = function (err) { - logger.error(err.message); - }; - var p = req.device.proxyReqRes(req, res, handler); - if (p.catch) { - p.catch(handler); - } - return; - } - - logger.debug("Proxying command to " + req.device.proxyHost + ":" + - req.device.proxyPort); - var sessRe = new RegExp('^/wd/hub/session/([^/]+)'); - var sessionRegxMatch = sessRe.exec(req.originalUrl); - var newPath, origSessId = null; - // there might be no session id in the orig. req. - if (sessionRegxMatch) { - origSessId = sessionRegxMatch[1]; - var sessId = req.device.proxySessionId ? req.device.proxySessionId : - origSessId; - newPath = req.originalUrl.replace(origSessId, sessId); - } else { - newPath = req.originalUrl; - } - var url = 'http://' + req.device.proxyHost + ':' + req.device.proxyPort + - newPath; - doRequest(url, req.method.toUpperCase(), req.body, - req.headers['content-type'], function (err, response, body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } catch (e) {} - } - if (err) { - return respondError(req, res, status.codes.UnknownError.code, - "Did not successfully proxy server command"); - } - if (body && body.value) { - var resStatusCode = body.status; - if (resStatusCode !== 0) { - var resErrorMessage = body.value.message; - return respondError(req, res, resStatusCode, resErrorMessage); - } - } - var sbody = body ? JSON.stringify(body).slice(0, 10000) : body; - - logger.debug("Proxied response received with status " + - response.statusCode + ": " + - sbody); - - // if the proxied response contains a sessionId that the downstream - // driver has generated, we don't want to return that to the client. - // Instead, return the id for the current appium session - if (body && body.sessionId && origSessId) { - body.sessionId = origSessId; - } - - safely(req, function () { - res.headers = response.headers; - res.set('Content-Type', response.headers['content-type']); - res.status(response.statusCode).send(body); - }); - }); -}; diff --git a/lib/server/responses.js b/lib/server/responses.js deleted file mode 100644 index 9ab6c30e..00000000 --- a/lib/server/responses.js +++ /dev/null @@ -1,162 +0,0 @@ -"use strict"; - -var logger = require('./logger.js').get('appium') - , status = require('./status.js') - , _ = require('underscore') - , safely = require('./helpers').safely; - -var getSessionId = function (req, response) { - var sessionId = (typeof response === 'undefined') ? undefined : response.sessionId; - if (typeof sessionId === "undefined") { - if (req.appium) { - sessionId = req.appium.sessionId || null; - } else { - sessionId = null; - } - } - if (typeof sessionId !== "string" && sessionId !== null) { - sessionId = null; - } - return sessionId; -}; - -var notImplementedInThisContext = function (req, res) { - logger.debug("Responding to client that a method is not implemented " + - "in this context"); - safely(req, function () { - res.status(501).send({ - status: status.codes.UnknownError.code - , sessionId: getSessionId(req) - , value: { - message: "Not implemented in this context, try switching " + - "into or out of a web view" - } - }); - }); -}; - -var respondError = exports.respondError = function (req, res, statusObj, value) { - var code = 1, message = "An unknown error occurred"; - var newValue = value; - if (typeof statusObj === "string") { - message = statusObj; - } else if (typeof statusObj === "undefined") { - message = "undefined status object"; - } else if (typeof statusObj === "number") { - code = statusObj; - message = status.getSummaryByCode(code); - } else if (typeof statusObj.code !== "undefined") { - code = statusObj.code; - message = statusObj.summary; - } else if (typeof statusObj.message !== "undefined") { - message = statusObj.message; - } - - if (typeof newValue === "object") { - if (newValue !== null && _.has(value, "message")) { - // make sure this doesn't get obliterated - value.origValue = value.message; - message += " (Original error: " + value.message + ")"; - } - newValue = _.extend({message: message}, value); - } else { - newValue = {message: message, origValue: value}; - } - var response = {status: code, value: newValue}; - response.sessionId = getSessionId(req, response); - logger.debug("Responding to client with error: " + JSON.stringify(response)); - safely(req, function () { - res.status(500).send(response); - }); -}; - -var respondSuccess = exports.respondSuccess = function (req, res, value, sid) { - var response = {status: status.codes.Success.code, value: value}; - response.sessionId = getSessionId(req, response) || sid; - if (typeof response.value === "undefined") { - response.value = ''; - } - var printResponse = _.clone(response); - var maxLen = 1000; - if (printResponse.value !== null && - typeof printResponse.value.length !== "undefined" && - printResponse.value.length > maxLen) { - printResponse.value = printResponse.value.slice(0, maxLen) + "..."; - } - res.jsonResp = JSON.stringify(printResponse); - logger.debug("Responding to client with success: " + res.jsonResp); - safely(req, function () { - res.status(200).send(response); - }); -}; - -exports.getResponseHandler = function (req, res) { - return function (err, response) { - if (typeof response === "undefined" || response === null) { - response = {}; - } - if (err !== null && typeof err !== "undefined" && typeof err.status !== 'undefined' && typeof err.value !== 'undefined') { - throw new Error("Looks like you passed in a response object as the " + - "first param to getResponseHandler. Err is always the " + - "first param! Fix your codes!"); - } else if (err !== null && typeof err !== "undefined") { - if (typeof err.name !== 'undefined') { - if (err.name === 'NotImplementedError') { - notImplementedInThisContext(req, res); - } else if (err.name === "NotYetImplementedError") { - notYetImplemented(req, res); - } else { - respondError(req, res, status.codes.UnknownError.code, err); - } - } else { - var value = response.value; - if (typeof value === "undefined") { - value = ''; - } - respondError(req, res, err.message, value); - } - } else { - if (response.status === 0) { - respondSuccess(req, res, response.value, response.sessionId); - } else { - respondError(req, res, response.status, response.value); - } - } - }; -}; - -exports.checkMissingParams = function (req, res, params, strict) { - if (typeof strict === "undefined") { - strict = false; - } - var missingParamNames = []; - _.each(params, function (param, paramName) { - if (typeof param === "undefined" || (strict && !param)) { - missingParamNames.push(paramName); - } - }); - if (missingParamNames.length > 0) { - var missingList = JSON.stringify(missingParamNames); - logger.debug("Missing params for request: " + missingList); - safely(req, function () { - res.status(400).send("Missing parameters: " + missingList); - }); - return false; - } else { - return true; - } -}; - -var notYetImplemented = exports.notYetImplemented = function (req, res) { - logger.debug("Responding to client that a method is not implemented"); - safely(req, function () { - res.status(501).send({ - status: status.codes.UnknownError.code - , sessionId: getSessionId(req) - , value: { - message: "Not yet implemented. " + - "Please help us: http://appium.io/get-involved.html" - } - }); - }); -}; diff --git a/lib/server/routing.js b/lib/server/routing.js deleted file mode 100644 index f92de131..00000000 --- a/lib/server/routing.js +++ /dev/null @@ -1,174 +0,0 @@ -"use strict"; - -var controller = require('./controller.js'); - -module.exports = function (appium) { - var rest = appium.rest; - var globalBeforeFilter = controller.getGlobalBeforeFilter(appium); - // Make appium available to all REST http requests. - rest.all('/wd/*', globalBeforeFilter); - routeNotYetImplemented(rest); - rest.all('/wd/hub/session/*', controller.sessionBeforeFilter); - - rest.get('/wd/hub/status', controller.getStatus); - rest.post('/wd/hub/session', controller.createSession); - rest.get('/wd/hub/session/:sessionId?', controller.getSession); - rest.delete('/wd/hub/session/:sessionId?', controller.deleteSession); - rest.get('/wd/hub/sessions', controller.getSessions); - rest.get('/wd/hub/session/:sessionId?/context', controller.getCurrentContext); - rest.post('/wd/hub/session/:sessionId?/context', controller.setContext); - rest.get('/wd/hub/session/:sessionId?/contexts', controller.getContexts); - rest.post('/wd/hub/session/:sessionId?/element', controller.findElement); - rest.post('/wd/hub/session/:sessionId?/elements', controller.findElements); - rest.post('/wd/hub/session/:sessionId?/element/:elementId?/value', controller.setValue); - rest.post('/wd/hub/session/:sessionId?/element/:elementId?/click', controller.doClick); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/text', controller.getText); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/displayed', controller.elementDisplayed); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/enabled', controller.elementEnabled); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/selected', controller.elementSelected); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/location', controller.getLocation); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/location_in_view', controller.getLocationInView); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/size', controller.getSize); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/pageIndex', controller.getPageIndex); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/attribute/:name', controller.getAttribute); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/css/:propertyName', controller.getCssProperty); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/equals/:otherId', controller.equalsElement); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?/name', controller.getName); - rest.post('/wd/hub/session/:sessionId?/element/:elementId?/clear', controller.clear); - rest.post('/wd/hub/session/:sessionId?/frame', controller.frame); - rest.post('/wd/hub/session/:sessionId?/keys', controller.keys); - rest.post('/wd/hub/session/:sessionId?/location', controller.setLocation); - rest.get('/wd/hub/session/:sessionId?/source', controller.getPageSource); - rest.get('/wd/hub/session/:sessionId?/alert_text', controller.getAlertText); - rest.post('/wd/hub/session/:sessionId?/alert_text', controller.setAlertText); - rest.post('/wd/hub/session/:sessionId?/accept_alert', controller.postAcceptAlert); - rest.post('/wd/hub/session/:sessionId?/dismiss_alert', controller.postDismissAlert); - rest.post('/wd/hub/session/:sessionId?/timeouts/implicit_wait', controller.implicitWait); - rest.post('/wd/hub/session/:sessionId?/timeouts/async_script', controller.asyncScriptTimeout); - rest.get('/wd/hub/session/:sessionId?/orientation', controller.getOrientation); - rest.post('/wd/hub/session/:sessionId?/orientation', controller.setOrientation); - rest.get('/wd/hub/session/:sessionId?/screenshot', controller.getScreenshot); - rest.post('/wd/hub/session/:sessionId?/element/:elementId?/element', controller.findElementFromElement); - rest.post('/wd/hub/session/:sessionId?/element/:elementId?/elements', controller.findElementsFromElement); - rest.post('/wd/hub/session/:sessionId?/touch/click', controller.doClick); - rest.post('/wd/hub/session/:sessionId?/touch/longclick', controller.touchLongClick); - rest.post('/wd/hub/session/:sessionId?/touch/down', controller.touchDown); - rest.post('/wd/hub/session/:sessionId?/touch/up', controller.touchUp); - rest.post('/wd/hub/session/:sessionId?/touch/move', controller.touchMove); - rest.post('/wd/hub/session/:sessionId?/touch/flick', controller.pickAFlickMethod); - rest.post('/wd/hub/session/:sessionId?/url', controller.postUrl); - rest.get('/wd/hub/session/:sessionId?/url', controller.getUrl); - rest.post('/wd/hub/session/:sessionId?/element/active', controller.active); - rest.get('/wd/hub/session/:sessionId?/window_handle', controller.getWindowHandle); - rest.get('/wd/hub/session/:sessionId?/window_handles', controller.getWindowHandles); - rest.post('/wd/hub/session/:sessionId?/window', controller.setWindow); - rest.delete('/wd/hub/session/:sessionId?/window', controller.closeWindow); - rest.get('/wd/hub/session/:sessionId?/window/:windowhandle?/size', controller.getWindowSize); - rest.post('/wd/hub/session/:sessionId?/window/:windowhandle/maximize', controller.maximizeWindow); - rest.post('/wd/hub/session/:sessionId?/execute', controller.execute); - rest.post('/wd/hub/session/:sessionId?/execute_async', controller.executeAsync); - rest.get('/wd/hub/session/:sessionId?/title', controller.title); - rest.post('/wd/hub/session/:sessionId?/element/:elementId?/submit', controller.submit); - rest.post('/wd/hub/session/:sessionId?/moveto', controller.moveTo); - rest.post('/wd/hub/session/:sessionId?/click', controller.clickCurrent); - rest.post('/wd/hub/session/:sessionId?/back', controller.back); - rest.post('/wd/hub/session/:sessionId?/forward', controller.forward); - rest.post('/wd/hub/session/:sessionId?/refresh', controller.refresh); - rest.get('/wd/hub/session/:sessionId?/cookie', controller.getCookies); - rest.post('/wd/hub/session/:sessionId?/cookie', controller.setCookie); - rest.delete('/wd/hub/session/:sessionId?/cookie', controller.deleteCookies); - rest.delete('/wd/hub/session/:sessionId?/cookie/:name', controller.deleteCookie); - rest.post('/wd/hub/session/:sessionId?/log', controller.getLog); - rest.get('/wd/hub/session/:sessionId?/log/types', controller.getLogTypes); - rest.post('/wd/hub/session/:sessionId?/timeouts', controller.timeouts); - rest.get('/wd/hub/session/:sessionId?/network_connection', controller.getNetworkConnection); - rest.post('/wd/hub/session/:sessionId?/network_connection', controller.setNetworkConnection); - - // touch gesture endpoints - rest.post('/wd/hub/session/:sessionId?/touch/perform', controller.performTouch); - rest.post('/wd/hub/session/:sessionId?/touch/multi/perform', controller.performMultiAction); - - // allow appium to receive async response - rest.post('/wd/hub/session/:sessionId?/receive_async_response', controller.receiveAsyncResponse); - - //welcome page - rest.all('/welcome', controller.welcome); - - // these are for testing purposes only - rest.post('/wd/hub/produce_error', controller.produceError); - rest.post('/wd/hub/crash', controller.crash); - rest.all('/test/guinea-pig', controller.guineaPig); - - // IME specific - rest.get('/wd/hub/session/:sessionId?/ime/available_engines', controller.availableIMEEngines); - rest.get('/wd/hub/session/:sessionId?/ime/active_engine', controller.getActiveIMEEngine); - rest.get('/wd/hub/session/:sessionId?/ime/activated', controller.isIMEActivated); - rest.post('/wd/hub/session/:sessionId?/ime/deactivate', controller.deactivateIMEEngine); - rest.post('/wd/hub/session/:sessionId?/ime/activate', controller.activateIMEEngine); - - // appium-specific extensions to JSONWP - rest.post('/wd/hub/session/:sessionId?/appium/device/shake', controller.mobileShake); - rest.post('/wd/hub/session/:sessionId?/appium/device/lock', controller.lock); - rest.post('/wd/hub/session/:sessionId?/appium/device/unlock', controller.unlock); - rest.post('/wd/hub/session/:sessionId?/appium/device/is_locked', controller.isLocked); - rest.post('/wd/hub/session/:sessionId?/appium/device/press_keycode', controller.pressKeyCode); - rest.post('/wd/hub/session/:sessionId?/appium/device/long_press_keycode', controller.longPressKeyCode); - rest.post('/wd/hub/session/:sessionId?/appium/device/keyevent', controller.keyevent); - rest.post('/wd/hub/session/:sessionId?/appium/device/rotate', controller.mobileRotation); - rest.get('/wd/hub/session/:sessionId?/appium/device/current_activity', controller.getCurrentActivity); - rest.post('/wd/hub/session/:sessionId?/appium/device/install_app', controller.installApp); - rest.post('/wd/hub/session/:sessionId?/appium/device/remove_app', controller.removeApp); - rest.post('/wd/hub/session/:sessionId?/appium/device/app_installed', controller.isAppInstalled); - rest.post('/wd/hub/session/:sessionId?/appium/device/hide_keyboard', controller.hideKeyboard); - rest.post('/wd/hub/session/:sessionId?/appium/device/push_file', controller.pushFile); - rest.post('/wd/hub/session/:sessionId?/appium/device/pull_file', controller.pullFile); - rest.post('/wd/hub/session/:sessionId?/appium/device/pull_folder', controller.pullFolder); - rest.post('/wd/hub/session/:sessionId?/appium/device/toggle_airplane_mode', controller.toggleFlightMode); - rest.post('/wd/hub/session/:sessionId?/appium/device/toggle_data', controller.toggleData); - rest.post('/wd/hub/session/:sessionId?/appium/device/toggle_wifi', controller.toggleWiFi); - rest.post('/wd/hub/session/:sessionId?/appium/device/toggle_location_services', controller.toggleLocationServices); - rest.post('/wd/hub/session/:sessionId?/appium/device/open_notifications', controller.openNotifications); - rest.post('/wd/hub/session/:sessionId?/appium/device/start_activity', controller.startActivity); - - rest.post('/wd/hub/session/:sessionId?/appium/app/launch', controller.launchApp); - rest.post('/wd/hub/session/:sessionId?/appium/app/close', controller.closeApp); - rest.post('/wd/hub/session/:sessionId?/appium/app/reset', controller.reset); - rest.post('/wd/hub/session/:sessionId?/appium/app/background', controller.background); - rest.post('/wd/hub/session/:sessionId?/appium/app/end_test_coverage', controller.endCoverage); - rest.post('/wd/hub/session/:sessionId?/appium/app/strings', controller.getStrings); - - rest.post('/wd/hub/session/:sessionId?/appium/element/:elementId?/value', controller.setValueImmediate); - rest.post('/wd/hub/session/:sessionId?/appium/element/:elementId?/replace_value', controller.replaceValue); - - rest.post('/wd/hub/session/:sessionId?/appium/settings', controller.updateSettings); - rest.get('/wd/hub/session/:sessionId?/appium/settings', controller.getSettings); - - // keep this at the very end! - rest.all('/*', controller.unknownCommand); -}; - -var routeNotYetImplemented = function (rest) { - rest.get('/wd/hub/session/:sessionId?/local_storage', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/local_storage', controller.notYetImplemented); - rest.delete('/wd/hub/session/:sessionId?/local_storage', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/local_storage/key/:key', controller.notYetImplemented); - rest.delete('/wd/hub/session/:sessionId?/local_storage/key/:key', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/local_storage/size', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/window/:windowhandle/size', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/window/:windowhandle/position', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/window/:windowhandle/position', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/element/:elementId?', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/buttondown', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/buttonup', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/doubleclick', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/touch/scroll', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/touch/doubleclick', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/location', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/session_storage', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/session_storage', controller.notYetImplemented); - rest.delete('/wd/hub/session/:sessionId?/session_storage', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/session_storage/key/:key', controller.notYetImplemented); - rest.delete('/wd/hub/session/:sessionId?/session_storage/key/:key', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/session_storage/size', controller.notYetImplemented); - rest.get('/wd/hub/session/:sessionId?/application_cache/status', controller.notYetImplemented); -}; diff --git a/lib/server/static/favicon.ico b/lib/server/static/favicon.ico deleted file mode 100644 index 70bcbf868cc4a1033d49acde5de98f50b846435e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmah|O-qzf7(J7jrqeimXeQODj3FwOEn{{;L_Z*yUy!?Y(IRM3AXMriT1aUaK~aGg z8GTVoK^Qm`*;G)GeUR8UWnPZ0_}wlNrXWA31Y`dqW!poiG@O;P;}O4E#!8K)8+W{JgYSy=W@ADH<70czKOfm_ zcAsBAs>g}dFNox8;U%&5nbR{Gjf1Wh(+|HFUjHqp@&mDQim3QZB;H8lseC+CEyK=( z>FHwpRqu$huf(#SMCmu8@}u)ptC~z-OmAB?7k{>X{={3yFZnM1Ou)ajG~d)5pOpVE z;(j8^r-_}aXP11{ju2Zk2hV@JYTdZsKRb^#6YAY7`OdrAjdwH8uzz)XXT$kL-$Aru z7{nf-;hM{B<`+-@j`l-71~7+do&O;W3HGY!8!Jyd_c?wX`Nw^o9}~_Alfp4j_ats} z>^`Jr;T^Sm63FNCD{xS`Z-{pQ%w#fM;d$&I{I7<||0@N|Q~~eHgc4vRLQHW0v;RkD MM~?tUh0X`SU)3ZTk^lez diff --git a/lib/server/static/js/jquery.min.js b/lib/server/static/js/jquery.min.js deleted file mode 100644 index 32d50cb0..00000000 --- a/lib/server/static/js/jquery.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license -//@ sourceMappingURL=jquery.min.map -*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
    a",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="
    t
    ",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
    ",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj; -return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="
    ",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&>(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:b.support.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l) -}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b(" - - - - diff --git a/lib/server/static/test/subframe1.html b/lib/server/static/test/subframe1.html deleted file mode 100644 index c3e5e36e..00000000 --- a/lib/server/static/test/subframe1.html +++ /dev/null @@ -1,9 +0,0 @@ - - - Sub frame 1 - - -

    Sub frame 1

    - Open a named window - - diff --git a/lib/server/static/test/subframe2.html b/lib/server/static/test/subframe2.html deleted file mode 100644 index b13425c1..00000000 --- a/lib/server/static/test/subframe2.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Sub frame 2 - - -

    Sub frame 2

    - - diff --git a/lib/server/static/test/subframe3.html b/lib/server/static/test/subframe3.html deleted file mode 100644 index 7f0aaa82..00000000 --- a/lib/server/static/test/subframe3.html +++ /dev/null @@ -1,11 +0,0 @@ - - - Sub frame 3 - - -

    Sub frame 3

    -

    I have a child iframe

    - - - diff --git a/lib/server/static/test/touch.html b/lib/server/static/test/touch.html deleted file mode 100644 index f67ddc98..00000000 --- a/lib/server/static/test/touch.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - I am a page title - - - - - -

    This page is a Selenium sandbox

    - -
     

    - - - - - diff --git a/lib/server/status.js b/lib/server/status.js deleted file mode 100644 index 34cb2c4e..00000000 --- a/lib/server/status.js +++ /dev/null @@ -1,121 +0,0 @@ -"use strict"; - -var codes = { - Success: { - code: 0, - summary: 'The command executed successfully.' - }, - NoSuchDriver: { - code: 6, - summary: 'A session is either terminated or not started' - }, - NoSuchElement: { - code: 7, - summary: 'An element could not be located on the page using the given search parameters.' - }, - NoSuchFrame: { - code: 8, - summary: 'A request to switch to a frame could not be satisfied because the frame could not be found.' - }, - UnknownCommand: { - code: 9, - summary: 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.' - }, - StaleElementReference: { - code: 10, - summary: 'An element command failed because the referenced element is no longer attached to the DOM.' - }, - ElementNotVisible: { - code: 11, - summary: 'An element command could not be completed because the element is not visible on the page.' - }, - InvalidElementState: { - code: 12, - summary: 'An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).' - }, - UnknownError: { - code: 13, - summary: 'An unknown server-side error occurred while processing the command.' - }, - ElementIsNotSelectable: { - code: 15, - summary: 'An attempt was made to select an element that cannot be selected.' - }, - JavaScriptError: { - code: 17, - summary: 'An error occurred while executing user supplied JavaScript.' - }, - XPathLookupError: { - code: 19, - summary: 'An error occurred while searching for an element by XPath.' - }, - Timeout: { - code: 21, - summary: 'An operation did not complete before its timeout expired.' - }, - NoSuchWindow: { - code: 23, - summary: 'A request to switch to a different window could not be satisfied because the window could not be found.' - }, - InvalidCookieDomain: { - code: 24, - summary: 'An illegal attempt was made to set a cookie under a different domain than the current page.' - }, - UnableToSetCookie: { - code: 25, - summary: 'A request to set a cookie\'s value could not be satisfied.' - }, - UnexpectedAlertOpen: { - code: 26, - summary: 'A modal dialog was open, blocking this operation' - }, - NoAlertOpenError: { - code: 27, - summary: 'An attempt was made to operate on a modal dialog when one was not open.' - }, - ScriptTimeout: { - code: 28, - summary: 'A script did not complete before its timeout expired.' - }, - InvalidElementCoordinates: { - code: 29, - summary: 'The coordinates provided to an interactions operation are invalid.' - }, - IMENotAvailable: { - code: 30, - summary: 'IME was not available.' - }, - IMEEngineActivationFailed: { - code: 31, - summary: 'An IME engine could not be started.' - }, - InvalidSelector: { - code: 32, - summary: 'Argument was an invalid selector (e.g. XPath/CSS).' - }, - SessionNotCreatedException: { - code: 33, - summary: 'A new session could not be created.' - }, - MoveTargetOutOfBounds: { - code: 34, - summary: 'Target provided for a move action is out of bounds.' - }, - NoSuchContext: { - code: 35, - summary: 'No such context found.' - } -}; - -if (typeof module !== "undefined") { - module.exports.codes = codes; - module.exports.getSummaryByCode = function (code) { - code = parseInt(code, 10); - for (var c in codes) { - if (typeof codes[c].code !== "undefined" && codes[c].code === code) { - return codes[c].summary; - } - } - return 'An error occurred'; - }; -} diff --git a/lib/server/templates/guinea-pig.html b/lib/server/templates/guinea-pig.html deleted file mode 100644 index a24ef2ff..00000000 --- a/lib/server/templates/guinea-pig.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - I am a page title - - - - -

    This page is a Selenium sandbox

    - -I am some page content -
    I am a div
    -i am a link
    -i am a new window link -
    i appear 3 times
    -
    i appear 3 times
    -
    i appear 3 times
    - - - - -

    - -

    - - - - - - - Your comments: {{ comment }}
    -
    -ñ☃ - - -
    - -
    -

    -
    - -

    -

    -
    - -

    -
    - -
    - -
    - -

    Server time: {{ serverTime }}

    -

    Client time:

    - - -

    {{ userAgent }}

    - - - diff --git a/lib/server/templates/welcome.html b/lib/server/templates/welcome.html deleted file mode 100644 index ce2becd9..00000000 --- a/lib/server/templates/welcome.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - Appium/welcome - - -

    {{message}}

    - - diff --git a/reset.bat b/reset.bat deleted file mode 100644 index f77ead75..00000000 --- a/reset.bat +++ /dev/null @@ -1,271 +0,0 @@ -@echo off - -:: Go to directory containing batch file -FOR /f %%i in ("%0") DO SET curpath=%%~dpi -CD /d %curpath% - -:: Flags to determine which parts will be executed -SET doDev=0 -SET doSelendroid=0 -SET doAndroid=0 -SET doVerbose=0 -SET doForce=0 -SET pigsFly=0 -SET chromedriver_version= -SET udid= - -:: Read in command line switches -:loop -IF "%~1" neq "" ( - IF "%1" == "--dev" SET doDev=1 - IF "%1" == "--android" SET doAndroid=1 - IF "%1" == "--selendroid" SET doSelendroid=1 - IF "%1" == "--verbose" SET doVerbose=1 - IF "%1" == "--force" SET doForce=1 - IF "%1" == "--chromedriver-version" IF "%2" neq "" ( - SET "chromedriver_version=%2" - ) - IF "%1" == "--udid" IF "%2" neq "" ( - SET "udid=%2" - ) - shift - goto :loop -) - -:: If nothing is flagged do only android -IF %doDev% == 0 IF %doSelendroid% == 0 IF %doAndroid% == 0 SET doAndroid=1 - -:: Install Package and Dependencies -ECHO. -ECHO =====Installing dependencies with npm===== -ECHO. -CALL :runCmd "npm install ." -ECHO =====Finished installing dependencies with npm===== -ECHO. - -:: Install Dev Dependencies -if %doDev% == 1 ( - ECHO. - ECHO =====Installing development dependencies with npm===== - ECHO. - CALL :runCmd "npm install . --dev" - ECHO =====Finished installing development dependencies with npm===== - ECHO. -) - -:: Install Sample Code -if %doDev% == 1 ( - CALL :runCmd "node_modules\.bin\grunt getSampleCode" -) - -:: Reset Android -if %doAndroid% == 1 ( - ECHO. - ECHO =====Resetting Android===== - ECHO. - CALL :runCmd "node_modules\.bin\grunt configAndroidBootstrap" - CALL :runCmd "node_modules\.bin\grunt buildAndroidBootstrap" - CALL :runCmd "node_modules\.bin\grunt setConfigVer:android" - ECHO. - ECHO =====Reset Android Complete===== - - ECHO. - ECHO =====Resetting Unlock.apk===== - ECHO. - CALL :runCmd "RD /S /Q build\unlock_apk | VER > NUL" - CALL :runCmd "MKDIR build\unlock_apk" - ECHO Building Unlock.apk - CALL :runCmd "git submodule update --init submodules\unlock_apk" - CALL :runCmd "PUSHD submodules\unlock_apk" - CALL :runCmd "ant clean" - CALL :runCmd "ant debug" - CALL :runCmd "POPD" - CALL :runCmd "COPY submodules\unlock_apk\bin\unlock_apk-debug.apk build\unlock_apk\unlock_apk-debug.apk" - ECHO. - ECHO =====Reset Unlock.apk Complete===== - - ECHO. - ECHO =====Resetting UnicodeIME.apk===== - ECHO. - CALL :runCmd "RD /S /Q build\unicode_ime_apk | VER > NUL" - CALL :runCmd "MKDIR build\unicode_ime_apk" - ECHO Building UnicodeIME.apk - CALL :runCmd "git submodule update --init submodules\io.appium.android.ime" - CALL :runCmd "PUSHD submodules\io.appium.android.ime" - CALL :runCmd "ant clean" - CALL :runCmd "ant debug" - CALL :runCmd "POPD" - CALL :runCmd "COPY submodules\io.appium.android.ime\bin\UnicodeIME-debug.apk build\unicode_ime_apk\UnicodeIME-debug.apk" - ECHO. - ECHO =====Reset UnicodeIME.apk Complete===== - - ECHO. - ECHO =====Resetting Settings.apk===== - ECHO. - CALL :runCmd "RD /S /Q build\settings_apk | VER > NUL" - CALL :runCmd "MKDIR build\settings_apk" - ECHO Building Settings.apk - CALL :runCmd "git submodule update --init submodules\io.appium.settings" - CALL :runCmd "PUSHD submodules\io.appium.settings" - CALL :runCmd "ant clean" - CALL :runCmd "ant debug" - CALL :runCmd "POPD" - CALL :runCmd "COPY submodules\io.appium.settings\bin\settings_apk-debug.apk build\settings_apk\settings_apk-debug.apk" - ECHO. - ECHO =====Reset Settings.apk Complete===== - - :: Reset Android Dev - IF %doDev% == 1 ( - ECHO. - ECHO =====Resetting API Demos===== - ECHO. - ECHO Cloning/updating Android test app: ApiDemos - CALL :runCmd "git submodule update --init submodules\ApiDemos" - CALL :runCmd "RD /S /Q sample-code\apps\ApiDemos | VER > NUL" - CALL :runCmd "MKDIR sample-code\apps\ApiDemos" - CALL :runCmd "XCOPY submodules\ApiDemos sample-code\apps\ApiDemos /E /Q" - CALL :runCmd "node_modules\.bin\grunt configAndroidApp:ApiDemos" - CALL :runCmd "node_modules\.bin\grunt buildAndroidApp:ApiDemos" - ECHO. - ECHO =====Reset API Demos Complete===== - ) - - :: Reset ChromeDriver - echo =====Resetting ChromeDriver===== - setlocal enabledelayedexpansion - SET "chromedriver_build_directory=.\build\chromedriver\windows" - echo Building directory structure - IF NOT EXIST .\build CALL :runCmd "mkdir .\build" - IF NOT EXIST .\build\chromedriver CALL :runCmd "mkdir .\build\chromedriver" - IF NOT EXIST .\build\chromedriver\windows CALL :runCmd "mkdir !chromedriver_build_directory!" - - echo Removing old files - IF EXIST !chromedriver_build_directory!\chromedriver.zip CALL :runCmd "del !chromedriver_build_directory!\chromedriver.zip" - IF EXIST !chromedriver_build_directory!\chromedriver.exe CALL :runCmd "del !chromedriver_build_directory!\chromedriver.exe" - - IF NOT DEFINED chromedriver_version ( - echo Finding latest version - for /f "delims=" %%a in ('curl -L http://chromedriver.storage.googleapis.com/LATEST_RELEASE') do SET "chromedriver_version=%%a" - ) - - echo !chromedriver_build_directory! - echo Downloading and installing version !chromedriver_version! - CALL :runCmd "curl -L http://chromedriver.storage.googleapis.com/!chromedriver_version!/chromedriver_win32.zip -o !chromedriver_build_directory!\chromedriver.zip" - CALL :runCmd "PUSHD !chromedriver_build_directory!" - CALL :runCmd "jar xf chromedriver.zip" - CALL :runCmd "del chromedriver.zip" - CALL :runCmd "POPD" - - echo =====Reset ChromeDriver Complete===== -) - -:: Reset Selendroid -IF %doSelendroid% == 1 ( - ECHO. - ECHO =====Resetting Selendroid===== - ECHO. - ECHO Clearing out any old modified server apks - CALL :runCmd "RD -/S /Q %windir%\Temp\selendroid*.apk | VER > NUL" - ECHO Cloning/updating selendroid - CALL :runCmd "RD -/S /Q submodules\selendroid\selendroid-server\target | VER > NUL" - CALL :runCmd "git submodule update --init submodules\selendroid" - CALL :runCmd "RD /S /Q selendroid | VER > NUL" - ECHO Building selendroid server and supporting libraries - CALL :runCmd "set MAVEN_OPTS=-Xms512m -Xmx512m -Xss2048k" - CALL :runCmd "node_modules\.bin\grunt buildSelendroidServer" - CALL :runCmd "set MAVEN_OPTS=" - - :: Reset Selendroid Dev - IF %doDev% == 1 ( - ECHO. - ECHO =====Resetting Selendroid - Dev===== - ECHO. - ECHO Linking selendroid test app: WebViewDemo - CALL :runCmd "RD /S /Q sample-code\apps\WebViewDemo | VER > NUL" - CALL :runCmd "MKDIR sample-code\apps\WebViewDemo" - CALL :runCmd "XCOPY submodules\selendroid\selendroid-test-app sample-code\apps\WebViewDemo /E /Q" - CALL :uninstallAndroidApp io.appium.android.apis.selendroid - CALL :uninstallAndroidApp io.selendroid.testapp - CALL :uninstallAndroidApp io.selendroid.testapp.selendroid - CALL :uninstallAndroidApp org.openqa.selendroid.testapp - CALL :uninstallAndroidApp openqa.selendroid.testapp.selendroid - ECHO. - ECHO =====Reset Selendroid - Dev Complete===== - ) - - ECHO Setting Selendroid config to Appium's version - CALL :runCmd "node_modules\.bin\grunt setConfigVer:selendroid" - ECHO. - ECHO =====Reset Selendroid Complete===== -) -:: Reset Gappium -IF %pigsFly% == 1 ( - ECHO. - ECHO =====Resetting Gappium===== - ECHO. - ECHO Clearing out old links - CALL :runCmd "RD /S /Q sample-code\apps\io.appium.gappium.sampleapp | VER > NUL" - ECHO Cloning/updating Gappium - CALL :runCmd "git submodule update --init submodules\io.appium.gappium.sampleapp" - CALL :runCmd "PUSHD submodules\io.appium.gappium.sampleapp" - ECHO Building Gappium test app - CALL :runCmd "reset.bat" - CALL :runCmd "POPD" - ECHO Linking Gappium test app - CALL :runCmd "XCOPY submodules\io.appium.gappium.sampleapp sample-code\apps\io.appium.gappium.sampleapp /E /Q" - ECHO. - ECHO =====Reset Gappium===== -) -ECHO Done, No Errors -GOTO :EOF - -:: Function to uninstall an Android app -:uninstallAndroidApp - ECHO Attempting to uninstall android app %~1 - FOR /F "delims=" %%i in ('adb devices ^| FINDSTR /R "device$" ^| find /v /c ""') DO SET deviceCount=%%i - IF %deviceCount% GEQ 1 ( - GOTO :deviceExists - ) ELSE ECHO No android devices detected, skipping uninstallation -GOTO :EOF - -:deviceExists -IF DEFINED udid ( - GOTO :udidSpecified -) ELSE IF %deviceCount% EQU 1 ( - CALL :runCmd "adb uninstall %~1 | VER > NUL" -) ELSE ECHO More than one device present, but no device serial provided, skipping uninstallation (use --udid) -GOTO :EOF - -:udidSpecified -FOR /F "delims=" %%i in ('adb devices ^| FINDSTR /R "^%udid%" ^| find /v /c ""') DO SET specifiedDeviceCount=%%i -IF %specifiedDeviceCount% EQU 1 ( - CALL :runCmd "adb -s %udid% uninstall %~1 | VER > NUL" -) ELSE ECHO Device with serial %udid% not found, skipping installation -GOTO :EOF - -:: Function to run commands -:runCmd - function to run a command - IF %doVerbose% == 1 ECHO %~1 - CALL %~1 - IF %ERRORLEVEL% NEQ 0 IF %doForce% == 0 ( - CD /D %curpath% - ECHO. - ECHO Stopping because there was an error and --force was not used - CALL :halt 1 - ) -GOTO :EOF - -:: Sets the errorlevel and stops the batch immediately -:halt -CALL :__SetErrorLevel %1 -CALL :__ErrorExit 2> NUL -GOTO :EOF - -:__ErrorExit -REM Creates a syntax error, stops immediately -() -GOTO :EOF - -:__SetErrorLevel -EXIT /B %TIME:~-2% -GOTO :EOF diff --git a/reset.sh b/reset.sh deleted file mode 100755 index 7f43c3d3..00000000 --- a/reset.sh +++ /dev/null @@ -1,538 +0,0 @@ -#!/bin/bash -# -# reset.sh: INSTALL OR RESET APPIUM -# This script should ensure that after pulling the most recent code, -# you will be in a state where you can run tests and use appium -# -set -e -should_reset_android=false -should_reset_ios=false -should_reset_selendroid=false -should_reset_selendroid_quick=false -should_reset_gappium=false -should_reset_firefoxos=false -should_reset_realsafari=false -code_sign_identity=''; -provisioning_profile=''; -include_dev=false -prod_deps=false -appium_home=$(pwd) -reset_successful=false -has_reset_unlock_apk=false -has_reset_ime_apk=false -has_reset_settings_apk=false -apidemos_reset=false -toggletest_reset=false -hardcore=false -grunt="$(npm bin)/grunt" # might not have grunt-cli installed with -g -verbose=false -chromedriver_version=false -chromedriver_install_all=false -npmlink=true -if test -d .git ; then - is_git_checkout=true -else - is_git_checkout=false -fi - -while test $# != 0 -do - case "$1" in - "--android") should_reset_android=true;; - "--ios") should_reset_ios=true;; - "--real-safari") should_reset_realsafari=true;; - "--code-sign") code_sign_identity=$2;; - "--profile") provisioning_profile=$2;; - "--selendroid") should_reset_selendroid=true;; - "--selendroid-quick") should_reset_selendroid_quick=true;; - "--firefoxos") should_reset_firefoxos=true;; - "--gappium") should_reset_gappium=true;; - "--dev") include_dev=true;; - "--prod") prod_deps=true;; - "-v") verbose=true;; - "--verbose") verbose=true;; - "--hardcore") hardcore=true;; - "--chromedriver-version") chromedriver_version=$2;; - "--chromedriver-install-all") chromedriver_install_all=true;; - "--udid") udid=$2;; - "--no-npmlink") npmlink=false;; - esac - - if [[ -n "$2" ]] && [[ "$2" != --* ]]; then - shift - shift - else - shift - fi -done - -run_cmd_output() { - if $verbose ; then - "$@" - else - "$@" 2> /dev/null - fi -} - -echo "* Determining platform" -if [ $(run_cmd_output uname -s) == "Darwin" ]; then - platform="mac" -else - platform="linux" -fi -echo "* Platform is $platform" - -if ! $should_reset_android && ! $should_reset_ios && ! $should_reset_selendroid \ - && ! $should_reset_gappium && ! $should_reset_firefoxos && ! $should_reset_selendroid_quick ; then - should_reset_android=true - if [ "$platform" == "mac" ]; then - should_reset_ios=true - fi - should_reset_selendroid=true - should_reset_gappium=true - should_reset_firefoxos=true -fi - -if ! $should_reset_ios && $should_reset_realsafari; then - should_reset_ios=true -fi - -if $include_dev && ! $is_git_checkout ; then - echo "Cannot run reset.sh in --dev mode if this is not a git repo" - exit 1; -fi - -run_cmd() { - if $verbose ; then - "$@" - else - "$@" >/dev/null 2>&1 - fi -} - -reset_npm() { - echo "RESETTING NPM" - if $hardcore ; then - echo "* Removing NPM modules" - run_cmd rm -rf node_modules - echo "* Clearing out old .appiumconfig.json" - run_cmd rm -rf ./.appiumconfig #remove legacy config file - run_cmd rm -rf ./.appiumconfig.json - fi - if $prod_deps ; then - echo "* Installing new or updated NPM modules" - run_cmd npm install --production . - else - echo "* Installing new or updated NPM modules (including devDeps)" - run_cmd npm install . - fi -} - -reset_general() { - echo "RESETTING GENERAL" - if $hardcore ; then - echo "* Clearing out build dir" - run_cmd rm -rf build - fi - run_cmd mkdir -p build - if $is_git_checkout ; then - echo "* Setting git revision data" - run_cmd "$grunt" setGitRev - if $include_dev ; then - 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" - fi -} - -reset_sample_code() { - echo "* Initializing sample code and test apps" - if $hardcore ; then - run_cmd "$grunt" getSampleCode:hardcore - else - run_cmd "$grunt" getSampleCode - fi -} - -reset_ios() { - echo "RESETTING IOS" - set +e - sdk_ver=$(xcrun --sdk iphonesimulator --show-sdk-version 2>/dev/null) - sdk_status=$? - ios7_active=false - ios8_active=false - if [[ "$sdk_ver" == "7."* ]]; then - ios7_active=true - elif [[ "$sdk_ver" == "8."* ]]; then - ios8_active=true - fi - if [ $sdk_status -gt 0 ] || (! $ios7_active && ! $ios8_active); then - echo "---------------------------------------------------" - echo "WARNING: you do not appear to have iOS7/8 SDK active" - echo "---------------------------------------------------" - fi - set -e - echo "* Setting iOS config to Appium's version" - run_cmd "$grunt" setConfigVer:ios - if $include_dev ; then - if $npmlink ; then - echo "* Cloning/npm linking appium-atoms" - run_cmd ./bin/npmlink.sh -l appium-atoms - echo "* Cloning/npm linking appium-instruments" - run_cmd ./bin/npmlink.sh -l appium-instruments - echo "* Cloning/npm linking appium-uiauto" - run_cmd ./bin/npmlink.sh -l appium-uiauto - fi - fi - if $should_reset_realsafari; then - echo "* Building SafariLauncher for real devices" - run_cmd rm -rf build/SafariLauncher - run_cmd mkdir -p build/SafariLauncher - 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 >> build/SafariLauncher/target.xcconfig - else - echo "IDENTITY_NAME = iPhone Developer" >> build/SafariLauncher/target.xcconfig - fi - 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 node_modules/safari-launcher/build/Release-iphoneos/SafariLauncher.app - fi - echo "* Cloning/updating libimobiledevice-macosx" - run_cmd git submodule update --init submodules/libimobiledevice-macosx - echo "* Copying libimobiledevice-macosx to build" - run_cmd rm -rf build/libimobiledevice-macosx - run_cmd cp -r submodules/libimobiledevice-macosx build/libimobiledevice-macosx - echo "* Cloning/updating deviceconsole" - run_cmd git submodule update --init submodules/deviceconsole - echo "* Building deviceconsole" - run_cmd pushd submodules/deviceconsole - run_cmd make - run_cmd popd - echo "* Copying deviceconsole to build" - run_cmd rm -rf build/deviceconsole - run_cmd mkdir -p build/deviceconsole - run_cmd cp -r submodules/deviceconsole/deviceconsole build/deviceconsole/deviceconsole -} - -get_apidemos() { - echo "* Cloning/updating Android test app: ApiDemos" - run_cmd git submodule update --init submodules/ApiDemos - run_cmd rm -rf sample-code/apps/ApiDemos - run_cmd ln -s "$appium_home"/submodules/ApiDemos "$appium_home"/sample-code/apps/ApiDemos -} - -uninstall_android_app() { - echo "* Attempting to uninstall android app $1" - if (which adb >/dev/null); then - if (adb devices | grep "device$" >/dev/null); then - if [[ ! -z $udid ]]; then - if (adb devices | grep "^$udid" >/dev/null); then - run_cmd adb -s $udid uninstall $1 - else - echo "* Device with serial $udid not found, skipping" - fi - elif [[ $(adb devices | grep "device$" | wc -l) -eq 1 ]]; then - run_cmd adb uninstall $1 - else - echo "* More than one device present, but no device serial provided, skipping (use --udid)" - fi - else - echo "* No devices found, skipping" - fi - else - echo "* ADB not found, skipping" - fi -} - -reset_apidemos() { - run_cmd get_apidemos - echo "* Configuring and cleaning/building Android test app: ApiDemos" - run_cmd "$grunt" configAndroidApp:ApiDemos - run_cmd "$grunt" buildAndroidApp:ApiDemos - uninstall_android_app io.appium.android.apis - apidemos_reset=true -} - -reset_toggle_test() { - echo "* Configuring and cleaning/building Android test app: ToggleTest" - run_cmd "$grunt" configAndroidApp:ToggleTest - run_cmd "$grunt" buildAndroidApp:ToggleTest - uninstall_android_app com.example.toggletest - toggletest_reset=true -} - -reset_unlock_apk() { - if ! $has_reset_unlock_apk; then - run_cmd rm -rf build/unlock_apk - run_cmd mkdir -p build/unlock_apk - echo "* Building Unlock.apk" - unlock_base="submodules/unlock_apk" - run_cmd git submodule update --init $unlock_base - run_cmd pushd $unlock_base - run_cmd ant clean && run_cmd ant debug - run_cmd popd - run_cmd cp $unlock_base/bin/unlock_apk-debug.apk build/unlock_apk - has_reset_unlock_apk=true - fi -} - -reset_unicode_ime() { - if ! $has_reset_ime_apk; then - run_cmd rm -rf build/unicode_ime_apk - run_cmd mkdir -p build/unicode_ime_apk - echo "* Building UnicodeIME.apk" - ime_base="submodules/io.appium.android.ime" - run_cmd git submodule update --init $ime_base - run_cmd pushd $ime_base - run_cmd ant clean && run_cmd ant debug - run_cmd popd - run_cmd cp $ime_base/bin/UnicodeIME-debug.apk build/unicode_ime_apk - uninstall_android_app "io.appium.android.ime" - has_reset_ime_apk=true - fi -} - -reset_settings_apk() { - if ! $has_reset_settings_apk; then - run_cmd rm -rf build/settings_apk - run_cmd mkdir -p build/settings_apk - echo "* Building Settings.apk" - settings_base="submodules/io.appium.settings" - run_cmd git submodule update --init $settings_base - run_cmd pushd $settings_base - run_cmd ant clean && run_cmd ant debug - run_cmd popd - run_cmd cp $settings_base/bin/settings_apk-debug.apk build/settings_apk - uninstall_android_app "io.appium.settings" - has_reset_settings_apk=true - fi -} - -link_appium_adb() { - echo "* Cloning/npm linking appium-adb" - run_cmd ./bin/npmlink.sh -l appium-adb -} - -reset_android() { - echo "RESETTING ANDROID" - require_java - echo "* Configuring Android bootstrap" - run_cmd rm -rf build/android_bootstrap - run_cmd "$grunt" configAndroidBootstrap - echo "* Building Android bootstrap" - run_cmd "$grunt" buildAndroidBootstrap - reset_unlock_apk - reset_unicode_ime - reset_settings_apk - if $include_dev ; then - reset_apidemos - reset_toggle_test - if $npmlink ; then - link_appium_adb - fi - fi - echo "* Setting Android config to Appium's version" - run_cmd "$grunt" setConfigVer:android - reset_chromedriver -} - -require_java() { - [ '${JAVA_HOME:?"Warning: Make sure JAVA_HOME is set properly for Java builds."}' ] -} - -reset_selendroid_quick() { - echo "RESETTING SELENDROID (QUICK)" - run_cmd rm -rf "${appium_home}/build/selendroid" - run_cmd mkdir -p "${appium_home}/build/selendroid" - run_cmd rm -rf /tmp/appium/selendroid - run_cmd mkdir -p /tmp/appium/selendroid - run_cmd pushd /tmp/appium/selendroid - echo "* Downloading metatata" - run_cmd wget http://search.maven.org/remotecontent?filepath=io/selendroid/selendroid-standalone/maven-metadata.xml -O maven-metadata.xml - selendroid_version=$(grep latest maven-metadata.xml | sed 's/ *<\/*latest> *//g') - echo "* Selendroid version is ${selendroid_version}" - echo "* Downloading selendroid server" - run_cmd wget https://github.com/selendroid/selendroid/releases/download/${selendroid_version}/selendroid-standalone-${selendroid_version}-with-dependencies.jar - ANDROID_MANIFEST=$(jar tf selendroid-standalone-${selendroid_version}-with-dependencies.jar| grep AndroidManifest | grep -v class) - run_cmd jar xf selendroid-standalone-${selendroid_version}-with-dependencies.jar $ANDROID_MANIFEST prebuild/selendroid-server-${selendroid_version}.apk - mv $ANDROID_MANIFEST AndroidManifest.xml - run_cmd cp /tmp/appium/selendroid/prebuild/selendroid-server-${selendroid_version}.apk "${appium_home}/build/selendroid/selendroid.apk" - run_cmd cp /tmp/appium/selendroid/AndroidManifest.xml "${appium_home}/build/selendroid/AndroidManifest.xml" - run_cmd popd - run_cmd "$grunt" fixSelendroidAndroidManifest - if $include_dev ; then - if $npmlink ; then - link_appium_adb - fi - if ! $apidemos_reset; then - reset_apidemos - uninstall_android_app io.appium.android.apis.selendroid - fi - if ! $toggletest_reset; then - reset_toggle_test - uninstall_android_app io.appium.toggletest.selendroid - fi - run_cmd pushd /tmp/appium/selendroid - echo "* Downloading selendroid test app" - run_cmd wget http://search.maven.org/remotecontent?filepath=io/selendroid/selendroid-test-app/${selendroid_version}/selendroid-test-app-${selendroid_version}.apk -O selendroid-test-app-${selendroid_version}.apk - run_cmd popd - run_cmd rm -rf "${appium_home}/sample-code/apps/selendroid-test-app.apk" - cp /tmp/appium/selendroid/selendroid-test-app-${selendroid_version}.apk "${appium_home}/sample-code/apps/selendroid-test-app.apk" - echo "* Attempting to uninstall app" - # uninstalling app - uninstall_android_app io.selendroid.testapp.selendroid - uninstall_android_app io.selendroid.testapp - # keep older versions of package around to clean up - uninstall_android_app org.openqa.selendroid.testapp.selendroid - uninstall_android_app org.openqa.selendroid.testapp - fi - echo "* Setting Selendroid config to Appium's version" - run_cmd "$grunt" setConfigVer:selendroid -} - -reset_selendroid() { - echo "RESETTING SELENDROID" - require_java - echo "* Clearing out any old modified server apks" - run_cmd rm -rf /tmp/selendroid*.apk - echo "* Cloning/updating selendroid" - run_cmd rm -rf submodules/selendroid/selendroid-server/target - run_cmd git submodule update --init submodules/selendroid - run_cmd rm -rf selendroid - echo "* Building selendroid server and supporting libraries" - run_cmd "$grunt" buildSelendroidServer - run_cmd pushd submodules/selendroid - run_cmd git reset --hard - run_cmd popd - reset_unlock_apk - reset_unicode_ime - if $include_dev ; then - if $npmlink ; then - link_appium_adb - fi - if ! $apidemos_reset; then - reset_apidemos - uninstall_android_app io.appium.android.apis.selendroid - fi - if ! $toggletest_reset; then - reset_toggle_test - uninstall_android_app io.appium.toggletest.selendroid - fi - echo "* Linking selendroid test app" - run_cmd rm -rf "$appium_home"/sample-code/apps/selendroid-test-app.apk - test_apk=$(ls "$appium_home"/submodules/selendroid/selendroid-test-app/target/*.apk | head -1) - run_cmd ln -s "$test_apk" "$appium_home"/sample-code/apps/selendroid-test-app.apk - uninstall_android_app io.selendroid.testapp.selendroid - uninstall_android_app io.selendroid.testapp - # keep older versions of package around to clean up - uninstall_android_app org.openqa.selendroid.testapp.selendroid - uninstall_android_app org.openqa.selendroid.testapp - fi - echo "* Setting Selendroid config to Appium's version" - run_cmd "$grunt" setConfigVer:selendroid -} - -reset_gappium() { - if $include_dev ; then - echo "RESETTING GAPPIUM" - if $hardcore ; then - echo "* Clearing out Gappium submodule" - run_cmd rm -rf "$appium_home"/submodules/io.appium.gappium.sampleapp - fi - echo "* Clearing out old links" - run_cmd rm -rf "$appium_home"/sample-code/apps/io.appium.gappium.sampleapp - echo "* Cloning/updating Gappium" - run_cmd git submodule update --init submodules/io.appium.gappium.sampleapp - run_cmd pushd submodules/io.appium.gappium.sampleapp - echo "* Building Gappium test app" - run_cmd ./reset.sh -v --platform $platform - run_cmd popd - echo "* Linking Gappium test app" - run_cmd ln -s "$appium_home"/submodules/io.appium.gappium.sampleapp "$appium_home"/sample-code/apps/io.appium.gappium.sampleapp - fi -} - -reset_chromedriver() { - 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 -} - -reset_firefoxos() { - echo "RESETTING FIREFOXOS" - echo "* Setting Firefox OS config to Appium's version" - run_cmd "$grunt" setConfigVer:firefoxos -} - -cleanup() { - echo "CLEANING UP" - echo "* Cleaning any temp files" - run_cmd rm -rf /tmp/instruments_sock - run_cmd rm -rf *.trace -} - -main() { - echo "---- Resetting / Initializing Appium ----" - if $include_dev ; then - echo "* Dev mode is on, will download/build test apps" - fi - if $hardcore ; then - echo "* Hardcore mode is on, will do extra crazy stuff" - fi - if $prod_deps ; then - echo "* Prod mode is on, will only install prod deps" - fi - reset_npm - reset_general - if $include_dev ; then - reset_sample_code - fi - if $should_reset_ios ; then - reset_ios - fi - if $should_reset_android ; then - reset_android - fi - if $should_reset_selendroid ; then - reset_selendroid - fi - if $should_reset_selendroid_quick ; then - reset_selendroid_quick - fi - if $should_reset_firefoxos ; then - reset_firefoxos - fi - if $should_reset_gappium ; then - reset_gappium - fi - cleanup - echo "* Setting build time and SHA info" - run_cmd "$grunt" setBuildTime - reset_successful=true -} - -on_exit() { - if $reset_successful ; then - echo "---- reset.sh completed successfully ----" - else - echo "---- FAILURE: reset.sh exited with status $? ----" - if ! $verbose ; then - echo "---- Retry with --verbose to see errors ----" - fi - fi -} - -trap on_exit EXIT -main diff --git a/trigger.txt b/trigger.txt deleted file mode 100644 index 03ca53da..00000000 --- a/trigger.txt +++ /dev/null @@ -1,2 +0,0 @@ -This is used to test github webhooks. -0