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

chore(exitCodes): adding exit code for browser connect errors #3133

Merged
merged 8 commits into from
Jun 14, 2016
77 changes: 41 additions & 36 deletions lib/driverProviders/browserStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as request from 'request';
import * as q from 'q';
import * as util from 'util';

import {BrowserError} from '../exitCodes';
import {Config} from '../configParser';
import {DriverProvider} from './driverProvider';
import {Logger} from '../logger2';
Expand All @@ -22,42 +23,46 @@ export class BrowserStack extends DriverProvider {
* @param {Object} update
* @return {q.promise} A promise that will resolve when the update is complete.
*/
updateJob(update: any): q.Promise<any> {
let deferredArray = this.drivers_.map((driver: webdriver.WebDriver) => {
let deferred = q.defer();
driver.getSession().then((session: webdriver.Session) => {
var jobStatus = update.passed ? 'completed' : 'error';
logger.info(
'BrowserStack results available at ' +
'https://www.browserstack.com/automate');
request(
{
url: 'https://www.browserstack.com/automate/sessions/' +
session.getId() + '.json',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' +
new Buffer(
this.config_.browserstackUser + ':' +
this.config_.browserstackKey)
.toString('base64')
},
method: 'PUT',
form: {'status': jobStatus}
},
(error: Error) => {
if (error) {
throw new Error(
'Error updating BrowserStack pass/fail status: ' +
util.inspect(error));
}
});
deferred.resolve();
});
return deferred.promise;
});
return q.all(deferredArray);
}
updateJob(update: any): q.Promise<any> {
let deferredArray = this.drivers_.map((driver: webdriver.WebDriver) => {
let deferred = q.defer();
driver.getSession().then((session: webdriver.Session) => {
var jobStatus = update.passed ? 'completed' : 'error';
logger.info(
'BrowserStack results available at ' +
'https://www.browserstack.com/automate');
request(
{
url: 'https://www.browserstack.com/automate/sessions/' +
session.getId() + '.json',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' +
new Buffer(
this.config_.browserstackUser + ':' +
this.config_.browserstackKey)
.toString('base64')
},
method: 'PUT',
form: {'status': jobStatus}
},
(error: Error) => {
if (error) {
throw new BrowserError(
logger,
'Error updating BrowserStack pass/fail status: ' +
util.inspect(error));
}
});
deferred.resolve();
});
return deferred.promise;
});
return q.all(deferredArray);
}




/**
* Configure and launch (if applicable) the object's environment.
Expand Down
16 changes: 9 additions & 7 deletions lib/driverProviders/direct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as fs from 'fs';
import * as path from 'path';
import * as util from 'util';

import {BrowserError} from '../exitCodes';
import {Config} from '../configParser';
import {DriverProvider} from './driverProvider';
import {Logger} from '../logger2';
Expand Down Expand Up @@ -39,9 +40,9 @@ export class Direct extends DriverProvider {
logger.info('Using FirefoxDriver directly...');
break;
default:
throw new Error(
'browserName (' + this.config_.capabilities.browserName +
') is not supported with directConnect.');
throw new BrowserError(
logger, 'browserName ' + this.config_.capabilities.browserName +
' is not supported with directConnect.');
}
return q.fcall(function() {});
}
Expand Down Expand Up @@ -69,7 +70,8 @@ export class Direct extends DriverProvider {
this.config_.chromeDriver || defaultChromeDriverPath;

if (!fs.existsSync(chromeDriverFile)) {
throw new Error('Could not find chromedriver at ' + chromeDriverFile);
throw new BrowserError(
logger, 'Could not find chromedriver at ' + chromeDriverFile);
}

let service = new chrome.ServiceBuilder(chromeDriverFile).build();
Expand All @@ -83,9 +85,9 @@ export class Direct extends DriverProvider {
driver = new firefox.Driver(this.config_.capabilities);
break;
default:
throw new Error(
'browserName ' + this.config_.capabilities.browserName +
'is not supported with directConnect.');
throw new BrowserError(
logger, 'browserName ' + this.config_.capabilities.browserName +
' is not supported with directConnect.');
}
this.drivers_.push(driver);
return driver;
Expand Down
12 changes: 7 additions & 5 deletions lib/driverProviders/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as path from 'path';
import * as q from 'q';
import * as util from 'util';

import {BrowserError} from '../exitCodes';
import {Config} from '../configParser';
import {DriverProvider} from './driverProvider';
import {Logger} from '../logger2';
Expand Down Expand Up @@ -42,10 +43,10 @@ export class Local extends DriverProvider {
new SeleniumStandAlone().executableFilename());
}
if (!fs.existsSync(this.config_.seleniumServerJar)) {
throw new Error(
'No selenium server jar found at the specified ' +
'location (' + this.config_.seleniumServerJar +
'). Check that the version number is up to date.');
throw new BrowserError(
logger, 'No selenium server jar found at the specified ' +
'location (' + this.config_.seleniumServerJar +
'). Check that the version number is up to date.');
}
if (this.config_.capabilities.browserName === 'chrome') {
if (!this.config_.chromeDriver) {
Expand All @@ -62,7 +63,8 @@ export class Local extends DriverProvider {
if (fs.existsSync(this.config_.chromeDriver + '.exe')) {
this.config_.chromeDriver += '.exe';
} else {
throw new Error(
throw new BrowserError(
logger,
'Could not find chromedriver at ' + this.config_.chromeDriver);
}
}
Expand Down
91 changes: 79 additions & 12 deletions lib/exitCodes.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,48 @@
import {Logger} from './logger2';

const CONFIG_ERROR_CODE = 105;
const BROWSER_CONNECT_ERROR_CODE = 135;
const KITCHEN_SINK_CODE = 199;

export class ProtractorError {
error: Error;
description: string;
export class ProtractorError extends Error {
static ERR_MSGS: string[];
static CODE = KITCHEN_SINK_CODE;

message: string; // a one liner, if more than one line is sent, it will be cut off
stack: string; // has the message with the stack trace
code: number;
stack: string;
constructor(logger: Logger, description: string, code: number) {
this.error = new Error();
this.description = description;

/**
* Captures the stack trace to this.stack from the Error.captureStackTrace.
* this allows us to capture the error of this error object. Note:
* called with Error set to any to quiet typescript warnings.
*/
captureStackTrace() {
(Error as any).captureStackTrace(this, this.constructor);
}

constructor(logger: Logger, message: string, code: number) {
super(message);
this.message = message;
this.code = code;
logger.error('error code: ' + this.code);
logger.error('description: ' + this.description);
this.stack = this.error.stack;
this.captureStackTrace();
this.logError(logger);

process.exit(this.code);
}

logError(logger: Logger) {
ProtractorError.log(logger, this.code, this.message, this.stack);
}

static log(logger: Logger, code: number, message: string, stack: string) {
let messages = message.split('\n');
if (messages.length > 1) {
message = messages[0];
}
logger.error('Error code: ' + code);
logger.error('Error message: ' + message);
logger.error(stack);
}
}

Expand All @@ -22,7 +51,45 @@ export class ProtractorError {
*/
export class ConfigError extends ProtractorError {
static CODE = CONFIG_ERROR_CODE;
constructor(logger: Logger, description: string) {
super(logger, description, ConfigError.CODE);
constructor(logger: Logger, message: string) {
super(logger, message, ConfigError.CODE);
}
}

/**
* Browser errors including getting a driver session, direct connect, etc.
*/
export class BrowserError extends ProtractorError {
static CODE = BROWSER_CONNECT_ERROR_CODE;
static ERR_MSGS = [
'ECONNREFUSED connect ECONNREFUSED', 'Sauce Labs Authentication Error',
'Invalid username or password'
];
constructor(logger: Logger, message: string) {
super(logger, message, BrowserError.CODE);
}
}

export class ErrorHandler {
static isError(errMsgs: string[], e: Error): boolean {
if (errMsgs && errMsgs.length > 0) {
for (let errPos in errMsgs) {
let errMsg = errMsgs[errPos];
if (e.message.indexOf(errMsg) !== -1) {
return true;
}
}
}
return false;
}

static parseError(e: Error): number {
if (ErrorHandler.isError(ConfigError.ERR_MSGS, e)) {
return ConfigError.CODE;
}
if (ErrorHandler.isError(BrowserError.ERR_MSGS, e)) {
return BrowserError.CODE;
}
return null;
}
}
20 changes: 16 additions & 4 deletions lib/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
import * as q from 'q';
import {Config, ConfigParser} from './configParser';
import {ProtractorError, ConfigError, ErrorHandler} from './exitCodes';
import {Logger} from './logger2';
import {Runner} from './runner';
import {TaskRunner} from './taskRunner';
Expand Down Expand Up @@ -176,6 +177,18 @@ let initFn = function(configFile: string, additionalConfig: Config) {
// 4) Run tests.
let scheduler = new TaskScheduler(config);

process.on('uncaughtException', (e: Error) => {
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('"process.on(\'uncaughtException\'" error, see launcher');
process.exit(ProtractorError.CODE);
}
});

process.on('exit', (code: number) => {
if (code) {
logger.error('Process exited with error code ' + code);
Expand Down Expand Up @@ -212,9 +225,8 @@ let initFn = function(configFile: string, additionalConfig: Config) {
1) { // Start new processes only if there are >1 tasks.
forkProcess = true;
if (config.debug) {
throw new Error(
'Cannot run in debug mode with ' +
'multiCapabilities, count > 1, or sharding');
throw new ConfigError(logger,
'Cannot run in debug mode with multiCapabilities, count > 1, or sharding');
}
}

Expand Down Expand Up @@ -243,7 +255,7 @@ let initFn = function(configFile: string, additionalConfig: Config) {
' instance(s) of WebDriver still running');
})
.catch((err: Error) => {
logger.error('Error:', err.stack || err.message || err);
logger.error('Error:', (err as any).stack || err.message || err);
cleanUpAndExit(RUNNERS_FAILED_EXIT_CODE);
});
}
Expand Down
36 changes: 20 additions & 16 deletions lib/ptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import {Browser, ElementHelper} from './browser';
import {ProtractorBy} from './locators';
Copy link
Member

Choose a reason for hiding this comment

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

This looks like an unrelated change.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I need to stop sneaking these in. Will revert this file back.

import {ElementFinder, ElementArrayFinder} from './element';
import * as EC from './expectedConditions';
import {Promise} from './selenium-webdriver/promise';

let SeleniumWebdriverActions = require('selenium-webdriver/lib/actions');
let SeleniumWebdriverInput = require('selenium-webdriver/lib/input');
let SeleniumWebdriverPromise = require('selenium-webdriver/lib/promise');
let SeleniumWebdriverCommand = require('selenium-webdriver/lib/command');

export namespace protractor {
export let browser: Browser;
Expand All @@ -20,21 +24,21 @@ export namespace protractor {
export let wrapDriver: Function;
export let ExpectedConditions = new EC.ExpectedConditions();
export let promise = {
controlFlow: require('selenium-webdriver/lib/promise').controlFlow,
createFlow: require('selenium-webdriver/lib/promise').createFlow,
defer: require('selenium-webdriver/lib/promise').defer,
delayed: require('selenium-webdriver/lib/promise').delayed,
filter: require('selenium-webdriver/lib/promise').filter,
fulfilled: require('selenium-webdriver/lib/promise').fulfilled,
fullyResolved: require('selenium-webdriver/lib/promise').fullyResolved,
isPromise: require('selenium-webdriver/lib/promise').isPromise,
rejected: require('selenium-webdriver/lib/promise').rejected,
thenFinally: require('selenium-webdriver/lib/promise').thenFinally,
when: require('selenium-webdriver/lib/promise').when
controlFlow: SeleniumWebdriverPromise.controlFlow,
createFlow: SeleniumWebdriverPromise.createFlow,
defer: SeleniumWebdriverPromise.defer,
delayed: SeleniumWebdriverPromise.delayed,
filter: SeleniumWebdriverPromise.filter,
fulfilled: SeleniumWebdriverPromise.fulfilled,
fullyResolved: SeleniumWebdriverPromise.fullyResolved,
isPromise: SeleniumWebdriverPromise.isPromise,
rejected: SeleniumWebdriverPromise.rejected,
thenFinally: SeleniumWebdriverPromise.thenFinally,
when: SeleniumWebdriverPromise.when
};
export let ActionSequence = require('selenium-webdriver/lib/actions').ActionSequence;
export let Key = require('selenium-webdriver/lib/input').Key;
export let ActionSequence = SeleniumWebdriverActions.ActionSequence;
export let Key = SeleniumWebdriverInput.Key;

export let Command = require('selenium-webdriver/lib/command').Command;
export let CommandName = require('selenium-webdriver/lib/command').Name;
export let Command = SeleniumWebdriverCommand.Command;
export let CommandName = SeleniumWebdriverCommand.Name;
}
Loading