зеркало из https://github.com/xamarin/appium.old.git
move all old code out of git
This commit is contained in:
Родитель
fbbb1261cb
Коммит
3da51d05cf
|
@ -29,3 +29,4 @@ build/
|
|||
.idea
|
||||
*DerivedData
|
||||
__pycache__
|
||||
old
|
||||
|
|
127
Gruntfile.js
127
Gruntfile.js
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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); */ });
|
||||
}
|
|
@ -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");
|
||||
});
|
||||
}
|
|
@ -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 <UDID>: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();
|
||||
|
|
@ -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] <pkg1> <pkg2>...'
|
||||
echo ' unlink specific packages: dev.sh --unlink <pkg1> <pkg2>...'
|
||||
echo 'Short verion:'
|
||||
echo ' link all: dev.sh -l [-m]'
|
||||
echo ' unlink all: dev.sh -u'
|
||||
echo ' link specific packages: dev.sh -l [-m] <pkg1> <pkg2>...'
|
||||
echo ' unlink specific packages: dev.sh -u <pkg1> <pkg2>...'
|
||||
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
|
|
@ -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
|
15
bin/test.ps1
15
bin/test.ps1
|
@ -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")
|
||||
}
|
174
bin/test.sh
174
bin/test.sh
|
@ -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
|
|
@ -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 "$*"
|
16
ci/setup
16
ci/setup
|
@ -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
|
||||
|
804
grunt-helpers.js
804
grunt-helpers.js
|
@ -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<expand_table>\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 = "<!-- THIS FILE IS AUTOMATICALLY GENERATED DO NOT EDIT -->\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 += '<li><a href="#' + link + '"><i class="icon-chevron-right"></i> ' +
|
||||
header + '</a></li>\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 += '</section>\n';
|
||||
}
|
||||
html += '<section id="' + headers[mdObj.text] + '">\n';
|
||||
html += '<h2>\n<a href="#' + headers[mdObj.text] + '" class="anchor" ' +
|
||||
'name="requirements">\n<span class="mini-icon mini-icon-link">' +
|
||||
'</span>\n</a>\n' + headerHtml + '\n</h2>\n\n';
|
||||
inSection = true;
|
||||
} else if (inSection) {
|
||||
inBetweens.push(mdObj);
|
||||
}
|
||||
if (i === readmeMd.length - 1) {
|
||||
addInBetweenHtml();
|
||||
}
|
||||
}
|
||||
html += '</section>';
|
||||
return html;
|
||||
};
|
310
gulpfile.js
310
gulpfile.js
|
@ -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');
|
||||
});
|
|
@ -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 <Enter when asked>
|
||||
npm install appium@beta
|
||||
```
|
||||
|
||||
Start with
|
||||
```
|
||||
"node_modules/.bin/appium"
|
||||
```
|
||||
|
|
@ -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 <Enter when asked>
|
||||
npm install appium
|
||||
```
|
||||
|
||||
Start with
|
||||
```
|
||||
"node_modules/.bin/appium"
|
||||
```
|
||||
|
410
lib/appium.js
410
lib/appium.js
|
@ -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);
|
||||
};
|
|
@ -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" }));
|
||||
};
|
||||
|
||||
};
|
|
@ -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;
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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;
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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_<index>' will be returned
|
||||
// where <index> 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;
|
|
@ -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;
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Appium</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
|
@ -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
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
|
@ -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`
|
|
@ -1,92 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="AppiumBootstrap" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contains the path to the SDK. It should *NOT* be checked into
|
||||
Version Control Systems. -->
|
||||
<property file="local.properties" />
|
||||
|
||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||
'android' tool to add properties to it.
|
||||
This is the place to change some Ant specific build properties.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
source.dir
|
||||
The name of the source directory. Default is 'src'.
|
||||
out.dir
|
||||
The name of the output directory. Default is 'bin'.
|
||||
|
||||
For other overridable properties, look at the beginning of the rules
|
||||
files in the SDK, at tools/ant/build.xml
|
||||
|
||||
Properties related to the SDK location or the project target should
|
||||
be updated using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="ant.properties" />
|
||||
|
||||
<!-- if sdk.dir was not set from one of the property file, then
|
||||
get it from the ANDROID_HOME env var.
|
||||
This must be done before we load project.properties since
|
||||
the proguard config can use sdk.dir -->
|
||||
<property environment="env" />
|
||||
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||
<isset property="env.ANDROID_HOME" />
|
||||
</condition>
|
||||
|
||||
<!-- The project.properties file is created and updated by the 'android'
|
||||
tool, as well as ADT.
|
||||
|
||||
This contains project specific properties such as project target, and library
|
||||
dependencies. Lower level build properties are stored in ant.properties
|
||||
(or in .classpath for Eclipse projects).
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems. -->
|
||||
<loadproperties srcFile="project.properties" />
|
||||
|
||||
<!-- quick check on sdk.dir -->
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
<import file="custom_rules.xml" optional="true" />
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
To customize existing targets, there are two options:
|
||||
- Customize only one target:
|
||||
- copy/paste the target into this file, *before* the
|
||||
<import> task.
|
||||
- customize it to your needs.
|
||||
- Customize the whole content of build.xml
|
||||
- copy/paste the content of the rules files (minus the top node)
|
||||
into this file, replacing the <import> task.
|
||||
- customize to your needs.
|
||||
|
||||
***********************
|
||||
****** IMPORTANT ******
|
||||
***********************
|
||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||
in order to avoid having your file be overridden by tools such as "android update project"
|
||||
-->
|
||||
<!-- version-tag: VERSION_TAG -->
|
||||
<import file="${sdk.dir}/tools/ant/uibuild.xml" />
|
||||
|
||||
</project>
|
|
@ -1,56 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>io.appium.android</groupId>
|
||||
<artifactId>bootstrap</artifactId>
|
||||
<description>Maven project for the Appium Android Bootstrap</description>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>${basedir}/src</sourceDirectory>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20080701</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>android</groupId>
|
||||
<artifactId>android</artifactId>
|
||||
<version>4.4.2_r3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>android.test.uiautomator</groupId>
|
||||
<artifactId>uiautomator</artifactId>
|
||||
<version>4.4.2_r3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -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<String> mErrors = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object>
|
||||
* @throws JSONException
|
||||
*/
|
||||
public Hashtable<String, Object> params() throws JSONException {
|
||||
final JSONObject paramsObj = json.getJSONObject("params");
|
||||
final Hashtable<String, Object> newParams = new Hashtable<String, Object>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, CommandHandler> map = new HashMap<String, CommandHandler>();
|
||||
|
||||
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!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package io.appium.android.bootstrap;
|
||||
|
||||
/**
|
||||
* Enumeration for all the command types.
|
||||
*
|
||||
*/
|
||||
public enum AndroidCommandType {
|
||||
ACTION, SHUTDOWN
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, AndroidElement> elements;
|
||||
private Integer counter;
|
||||
|
||||
private static AndroidElementsHash instance;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public AndroidElementsHash() {
|
||||
counter = 0;
|
||||
elements = new Hashtable<String, AndroidElement>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<AndroidElement> 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<AndroidElement> elements = new ArrayList<AndroidElement>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<T> 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<String> finalize(
|
||||
final List<AndroidElement> elements, final int finalizer)
|
||||
throws Exception {
|
||||
final ArrayList<String> results = new ArrayList<String>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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); }
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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); }
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
* <p/>
|
||||
* 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<AndroidElement> 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<String, Object> 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<UiSelector> 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<AndroidElement> foundElements = new ArrayList<AndroidElement>();
|
||||
for (final UiSelector sel : selectors) {
|
||||
// With multiple selectors, we expect that some elements may not
|
||||
// exist.
|
||||
try {
|
||||
Logger.debug("Using: " + sel.toString());
|
||||
final List<AndroidElement> 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<AndroidElement>
|
||||
* @throws UiObjectNotFoundException
|
||||
*/
|
||||
private ArrayList<AndroidElement> 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<UiSelector> getSelectors(final Strategy strategy,
|
||||
final String text, final boolean many) throws InvalidStrategyException,
|
||||
ElementNotFoundException, UiSelectorSyntaxException,
|
||||
ParserConfigurationException, InvalidSelectorException {
|
||||
final List<UiSelector> selectors = new ArrayList<UiSelector>();
|
||||
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<UiSelector> 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<UiSelector> getXPathSelectors(final String expression,
|
||||
final boolean multiple) throws ElementNotFoundException,
|
||||
ParserConfigurationException, InvalidSelectorException {
|
||||
final List<UiSelector> selectors = new ArrayList<UiSelector>();
|
||||
|
||||
final ArrayList<ClassInstancePair> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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.");
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> params = command.params();
|
||||
boolean replace = Boolean.parseBoolean(params.get("replace").toString());
|
||||
String text = params.get("text").toString();
|
||||
boolean pressEnter = false;
|
||||
if (text.endsWith("\\n")) {
|
||||
pressEnter = true;
|
||||
text = text.replace("\\n", "");
|
||||
Logger.debug("Will press enter after setting text");
|
||||
}
|
||||
boolean unicodeKeyboard = false;
|
||||
if (params.get("unicodeKeyboard") != null) {
|
||||
unicodeKeyboard = Boolean.parseBoolean(params.get("unicodeKeyboard").toString());
|
||||
}
|
||||
String currText = el.getText();
|
||||
new Clear().execute(command);
|
||||
if (!el.getText().isEmpty()) {
|
||||
// clear could have failed, or we could have a hint in the field
|
||||
// we'll assume it is the latter
|
||||
Logger.debug("Text not cleared. Assuming remainder is hint text.");
|
||||
currText = "";
|
||||
}
|
||||
if (!replace) {
|
||||
text = currText + text;
|
||||
}
|
||||
final boolean result = el.setText(text, unicodeKeyboard);
|
||||
if (!result) {
|
||||
return getErrorResult("el.setText() failed!");
|
||||
}
|
||||
if (pressEnter) {
|
||||
final UiDevice d = UiDevice.getInstance();
|
||||
d.pressEnter();
|
||||
}
|
||||
return getSuccessResult(result);
|
||||
} catch (final UiObjectNotFoundException e) {
|
||||
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
|
||||
e.getMessage());
|
||||
} catch (final Exception e) { // handle NullPointerException
|
||||
return getErrorResult("Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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<AndroidElement> dedupe(List<AndroidElement> elements) {
|
||||
try {
|
||||
findAccessibilityNodeInfo = method(UiObject.class, "findAccessibilityNodeInfo", long.class);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
List<AndroidElement> result = new ArrayList<AndroidElement>();
|
||||
List<AccessibilityNodeInfo> nodes = new ArrayList<AccessibilityNodeInfo>();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<UiSelector> selectors;
|
||||
private UiScrollableParser scrollableParser = new UiScrollableParser();
|
||||
private UiSelectorParser selectorParser = new UiSelectorParser();
|
||||
|
||||
public List<UiSelector> 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<UiSelector>();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> 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<String> 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<String> 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<Object> convertedArgs = new ArrayList<Object>();
|
||||
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<T>")) {
|
||||
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<String> splitArgs(String argumentString) throws UiSelectorSyntaxException {
|
||||
ArrayList<String> args = new ArrayList<String>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<Method> overloadedMethods = getSelectorMethods(methodName);
|
||||
if (overloadedMethods.size() < 1) {
|
||||
throw new UiSelectorSyntaxException("UiSelector has no " + methodName + " method");
|
||||
}
|
||||
|
||||
selector = applyArgToMethods(overloadedMethods, argument.toString());
|
||||
}
|
||||
|
||||
private ArrayList<Method> getSelectorMethods(String methodName) {
|
||||
ArrayList<Method> ret = new ArrayList<Method>();
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals(methodName)) {
|
||||
ret.add(method);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private UiSelector applyArgToMethods(ArrayList<Method> 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<T>")) {
|
||||
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");
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче