You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by al...@apache.org on 2016/07/11 11:11:32 UTC
cordova-paramedic git commit: CB-11546 Appium tests support
Repository: cordova-paramedic
Updated Branches:
refs/heads/master 09d861aef -> afad9b3d0
CB-11546 Appium tests support
Project: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/commit/afad9b3d
Tree: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/tree/afad9b3d
Diff: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/diff/afad9b3d
Branch: refs/heads/master
Commit: afad9b3d06c77cdf94e49fe57bd8c38478a7a529
Parents: 09d861a
Author: Alexander Sorokin <al...@akvelon.com>
Authored: Thu Jul 7 19:31:39 2016 +0300
Committer: Alexander Sorokin <al...@akvelon.com>
Committed: Thu Jul 7 19:31:39 2016 +0300
----------------------------------------------------------------------
lib/ParamedicConfig.js | 87 ++++--
lib/ParamedicLog.js | 10 +-
lib/Reporters.js | 22 +-
lib/appium/.jshintrc | 11 +
lib/appium/AppiumRunner.js | 473 +++++++++++++++++++++++++++++++++
lib/appium/cordova_logo_thumb.jpg | Bin 0 -> 1932 bytes
lib/appium/helpers/wdHelper.js | 103 +++++--
lib/paramedic.js | 299 ++++++++++++++-------
lib/utils/utilities.js | 23 +-
main.js | 43 ++-
package.json | 8 +
11 files changed, 930 insertions(+), 149 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/ParamedicConfig.js
----------------------------------------------------------------------
diff --git a/lib/ParamedicConfig.js b/lib/ParamedicConfig.js
index 51a1114..b68345c 100644
--- a/lib/ParamedicConfig.js
+++ b/lib/ParamedicConfig.js
@@ -19,7 +19,13 @@
var DEFAULT_START_PORT = 8008;
var DEFAULT_END_PORT = 8018;
-var DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10 minutes in msec - this will become a param
+var DEFAULT_TIMEOUT = 60 * 60 * 1000; // 60 minutes in msec - this will become a param
+var DEFAULT_SAUCE_DEVICE_NAME_ANDROID = 'Android Emulator';
+var DEFAULT_SAUCE_PLATFORM_VERSION_ANDROID = '4.4';
+var DEFAULT_SAUCE_DEVICE_NAME_IOS = 'iPhone Simulator';
+var DEFAULT_SAUCE_PLATFORM_VERSION_IOS = '9.3';
+var DEFAULT_SAUCE_APPIUM_VERSION = '1.5.3';
+var DEFAULT_BUILD_NAME = 'Paramedic sauce test';
var util = require('./utils').utilities;
@@ -27,25 +33,36 @@ function ParamedicConfig(json) {
this._config = json;
}
+ParamedicConfig.prototype.getDefaultSauceDeviceName = function () {
+ return this._config.platform === 'android' ? DEFAULT_SAUCE_DEVICE_NAME_ANDROID : DEFAULT_SAUCE_DEVICE_NAME_IOS;
+};
+
+ParamedicConfig.prototype.getDefaultSaucePlatformVersion = function () {
+ return this._config.platform === 'android' ? DEFAULT_SAUCE_PLATFORM_VERSION_ANDROID : DEFAULT_SAUCE_PLATFORM_VERSION_IOS;
+};
+
ParamedicConfig.parseFromArguments = function (argv) {
return new ParamedicConfig({
- platform: argv.platform,
- action: !!argv.justbuild ? 'build' : 'run',
- args: (!!argv.browserify ? '--browserify ' : ''),
- plugins: Array.isArray(argv.plugin) ? argv.plugin : [argv.plugin],
- useTunnel: !!argv.useTunnel,
- verbose: !!argv.verbose,
- startPort: argv.startport || argv.port,
- endPort: argv.endport || argv.port,
- externalServerUrl: argv.externalServerUrl,
- outputDir: !!argv.outputDir? argv.outputDir: null,
- logMins: !!argv.logMins? argv.logMins: null,
- tccDb: !!argv.tccDbPath? argv.tccDb: null,
- cleanUpAfterRun: !!argv.cleanUpAfterRun? true: false,
- shouldUseSauce: !!argv.shouldUseSauce || false,
- buildName: argv.buildName || 'Paramedic sauce test',
- sauceUser: argv.sauceUser || process.env[util.SAUCE_USER_ENV_VAR],
- sauceKey: argv.sauceKey || process.env[util.SAUCE_KEY_ENV_VAR]
+ platform: argv.platform,
+ action: !!argv.justbuild ? 'build' : 'run',
+ args: (!!argv.browserify ? '--browserify ' : ''),
+ plugins: Array.isArray(argv.plugin) ? argv.plugin : [argv.plugin],
+ useTunnel: !!argv.useTunnel,
+ verbose: !!argv.verbose,
+ startPort: argv.startport || argv.port,
+ endPort: argv.endport || argv.port,
+ externalServerUrl: argv.externalServerUrl,
+ outputDir: !!argv.outputDir? argv.outputDir: null,
+ logMins: !!argv.logMins? argv.logMins: null,
+ tccDb: !!argv.tccDbPath? argv.tccDb: null,
+ cleanUpAfterRun: !!argv.cleanUpAfterRun? true: false,
+ shouldUseSauce: !!argv.shouldUseSauce || false,
+ buildName: argv.buildName,
+ sauceUser: argv.sauceUser,
+ sauceKey: argv.sauceKey,
+ sauceDeviceName: argv.sauceDeviceName && argv.sauceDeviceName.toString(),
+ saucePlatformVersion: argv.saucePlatformVersion && argv.saucePlatformVersion.toString(),
+ sauceAppiumVersion: argv.sauceAppiumVersion && argv.sauceAppiumVersion.toString()
});
};
@@ -126,15 +143,19 @@ ParamedicConfig.prototype.setShouldUseSauce = function (sus) {
};
ParamedicConfig.prototype.getBuildName = function () {
- return this._config.buildName;
+ return this._config.buildName || DEFAULT_BUILD_NAME;
};
ParamedicConfig.prototype.setBuildName = function (buildName) {
this._config.buildName = buildName;
};
+ParamedicConfig.prototype.getDefaultBuildName = function () {
+ return DEFAULT_BUILD_NAME;
+};
+
ParamedicConfig.prototype.getSauceUser = function () {
- return this._config.sauceUser;
+ return this._config.sauceUser || process.env[util.SAUCE_USER_ENV_VAR];
};
ParamedicConfig.prototype.setSauceUser = function (sauceUser) {
@@ -142,13 +163,37 @@ ParamedicConfig.prototype.setSauceUser = function (sauceUser) {
};
ParamedicConfig.prototype.getSauceKey = function () {
- return this._config.sauceKey;
+ return this._config.sauceKey || process.env[util.SAUCE_KEY_ENV_VAR];
};
ParamedicConfig.prototype.setSauceKey = function (sauceKey) {
this._config.sauceKey = sauceKey;
};
+ParamedicConfig.prototype.getSauceDeviceName = function () {
+ return this._config.sauceDeviceName || this.getDefaultSauceDeviceName();
+};
+
+ParamedicConfig.prototype.setSauceDeviceName = function (sauceDeviceName) {
+ this._config.sauceDeviceName = sauceDeviceName.toString();
+};
+
+ParamedicConfig.prototype.getSaucePlatformVersion = function () {
+ return this._config.saucePlatformVersion || this.getDefaultSaucePlatformVersion();
+};
+
+ParamedicConfig.prototype.setSaucePlatformVersion = function (saucePlatformVersion) {
+ this._config.saucePlatformVersion = saucePlatformVersion.toString();
+};
+
+ParamedicConfig.prototype.getSauceAppiumVersion = function () {
+ return this._config.sauceAppiumVersion || DEFAULT_SAUCE_APPIUM_VERSION;
+};
+
+ParamedicConfig.prototype.setSauceAppiumVersion = function (sauceAppiumVersion) {
+ this._config.sauceAppiumVersion = sauceAppiumVersion.toString();
+};
+
ParamedicConfig.prototype.isBrowserify = function () {
return this._config.browserify;
};
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/ParamedicLog.js
----------------------------------------------------------------------
diff --git a/lib/ParamedicLog.js b/lib/ParamedicLog.js
index 14dea4b..3180d09 100644
--- a/lib/ParamedicLog.js
+++ b/lib/ParamedicLog.js
@@ -37,6 +37,10 @@ function ParamedicLog(platform, appPath, outputDir, targetObj) {
}
ParamedicLog.prototype.logIOS = function (appPath) {
+ if (!this.targetObj) {
+ logger.warn('It looks like there is no target to get logs from.');
+ return;
+ }
var simId = this.targetObj.simId;
if (simId) {
@@ -62,8 +66,12 @@ ParamedicLog.prototype.logWindows = function (appPath, logMins) {
};
ParamedicLog.prototype.logAndroid = function () {
- var logCommand = 'adb -s ' + this.targetObj.target + ' logcat -d -v time';
+ if (!this.targetObj) {
+ logger.warn('It looks like there is no target to get logs from.');
+ return;
+ }
+ var logCommand = 'adb -s ' + this.targetObj.target + ' logcat -d -v time';
var numDevices = util.countAndroidDevices();
if (numDevices != 1) {
logger.error('there must be exactly one emulator/device attached');
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/Reporters.js
----------------------------------------------------------------------
diff --git a/lib/Reporters.js b/lib/Reporters.js
index e204c45..2191e26 100644
--- a/lib/Reporters.js
+++ b/lib/Reporters.js
@@ -20,7 +20,7 @@
var JasmineSpecReporter = require('jasmine-spec-reporter');
var jasmineReporters = require('jasmine-reporters');
-module.exports = function(outputDir) {
+module.exports.getReporters = function(outputDir) {
var reporters = [new JasmineSpecReporter({displayPendingSummary: false, displaySuiteNumber: true})];
if (outputDir) {
@@ -29,3 +29,23 @@ module.exports = function(outputDir) {
return reporters;
};
+
+module.exports.ParamedicReporter = ParamedicReporter;
+
+function ParamedicReporter(callback) {
+ this.allDoneCallback = callback;
+ this.failed = false;
+}
+
+ParamedicReporter.prototype = {
+ specDone: function (spec) {
+ if (spec.status === 'failed') {
+ this.failed = true;
+ }
+ },
+ jasmineDone: function () {
+ if (this.allDoneCallback instanceof Function) {
+ this.allDoneCallback(!this.failed);
+ }
+ }
+};
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/appium/.jshintrc
----------------------------------------------------------------------
diff --git a/lib/appium/.jshintrc b/lib/appium/.jshintrc
new file mode 100644
index 0000000..6997763
--- /dev/null
+++ b/lib/appium/.jshintrc
@@ -0,0 +1,11 @@
+{
+ "node": true,
+ "bitwise": true,
+ "undef": true,
+ "trailing": true,
+ "quotmark": true,
+ "indent": 4,
+ "unused": "vars",
+ "latedef": "nofunc",
+ "-W030": false
+}
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/appium/AppiumRunner.js
----------------------------------------------------------------------
diff --git a/lib/appium/AppiumRunner.js b/lib/appium/AppiumRunner.js
new file mode 100644
index 0000000..d126dd7
--- /dev/null
+++ b/lib/appium/AppiumRunner.js
@@ -0,0 +1,473 @@
+#!/usr/bin/env node
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/* jshint node: true */
+
+'use strict';
+
+var fs = require('fs');
+var path = require('path');
+var util = require('../utils').utilities;
+var logger = require('../utils').logger;
+var wd = require('wd');
+var wdHelper = require('./helpers/wdHelper');
+var screenshotHelper = require('./helpers/screenshotHelper');
+var kill = require('tree-kill');
+var child_process = require('child_process');
+var expectTelnet = require('expect-telnet');
+var shell = require('shelljs');
+var Jasmine = require('jasmine');
+var unorm = require('unorm');
+var Q = require('q');
+var ConfigParser = require('cordova-common').ConfigParser;
+var Reporters = require('../Reporters');
+var execPromise = require('../utils').execPromise;
+var Reporters = require('../Reporters');
+
+var KILL_SIGNAL = 'SIGINT';
+var SMALL_BUFFER_SIZE = 1024 * 1024;
+var BIG_BUFFER_SIZE = 50 * 1024 * 1024;
+var APPIUM_SERVER_PATH = getAppiumServerPath();
+
+function AppiumRunner(options) {
+ this.options = options;
+ this.prepareOptions();
+ this.createScreenshotDir();
+ this.findTests();
+ this.setGlobals();
+}
+
+function getAppiumServerPath() {
+ return path.resolve(process.cwd(), 'cordova-paramedic/node_modules/appium/build/lib/main.js');
+}
+
+function getFullAppPath(appPath) {
+ var fullPath = appPath;
+ if (!path.isAbsolute(appPath)) {
+ fullPath = path.join(__dirname, '../..', appPath);
+ }
+ return fullPath;
+}
+
+function getPackagePath(options) {
+ if (options.sauce) {
+ return options.sauceAppPath;
+ }
+
+ var fullAppPath = getFullAppPath(options.appPath);
+
+ switch (options.platform) {
+ case 'android':
+ var packagePath = path.join(fullAppPath, '/platforms/android/build/outputs/apk/android-debug.apk');
+ if (fs.existsSync(packagePath)) {
+ return packagePath;
+ }
+ throw new Error('Could not find apk');
+ case 'ios':
+ var searchDir = options.device ?
+ path.join(fullAppPath, '/platforms/ios/build/device/') :
+ path.join(fullAppPath, '/platforms/ios/build/emulator/');
+ var fileMask = options.device ? '*.ipa' : '*.app';
+ var files = shell.ls(searchDir + fileMask);
+ logger.normal('paramedic-appium: Looking for app package in ' + searchDir);
+ if (files && files.length > 0) {
+ logger.normal('paramedic-appium: Found app package: ' + files[0]);
+ return files[0];
+ }
+ throw new Error('Could not find the app package');
+ }
+}
+
+function getPluginDirs(appPath) {
+ return shell.ls(path.join(appPath, '/plugins/cordova-plugin-*'));
+}
+
+function getConfigPath(appPath) {
+ return path.join(appPath, 'config.xml');
+}
+
+function addCspSource(appPath, directive, source) {
+ var cspInclFile = path.join(appPath, 'www/csp-incl.js');
+ var indexFile = path.join(appPath, 'www/index.html');
+ var cspFile = fs.existsSync(cspInclFile) ? cspInclFile : indexFile;
+ var cspContent = fs.readFileSync(cspFile, util.DEFAULT_ENCODING);
+ var cspTagOpening = '<meta http-equiv="Content-Security-Policy" content=\'';
+ var cspRule = directive + ' ' + source;
+ var cspRuleReg = new RegExp(directive + '[^;"]+' + source.replace('*', '\\*'));
+
+ logger.normal('paramedic-appium: Adding CSP source "' + source + '" to directive "' + directive + '"');
+
+ if (cspContent.match(cspRuleReg)) {
+ logger.normal('paramedic-appium: It\'s already there.');
+ } else if (util.contains(cspContent, directive)) {
+ // if the directive is there, just add the source to it
+ cspContent = cspContent.replace(directive, cspRule);
+ fs.writeFileSync(cspFile, cspContent, util.DEFAULT_ENCODING);
+ } else if (cspContent.match(/content=".*?default-src.+?"/)) {
+ // needed directive is not there but there is default-src directive
+ // creating needed directive and copying default-src sources to it
+ var defaultSrcReg = /(content=".*?default-src)(.+?);/;
+ cspContent = cspContent.replace(defaultSrcReg, '$1$2; ' + cspRule + '$2;');
+ fs.writeFileSync(cspFile, cspContent, util.DEFAULT_ENCODING);
+ } else if (util.contains(cspContent, cspTagOpening)) {
+ // needed directive is not there and there is no default-src directive
+ // but the CSP tag is till present
+ // just adding needed directive to a start of CSP tag content
+ cspContent = cspContent.replace(cspTagOpening, cspTagOpening + directive + ' ' + source + '; ');
+ fs.writeFileSync(cspFile, cspContent, util.DEFAULT_ENCODING);
+ } else {
+ // no CSP tag, skipping
+ logger.normal('paramedic-appium: WARNING: No CSP tag found.');
+ }
+}
+
+function setPreference(appPath, preference, value) {
+ var configFile = getConfigPath(appPath);
+ var config = new ConfigParser(configFile);
+
+ logger.normal('paramedic-appium: Setting "' + preference + '" preference to "' + value + '"');
+ config.setGlobalPreference(preference, value);
+ config.write();
+}
+
+function permitAccess(appPath, origin) {
+ var configFile = getConfigPath(appPath);
+ var config = new ConfigParser(configFile);
+ var accesses = config.getAccesses();
+ var accessPresent = false;
+
+ logger.normal('paramedic-appium: Adding a whitelist "access" rule for origin: ' + origin);
+ accesses.forEach(function (access) {
+ if (access.origin == origin) {
+ accessPresent = true;
+ }
+ });
+
+ if (accessPresent) {
+ logger.normal('paramedic-appium: It is already in place');
+ } else {
+ config.addElement('access', { origin: origin });
+ config.write();
+ }
+}
+
+function runCommand(command, appPath) {
+ if (appPath) {
+ shell.pushd(appPath);
+ }
+ shell.exec(command);
+ if (appPath) {
+ shell.popd();
+ }
+}
+
+function isFailFastError(error) {
+ if (error && error.message) {
+ return error.message.indexOf('Could not find a connected') > -1 ||
+ error.message.indexOf('Bad app') > -1;
+ }
+ return false;
+}
+
+function killProcess(procObj, killSignal, callback) {
+ if (procObj && procObj.alive) {
+ procObj.alive = false;
+ setTimeout(function () {
+ kill(procObj.process.pid, killSignal, callback);
+ }, 1000);
+ } else {
+ callback();
+ }
+}
+
+function installAppiumServer() {
+ logger.normal('paramedic-appium: Installing Appium server...');
+ shell.pushd(path.join(__dirname, '../..'));
+ return execPromise('npm install appium').then(function () {
+ shell.popd();
+ });
+}
+
+AppiumRunner.prototype.createScreenshotDir = function () {
+ util.mkdirSync(this.options.screenshotPath);
+};
+
+AppiumRunner.prototype.prepareOptions = function () {
+ if (!this.options.hasOwnProperty('device')) {
+ this.options.device = false;
+ }
+ if (this.options.platform === 'ios' && this.options.appiumDeviceName) {
+ this.options.appiumDeviceName = this.options.appiumDeviceName.replace('-', ' ');
+ }
+};
+
+AppiumRunner.prototype.cleanUp = function (callback) {
+ var self = this;
+
+ killProcess(self.appium, KILL_SIGNAL, function () {
+ killProcess(self.iosProxy, KILL_SIGNAL, function () {
+ callback();
+ });
+ });
+};
+
+AppiumRunner.prototype.startTests = function () {
+ var jasmine = new Jasmine();
+ var self = this;
+ var d = Q.defer();
+
+ function exitGracefully(e) {
+ if (self.exiting) {
+ return;
+ }
+ if (!!e) {
+ logger.normal('paramedic-appium: ' + e);
+ }
+ logger.normal('paramedic-appium: Uncaught exception! Killing server and exiting in 2 seconds...');
+ self.exiting = true;
+ self.cleanUp(function () {
+ setTimeout(function () {
+ d.reject(e.stack);
+ }, 2000);
+ });
+ }
+
+ process.on('uncaughtException', function(err) {
+ exitGracefully(err);
+ });
+
+ logger.normal('paramedic-appium: Running tests from:');
+ self.options.testPaths.forEach(function (testPath) {
+ logger.normal('paramedic-appium: ' + testPath);
+ });
+
+ jasmine.loadConfig({
+ spec_dir: '',
+ spec_files: self.options.testPaths
+ });
+
+ // don't use default reporter, it exits the process before
+ // we would get the chance to kill appium server
+ //jasmine.configureDefaultReporter({ showColors: false });
+
+ var outputDir = self.options.output || process.cwd();
+ var reporters = Reporters.getReporters(outputDir);
+ var paramedicReporter = new Reporters.ParamedicReporter(function (passed) {
+ self.passed = passed;
+ self.cleanUp(d.resolve);
+ });
+
+ reporters.forEach(function (reporter) {
+ jasmine.addReporter(reporter);
+ });
+ jasmine.addReporter(paramedicReporter);
+
+ try {
+ // Launch the tests!
+ jasmine.execute();
+ } catch (e) {
+ exitGracefully(e);
+ }
+
+ return d.promise;
+};
+
+AppiumRunner.prototype.startIosProxy = function () {
+ var self = this;
+ var iosProxyCommand;
+ self.iosProxy = {
+ alive: false,
+ process: null
+ };
+
+ if (this.options.platform === 'ios' && this.options.device && this.options.udid) {
+ iosProxyCommand = 'ios_webkit_debug_proxy -c ' + this.options.udid + ':27753';
+ logger.normal('paramedic-appium: Running:');
+ logger.normal('paramedic-appium: ' + iosProxyCommand);
+ self.iosProxy.alive = true;
+ self.iosProxy.process = child_process.exec(iosProxyCommand, { maxBuffer: BIG_BUFFER_SIZE }, function () {
+ self.iosProxy.alive = false;
+ logger.normal('paramedic-appium: iOS proxy process exited.');
+ });
+ }
+};
+
+AppiumRunner.prototype.startAppiumServer = function () {
+ var d = Q.defer();
+ var self = this;
+ var appiumServerCommand;
+ var additionalArgs = '';
+ self.appium = {
+ alive: false,
+ process: null
+ };
+
+ // compose a command to run the Appium server
+ switch (self.options.platform) {
+ case 'android':
+ break;
+ case 'ios':
+ if (self.options.udid) {
+ additionalArgs += ' --udid ' + self.options.udid;
+ }
+ break;
+ default:
+ throw new Error('Unsupported platform: ' + self.options.platform);
+ }
+ if (self.options.logFile) {
+ additionalArgs += ' --log ' + self.options.logFile;
+ }
+
+ appiumServerCommand = 'node ' + APPIUM_SERVER_PATH + additionalArgs;
+
+ // run the Appium server
+ logger.normal('paramedic-appium: Running:');
+ logger.normal('paramedic-appium: ' + appiumServerCommand);
+ self.appium.alive = true;
+ self.appium.process = child_process.exec(appiumServerCommand, { maxBuffer: BIG_BUFFER_SIZE }, function (error) {
+ logger.normal('paramedic-appium: Appium process exited.');
+ if (self.appium.alive && error) {
+ logger.normal('paramedic-appium: Error running appium server: ' + error);
+ if (isFailFastError(error)) {
+ self.cleanUp(d.reject);
+ } else {
+ logger.normal('paramedic-appium: Another instance already running? Will try to run tests on it.');
+ d.resolve();
+ }
+ }
+ self.appium.alive = false;
+ });
+
+ // Wait for the Appium server to start up
+ self.appium.process.stdout.on('data', function (data) {
+ if (data.indexOf('Appium REST http interface listener started') > -1) {
+ d.resolve();
+ }
+ });
+
+ return d.promise;
+};
+
+AppiumRunner.prototype.findTests = function () {
+ var self = this;
+
+ if (!self.options.pluginRepos) {
+ self.options.pluginRepos = getPluginDirs(self.options.appPath);
+ }
+
+ // looking for the tests
+ self.options.testPaths = [];
+ var searchPaths = [];
+ self.options.pluginRepos.forEach(function (pluginRepo) {
+ searchPaths.push(path.join(pluginRepo, 'appium-tests', self.options.platform));
+ searchPaths.push(path.join(pluginRepo, 'appium-tests', 'common'));
+ });
+ searchPaths.forEach(function (searchPath) {
+ if (fs.existsSync(searchPath)) {
+ logger.normal('paramedic-appium: Found tests in: ' + searchPath);
+ if (path.isAbsolute(searchPath)) {
+ searchPath = path.relative(process.cwd(), searchPath);
+ }
+ self.options.testPaths.push(path.join(searchPath, '*.spec.js'));
+ }
+ });
+};
+
+AppiumRunner.prototype.setGlobals = function () {
+ // setting up the global variables so the tests could use them
+ global.WD = wd;
+ global.WD_HELPER = wdHelper;
+ global.SCREENSHOT_HELPER = screenshotHelper;
+ global.ET = expectTelnet;
+ global.SHELL = shell;
+ global.DEVICE = this.options.device;
+ global.DEVICE_NAME = this.options.appiumDeviceName;
+ global.PLATFORM = this.options.platform;
+ global.PLATFORM_VERSION = this.options.appiumPlatformVersion;
+ global.SCREENSHOT_PATH = this.options.screenshotPath;
+ global.UNORM = unorm;
+ global.UDID = this.options.udid;
+ global.VERBOSE = this.options.verbose;
+ global.USE_SAUCE = this.options.sauce;
+ global.SAUCE_USER = this.options.sauceUser;
+ global.SAUCE_KEY = this.options.sauceKey;
+ global.SAUCE_CAPS = this.options.sauceCaps;
+ global.VERBOSE = this.options.verbose;
+ global.SAUCE_SERVER_HOST = util.SAUCE_HOST;
+ global.SAUCE_SERVER_PORT = util.SAUCE_PORT;
+};
+
+AppiumRunner.prototype.prepareApp = function () {
+ var self = this;
+ var d = Q.defer();
+ var fullAppPath = getFullAppPath(self.options.appPath);
+ var deviceString = self.options.device ? ' --device' : '';
+ var buildCommand = 'cordova build ' + self.options.platform + deviceString;
+
+ // remove medic.json and (re)build
+ shell.rm(path.join(fullAppPath, 'www', 'medic.json'));
+ fs.stat(fullAppPath, function (error, stats) {
+ // check if the app exists
+ if (error || !stats.isDirectory()) {
+ d.reject('The app directory doesn\'t exist: ' + fullAppPath);
+ }
+
+ // set properties/CSP rules
+ if (self.options.platform === 'ios') {
+ setPreference(fullAppPath, 'CameraUsesGeolocation', 'true');
+ } else if (self.options.platform === 'android') {
+ setPreference(fullAppPath, 'loadUrlTimeoutValue', 60000);
+ }
+ addCspSource(fullAppPath, 'connect-src', 'http://*');
+ permitAccess(fullAppPath, '*');
+ // add cordova-save-image-gallery plugin from npm to enable
+ // Appium tests for camera plugin to save test image to the gallery
+ runCommand('cordova plugin add cordova-save-image-gallery', fullAppPath);
+
+ // rebuild the app
+ logger.normal('paramedic-appium: Building the app...');
+ child_process.exec(buildCommand, { cwd: fullAppPath, maxBuffer: SMALL_BUFFER_SIZE }, function (error) {
+ if (error) {
+ d.reject('Couldn\'t build the app: ' + error);
+ } else {
+ global.PACKAGE_PATH = getPackagePath(self.options);
+ d.resolve();
+ }
+ });
+ });
+ return d.promise;
+};
+
+AppiumRunner.prototype.runTests = function (useSauce) {
+ var self = this;
+
+ return Q().then(function () {
+ if (!useSauce) {
+ self.startIosProxy();
+ return installAppiumServer()
+ .then(self.startAppiumServer.bind(self));
+ }
+ })
+ .then(self.startTests.bind(self))
+ .then(function () { return self.passed; });
+};
+
+module.exports = AppiumRunner;
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/appium/cordova_logo_thumb.jpg
----------------------------------------------------------------------
diff --git a/lib/appium/cordova_logo_thumb.jpg b/lib/appium/cordova_logo_thumb.jpg
new file mode 100644
index 0000000..5bc04bf
Binary files /dev/null and b/lib/appium/cordova_logo_thumb.jpg differ
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/appium/helpers/wdHelper.js
----------------------------------------------------------------------
diff --git a/lib/appium/helpers/wdHelper.js b/lib/appium/helpers/wdHelper.js
index ce32daa..61505d5 100644
--- a/lib/appium/helpers/wdHelper.js
+++ b/lib/appium/helpers/wdHelper.js
@@ -35,6 +35,9 @@ var wd = global.WD || require('wd');
module.exports.getDriver = function (platform) {
var normalizedPlatform;
+ var driverConfig = {};
+ var serverConfig = {};
+ var driver;
switch (platform.toLowerCase()) {
case 'android':
normalizedPlatform = 'Android';
@@ -46,24 +49,37 @@ module.exports.getDriver = function (platform) {
throw 'Unknown platform: ' + platform;
}
- var serverConfig = {
- host: APPIUM_SERVER_HOST,
- port: APPIUM_SERVER_PORT
- };
-
- var driverConfig = {
- browserName: '',
- platformName: normalizedPlatform,
- platformVersion: global.PLATFORM_VERSION || '',
- deviceName: global.DEVICE_NAME || '',
- app: global.PACKAGE_PATH,
- autoAcceptAlerts: true,
- };
- if (global.UDID) {
- driverConfig.udid = global.UDID;
+ if (global.USE_SAUCE) {
+ serverConfig = {
+ host: global.SAUCE_SERVER_HOST,
+ port: global.SAUCE_SERVER_PORT
+ };
+
+ driverConfig = global.SAUCE_CAPS;
+
+ driver = global.WD.promiseChainRemote(serverConfig.host, serverConfig.port, global.SAUCE_USER, global.SAUCE_KEY);
+ } else {
+ serverConfig = {
+ host: APPIUM_SERVER_HOST,
+ port: APPIUM_SERVER_PORT
+ };
+
+ driverConfig = {
+ browserName: '',
+ platformName: normalizedPlatform,
+ platformVersion: global.PLATFORM_VERSION || '',
+ deviceName: global.DEVICE_NAME || '',
+ app: global.PACKAGE_PATH,
+ autoAcceptAlerts: true,
+ };
+
+ if (global.UDID) {
+ driverConfig.udid = global.UDID;
+ }
+
+ driver = global.WD.promiseChainRemote(serverConfig);
}
- var driver = global.WD.promiseChainRemote(serverConfig);
module.exports.configureLogging(driver);
return driver
@@ -122,6 +138,9 @@ module.exports.injectLibraries = function (driver) {
};
module.exports.configureLogging = function (driver) {
+ if (!global.VERBOSE) {
+ return;
+ }
driver.on('status', function (info) {
console.log(info);
});
@@ -200,20 +219,54 @@ module.exports.pollForEvents = function (driver, isAndroid, windowOffset) {
});
};
+module.exports.addFillerImage = function (driver) {
+ var bitmap = fs.readFileSync(path.join(__dirname, '../cordova_logo_thumb.jpg'));
+ var base64str = new Buffer(bitmap).toString('base64');
+
+ return driver.executeAsync(function (b64str, cb) {
+ if (window.imageSaver) {
+ window.imageSaver.saveBase64Image( {
+ data: b64str
+ }, function (fpath) {
+ cb(fpath);
+ }, function (err) {
+ cb('ERROR: ' + err);
+ });
+ } else {
+ cb();
+ }
+ }, [base64str]);
+};
+
+module.exports.deleteFillerImage = function (driver, testImagePath) {
+ if (!testImagePath) {
+ return driver;
+ }
+ return driver.executeAsync(function (testImagePath, cb) {
+ if (window.imageSaver) {
+ window.imageSaver.removeImage({
+ data: testImagePath
+ }, function () {
+ cb();
+ }, function (err) {
+ cb('ERROR: ' + err);
+ });
+ } else {
+ cb();
+ }
+ }, [testImagePath]);
+};
+
wd.addPromiseChainMethod('getWebviewContext', function (retries) {
return module.exports.getWebviewContext(this, retries);
});
-wd.addPromiseChainMethod('injectLibraries', function () {
- return module.exports.tapElementByXPath(this);
-});
-
wd.addPromiseChainMethod('waitForDeviceReady', function () {
return module.exports.waitForDeviceReady(this);
});
wd.addPromiseChainMethod('injectLibraries', function () {
- return module.exports.tapElementByXPath(this);
+ return module.exports.injectLibraries(this);
});
wd.addPromiseChainMethod('tapElementByXPath', function (xpath) {
@@ -223,3 +276,11 @@ wd.addPromiseChainMethod('tapElementByXPath', function (xpath) {
wd.addPromiseChainMethod('pollForEvents', function (isAndroid) {
return module.exports.pollForEvents(this, isAndroid);
});
+
+wd.addPromiseChainMethod('addFillerImage', function () {
+ return module.exports.addFillerImage(this);
+});
+
+wd.addPromiseChainMethod('deleteFillerImage', function (testImagePath) {
+ return module.exports.deleteFillerImage(this, testImagePath);
+});
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/paramedic.js
----------------------------------------------------------------------
diff --git a/lib/paramedic.js b/lib/paramedic.js
index b2c83a1..ee1a084 100644
--- a/lib/paramedic.js
+++ b/lib/paramedic.js
@@ -28,11 +28,13 @@ var fs = require('fs');
var logger = require('./utils').logger;
var util = require('./utils').utilities;
var PluginsManager = require('./PluginsManager');
-var getReporters = require('./Reporters');
+var Reporters = require('./Reporters');
var ParamedicKill = require('./ParamedicKill');
var ParamedicLog = require('./ParamedicLog');
var wd = require('wd');
var SauceLabs = require('saucelabs');
+var randomstring = require('randomstring');
+var AppiumRunner = require('./appium/AppiumRunner');
var ParamediciOSPermissions = require('./ParamediciOSPermissions');
var ParamedicTargetChooser = require('./ParamedicTargetChooser');
var ParamedicAppUninstall = require('./ParamedicAppUninstall');
@@ -44,9 +46,6 @@ require('./appium/helpers/wdHelper');
// If device has not connected within this interval the tests are stopped.
var INITIAL_CONNECTION_TIMEOUT = 300000; // 5mins
-var SAUCE_HOST = 'ondemand.saucelabs.com';
-var SAUCE_PORT = 80;
-
var applicationsToGrantPermission = [
'kTCCServiceAddressBook'
];
@@ -63,6 +62,7 @@ function ParamedicRunner(config, _callback) {
ParamedicRunner.prototype.run = function () {
var self = this;
+ var isTestPassed = false;
this.checkSauceRequirements();
@@ -84,7 +84,9 @@ ParamedicRunner.prototype.run = function () {
logger.normal('Start running tests at ' + (new Date()).toLocaleTimeString());
return self.runTests();
})
+ .timeout(self.config.getTimeout(), 'Timed out after waiting for ' + self.config.getTimeout() + ' ms.')
.fin(function (result) {
+ isTestPassed = result;
logger.normal('Completed tests at ' + (new Date()).toLocaleTimeString());
// if we do --justbuild or run on sauce,
// we should NOT do actions below
@@ -94,7 +96,7 @@ ParamedicRunner.prototype.run = function () {
self.killEmulatorProcess();
}
self.cleanUpProject();
- return result;
+ return self.displaySauceDetails();
});
};
@@ -164,7 +166,7 @@ ParamedicRunner.prototype.setPermissions = function () {
ParamedicRunner.prototype.injectReporters = function () {
var self = this;
- var reporters = getReporters(self.config.getOutputDir());
+ var reporters = Reporters.getReporters(self.config.getOutputDir());
['jasmineStarted', 'specStarted', 'specDone',
'suiteStarted', 'suiteDone', 'jasmineDone'].forEach(function(route) {
@@ -190,48 +192,124 @@ ParamedicRunner.prototype.writeMedicConnectionUrl = function(url) {
fs.writeFileSync(path.join('www','medic.json'), JSON.stringify({logurl:url}));
};
-ParamedicRunner.prototype.runTests = function () {
+ParamedicRunner.prototype.buildApp = function () {
var self = this;
- if (this.config.shouldUseSauce()) {
- var command = this.getCommandForBuilding();
+ var command = this.getCommandForBuilding();
+
+ logger.normal('cordova-paramedic: running command ' + command);
+
+ return execPromise(command)
+ .fail(function(output) {
+ // this trace is automatically available in verbose mode
+ // so we check for this flag to not trace twice
+ if (!self.config.verbose) {
+ logger.normal(output);
+ }
+ throw new Error('Unable to build project.');
+ });
+};
+
+ParamedicRunner.prototype.runLocalTests = function () {
+ var self = this;
+
+ return self.getCommandForStartingTests()
+ .then(function(command) {
+ self.setPermissions();
logger.normal('cordova-paramedic: running command ' + command);
- return execPromise(command)
- .then(self.runSauceTests.bind(self), function(output) {
- // this trace is automatically available in verbose mode
- // so we check for this flag to not trace twice
- if (!self.config.verbose) {
- logger.normal(output);
- }
- logger.normal('cordova-paramedic: unable to build project; command log is available above');
- throw new Error('Command "' + command + '" failed.');
+ return execPromise(command);
+ })
+ .then(function() {
+ // skip tests if it was just build
+ if (self.shouldWaitForTestResult()) {
+ return Q.promise(function(resolve, reject) {
+ // reject if timed out
+ self.waitForConnection().catch(reject);
+ // resolve if got results
+ self.waitForTests().then(resolve);
+ });
+ }
+ }, function(output) {
+ // this trace is automatically available in verbose mode
+ // so we check for this flag to not trace twice
+ if (!self.config.verbose) {
+ logger.normal(output);
+ }
+ throw new Error('Unable to run tests.');
+ });
+};
+
+ParamedicRunner.prototype.runAppiumTests = function (useSauce) {
+ var platform = this.config.getPlatformId();
+ var self = this;
+ if (platform !== 'android' && platform !== 'ios') {
+ logger.warn('Unsupported platform for Appium test run: ' + platform);
+ // just skip Appium tests
+ return Q(util.TEST_PASSED);
+ }
+ if (!useSauce && (!self.targetObj || !self.targetObj.target)) {
+ throw new Error('Cannot determine device name for Appium');
+ }
+
+ logger.normal('Running Appium tests ' + useSauce ? 'on Sauce Labs' : 'locally');
+
+ var options = {
+ platform: self.config.getPlatformId(),
+ appPath: self.tempFolder.name,
+ appiumDeviceName: self.targetObj && self.targetObj.target,
+ appiumPlatformVersion: null,
+ screenshotPath: path.join(process.cwd(), 'appium_screenshots'),
+ output: self.config.getOutputDir(),
+ verbose: self.config.isVerbose(),
+ sauce: useSauce
+ };
+ if (useSauce) {
+ options.sauceAppPath = 'sauce-storage:' + this.getAppName();
+ options.sauceUser = this.config.getSauceUser();
+ options.sauceKey = this.config.getSauceKey();
+ options.sauceCaps = this.getSauceCaps();
+ options.sauceCaps.name += '_Appium';
+ }
+
+ var appiumRunner = new AppiumRunner(options);
+ if (appiumRunner.options.testPaths && appiumRunner.options.testPaths.length === 0) {
+ logger.warn('Couldn\'t find Appium tests, skipping...');
+ return Q(util.TEST_PASSED);
+ }
+ return Q()
+ .then(function () {
+ return appiumRunner.prepareApp();
+ })
+ .then(function () {
+ if (useSauce) {
+ return self.uploadApp.bind(self);
+ }
+ })
+ .then(function () {
+ return appiumRunner.runTests(useSauce);
+ });
+};
+
+ParamedicRunner.prototype.runTests = function () {
+ var isTestPassed = false;
+ var self = this;
+ if (this.config.shouldUseSauce()) {
+ return this.runSauceTests()
+ .then(function (result) {
+ isTestPassed = result;
+ return self.runAppiumTests(true);
+ })
+ .then(function (isAppiumTestPassed) {
+ return isTestPassed == util.TEST_PASSED && isAppiumTestPassed == util.TEST_PASSED;
});
} else {
- return self.getCommandForStartingTests()
- .then(function(command) {
- self.setPermissions();
- logger.normal('cordova-paramedic: running command ' + command);
-
- return execPromise(command);
+ return this.runLocalTests()
+ .then(function (result) {
+ isTestPassed = result;
})
- .then(function() {
- // skip tests if it was just build
- if (self.shouldWaitForTestResult()) {
- return Q.promise(function(resolve, reject) {
- // reject if timed out
- self.waitForConnection().catch(reject);
- // resolve if got results
- self.waitForTests().then(resolve);
- });
- }
- }, function(output) {
- // this trace is automatically available in verbose mode
- // so we check for this flag to not trace twice
- if (!self.config.verbose) {
- logger.normal(output);
- }
- logger.normal('cordova-paramedic: unable to run tests; command log is available above');
- throw new Error('Command "' + command + '" failed.');
+ .then(self.runAppiumTests.bind(this))
+ .then(function (isAppiumTestPassed) {
+ return isTestPassed == util.TEST_PASSED && isAppiumTestPassed == util.TEST_PASSED;
});
}
};
@@ -418,7 +496,6 @@ ParamedicRunner.prototype.uninstallApp = function () {
paramedicAppUninstall.uninstallApp(this.targetObj,util.PARAMEDIC_DEFAULT_APP_NAME);
};
-
ParamedicRunner.prototype.getPackageFolder = function () {
var packageFolder;
switch (this.config.getPlatformId()) {
@@ -479,35 +556,49 @@ ParamedicRunner.prototype.getBinaryName = function () {
return binaryName;
};
+// Returns a name of the file at the SauceLabs storage
ParamedicRunner.prototype.getAppName = function () {
- var appName;
+ if (this.appName) {
+ return this.appName;
+ }
+ var appName = randomstring.generate();
switch (this.config.getPlatformId()) {
case 'android':
- appName = 'mobilespec.apk';
+ appName += '.apk';
break;
case 'ios':
- appName = 'HelloCordova.zip';
+ appName += '.zip';
break;
default:
throw new Error('Unsupported platform for sauce labs testing: ' + this.config.getPlatformId());
}
+ this.appName = appName;
return appName;
};
-ParamedicRunner.prototype.getSauceDetails = function () {
+ParamedicRunner.prototype.displaySauceDetails = function () {
+ if (!this.config.shouldUseSauce()) {
+ return Q();
+ }
+
var self = this;
var d = Q.defer();
- logger.normal('Getting saucelabs job details...\n');
+ logger.normal('Getting saucelabs jobs details...\n');
var sauce = new SauceLabs({
username: self.config.getSauceUser(),
password: self.config.getSauceKey()
});
+ if (self.config.getBuildName() === self.config.getDefaultBuildName()) {
+ logger.warn('Build name is not specified, showing all sauce jobs with default name...');
+ }
+
sauce.getJobs(function (err, jobs) {
+ var found = false;
for (var job in jobs) {
- if (jobs.hasOwnProperty(job) && jobs[job].name === self.config.getBuildName()) {
+ if (jobs.hasOwnProperty(job) && jobs[job].name && jobs[job].name.indexOf(self.config.getBuildName()) === 0) {
var jobUrl = 'https://saucelabs.com/beta/tests/' + jobs[job].id;
logger.normal('============================================================================================');
logger.normal('Job name: ' + jobs[job].name);
@@ -520,18 +611,66 @@ ParamedicRunner.prototype.getSauceDetails = function () {
}
logger.normal('============================================================================================');
logger.normal('');
- d.resolve();
- break;
+ found = true;
}
}
- if (d.promise.inspect().state !== 'fulfilled') {
+
+ if (!found) {
logger.warn('Can not find saucelabs job. Logs and video will be unavailable.');
- d.resolve();
}
+ d.resolve();
});
return d.promise;
};
+ParamedicRunner.prototype.getSauceCaps = function () {
+ var caps = {
+ name: this.config.getBuildName(),
+ browserName: '',
+ appiumVersion: this.config.getSauceAppiumVersion(),
+ deviceOrientation: 'portrait',
+ deviceType: 'phone',
+ idleTimeout: '100', // in seconds
+ app: 'sauce-storage:' + this.getAppName(),
+ deviceName: this.config.getSauceDeviceName(),
+ platformVersion: this.config.getSaucePlatformVersion(),
+ maxDuration: util.SAUCE_MAX_DURATION
+ };
+
+ switch(this.config.getPlatformId()) {
+ case 'android':
+ caps.platformName = 'Android';
+ caps.appPackage = 'io.cordova.hellocordova';
+ caps.appActivity = 'io.cordova.hellocordova.MainActivity';
+ break;
+ case 'ios':
+ caps.platformName = 'iOS';
+ caps.autoAcceptAlerts = true;
+ caps.waitForAppScript = 'true;';
+ break;
+ default:
+ throw new Error('Unsupported platform for sauce labs testing: ' + this.config.getPlatformId());
+ }
+ return caps;
+};
+
+ParamedicRunner.prototype.connectWebdriver = function () {
+ var user = this.config.getSauceUser();
+ var key = this.config.getSauceKey();
+ var caps = this.getSauceCaps();
+
+ logger.normal('cordova-paramedic: connecting webdriver');
+
+ wd.configureHttp({
+ timeout: 3 * 60 * 1000,
+ retryDelay: 15000,
+ retries: 5
+ });
+
+ var driver = wd.promiseChainRemote(util.SAUCE_HOST, util.SAUCE_PORT, user, key);
+ return driver.init(caps);
+};
+
ParamedicRunner.prototype.runSauceTests = function () {
logger.info('cordova-paramedic: running sauce tests');
var self = this;
@@ -539,52 +678,12 @@ ParamedicRunner.prototype.runSauceTests = function () {
var pollForResults;
var driver;
- return self.packageApp()
+ return this.buildApp()
+ .then(self.packageApp.bind(self))
.then(self.uploadApp.bind(self))
- .then(function() {
- logger.normal('cordova-paramedic: app uploaded; starting tests');
-
- var user = self.config.getSauceUser();
- var key = self.config.getSauceKey();
-
- var caps = {
- name: self.config.getBuildName(),
- browserName: '',
- appiumVersion: '1.5.2',
- deviceOrientation: 'portrait',
- deviceType: 'phone',
- idleTimeout: '100', // in seconds
- app: 'sauce-storage:' + self.getAppName()
- };
-
- switch(self.config.getPlatformId()) {
- case 'android':
- caps.deviceName = 'Android Emulator';
- caps.platformVersion = '4.4';
- caps.platformName = 'Android';
- caps.appPackage = 'io.cordova.hellocordova';
- caps.appActivity = 'io.cordova.hellocordova.MainActivity';
- break;
- case 'ios':
- caps.deviceName = 'iPhone Simulator';
- caps.platformVersion = '9.3';
- caps.platformName = 'iOS';
- caps.autoAcceptAlerts = true;
- break;
- default:
- throw new Error('Unsupported platform for sauce labs testing: ' + this.config.getPlatformId());
- }
-
- logger.normal('cordova-paramedic: connecting webdriver');
-
- wd.configureHttp({
- timeout: 3 * 60 * 1000,
- retryDelay: 15000,
- retries: 5
- });
-
- driver = wd.promiseChainRemote(SAUCE_HOST, SAUCE_PORT, user, key);
- return driver.init(caps);
+ .then(function () {
+ driver = self.connectWebdriver();
+ return driver;
})
.then(function () {
if (self.config.getUseTunnel()) {
@@ -628,7 +727,6 @@ ParamedicRunner.prototype.runSauceTests = function () {
}
return driver.quit();
})
- .fin(self.getSauceDetails.bind(self))
.then(function () {
return isTestPassed;
});
@@ -643,6 +741,5 @@ exports.run = function(paramedicConfig) {
var runner = new ParamedicRunner(paramedicConfig, null);
runner.storedCWD = storedCWD;
- return runner.run()
- .timeout(paramedicConfig.getTimeout(), 'This test seems to be blocked :: timeout exceeded. Exiting ...');
+ return runner.run();
};
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/lib/utils/utilities.js
----------------------------------------------------------------------
diff --git a/lib/utils/utilities.js b/lib/utils/utilities.js
index a2a8c82..87736ba 100644
--- a/lib/utils/utilities.js
+++ b/lib/utils/utilities.js
@@ -123,12 +123,24 @@ function doesFileExist(filePath) {
return fileExists;
}
+function mkdirSync(path) {
+ try {
+ fs.mkdirSync(path);
+ } catch(e) {
+ if ( e.code != 'EEXIST' ) throw e;
+ }
+}
+
function getSqlite3InsertionCommand(destinationTCCFile, service, appName) {
return util.format('sqlite3 %s "insert into access' +
'(service, client, client_type, allowed, prompt_count, csreq) values(\'%s\', \'%s\', ' +
'0,1,1,NULL)"', destinationTCCFile, service, appName);
}
+function contains(collection, item) {
+ return collection.indexOf(item) !== (-1);
+}
+
module.exports = {
ANDROID: 'android',
IOS: 'ios',
@@ -136,10 +148,17 @@ module.exports = {
PARAMEDIC_DEFAULT_APP_NAME: 'io.cordova.hellocordova',
SAUCE_USER_ENV_VAR: 'SAUCE_USER',
SAUCE_KEY_ENV_VAR: 'SAUCE_ACCESS_KEY',
+ SAUCE_HOST: 'ondemand.saucelabs.com',
+ SAUCE_PORT: 80,
+ SAUCE_MAX_DURATION: 5400, // in seconds
+ DEFAULT_ENCODING: 'utf-8',
DEFAULT_LOG_TIME: 15,
DEFAULT_LOG_TIME_ADDITIONAL: 2,
+ TEST_PASSED: 1,
+ TEST_FAILED: 0,
+
secToMin: secToMin,
isWindows: isWindows,
countAndroidDevices: countAndroidDevices,
@@ -147,5 +166,7 @@ module.exports = {
doesFileExist: doesFileExist,
getSqlite3InsertionCommand: getSqlite3InsertionCommand,
getSimulatorModelId: getSimulatorModelId,
- getSimulatorId: getSimulatorId
+ getSimulatorId: getSimulatorId,
+ contains: contains,
+ mkdirSync: mkdirSync
};
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/main.js
----------------------------------------------------------------------
diff --git a/main.js b/main.js
index 15b0b49..65c763d 100755
--- a/main.js
+++ b/main.js
@@ -1,5 +1,24 @@
#!/usr/bin/env node
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
var parseArgs = require('minimist');
var path = require('path');
var paramedic = require('./lib/paramedic');
@@ -27,11 +46,14 @@ var USAGE = "Error missing args. \n" +
"--outputDir: (optional) path to save Junit results file & Device logs\n" +
"--cleanUpAfterRun: (optional) cleans up the application after the run\n" +
"--logMins: (optional) Windows only - specifies number of minutes to get logs\n" +
- "--tccDb: (optional) iOS only - specifies the path for the TCC.db file to be copied." +
+ "--tccDb: (optional) iOS only - specifies the path for the TCC.db file to be copied.\n" +
"--shouldUseSauce: (optional) run tests on Saucelabs\n" +
"--buildName: (optional) Build name to show in Saucelabs dashboard\n" +
"--sauceUser: (optional) Saucelabs username\n" +
- "--sauceKey: (optional) Saucelabs access key";
+ "--sauceKey: (optional) Saucelabs access key\n" +
+ "--sauceDeviceName: (optional) Name of the SauceLabs emulator. For example, \"iPhone Simulator\"\n" +
+ "--saucePlatformVersion: (optional) Platform version of the SauceLabs emulator. For example, \"9.3\"" +
+ "--sauceAppiumVersion: (optional) Appium version to use when running on Saucelabs. For example, \"1.5.3\"";
var argv = parseArgs(process.argv.slice(2));
var pathToParamedicConfig = argv.config && path.resolve(argv.config);
@@ -86,6 +108,18 @@ if (pathToParamedicConfig || // --config
paramedicConfig.setSauceKey(argv.sauceKey);
}
+ if (argv.sauceDeviceName) {
+ paramedicConfig.setSauceDeviceName(argv.sauceDeviceName);
+ }
+
+ if (argv.saucePlatformVersion) {
+ paramedicConfig.setSaucePlatformVersion(argv.saucePlatformVersion);
+ }
+
+ if (argv.sauceAppiumVersion) {
+ paramedicConfig.setSauceAppiumVersion(argv.sauceAppiumVersion);
+ }
+
if (argv.useTunnel) {
if (argv.useTunnel === 'false') {
argv.useTunnel = false;
@@ -103,7 +137,10 @@ if (pathToParamedicConfig || // --config
process.exit(1);
})
.done(function(isTestPassed) {
- process.exit(isTestPassed ? 0 : 1);
+ var exitCode = isTestPassed ? 0 : 1;
+
+ console.log('Finished with exit code ' + exitCode);
+ process.exit(exitCode);
});
} else {
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/afad9b3d/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 41ce5b3..508e80d 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,9 @@
"bin": {
"cordova-paramedic": "./main.js"
},
+ "engines" : {
+ "node" : ">=0.11.2"
+ },
"repository": {
"type": "git",
"url": "git://github.com/apache/cordova-paramedic.git"
@@ -29,17 +32,22 @@
"author": "Jesse MacFadyen",
"dependencies": {
"cordova-common": "^1.1.0",
+ "expect-telnet": "^0.5.2",
+ "jasmine": "^2.4.1",
"jasmine-reporters": "^2.1.1",
"jasmine-spec-reporter": "^2.4.0",
"localtunnel": "~1.5.0",
"minimist": "~1.1.0",
"path-extra": "^3.0.0",
"q": "^1.4.1",
+ "randomstring": "^1.1.5",
"saucelabs": "^1.2.0",
"shelljs": "~0.3.0",
"socket.io": "^1.4.5",
"tcp-port-used": "^0.1.2",
"tmp": "0.0.25",
+ "tree-kill": "^1.1.0",
+ "unorm": "^1.4.1",
"wd": "^0.4.0"
},
"devDependencies": {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org