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

chore(promises): remove q promises and webdriver promises #5052

Merged
merged 5 commits into from
Nov 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,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) {
Copy link
Contributor

@CrispusDH CrispusDH Nov 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need it check still here with TS? Probably, not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure. Could you clarify?

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