You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ja...@apache.org on 2018/12/03 17:22:07 UTC

[cordova-paramedic] branch janpio-split_saucelabs created (now 36dc190)

This is an automated email from the ASF dual-hosted git repository.

janpio pushed a change to branch janpio-split_saucelabs
in repository https://gitbox.apache.org/repos/asf/cordova-paramedic.git.


      at 36dc190  ParamedicSauceLabs: all Sauce Labs only methods extracted from paramedic.js

This branch includes the following new commits:

     new 36dc190  ParamedicSauceLabs: all Sauce Labs only methods extracted from paramedic.js

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[cordova-paramedic] 01/01: ParamedicSauceLabs: all Sauce Labs only methods extracted from paramedic.js

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

janpio pushed a commit to branch janpio-split_saucelabs
in repository https://gitbox.apache.org/repos/asf/cordova-paramedic.git

commit 36dc1900c10425ed7dd5dbd7d021ea223607a73a
Author: Jan Piotrowski <pi...@gmail.com>
AuthorDate: Mon Dec 3 18:21:53 2018 +0100

    ParamedicSauceLabs: all Sauce Labs only methods extracted from paramedic.js
---
 lib/{paramedic.js => ParamedicSauceLabs.js} | 519 ++--------------------------
 lib/paramedic.js                            | 501 +--------------------------
 2 files changed, 35 insertions(+), 985 deletions(-)

diff --git a/lib/paramedic.js b/lib/ParamedicSauceLabs.js
similarity index 51%
copy from lib/paramedic.js
copy to lib/ParamedicSauceLabs.js
index 9f01fcb..15f9a1d 100644
--- a/lib/paramedic.js
+++ b/lib/ParamedicSauceLabs.js
@@ -1,3 +1,5 @@
+#!/usr/bin/env node
+
 /**
     Licensed to the Apache Software Foundation (ASF) under one
     or more contributor license agreements.  See the NOTICE file
@@ -16,467 +18,26 @@
     specific language governing permissions and limitations
     under the License.
 */
-
-var cp              = require('child_process');
-var exec            = require('./utils').exec;
-var execPromise     = require('./utils').execPromise;
-var shell           = require('shelljs');
-var Server          = require('./LocalServer');
 var path            = require('path');
+var cp              = require('child_process');
 var Q               = require('q');
+var shell           = require('shelljs');
+var randomstring    = require('randomstring');
 var fs              = require('fs');
 var wd              = require('wd');
 var SauceLabs       = require('saucelabs');
-var randomstring    = require('randomstring');
 var sauceConnectLauncher    = require('sauce-connect-launcher');
 
+var exec            = require('./utils').exec;
+var execPromise     = require('./utils').execPromise;
 var logger          = require('./utils').logger;
 var util            = require('./utils').utilities;
-var Reporters       = require('./Reporters');
-var ParamedicKill   = require('./ParamedicKill');
-var AppiumRunner    = require('./appium/AppiumRunner');
 var appPatcher      = require('./appium/helpers/appPatcher');
-var ParamedicLogCollector   = require('./ParamedicLogCollector');
-var ParamediciOSPermissions = require('./ParamediciOSPermissions');
-var ParamedicTargetChooser  = require('./ParamedicTargetChooser');
-var ParamedicAppUninstall   = require('./ParamedicAppUninstall');
-var ParamedicApp   = require('./ParamedicApp');
-
-//this will add custom promise chain methods to the driver prototype
-require('./appium/helpers/wdHelper');
 
-// Time to wait for initial device connection.
-// If device has not connected within this interval the tests are stopped.
-var INITIAL_CONNECTION_TIMEOUT = 540000; // 9mins
-
-
-function ParamedicRunner(config, _callback) {
-    this.tempFolder = null;
-
-    this.config = config;
-    this.targetObj = undefined;
-
-    exec.setVerboseLevel(config.isVerbose());
+function ParamedicSauceLabs() {
 }
 
-ParamedicRunner.prototype.run = function () {
-    var self = this;
-    var isTestPassed = false;
-
-    self.checkConfig();
-
-    return Q().then(function () {
-        // create project and prepare (install plugins, setup test startpage, install platform, check platform requirements)
-        var paramedicApp = new ParamedicApp(self.config, self.storedCWD);
-        self.tempFolder = paramedicApp.createTempProject();
-        shell.pushd(self.tempFolder.name);
-        return paramedicApp.prepareProjectToRunTests();
-    })
-    .then(function () {
-        if (self.config.runMainTests()) {
-            // start server
-            var noListener = (self.config.getPlatformId() === util.BROWSER) && self.config.shouldUseSauce();
-            return Server.startServer(self.config.getPorts(), self.config.getExternalServerUrl(), self.config.getUseTunnel(), noListener);
-        }
-    })
-    .then(function (server) {
-        if (self.config.runMainTests()) {
-            // configure server usage
-            self.server = server;
-
-            self.injectReporters();
-            self.subcribeForEvents();
-
-            var logUrl = self.server.getConnectionUrl(self.config.getPlatformId());
-            self.writeMedicJson(logUrl);
-
-            logger.normal('Start running tests at ' + (new Date()).toLocaleTimeString());
-        }
-        // run tests
-        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
-        if (self.config.getAction() !== 'build' && !self.config.shouldUseSauce()) {
-            // collect logs and uninstall app
-            self.collectDeviceLogs();
-            return self.uninstallApp()
-                .fail(function () {
-                    // do not fail if uninstall fails
-                })
-                .fin(function () {
-                    self.killEmulatorProcess();
-                });
-        }
-        return self.displaySauceDetails(self.sauceBuildName);
-    })
-    .fin(function () {
-        self.cleanUpProject();
-    });
-};
-
-ParamedicRunner.prototype.checkConfig = function () {
-    this.checkSauceRequirements();
-    if (!this.config.runMainTests() && !this.config.runAppiumTests()) {
-        throw new Error('No tests to run: both --skipAppiumTests and --skipMainTests are used');
-    }
-    checkCli: {
-        if (this.config.getCli() !== 'cordova' && this.config.getCli() !== 'phonegap') {
-            if (path.isAbsolute(this.config.getCli())) {
-                break checkCli;
-            }
-            var cliAbsolutePath = path.resolve(this.config.getCli());
-            this.config.setCli(cliAbsolutePath);
-        }
-    }
-    logger.info('cordova-paramedic: Will use the following cli: ' + this.config.getCli());
-};
-
-ParamedicRunner.prototype.setPermissions = function () {
-    var applicationsToGrantPermission = [
-        'kTCCServiceAddressBook'
-    ];
-    if(this.config.getPlatformId() === util.IOS) {
-        logger.info('cordova-paramedic: Setting required permissions.');
-        var tccDb = this.config.getTccDb();
-        if(tccDb) {
-            var appName                 = util.PARAMEDIC_DEFAULT_APP_NAME;
-            var paramediciOSPermissions = new ParamediciOSPermissions(appName, tccDb, this.targetObj);
-            paramediciOSPermissions.updatePermissions(applicationsToGrantPermission);
-        }
-    }
-};
-
-ParamedicRunner.prototype.injectReporters = function () {
-    var self = this;
-    var reporters = Reporters.getReporters(self.config.getOutputDir());
-
-    ['jasmineStarted', 'specStarted', 'specDone',
-    'suiteStarted', 'suiteDone', 'jasmineDone'].forEach(function(route) {
-        reporters.forEach(function(reporter) {
-            if (reporter[route] instanceof Function)
-                self.server.on(route, reporter[route].bind(reporter));
-        });
-    });
-};
-
-ParamedicRunner.prototype.subcribeForEvents = function () {
-    this.server.on('deviceLog', function (data) {
-        logger.verbose('device|console.' + data.type + ': '  + data.msg[0]);
-    });
-
-    this.server.on('deviceInfo', function (data) {
-        logger.normal('cordova-paramedic: Device info: ' + JSON.stringify(data));
-    });
-};
-
-ParamedicRunner.prototype.writeMedicJson = function(logUrl) {
-    logger.normal('cordova-paramedic: writing medic log url to project ' + logUrl);
-
-    fs.writeFileSync(path.join('www','medic.json'), JSON.stringify({logurl:logUrl}));
-};
-
-ParamedicRunner.prototype.buildApp = function () {
-    var self = this;
-    var command = this.getCommandForBuilding();
-
-    logger.normal('cordova-paramedic: running command ' + command);
-
-    return execPromise(command)
-    .then(function(output) {
-        if (output.indexOf ('BUILD FAILED') >= 0) {
-            throw new Error('Unable to build the project.');
-        }
-    }, 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 the project.');
-    });
-};
-
-ParamedicRunner.prototype.maybeRunFileTransferServer = function () {
-    var self = this;
-    return Q().then(function () {
-        var plugins = self.config.getPlugins();
-        for (var i = 0; i < plugins.length; i++) {
-            if (plugins[i].indexOf('cordova-plugin-file-transfer') >= 0 && !self.config.getFileTransferServer() && !self.config.isCI()) {
-                return self.server.startFileTransferServer(self.tempFolder.name);
-            }
-        }
-    });
-};
-
-ParamedicRunner.prototype.runLocalTests = function () {
-    var self = this;
-    var runProcess = null;
-
-    // checking for Android platform here because in this case we still need to start an emulator
-    // will check again a bit lower
-    if (!self.config.runMainTests() && self.config.getPlatformId() !== util.ANDROID) {
-        logger.normal('Skipping main tests...');
-        return Q(util.TEST_PASSED);
-    }
-
-    logger.info('cordova-paramedic: running tests locally');
-
-    return Q().then(function () {
-        return self.maybeRunFileTransferServer();
-    })
-    .then(function () {
-        return self.getCommandForStartingTests();
-    })
-    .then(function(command) {
-        self.setPermissions();
-        logger.normal('cordova-paramedic: running command ' + command);
-
-        if (self.config.getPlatformId() !== util.BROWSER) {
-            return execPromise(command);
-        }
-        console.log('$ ' + command);
-        runProcess = cp.exec(command, function () {
-            // a precaution not to try to kill some other process
-            runProcess = null;
-        });
-    })
-    .then(function() {
-        // skipping here and not at the beginning because we need to
-        // start up the Android emulator for Appium tests to run on
-        if (!self.config.runMainTests()) {
-            logger.normal('Skipping main tests...');
-            return util.TEST_PASSED;
-        }
-
-        // 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);
-            });
-        }
-
-        return util.TEST_PASSED; // if we're not waiting for a test result, just report tests as passed
-    })
-    .fin(function (result) {
-        if (runProcess) {
-            return Q.Promise(function (resolve, reject) {
-                util.killProcess(runProcess.pid, function () {
-                    resolve(result);
-                });
-            });
-        }
-        return result;
-    });
-};
-
-ParamedicRunner.prototype.runAppiumTests = function (useSauce) {
-    var platform = this.config.getPlatformId();
-    var self = this;
-    logger.normal('Start running Appium tests...');
-
-    if (self.config.getAction() === 'build') {
-        logger.normal('Skipping Appium tests: action = build ...');
-        return Q(util.TEST_PASSED);
-    }
-    if (!self.config.runAppiumTests()) {
-        logger.normal('Skipping Appium tests: not configured to run ...');
-        return Q(util.TEST_PASSED);
-    }
-    if (platform !== util.ANDROID && platform !== util.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 local device name for Appium');
-    }
-
-    logger.normal('Running Appium tests ' + (useSauce ? 'on Sauce Labs' : 'locally'));
-
-    var options = {
-        platform: self.config.getPlatformId(),
-        appPath: self.tempFolder.name,
-        pluginRepos: self.config.getPlugins().map(function (plugin) {
-            return path.join(self.tempFolder.name, 'plugins', path.basename(plugin));
-        }),
-        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,
-        browserify: self.config.isBrowserify,
-        cli: self.config.getCli(),
-    };
-    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.packageApp()
-            .then(self.uploadApp.bind(self));
-        }
-    })
-    .then(function () {
-        return appiumRunner.runTests(useSauce);
-    });
-};
-
-ParamedicRunner.prototype.runTests = function () {
-    var isTestPassed = false;
-    var self = this;
-    // Sauce Labs
-    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;
-        });
-    // Not Sauce Labs
-    } else {
-        return this.runLocalTests()
-        .then(function (result) {
-            isTestPassed = result;
-        })
-        .then(self.runAppiumTests.bind(this))
-        .then(function (isAppiumTestPassed) {
-            return isTestPassed == util.TEST_PASSED && isAppiumTestPassed == util.TEST_PASSED;
-        });
-    }
-};
-
-ParamedicRunner.prototype.waitForTests = function () {
-    var self = this;
-    logger.info('cordova-paramedic: waiting for test results');
-    return Q.promise(function(resolve, reject) {
-
-        // time out if connection takes too long
-        var ERR_MSG = 'waitForTests: Seems like device not connected to local server in ' + INITIAL_CONNECTION_TIMEOUT / 1000 + ' secs';
-        setTimeout(function() {
-            if (!self.server.isDeviceConnected()) {
-                reject(new Error(ERR_MSG));
-            }
-        }, INITIAL_CONNECTION_TIMEOUT);
-
-        self.server.on('jasmineDone', function (data) {
-            logger.info('cordova-paramedic: tests have been completed');
-
-            var isTestPassed = (data.specResults.specFailed === 0);
-
-            resolve(isTestPassed);
-        });
-
-        self.server.on('disconnect', function () {
-            reject(new Error('device is disconnected before passing the tests'));
-        });
-    });
-};
-
-ParamedicRunner.prototype.getCommandForStartingTests = function () {
-    var self = this;
-    var cmd  = self.config.getCli() + ' ' + this.config.getAction() + ' ' + this.config.getPlatformId() + util.PARAMEDIC_COMMON_CLI_ARGS;
-
-    function addConfigArgs(cmd) {
-        if (self.config.getArgs()) {
-            cmd += ' ' + self.config.getArgs();
-        }
-        return cmd;
-    }
-
-    if (self.config.getPlatformId() === util.BROWSER) {
-        return addConfigArgs(cmd);
-    }
-
-    var paramedicTargetChooser = new ParamedicTargetChooser(this.tempFolder.name, this.config);
-
-    if (self.config.getAction() === 'build' || (self.config.getPlatformId() === util.WINDOWS && self.config.getArgs().indexOf('appx=8.1-phone') < 0)) {
-        //The app is to be run as a store app or just build. So no need to choose a target.
-        return Q(addConfigArgs(cmd));
-    }
-
-    // For now we always trying to run test app on emulator
-    return Q().then(function () {
-        var configTarget = self.config.getTarget();
-        return paramedicTargetChooser.chooseTarget(/*useEmulator=*/true, /*preferredTarget=*/configTarget);
-    })
-    .then(function(targetObj){
-        self.targetObj = targetObj;
-        cmd += ' --target ' + self.targetObj.target;
-
-        // CB-11472 In case of iOS provide additional '--emulator' flag, otherwise
-        // 'cordova run ios --target' would hang waiting for device with name
-        // as specified in 'target' in case if any device is physically connected
-        if (self.config.getPlatformId() === util.IOS) {
-            cmd += ' --emulator';
-        }
-
-        return addConfigArgs(cmd);
-    });
-};
-
-ParamedicRunner.prototype.getCommandForBuilding = function () {
-    var browserifyArg = this.config.isBrowserify() ? ' --browserify' : '';
-    var cmd = this.config.getCli() + ' build ' + this.config.getPlatformId() + browserifyArg + util.PARAMEDIC_COMMON_CLI_ARGS;
-
-    return cmd;
-};
-
-ParamedicRunner.prototype.shouldWaitForTestResult = function () {
-    var action = this.config.getAction();
-    return (action.indexOf('run') === 0) || (action.indexOf('emulate') === 0);
-};
-
-ParamedicRunner.prototype.waitForConnection = function () {
-    var self = this;
-
-    var ERR_MSG = 'waitForConnection: Seems like device not connected to local server in ' + INITIAL_CONNECTION_TIMEOUT / 1000 + ' secs';
-
-    return Q.promise(function(resolve, reject) {
-        setTimeout(function () {
-            if (!self.server.isDeviceConnected()) {
-                reject(new Error(ERR_MSG));
-            } else {
-                resolve();
-            }
-        }, INITIAL_CONNECTION_TIMEOUT);
-    });
-};
-
-ParamedicRunner.prototype.cleanUpProject = function () {
-    this.server && this.server.cleanUp();
-    if (this.config.shouldCleanUpAfterRun()) {
-        logger.info('cordova-paramedic: Deleting the application: ' + this.tempFolder.name);
-        shell.popd();
-        shell.rm('-rf', this.tempFolder.name);
-    }
-};
-
-ParamedicRunner.prototype.checkSauceRequirements = function () {
+ParamedicSauceLabs.prototype.checkSauceRequirements = function () {
     if (this.config.shouldUseSauce()) {
         var platformId = this.config.getPlatformId();
         if (platformId !== util.ANDROID && platformId !== util.IOS && platformId !== util.BROWSER) {
@@ -495,7 +56,7 @@ ParamedicRunner.prototype.checkSauceRequirements = function () {
     }
 };
 
-ParamedicRunner.prototype.packageApp = function () {
+ParamedicSauceLabs.prototype.packageApp = function () {
     var self = this;
     switch (this.config.getPlatformId()) {
         case util.IOS: {
@@ -524,7 +85,7 @@ ParamedicRunner.prototype.packageApp = function () {
     return Q.resolve();
 };
 
-ParamedicRunner.prototype.uploadApp = function () {
+ParamedicSauceLabs.prototype.uploadApp = function () {
     logger.normal('cordova-paramedic: uploading ' + this.getAppName() + ' to Sauce Storage');
 
     var sauceUser = this.config.getSauceUser();
@@ -540,33 +101,11 @@ ParamedicRunner.prototype.uploadApp = function () {
     return execPromise(uploadCommand);
 };
 
-ParamedicRunner.prototype.getPackagedPath = function () {
+ParamedicSauceLabs.prototype.getPackagedPath = function () {
     return path.join(this.getPackageFolder(), this.getPackageName());
 };
 
-ParamedicRunner.prototype.killEmulatorProcess = function () {
-    if(this.config.shouldCleanUpAfterRun()){
-        logger.info('cordova-paramedic: Killing the emulator process.');
-        var paramedicKill = new ParamedicKill(this.config.getPlatformId());
-        paramedicKill.kill();
-    }
-};
-
-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 paramedicLogCollector = new ParamedicLogCollector(this.config.getPlatformId(), this.tempFolder.name, outputDir, this.targetObj);
-    paramedicLogCollector.collectLogs(logMins);
-};
-
-ParamedicRunner.prototype.uninstallApp = function () {
-    logger.info('Uninstalling the app.');
-    var paramedicAppUninstall = new ParamedicAppUninstall(this.tempFolder.name, this.config.getPlatformId());
-    return paramedicAppUninstall.uninstallApp(this.targetObj,util.PARAMEDIC_DEFAULT_APP_NAME);
-};
-
-ParamedicRunner.prototype.getPackageFolder = function () {
+ParamedicSauceLabs.prototype.getPackageFolder = function () {
     var packageDirs = this.getPackageFolders();
     var foundDir = null;
     packageDirs.forEach (function (dir) {
@@ -581,7 +120,7 @@ ParamedicRunner.prototype.getPackageFolder = function () {
     throw new Error ('Couldn\'t locate a built app directory. Looked here: ' + packageDirs);
 };
 
-ParamedicRunner.prototype.getPackageFolders = function () {
+ParamedicSauceLabs.prototype.getPackageFolders = function () {
     var packageFolders;
     switch (this.config.getPlatformId()) {
         case util.ANDROID:
@@ -597,7 +136,7 @@ ParamedicRunner.prototype.getPackageFolders = function () {
     return packageFolders;
 };
 
-ParamedicRunner.prototype.getPackageName = function () {
+ParamedicSauceLabs.prototype.getPackageName = function () {
     var packageName;
     switch (this.config.getPlatformId()) {
         case util.IOS:
@@ -612,7 +151,7 @@ ParamedicRunner.prototype.getPackageName = function () {
     return packageName;
 };
 
-ParamedicRunner.prototype.getBinaryName = function () {
+ParamedicSauceLabs.prototype.getBinaryName = function () {
     var binaryName;
     switch (this.config.getPlatformId()) {
         case util.ANDROID:
@@ -642,7 +181,7 @@ ParamedicRunner.prototype.getBinaryName = function () {
 };
 
 // Returns a name of the file at the SauceLabs storage
-ParamedicRunner.prototype.getAppName = function () {
+ParamedicSauceLabs.prototype.getAppName = function () {
     if (this.appName) {
         return this.appName;
     }
@@ -661,7 +200,7 @@ ParamedicRunner.prototype.getAppName = function () {
     return appName;
 };
 
-ParamedicRunner.prototype.displaySauceDetails = function (buildName) {
+ParamedicSauceLabs.prototype.displaySauceDetails = function (buildName) {
     if (!this.config.shouldUseSauce()) {
         return Q();
     }
@@ -707,7 +246,7 @@ ParamedicRunner.prototype.displaySauceDetails = function (buildName) {
     return d.promise;
 };
 
-ParamedicRunner.prototype.getSauceCaps = function () {
+ParamedicSauceLabs.prototype.getSauceCaps = function () {
     this.sauceBuildName = this.sauceBuildName || this.config.getBuildName();
     var caps = {
         name: this.sauceBuildName,
@@ -758,7 +297,7 @@ ParamedicRunner.prototype.getSauceCaps = function () {
     return caps;
 };
 
-ParamedicRunner.prototype.connectWebdriver = function () {
+ParamedicSauceLabs.prototype.connectWebdriver = function () {
     var user = this.config.getSauceUser();
     var key = this.config.getSauceKey();
     var caps = this.getSauceCaps();
@@ -787,7 +326,7 @@ ParamedicRunner.prototype.connectWebdriver = function () {
         });
 };
 
-ParamedicRunner.prototype.connectSauceConnect = function () {
+ParamedicSauceLabs.prototype.connectSauceConnect = function () {
     var self = this;
     var isBrowser = self.config.getPlatformId() === util.BROWSER;
 
@@ -821,7 +360,7 @@ ParamedicRunner.prototype.connectSauceConnect = function () {
     });
 };
 
-ParamedicRunner.prototype.runSauceTests = function () {
+ParamedicSauceLabs.prototype.runSauceTests = function () {
     var self = this;
     var isTestPassed = false;
     var pollForResults;
@@ -979,16 +518,4 @@ ParamedicRunner.prototype.runSauceTests = function () {
     .then(function () {
         return isTestPassed;
     });
-};
-
-var storedCWD = null;
-
-exports.run = function(paramedicConfig) {
-
-    storedCWD = storedCWD || process.cwd();
-
-    var runner = new ParamedicRunner(paramedicConfig, null);
-    runner.storedCWD = storedCWD;
-
-    return runner.run();
-};
+};
\ No newline at end of file
diff --git a/lib/paramedic.js b/lib/paramedic.js
index 9f01fcb..d9f369b 100644
--- a/lib/paramedic.js
+++ b/lib/paramedic.js
@@ -25,17 +25,12 @@ var Server          = require('./LocalServer');
 var path            = require('path');
 var Q               = require('q');
 var fs              = require('fs');
-var wd              = require('wd');
-var SauceLabs       = require('saucelabs');
-var randomstring    = require('randomstring');
-var sauceConnectLauncher    = require('sauce-connect-launcher');
 
 var logger          = require('./utils').logger;
 var util            = require('./utils').utilities;
 var Reporters       = require('./Reporters');
 var ParamedicKill   = require('./ParamedicKill');
 var AppiumRunner    = require('./appium/AppiumRunner');
-var appPatcher      = require('./appium/helpers/appPatcher');
 var ParamedicLogCollector   = require('./ParamedicLogCollector');
 var ParamediciOSPermissions = require('./ParamediciOSPermissions');
 var ParamedicTargetChooser  = require('./ParamedicTargetChooser');
@@ -49,6 +44,7 @@ require('./appium/helpers/wdHelper');
 // If device has not connected within this interval the tests are stopped.
 var INITIAL_CONNECTION_TIMEOUT = 540000; // 9mins
 
+Q.longStackSupport = true;
 
 function ParamedicRunner(config, _callback) {
     this.tempFolder = null;
@@ -57,6 +53,8 @@ function ParamedicRunner(config, _callback) {
     this.targetObj = undefined;
 
     exec.setVerboseLevel(config.isVerbose());
+
+    this.paramedicSauceLabs = null;
 }
 
 ParamedicRunner.prototype.run = function () {
@@ -96,6 +94,11 @@ ParamedicRunner.prototype.run = function () {
         return self.runTests();
     })
     .timeout(self.config.getTimeout(), 'Timed out after waiting for ' + self.config.getTimeout() + ' ms.')
+    .catch(function (error) {
+        logger.error(error);
+        console.log(error.stack);
+        throw new Error(error);
+    })
     .fin(function (result) {
         isTestPassed = result;
         logger.normal('Completed tests at ' + (new Date()).toLocaleTimeString());
@@ -120,7 +123,10 @@ ParamedicRunner.prototype.run = function () {
 };
 
 ParamedicRunner.prototype.checkConfig = function () {
-    this.checkSauceRequirements();
+    if (this.config.shouldUseSauce()) {
+        this.paramedicSauceLabs = new ParamedicSauceLabs();
+        this.paramedicSauceLabs.checkSauceRequirements();
+    }
     if (!this.config.runMainTests() && !this.config.runAppiumTests()) {
         throw new Error('No tests to run: both --skipAppiumTests and --skipMainTests are used');
     }
@@ -476,74 +482,6 @@ ParamedicRunner.prototype.cleanUpProject = function () {
     }
 };
 
-ParamedicRunner.prototype.checkSauceRequirements = function () {
-    if (this.config.shouldUseSauce()) {
-        var platformId = this.config.getPlatformId();
-        if (platformId !== util.ANDROID && platformId !== util.IOS && platformId !== util.BROWSER) {
-            logger.warn('Saucelabs only supports Android and iOS (and browser), falling back to testing locally.');
-            this.config.setShouldUseSauce(false);
-        } else if (!this.config.getSauceKey()) {
-            throw new Error('Saucelabs key not set. Please set it via environmental variable ' +
-                util.SAUCE_KEY_ENV_VAR + ' or pass it with the --sauceKey parameter.');
-        } else if (!this.config.getSauceUser()) {
-            throw new Error('Saucelabs user not set. Please set it via environmental variable ' +
-                util.SAUCE_USER_ENV_VAR + ' or pass it with the --sauceUser parameter.');
-        } else if (!this.shouldWaitForTestResult()) {
-            // don't throw, just silently disable Sauce
-            this.config.setShouldUseSauce(false);
-        }
-    }
-};
-
-ParamedicRunner.prototype.packageApp = function () {
-    var self = this;
-    switch (this.config.getPlatformId()) {
-        case util.IOS: {
-            return Q.Promise(function (resolve, reject) {
-                var zipCommand = 'zip -r ' + self.getPackageName() + ' ' + self.getBinaryName();
-                shell.pushd(self.getPackageFolder());
-                shell.rm('-rf', self.getPackageName());
-                console.log('Running command: ' + zipCommand + ' in dir: ' + shell.pwd());
-                exec(zipCommand, { silent: !self.config.isVerbose() }, function (code, stdout, stderr) {
-                    shell.popd();
-                    if (code) {
-                        reject('zip command returned with error code ' + code);
-                    } else {
-                        resolve();
-                    }
-                });
-            });
-        }
-        case util.ANDROID:
-            break; // don't need to zip the app for Android
-        case util.BROWSER:
-            break; // don't need to bundle the app on Browser platform at all
-        default:
-            throw new Error('Don\'t know how to package the app for platform: ' + this.config.getPlatformId());
-    }
-    return Q.resolve();
-};
-
-ParamedicRunner.prototype.uploadApp = function () {
-    logger.normal('cordova-paramedic: uploading ' + this.getAppName() + ' to Sauce Storage');
-
-    var sauceUser = this.config.getSauceUser();
-    var key       = this.config.getSauceKey();
-
-    var uploadURI     = encodeURI('https://saucelabs.com/rest/v1/storage/' + sauceUser + '/' + this.getAppName() + '?overwrite=true');
-    var filePath      = this.getPackagedPath();
-    var uploadCommand =
-        'curl -u ' + sauceUser + ':' + key +
-        ' -X POST -H "Content-Type: application/octet-stream" ' +
-        uploadURI + ' --data-binary "@' + filePath + '"';
-
-    return execPromise(uploadCommand);
-};
-
-ParamedicRunner.prototype.getPackagedPath = function () {
-    return path.join(this.getPackageFolder(), this.getPackageName());
-};
-
 ParamedicRunner.prototype.killEmulatorProcess = function () {
     if(this.config.shouldCleanUpAfterRun()){
         logger.info('cordova-paramedic: Killing the emulator process.');
@@ -566,421 +504,6 @@ ParamedicRunner.prototype.uninstallApp = function () {
     return paramedicAppUninstall.uninstallApp(this.targetObj,util.PARAMEDIC_DEFAULT_APP_NAME);
 };
 
-ParamedicRunner.prototype.getPackageFolder = function () {
-    var packageDirs = this.getPackageFolders();
-    var foundDir = null;
-    packageDirs.forEach (function (dir) {
-        if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
-            foundDir = dir;
-            return;
-        }
-    });
-    if (foundDir != null) {
-        return foundDir;
-    }
-    throw new Error ('Couldn\'t locate a built app directory. Looked here: ' + packageDirs);
-};
-
-ParamedicRunner.prototype.getPackageFolders = function () {
-    var packageFolders;
-    switch (this.config.getPlatformId()) {
-        case util.ANDROID:
-            packageFolders =  [ path.join(this.tempFolder.name, 'platforms/android/app/build/outputs/apk/debug'),
-                                path.join(this.tempFolder.name, 'platforms/android/build/outputs/apk') ];
-            break;
-        case util.IOS:
-            packageFolders = [ path.join(this.tempFolder.name, 'platforms/ios/build/emulator') ];
-            break;
-        default:
-            throw new Error('Don\t know where the package foler is for platform: ' + this.config.getPlatformId());
-    }
-    return packageFolders;
-};
-
-ParamedicRunner.prototype.getPackageName = function () {
-    var packageName;
-    switch (this.config.getPlatformId()) {
-        case util.IOS:
-            packageName = 'HelloCordova.zip';
-            break;
-        case util.ANDROID:
-            packageName = this.getBinaryName();
-            break;
-        default:
-            throw new Error('Don\'t know what the package name is for platform: ' + this.config.getPlatformId());
-    }
-    return packageName;
-};
-
-ParamedicRunner.prototype.getBinaryName = function () {
-    var binaryName;
-    switch (this.config.getPlatformId()) {
-        case util.ANDROID:
-            shell.pushd(this.getPackageFolder());
-            var apks = shell.ls('*debug.apk');
-            if (apks.length > 0) {
-                binaryName = apks.reduce(function (previous, current) {
-                    // if there is any apk for x86, take it
-                    if (current.indexOf('x86') >= 0) {
-                        return current;
-                    }
-                    // if not, just take the first one
-                    return previous;
-                });
-            } else {
-                throw new Error('Couldn\'t locate built apk');
-            }
-            shell.popd();
-            break;
-        case util.IOS:
-            binaryName = 'HelloCordova.app';
-            break;
-        default:
-            throw new Error('Don\'t know the binary name for platform: ' + this.config.getPlatformId());
-    }
-    return binaryName;
-};
-
-// Returns a name of the file at the SauceLabs storage
-ParamedicRunner.prototype.getAppName = function () {
-    if (this.appName) {
-        return this.appName;
-    }
-    var appName = randomstring.generate();
-    switch (this.config.getPlatformId()) {
-        case util.ANDROID:
-            appName += '.apk';
-            break;
-        case util.IOS:
-            appName += '.zip';
-            break;
-        default:
-            throw new Error('Don\'t know the app name for platform: ' + this.config.getPlatformId());
-    }
-    this.appName = appName;
-    return appName;
-};
-
-ParamedicRunner.prototype.displaySauceDetails = function (buildName) {
-    if (!this.config.shouldUseSauce()) {
-        return Q();
-    }
-    if (!buildName) {
-        buildName = this.config.getBuildName();
-    }
-
-    var self = this;
-    var d = Q.defer();
-
-    logger.normal('Getting saucelabs jobs details...\n');
-
-    var sauce = new SauceLabs({
-        username: self.config.getSauceUser(),
-        password: self.config.getSauceKey()
-    });
-
-    sauce.getJobs(function (err, jobs) {
-        var found = false;
-        for (var job in jobs) {
-            if (jobs.hasOwnProperty(job) && jobs[job].name && jobs[job].name.indexOf(buildName) === 0) {
-                var jobUrl = 'https://saucelabs.com/beta/tests/' + jobs[job].id;
-                logger.normal('============================================================================================');
-                logger.normal('Job name: ' + jobs[job].name);
-                logger.normal('Job ID: ' + jobs[job].id);
-                logger.normal('Job URL: ' + jobUrl);
-                logger.normal('Video: ' + jobs[job].video_url);
-                logger.normal('Appium logs: ' + jobs[job].log_url);
-                if (self.config.getPlatformId() === util.ANDROID) {
-                    logger.normal('Logcat logs: ' + 'https://saucelabs.com/jobs/' + jobs[job].id + '/logcat.log');
-                }
-                logger.normal('============================================================================================');
-                logger.normal('');
-                found = true;
-            }
-        }
-
-        if (!found) {
-            logger.warn('Can not find saucelabs job. Logs and video will be unavailable.');
-        }
-        d.resolve();
-    });
-    return d.promise;
-};
-
-ParamedicRunner.prototype.getSauceCaps = function () {
-    this.sauceBuildName = this.sauceBuildName || this.config.getBuildName();
-    var caps = {
-        name: this.sauceBuildName,
-        idleTimeout: '100', // in seconds
-        maxDuration: util.SAUCE_MAX_DURATION,
-        tunnelIdentifier: this.config.getSauceTunnelId(),
-    };
-
-    switch(this.config.getPlatformId()) {
-        case util.ANDROID:
-            caps.platformName = 'Android';
-            caps.appPackage = 'io.cordova.hellocordova';
-            caps.appActivity = 'io.cordova.hellocordova.MainActivity';
-            caps.app = 'sauce-storage:' + this.getAppName();
-            caps.deviceType = 'phone';
-            caps.deviceOrientation = 'portrait';
-            caps.appiumVersion = this.config.getSauceAppiumVersion();
-            caps.deviceName = this.config.getSauceDeviceName();
-            caps.platformVersion = this.config.getSaucePlatformVersion();
-            break;
-        case util.IOS:
-            caps.platformName = 'iOS';
-            caps.autoAcceptAlerts = true;
-            caps.waitForAppScript = 'true;';
-            caps.app = 'sauce-storage:' + this.getAppName();
-            caps.deviceType = 'phone';
-            caps.deviceOrientation = 'portrait';
-            caps.appiumVersion = this.config.getSauceAppiumVersion();
-            caps.deviceName = this.config.getSauceDeviceName();
-            caps.platformVersion = this.config.getSaucePlatformVersion();
-            break;
-        case util.BROWSER:
-            caps.browserName = this.config.getSauceDeviceName() || 'chrome';
-            caps.version = this.config.getSaucePlatformVersion() || '45.0';
-            caps.platform = caps.browserName.indexOf('Edge') > 0 ? 'Windows 10' : 'macOS 10.13';
-            // setting from env.var here and not in the config
-            // because for any other platform we don't need to put the sauce connect up 
-            // unless the tunnel id is explicitly passed (means that user wants it anyway)
-            if (!caps.tunnelIdentifier && process.env[util.SAUCE_TUNNEL_ID_ENV_VAR]) {
-                caps.tunnelIdentifier = process.env[util.SAUCE_TUNNEL_ID_ENV_VAR];
-            } else if (!caps.tunnelIdentifier) {
-                throw new Error('Testing browser platform on Sauce Labs requires Sauce Connect tunnel. Please specify tunnel identifier via --sauceTunnelId');
-            }
-            break;
-        default:
-            throw new Error('Don\'t know the Sauce caps for platform: ' + 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');
-    var spamDots = setInterval(function () {
-        process.stdout.write('.');
-    }, 1000);
-
-    wd.configureHttp({
-        timeout: util.WD_TIMEOUT,
-        retryDelay: util.WD_RETRY_DELAY,
-        retries: util.WD_RETRIES
-    });
-
-    var driver = wd.promiseChainRemote(util.SAUCE_HOST, util.SAUCE_PORT, user, key);
-    return driver
-        .init(caps)
-        .then(function () {
-            clearInterval(spamDots);
-            process.stdout.write('\n');
-        }, function (error) {
-            clearInterval(spamDots);
-            process.stdout.write('\n');
-            throw(error);
-        });
-};
-
-ParamedicRunner.prototype.connectSauceConnect = function () {
-    var self = this;
-    var isBrowser = self.config.getPlatformId() === util.BROWSER;
-
-    // on platforms other than browser, only run sauce connect if user explicitly asks for it
-    if (!isBrowser && !self.config.getSauceTunnelId()) {
-        return Q();
-    }
-    // on browser, run sauce connect in any case
-    if (isBrowser && !self.config.getSauceTunnelId()) {
-        self.config.setSauceTunnelId(process.env[util.SAUCE_TUNNEL_ID_ENV_VAR] || self.config.getBuildName());
-    }
-
-    return Q.Promise(function (resolve, reject) {
-        logger.info('cordova-paramedic: Starting Sauce Connect...');
-        sauceConnectLauncher({
-            username: self.config.getSauceUser(),
-            accessKey: self.config.getSauceKey(),
-            tunnelIdentifier: self.config.getSauceTunnelId(),
-            connectRetries: util.SAUCE_CONNECT_CONNECTION_RETRIES,
-            connectRetryTimeout: util.SAUCE_CONNECT_CONNECTION_TIMEOUT,
-            downloadRetries: util.SAUCE_CONNECT_DOWNLOAD_RETRIES,
-            downloadRetryTimeout: util.SAUCE_CONNECT_DOWNLOAD_TIMEOUT,
-        }, function (err, sauceConnectProcess) {
-            if (err) {
-                reject(err);
-            }
-            self.sauceConnectProcess = sauceConnectProcess;
-            logger.info('cordova-paramedic: Sauce Connect ready');
-            resolve();
-        });
-    });
-};
-
-ParamedicRunner.prototype.runSauceTests = function () {
-    var self = this;
-    var isTestPassed = false;
-    var pollForResults;
-    var driver;
-    var runProcess = null;
-
-    if (!self.config.runMainTests()) {
-        logger.normal('Skipping main tests...');
-        return Q(util.TEST_PASSED);
-    }
-
-    logger.info('cordova-paramedic: running tests with sauce');
-
-    return Q().then(function () {
-        // Build + "Upload" app
-        if (self.config.getPlatformId() === util.BROWSER) {
-            // for browser, we need to serve the app for Sauce Connect
-            // we do it by just running "cordova run" and ignoring the chrome instance that pops up
-            return Q()
-                .then(function() {
-                    appPatcher.addCspSource(self.tempFolder.name, 'connect-src', 'http://*');
-                    appPatcher.permitAccess(self.tempFolder.name, '*');
-                    return self.getCommandForStartingTests();
-                })
-                .then(function (command) {
-                    console.log('$ ' + command);
-                    runProcess = cp.exec(command, function onExit() {
-                        // a precaution not to try to kill some other process
-                        runProcess = null;
-                    });
-                });
-        } else {
-            return self.buildApp()
-                .then(self.packageApp.bind(self))
-                .then(self.uploadApp.bind(self));
-        }
-    })
-    .then(function () {
-        return self.connectSauceConnect();
-    })
-    .then(function () {
-        driver = self.connectWebdriver();
-        if (self.config.getPlatformId() === util.BROWSER) {
-            return driver.get('http://localhost:8000/cdvtests/index.html');
-        }
-        return driver;
-    })
-    .then(function () {
-        if (self.config.getUseTunnel() || self.config.getPlatformId() === util.BROWSER) {
-            return driver;
-        }
-        return driver
-        .getWebviewContext()
-        .then(function (webview) {
-            return driver.context(webview);
-        });
-    })
-    .then(function () {
-        var isWkWebview = false;
-        var plugins = self.config.getPlugins();
-        for (var plugin in plugins) {
-            if (plugins[plugin].indexOf('wkwebview') >= 0) {
-                isWkWebview = true;
-            }
-        }
-        if (isWkWebview) {
-            logger.normal('cordova-paramedic: navigating to a test page');
-            return driver
-                .sleep(1000)
-                .elementByXPath('//*[text() = "Auto Tests"]')
-                .click();
-        }
-        return driver;
-    })
-    .then(function () {
-        logger.normal('cordova-paramedic: connecting to app');
-
-        var platform = self.config.getPlatformId();
-        var plugins = self.config.getPlugins();
-
-        var skipBuster = false;
-        // skip permission buster for splashscreen and inappbrowser plugins
-        // it hangs the test run on Android 7 for some reason
-        for (var i = 0; i < plugins.length; i++) {
-            if (plugins[i].indexOf('cordova-plugin-splashscreen') >= 0 || plugins[i].indexOf('cordova-plugin-inappbrowser') >= 0) {
-                skipBuster = true;
-            }
-        }
-        // always skip buster for browser platform
-        if (platform === util.BROWSER) {
-            skipBuster = true;
-        }
-
-        if (!self.config.getUseTunnel()) {
-            var polling = false;
-            pollForResults = setInterval(function () {
-                if (!polling) {
-                    polling = true;
-                    driver.pollForEvents(platform, skipBuster)
-                    .then(function (events) {
-                        for (var i = 0; i < events.length; i++) {
-                            self.server.emit(events[i].eventName, events[i].eventObject);
-                        }
-                        polling = false;
-                    })
-                    .fail(function (error) {
-                        logger.warn('appium: ' + error);
-                        polling = false;
-                    });
-                }
-            }, 2500);
-        }
-
-        return self.waitForTests();
-    })
-    .then(function (result) {
-        logger.normal('cordova-paramedic: Tests finished');
-        isTestPassed = result;
-    }, function (error) {
-        logger.normal('cordova-paramedic: Tests failed to complete; ending appium session. The error is:\n' + error.stack);
-    })
-    .fin(function () {
-        if (pollForResults) {
-            clearInterval(pollForResults);
-        }
-        if (driver && typeof driver.quit === 'function') {
-            return driver.quit();
-        }
-    })
-    .fin(function () {
-        if (self.config.getPlatformId() === util.BROWSER && !self.browserPatched) {
-            // we need to kill chrome
-            self.killEmulatorProcess();
-        }
-        if (runProcess) {
-            // as well as we need to kill the spawned node process serving our app
-            return Q.Promise(function (resolve, reject) {
-                util.killProcess(runProcess.pid, function () {
-                    resolve();
-                });
-            });
-        }
-    })
-    .fin(function () {
-        if (self.sauceConnectProcess) {
-            logger.info('cordova-paramedic: Closing Sauce Connect process...');
-            return Q.Promise(function (resolve, reject) {
-                self.sauceConnectProcess.close(function () {
-                    logger.info('cordova-paramedic: Successfully closed Sauce Connect process');
-                    resolve();
-                });
-            });
-        }
-    })
-    .then(function () {
-        return isTestPassed;
-    });
-};
-
 var storedCWD = null;
 
 exports.run = function(paramedicConfig) {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org