Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
chore(promises): remove q promises and webdriver promises (#5052)
Browse files Browse the repository at this point in the history
- remove q promises and webdriver promises from the runner, launcher, plugins, and taskRunner
- add deprecated message to element explorer.
- add unhandledRejection
- update browser versions used in travis tests
  • Loading branch information
cnishina committed Mar 23, 2019
1 parent c0917aa commit c9db3f3
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 492 deletions.
5 changes: 5 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,11 @@ export interface Config {
nodeDebug?: boolean;
debuggerServerPort?: number;
frameworkPath?: string;

/**
* Deprecated: Element explorer depends on the WebDriver control flow, and
* thus is no longer supported.
*/
elementExplorer?: any;
debug?: boolean;
unknownFlags_?: string[];
Expand Down
325 changes: 143 additions & 182 deletions lib/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
* input configuration and launching test runners.
*/
import * as fs from 'fs';
import * as q from 'q';

import {Config} from './config';
import {ConfigParser} from './configParser';
import {ConfigError, ErrorHandler, ProtractorError} from './exitCodes';
import {Logger} from './logger';
import {Runner} from './runner';
import {TaskRunner} from './taskRunner';
import {TaskScheduler} from './taskScheduler';
import * as helper from './util';
import {runFilenameOrFn_} from './util';


let logger = new Logger('launcher');
let RUNNERS_FAILED_EXIT_CODE = 100;
Expand Down Expand Up @@ -93,7 +92,7 @@ let taskResults_ = new TaskResults();
* @param {string=} configFile
* @param {Object=} additionalConfig
*/
let initFn = function(configFile: string, additionalConfig: Config) {
let initFn = async function(configFile: string, additionalConfig: Config) {
let configParser = new ConfigParser();
if (configFile) {
configParser.addFileConfig(configFile);
Expand All @@ -108,197 +107,159 @@ let initFn = function(configFile: string, additionalConfig: Config) {
logger.debug('Your base url for tests is ' + config.baseUrl);

// Run beforeLaunch
helper.runFilenameOrFn_(config.configDir, config.beforeLaunch)
.then(() => {
await runFilenameOrFn_(config.configDir, config.beforeLaunch);
// 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.
const waitMultiConfig = await config.getMultiCapabilities();
config.multiCapabilities = waitMultiConfig;
config.capabilities = null;
}

return q
.Promise<any>((resolve: Function, reject: Function) => {
// 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';
// 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'}];
}

let runner = new Runner(config);
return runner.run().then(
(exitCode: number) => {
process.exit(exitCode);
},
(err: Error) => {
logger.error(err);
process.exit(1);
});
}
})
.then(() => {
// 4) Run tests.
let scheduler = new TaskScheduler(config);
// 3) If we're in `elementExplorer` mode, throw an error and exit.
if (config.elementExplorer || config.framework === 'explorer') {
const err = new Error(
'Deprecated: Element explorer depends on the ' +
'WebDriver control flow, and thus is no longer supported.');
logger.error(err);
process.exit(1);
}

process.on('uncaughtException', (exc: (Error|string)) => {
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;
}
// 4) Run tests.
let scheduler = new TaskScheduler(config);

let errorCode = ErrorHandler.parseError(e);
if (errorCode) {
let protractorError = e as ProtractorError;
ProtractorError.log(logger, errorCode, protractorError.message, protractorError.stack);
process.exit(errorCode);
} else {
logger.error(e.message);
logger.error(e.stack);
process.exit(ProtractorError.CODE);
}
});
process.on('uncaughtException', (exc: (Error|string)) => {
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;
}
logger.error(e.message);
logger.error(e.stack);
if (e instanceof ProtractorError) {
let protractorError = e as ProtractorError;
process.exit(protractorError.code);
} else {
process.exit(1);
}
});

process.on('exit', (code: number) => {
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);
}
});
process.on('unhandledRejection', (reason: Error | any, p: Promise<any>) => {
logger.warn('Unhandled rejection at:', p, 'reason:', reason);
});

// Run afterlaunch and exit
let cleanUpAndExit = (exitCode: number) => {
return helper.runFilenameOrFn_(config.configDir, config.afterLaunch, [exitCode])
.then(
(returned) => {
if (typeof returned === 'number') {
process.exit(returned);
} else {
process.exit(exitCode);
}
},
(err: Error) => {
logger.error('Error:', err);
process.exit(1);
});
};
process.on('exit', (code: number) => {
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);
}
});

let totalTasks = scheduler.numTasksOutstanding();
let forkProcess = false;
if (totalTasks > 1) { // Start new processes only if there are >1 tasks.
forkProcess = true;
if (config.debug) {
throw new ConfigError(
logger, 'Cannot run in debug mode with multiCapabilities, count > 1, or sharding');
}
}
// Run afterlaunch and exit
const cleanUpAndExit = async (exitCode: number) => {
try {
const returned = await runFilenameOrFn_(config.configDir, config.afterLaunch, [exitCode]);
if (typeof returned === 'number') {
process.exit(returned);
} else {
process.exit(exitCode);
}
} catch (err) {
logger.error('Error:', err);
process.exit(1);
}
};

let deferred = q.defer<any>(); // Resolved when all tasks are completed
let createNextTaskRunner = () => {
let task = scheduler.nextTask();
if (task) {
let taskRunner = new 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: Error) => {
logger.error('Error:', (err as any).stack || err.message || err);
cleanUpAndExit(RUNNERS_FAILED_EXIT_CODE);
});
const totalTasks = scheduler.numTasksOutstanding();
let forkProcess = false;
if (totalTasks > 1) { // Start new processes only if there are >1 tasks.
forkProcess = true;
if (config.debug) {
throw new ConfigError(
logger, 'Cannot run in debug mode with multiCapabilities, count > 1, or sharding');
}
}

const createNextTaskRunner = async () => {
return new Promise(async (resolve) => {
const task = scheduler.nextTask();
if (task) {
const taskRunner = new TaskRunner(configFile, additionalConfig, task, forkProcess);
try {
const result = await taskRunner.run();
if (result.exitCode && !result.failedCount) {
logger.error('Runner process exited unexpectedly with error code: ' + result.exitCode);
}
};
// 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) {
taskResults_.add(result);
task.done();
createNextTaskRunner();
// If all tasks are finished
if (scheduler.numTasksOutstanding() === 0) {
resolve();
}
logger.info(scheduler.countActiveTasks() + ' instance(s) of WebDriver still running');
} catch (err) {
const errorCode = ErrorHandler.parseError(err);
logger.error('Error:', (err as any).stack || err.message || err);
await cleanUpAndExit(errorCode ? errorCode : RUNNERS_FAILED_EXIT_CODE);
}
logger.info('Running ' + scheduler.countActiveTasks() + ' instances of WebDriver');
}
});
};

const maxConcurrentTasks = scheduler.maxConcurrentTasks();
for (let i = 0; i < maxConcurrentTasks; ++i) {
await 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);
}
// By now all runners have completed.
// 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;
}
await cleanUpAndExit(exitCode);
// Start `const 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.

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();
};

export let init = initFn;
Loading

0 comments on commit c9db3f3

Please sign in to comment.