diff --git a/README.md b/README.md index 184395ea..d5aaac8f 100644 --- a/README.md +++ b/README.md @@ -59,14 +59,6 @@ Just builds the project, without running the tests. cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --justbuild ``` -####--device (optional) - -Tests must be run on connected device instead of emulator. - -``` -cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --device -``` - ####--externalServerUrl (optional) Useful when testing on real device (`--device` parameter) so that tests results from device could be posted back to paramedic server. @@ -180,4 +172,3 @@ You can also use cordova-paramedic as a module directly : paramedic.run(config); ``` - diff --git a/lib/ParamedicAppUninstall.js b/lib/ParamedicAppUninstall.js new file mode 100644 index 00000000..94739629 --- /dev/null +++ b/lib/ParamedicAppUninstall.js @@ -0,0 +1,68 @@ +var shelljs = require('shelljs'); +var path = require('path'); +var fs = require("fs"); +var logger = require('./utils').logger; +var util = require('./utils').utilities; + +function ParamedicAppUninstall(appPath, platform) { + this.appPath = appPath; + this.platform = platform; +} + +ParamedicAppUninstall.prototype.uninstallApp = function(targetObj, app) { + if(!targetObj || !targetObj.target) + return; + + switch(this.platform) { + case util.ANDROID: + this.uninstallAppAndroid(targetObj, app); + break; + case util.IOS: + this.uninstallAppIOS(targetObj, app); + break; + case util.WINDOWS: + this.uninstallAppWindows(targetObj, app); + break; + default: + break; + } +} + +ParamedicAppUninstall.prototype.uninstallAppAndroid = function(targetObj, app) { + var uninstallCommand = "adb -s " + targetObj.target + " uninstall " + app; + this.executeUninstallCommand(uninstallCommand); +} + +ParamedicAppUninstall.prototype.uninstallAppWindows = function(targetObj, app) { + var platformPath = path.join(this.appPath, "platforms", "windows"); + var packageJSPath = path.join(platformPath, "cordova", "lib", "package.js"); + var programFilesPath = process.env["ProgramFiles(x86)"] || process.env["ProgramFiles"]; + var appDeployPath = path.join(programFilesPath, "Microsoft SDKs", + "Windows Phone", "v8.1", "Tools", "AppDeploy", "AppDeployCmd.exe"); + appDeployPath = '"' + appDeployPath + '"'; + + if (fs.existsSync(packageJSPath)) { + var packageJS = require(packageJSPath); + var appId = packageJS.getAppId(platformPath); + var uninstallCommand = appDeployPath + " /uninstall " + appId + " /targetdevice:" + targetObj.target; + this.executeUninstallCommand(uninstallCommand); + } + return; +} + +ParamedicAppUninstall.prototype.uninstallAppIOS = function(targetObj, app) { + var uninstallCommand = "xcrun simctl uninstall " + targetObj.simId + " uninstall " + app; + this.executeUninstallCommand(uninstallCommand); +} + +ParamedicAppUninstall.prototype.executeUninstallCommand = function(uninstallCommand) { + logger.info("cordova-paramedic: Running command: " + uninstallCommand); + var uninstallResult = shelljs.exec(uninstallCommand, {silent: false, async: false}); + if (uninstallResult.code > 0) { + logger.error("Failed to uninstall the app" ); + logger.error("Error code: " + uninstallResult.code); + } + return; +} + +module.exports = ParamedicAppUninstall; diff --git a/lib/ParamedicConfig.js b/lib/ParamedicConfig.js index 52ab9c29..70c5ea2d 100644 --- a/lib/ParamedicConfig.js +++ b/lib/ParamedicConfig.js @@ -29,7 +29,7 @@ ParamedicConfig.parseFromArguments = function (argv) { return new ParamedicConfig({ platform: argv.platform, action: !!argv.justbuild ? 'build' : 'run', - args: (!!argv.browserify ? '--browserify ' : '') + (!!argv.device ? '--device' : ''), + args: (!!argv.browserify ? '--browserify ' : ''), plugins: Array.isArray(argv.plugin) ? argv.plugin : [argv.plugin], useTunnel: !!argv.useTunnel, verbose: !!argv.verbose, diff --git a/lib/ParamedicKill.js b/lib/ParamedicKill.js index e81b8bd1..83a0600e 100644 --- a/lib/ParamedicKill.js +++ b/lib/ParamedicKill.js @@ -36,7 +36,8 @@ ParamedicKill.prototype.tasksOnPlatform = function (platformName) { var tasks = []; switch (platformName) { case util.WINDOWS: - tasks = ["WWAHost.exe", "Xde.exe"]; + // tasks = ["WWAHost.exe", "Xde.exe"]; + tasks = ["WWAHost.exe"]; break; case util.IOS: tasks = ["Simulator", "iOS Simulator"]; diff --git a/lib/ParamedicLog.js b/lib/ParamedicLog.js index ed47b7d2..81e0bb3e 100644 --- a/lib/ParamedicLog.js +++ b/lib/ParamedicLog.js @@ -11,14 +11,15 @@ var util = require('./utils').utilities; var logger = require('./utils').logger; -function ParamedicLog(platform, appPath, outputDir){ +function ParamedicLog(platform, appPath, outputDir, targetObj){ this.platform = platform; this.appPath = appPath; this.outputDir = outputDir; + this.targetObj = targetObj; } ParamedicLog.prototype.logIOS = function (appPath) { - var simId = util.getSimId(); + var simId = this.targetObj.simId; if (simId) { // Now we can print out the log file @@ -26,7 +27,7 @@ ParamedicLog.prototype.logIOS = function (appPath) { var logCommand = "cat " + logPath; this.generateLogs(logCommand); } else { - logger.error("Failed to find ID of mobilespec simulator"); + logger.error("Failed to find ID of simulator"); } } @@ -43,7 +44,7 @@ ParamedicLog.prototype.logWindows = function (appPath, logMins) { } ParamedicLog.prototype.logAndroid = function (){ - var logCommand = "adb logcat -d -v time"; + var logCommand = "adb -s " + this.targetObj.target + " logcat -d -v time"; var numDevices = util.countAndroidDevices(); if (numDevices != 1) { diff --git a/lib/ParamedicTargetChooser.js b/lib/ParamedicTargetChooser.js new file mode 100644 index 00000000..bb4c6f3c --- /dev/null +++ b/lib/ParamedicTargetChooser.js @@ -0,0 +1,108 @@ +var Q = require('q'); +var shelljs = require('shelljs'); +var path = require("path-extra"); +var logger = require('./utils').logger; +var util = require('./utils').utilities; +var spawn = require('child_process').spawn; +var ParamedicKill = require('./ParamedicKill'); + +var ANDROID_RETRY_TIMES = 3; +var ANDROID_TIME_OUT = 300000; //5 Minutes + +function ParamedicTargetChooser(appPath, platform) { + this.appPath = appPath; + this.platform = platform; +} + +ParamedicTargetChooser.prototype.chooseTarget = function(emulator) { + var targetObj = ""; + switch(this.platform) { + case util.ANDROID: + targetObj = this.chooseTargetForAndroid(emulator); + break; + case util.IOS: + targetObj = this.chooseTargetForIOS(emulator); + break; + case util.WINDOWS: + targetObj = this.chooseTargetForWindows(emulator); + break; + default: + break; + } + return targetObj; +} + +ParamedicTargetChooser.prototype.chooseTargetForAndroid = function(emulator) { + logger.info("cordova-paramedic: Choosing Target for Android"); + var self = this; + return this.startAnAndroidEmulator().then(function(emulatorId){ + var obj = {}; + obj.target = emulatorId; + return obj; + }); +} + +ParamedicTargetChooser.prototype.startAnAndroidEmulator = function() { + logger.info("cordova-paramedic: Starting an Android emulator"); + + var emuPath = path.join(this.appPath, "platforms", "android", "cordova", "lib", "emulator"); + var emulator = require(emuPath); + + var tryStart = function(numberTriesRemaining) { + return emulator.start(null, ANDROID_TIME_OUT) + .then(function(emulatorId) { + if (emulatorId) { + return emulatorId; + } else if (numberTriesRemaining > 0) { + var paramedicKill = new ParamedicKill(util.ANDROID); + paramedicKill.kill(); + return tryStart(numberTriesRemaining - 1); + } else { + logger.error("cordova-paramedic: Could not start an android emulator"); + return null; + } + }); + }; + + // Check if the emulator has already been started + return emulator.list_started() + .then(function(started) { + if (started && started.length > 0) { + return started[0]; + } else { + return tryStart(ANDROID_RETRY_TIMES); + } + }); +} + +ParamedicTargetChooser.prototype.chooseTargetForWindows = function(emulator) { + logger.info("cordova-paramedic: Choosing Target for Windows"); + var windowsCommand = "cordova run --list --emulator"; + + logger.info("cordova-paramedic: Running command: " + windowsCommand); + var devicesResult = shelljs.exec(windowsCommand, {silent: true, async: false}); + if (devicesResult.code > 0) { + logger.error("Failed to get the list of devices for windows"); + return Q({target: undefined}); + } + + var lines = devicesResult.output.split(/\n/); + if(lines.length <= 1) { + logger.error("No devices/emulators available for windows"); + return Q({target: undefined}); + } + + return Q({target: lines[1].split('. ')[0].trim()}); +} + +ParamedicTargetChooser.prototype.chooseTargetForIOS = function(emulator) { + logger.info("cordova-paramedic: Choosing Target for iOS"); + var simulatorModelId = util.getSimulatorModelId(); + var split = simulatorModelId.split(", "); + var device = split[0].trim(); + + var simId = util.getSimulatorId(simulatorModelId); + return Q({target: device, simId: simId}); +} + +module.exports = ParamedicTargetChooser; diff --git a/lib/ParamediciOSPermissions.js b/lib/ParamediciOSPermissions.js index c7e03174..1d19d1c1 100644 --- a/lib/ParamediciOSPermissions.js +++ b/lib/ParamediciOSPermissions.js @@ -9,14 +9,15 @@ var util = require('./utils').utilities; var TCC_FOLDER_PERMISSION = 0755; -function ParamediciOSPermissions(appName, tccDb) { +function ParamediciOSPermissions(appName, tccDb, targetObj) { this.appName = appName; this.tccDb = tccDb; + this.targetObj = targetObj; } ParamediciOSPermissions.prototype.updatePermissions = function(serviceList){ var simulatorsFolder = util.getSimulatorsFolder(); - var simId = util.getSimId(); + var simId = this.targetObj.simId; logger.info('Sim Id is: ' + simId); var destinationTCCFile = path.join(simulatorsFolder, simId, '/data/Library/TCC/TCC.db'); diff --git a/lib/paramedic.js b/lib/paramedic.js index 1f68e6a0..65ba5ed1 100644 --- a/lib/paramedic.js +++ b/lib/paramedic.js @@ -31,6 +31,8 @@ var getReporters = require('./Reporters'); var ParamedicKill = require('./ParamedicKill'); var ParamedicLog = require('./ParamedicLog'); var ParamediciOSPermissions = require('./ParamediciOSPermissions'); +var ParamedicTargetChooser = require('./ParamedicTargetChooser'); +var ParamedicAppUninstall = require('./ParamedicAppUninstall'); // Time to wait for initial device connection. // If device has not connected within this interval the tests are stopped. @@ -45,6 +47,7 @@ function ParamedicRunner(config, _callback) { this.pluginsManager = null; this.config = config; + this.targetObj = undefined; exec.setVerboseLevel(config.isVerbose()); } @@ -71,6 +74,7 @@ ParamedicRunner.prototype.run = function() { }) .fin(function() { self.collectDeviceLogs(); + self.uninstallApp(); self.killEmulatorProcess(); self.cleanUpProject(); }); @@ -88,7 +92,6 @@ ParamedicRunner.prototype.prepareProjectToRunTests = function() { this.setUpStartPage(); this.installPlatform(); this.checkPlatformRequirements(); - this.setPermissions(); }; ParamedicRunner.prototype.installPlugins = function() { @@ -125,7 +128,7 @@ ParamedicRunner.prototype.setPermissions = function() { var tccDb = this.config.getTccDb(); if(tccDb) { var appName = util.PARAMEDIC_DEFAULT_APP_NAME; - var paramediciOSPermissions = new ParamediciOSPermissions(appName, tccDb); + var paramediciOSPermissions = new ParamediciOSPermissions(appName, tccDb, this.targetObj); paramediciOSPermissions.updatePermissions(applicationsToGrantPermission); } } @@ -175,39 +178,58 @@ ParamedicRunner.prototype.runTests = function() { reject(new Error('device is disconnected before passing the tests')); }); - var command = self.getCommandForStartingTests(); - logger.normal('cordova-paramedic: running command ' + command); - - exec(command, function(code, output) { - if(code) { - // 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); + return self.getCommandForStartingTests() + .then(function(command){ + self.setPermissions(); + logger.normal('cordova-paramedic: running command ' + command); + exec(command, function(code, output) { + if(code) { + // 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'); + return reject(new Error(command + " returned error code " + code)); } - logger.normal('cordova-paramedic: unable to run tests; command log is available above'); - return reject(new Error(command + " returned error code " + code)); - } - // skip tests if it was just build - if (!self.shouldWaitForTestResult()) { - return resolve(true); - } + // skip tests if it was just build + if (!self.shouldWaitForTestResult()) { + return resolve(true); + } - // reject if device not connected in pending time - self.waitForConnection().catch(reject); + // reject if device not connected in pending time + self.waitForConnection().catch(reject); + }); }); }); }; ParamedicRunner.prototype.getCommandForStartingTests = function() { - var cmd = "cordova " + this.config.getAction() + " " + this.config.getPlatformId(); + var self = this; + var cmd = "cordova " + this.config.getAction() + " " + this.config.getPlatformId(); + var paramedicTargetChooser = new ParamedicTargetChooser(this.tempFolder.name, this.config.getPlatformId()); + + if(self.config.getPlatformId() === "windows" && self.config.getArgs().indexOf('appx=8.1-phone') < 0) { + //The app is to be run as a store app. So no need to choose a target. + if (self.config.getArgs()) { + cmd += " " + self.config.getArgs(); + } - if (this.config.getArgs()) { - cmd += " " + this.config.getArgs(); + return Q(cmd); } - return cmd; + return paramedicTargetChooser.chooseTarget(true) + .then(function(targetObj){ + self.targetObj = targetObj; + cmd += " --target " + self.targetObj.target; + + if (self.config.getArgs()) { + cmd += " " + self.config.getArgs(); + } + + return cmd; + }); }; ParamedicRunner.prototype.shouldWaitForTestResult = function() { @@ -251,10 +273,16 @@ ParamedicRunner.prototype.collectDeviceLogs = function() { logger.info("Collecting logs for the devices."); var outputDir = this.config.getOutputDir()? this.config.getOutputDir(): this.tempFolder.name; var logMins = this.config.getLogMins()? this.config.getLogMins(): util.DEFAULT_LOG_TIME; - var paramedicLog = new ParamedicLog(this.config.getPlatformId(), this.tempFolder.name, outputDir); + var paramedicLog = new ParamedicLog(this.config.getPlatformId(), this.tempFolder.name, outputDir, this.targetObj); paramedicLog.collectLogs(logMins); }; +ParamedicRunner.prototype.uninstallApp = function() { + logger.info("Uninstalling the app."); + var paramedicAppUninstall = new ParamedicAppUninstall(this.tempFolder.name, this.config.getPlatformId()); + paramedicAppUninstall.uninstallApp(this.targetObj,util.PARAMEDIC_DEFAULT_APP_NAME); +} + var storedCWD = null; exports.run = function(paramedicConfig) { diff --git a/lib/utils/utilities.js b/lib/utils/utilities.js index 123a3b21..5a5d83ab 100644 --- a/lib/utils/utilities.js +++ b/lib/utils/utilities.js @@ -40,7 +40,7 @@ function getSimulatorsFolder() { return simulatorsFolderPath; } -function getSimId() { +function getSimulatorModelId() { var findSimCommand = "cordova run --list --emulator | grep ^iPhone | tail -n1"; logger.info("running:"); @@ -53,7 +53,11 @@ function getSimId() { return; } - var split = findSimResult.output.split(", "); + return findSimResult.output; +} + +function getSimulatorId(findSimResult) { + var split = findSimResult.split(", "); // Format of the output is "iPhone-6s-Plus, 9.1" // Extract the device name and the version number @@ -117,7 +121,8 @@ module.exports = { isWindows: isWindows, countAndroidDevices: countAndroidDevices, getSimulatorsFolder: getSimulatorsFolder, - getSimId: getSimId, doesFileExist: doesFileExist, - getSqlite3InsertionCommand: getSqlite3InsertionCommand + getSqlite3InsertionCommand: getSqlite3InsertionCommand, + getSimulatorModelId: getSimulatorModelId, + getSimulatorId: getSimulatorId };