This commit is contained in:
Jonathan Lipps 2015-05-21 20:50:36 -07:00
Родитель fbbb1261cb
Коммит 3da51d05cf
165 изменённых файлов: 1 добавлений и 27039 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -29,3 +29,4 @@ build/
.idea
*DerivedData
__pycache__
old

Просмотреть файл

@ -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

Просмотреть файл

@ -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")
}

Просмотреть файл

@ -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 "$*"

Просмотреть файл

@ -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

Просмотреть файл

@ -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;
};

Просмотреть файл

@ -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"
```

Просмотреть файл

@ -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");
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше