launcher.js 11.3 KB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * The launcher is responsible for parsing the capabilities from the
 * input configuration and launching test runners.
 */
const fs = require("fs");
const q = require("q");
const configParser_1 = require("./configParser");
const exitCodes_1 = require("./exitCodes");
const logger_1 = require("./logger");
const runner_1 = require("./runner");
const taskRunner_1 = require("./taskRunner");
const taskScheduler_1 = require("./taskScheduler");
const helper = require("./util");
let logger = new logger_1.Logger('launcher');
let RUNNERS_FAILED_EXIT_CODE = 100;
/**
 * Keeps track of a list of task results. Provides method to add a new
 * result, aggregate the results into a summary, count failures,
 * and save results into a JSON file.
 */
class TaskResults {
    constructor() {
        // TODO: set a type for result
        this.results_ = [];
    }
    add(result) {
        this.results_.push(result);
    }
    totalSpecFailures() {
        return this.results_.reduce((specFailures, result) => {
            return specFailures + result.failedCount;
        }, 0);
    }
    totalProcessFailures() {
        return this.results_.reduce((processFailures, result) => {
            return !result.failedCount && result.exitCode !== 0 ? processFailures + 1 : processFailures;
        }, 0);
    }
    saveResults(filepath) {
        let jsonOutput = this.results_.reduce((jsonOutput, result) => {
            return jsonOutput.concat(result.specResults);
        }, []);
        let json = JSON.stringify(jsonOutput, null, '  ');
        fs.writeFileSync(filepath, json);
    }
    reportSummary() {
        let specFailures = this.totalSpecFailures();
        let processFailures = this.totalProcessFailures();
        this.results_.forEach((result) => {
            let capabilities = result.capabilities;
            let shortName = (capabilities.browserName) ? capabilities.browserName : '';
            shortName = (capabilities.logName) ?
                capabilities.logName :
                (capabilities.browserName) ? capabilities.browserName : '';
            shortName += (capabilities.version) ? capabilities.version : '';
            shortName += (capabilities.logName && capabilities.count < 2) ? '' : ' #' + result.taskId;
            if (result.failedCount) {
                logger.info(shortName + ' failed ' + result.failedCount + ' test(s)');
            }
            else if (result.exitCode !== 0) {
                logger.info(shortName + ' failed with exit code: ' + result.exitCode);
            }
            else {
                logger.info(shortName + ' passed');
            }
        });
        if (specFailures && processFailures) {
            logger.info('overall: ' + specFailures + ' failed spec(s) and ' + processFailures +
                ' process(es) failed to complete');
        }
        else if (specFailures) {
            logger.info('overall: ' + specFailures + ' failed spec(s)');
        }
        else if (processFailures) {
            logger.info('overall: ' + processFailures + ' process(es) failed to complete');
        }
    }
}
let taskResults_ = new TaskResults();
/**
 * Initialize and run the tests.
 * Exits with 1 on test failure, and RUNNERS_FAILED_EXIT_CODE on unexpected
 * failures.
 *
 * @param {string=} configFile
 * @param {Object=} additionalConfig
 */
let initFn = function (configFile, additionalConfig) {
    let configParser = new configParser_1.ConfigParser();
    if (configFile) {
        configParser.addFileConfig(configFile);
    }
    if (additionalConfig) {
        configParser.addConfig(additionalConfig);
    }
    let config = configParser.getConfig();
    logger_1.Logger.set(config);
    logger.debug('Running with --troubleshoot');
    logger.debug('Protractor version: ' + require('../package.json').version);
    logger.debug('Your base url for tests is ' + config.baseUrl);
    // Run beforeLaunch
    helper.runFilenameOrFn_(config.configDir, config.beforeLaunch)
        .then(() => {
        return q
            .Promise((resolve, reject) => {
            // 1) If getMultiCapabilities is set, resolve that as
            // `multiCapabilities`.
            if (config.getMultiCapabilities &&
                typeof config.getMultiCapabilities === 'function') {
                if (config.multiCapabilities.length || config.capabilities) {
                    logger.warn('getMultiCapabilities() will override both capabilities ' +
                        'and multiCapabilities');
                }
                // If getMultiCapabilities is defined and a function, use this.
                q(config.getMultiCapabilities())
                    .then((multiCapabilities) => {
                    config.multiCapabilities = multiCapabilities;
                    config.capabilities = null;
                })
                    .then(() => {
                    resolve();
                })
                    .catch(err => {
                    reject(err);
                });
            }
            else {
                resolve();
            }
        })
            .then(() => {
            // 2) Set `multicapabilities` using `capabilities`,
            // `multicapabilities`,
            // or default
            if (config.capabilities) {
                if (config.multiCapabilities.length) {
                    logger.warn('You have specified both capabilities and ' +
                        'multiCapabilities. This will result in capabilities being ' +
                        'ignored');
                }
                else {
                    // Use capabilities if multiCapabilities is empty.
                    config.multiCapabilities = [config.capabilities];
                }
            }
            else if (!config.multiCapabilities.length) {
                // Default to chrome if no capabilities given
                config.multiCapabilities = [{ browserName: 'chrome' }];
            }
        });
    })
        .then(() => {
        // 3) If we're in `elementExplorer` mode, run only that.
        if (config.elementExplorer || config.framework === 'explorer') {
            if (config.multiCapabilities.length != 1) {
                throw new Error('Must specify only 1 browser while using elementExplorer');
            }
            else {
                config.capabilities = config.multiCapabilities[0];
            }
            config.framework = 'explorer';
            let runner = new runner_1.Runner(config);
            return runner.run().then((exitCode) => {
                process.exit(exitCode);
            }, (err) => {
                logger.error(err);
                process.exit(1);
            });
        }
    })
        .then(() => {
        // 4) Run tests.
        let scheduler = new taskScheduler_1.TaskScheduler(config);
        process.on('uncaughtException', (exc) => {
            let e = (exc instanceof Error) ? exc : new Error(exc);
            if (config.ignoreUncaughtExceptions) {
                // This can be a sign of a bug in the test framework, that it may
                // not be handling WebDriver errors properly. However, we don't
                // want these errors to prevent running the tests.
                logger.warn('Ignoring uncaught error ' + exc);
                return;
            }
            let errorCode = exitCodes_1.ErrorHandler.parseError(e);
            if (errorCode) {
                let protractorError = e;
                exitCodes_1.ProtractorError.log(logger, errorCode, protractorError.message, protractorError.stack);
                process.exit(errorCode);
            }
            else {
                logger.error(e.message);
                logger.error(e.stack);
                process.exit(exitCodes_1.ProtractorError.CODE);
            }
        });
        process.on('exit', (code) => {
            if (code) {
                logger.error('Process exited with error code ' + code);
            }
            else if (scheduler.numTasksOutstanding() > 0) {
                logger.error('BUG: launcher exited with ' + scheduler.numTasksOutstanding() +
                    ' tasks remaining');
                process.exit(RUNNERS_FAILED_EXIT_CODE);
            }
        });
        // Run afterlaunch and exit
        let cleanUpAndExit = (exitCode) => {
            return helper.runFilenameOrFn_(config.configDir, config.afterLaunch, [exitCode])
                .then((returned) => {
                if (typeof returned === 'number') {
                    process.exit(returned);
                }
                else {
                    process.exit(exitCode);
                }
            }, (err) => {
                logger.error('Error:', err);
                process.exit(1);
            });
        };
        let totalTasks = scheduler.numTasksOutstanding();
        let forkProcess = false;
        if (totalTasks > 1) {
            forkProcess = true;
            if (config.debug) {
                throw new exitCodes_1.ConfigError(logger, 'Cannot run in debug mode with multiCapabilities, count > 1, or sharding');
            }
        }
        let deferred = q.defer(); // Resolved when all tasks are completed
        let createNextTaskRunner = () => {
            let task = scheduler.nextTask();
            if (task) {
                let taskRunner = new taskRunner_1.TaskRunner(configFile, additionalConfig, task, forkProcess);
                taskRunner.run()
                    .then((result) => {
                    if (result.exitCode && !result.failedCount) {
                        logger.error('Runner process exited unexpectedly with error code: ' + result.exitCode);
                    }
                    taskResults_.add(result);
                    task.done();
                    createNextTaskRunner();
                    // If all tasks are finished
                    if (scheduler.numTasksOutstanding() === 0) {
                        deferred.resolve();
                    }
                    logger.info(scheduler.countActiveTasks() + ' instance(s) of WebDriver still running');
                })
                    .catch((err) => {
                    logger.error('Error:', err.stack || err.message || err);
                    cleanUpAndExit(RUNNERS_FAILED_EXIT_CODE);
                });
            }
        };
        // Start `scheduler.maxConcurrentTasks()` workers for handling tasks in
        // the beginning. As a worker finishes a task, it will pick up the next
        // task
        // from the scheduler's queue until all tasks are gone.
        for (let i = 0; i < scheduler.maxConcurrentTasks(); ++i) {
            createNextTaskRunner();
        }
        logger.info('Running ' + scheduler.countActiveTasks() + ' instances of WebDriver');
        // By now all runners have completed.
        deferred.promise
            .then(function () {
            // Save results if desired
            if (config.resultJsonOutputFile) {
                taskResults_.saveResults(config.resultJsonOutputFile);
            }
            taskResults_.reportSummary();
            let exitCode = 0;
            if (taskResults_.totalProcessFailures() > 0) {
                exitCode = RUNNERS_FAILED_EXIT_CODE;
            }
            else if (taskResults_.totalSpecFailures() > 0) {
                exitCode = 1;
            }
            return cleanUpAndExit(exitCode);
        })
            .done();
    })
        .done();
};
exports.init = initFn;
//# sourceMappingURL=launcher.js.map