From df2cb56fb223a867a7337f32d88b35a75d007a36 Mon Sep 17 00:00:00 2001 From: Craig Nishina Date: Thu, 14 Apr 2016 11:22:02 -0700 Subject: [PATCH] chore(exitCodes): adding exit code for browser connect errors --- lib/driverProviders/direct.ts | 16 +++--- lib/driverProviders/local.ts | 12 ++-- lib/exitCodes.ts | 59 ++++++++++++++++++-- lib/launcher.ts | 9 +++ scripts/exitCodes.js | 26 +++++++++ scripts/test.js | 1 + spec/errorTest/sauceLabNotAvailable.js | 23 ++++++++ spec/unit/driverProviders/direct_test.js | 68 +++++++++++++++++++++++ spec/unit/driverProviders/local_test.js | 71 ++++++++++++++++++++++++ 9 files changed, 267 insertions(+), 18 deletions(-) create mode 100644 scripts/exitCodes.js create mode 100644 spec/errorTest/sauceLabNotAvailable.js create mode 100644 spec/unit/driverProviders/direct_test.js create mode 100644 spec/unit/driverProviders/local_test.js diff --git a/lib/driverProviders/direct.ts b/lib/driverProviders/direct.ts index 57e86f771..b8d47bb1c 100644 --- a/lib/driverProviders/direct.ts +++ b/lib/driverProviders/direct.ts @@ -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'; @@ -35,9 +36,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() {}); } @@ -65,7 +66,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(); @@ -79,9 +81,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; diff --git a/lib/driverProviders/local.ts b/lib/driverProviders/local.ts index b39cfeaed..d3d1509a0 100644 --- a/lib/driverProviders/local.ts +++ b/lib/driverProviders/local.ts @@ -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'; @@ -39,10 +40,10 @@ export class Local extends DriverProvider { require('../../config.json').webdriverVersions.selenium + '.jar'); } 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) { @@ -59,7 +60,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); } } diff --git a/lib/exitCodes.ts b/lib/exitCodes.ts index 01701c9c0..7a001b2e6 100644 --- a/lib/exitCodes.ts +++ b/lib/exitCodes.ts @@ -1,28 +1,75 @@ import {Logger} from './logger2'; const CONFIG_ERROR_CODE = 105; +const BROWSER_ERROR_CODE = 135; + +export class ProtractorError extends Error { + static ERR_MSGS: string[]; + static DEFAULT_MSG = 'protractor encountered an error'; -export class ProtractorError { error: Error; description: string; code: number; stack: string; constructor(logger: Logger, description: string, code: number) { + super(); this.error = new Error(); - this.description = description; this.code = code; + this.description = description; + this.stack = this.error.stack; + this.logError(logger); + } + + logError(logger: Logger) { logger.error('error code: ' + this.code); logger.error('description: ' + this.description); - this.stack = this.error.stack; + logger.error(this.stack); } } - /** * Configuration file error */ export class ConfigError extends ProtractorError { + static DEFAULT_MSG = 'configuration error'; static CODE = CONFIG_ERROR_CODE; - constructor(logger: Logger, description: string) { - super(logger, description, ConfigError.CODE); + constructor(logger: Logger, opt_msg?: string) { + super(logger, opt_msg || ConfigError.DEFAULT_MSG, ConfigError.CODE); + } +} + +/** + * Browser errors including getting a driver session, direct connect, etc. + */ +export class BrowserError extends ProtractorError { + static DEFAULT_MSG = 'browser error'; + static CODE = BROWSER_ERROR_CODE; + static ERR_MSGS = + ['ECONNREFUSED connect ECONNREFUSED', 'Sauce Labs Authentication Error']; + constructor(logger: Logger, opt_msg?: string) { + super(logger, opt_msg || BrowserError.DEFAULT_MSG, 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; } } diff --git a/lib/launcher.ts b/lib/launcher.ts index 9df8519cd..5b9948f5e 100644 --- a/lib/launcher.ts +++ b/lib/launcher.ts @@ -4,6 +4,7 @@ */ import * as q from 'q'; import {Config, ConfigParser} from './configParser'; +import {ErrorHandler} from './exitCodes'; import {Logger} from './logger2'; import {Runner} from './runner'; import {TaskRunner} from './taskRunner'; @@ -176,6 +177,14 @@ 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) { + logger.error(e.stack); + process.exit(errorCode); + } + }); + process.on('exit', (code: number) => { if (code) { logger.error('Process exited with error code ' + code); diff --git a/scripts/exitCodes.js b/scripts/exitCodes.js new file mode 100644 index 000000000..84c1b899a --- /dev/null +++ b/scripts/exitCodes.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +'use strict'; + +var spawn = require('child_process').spawnSync; +var runProtractor, output, messages; +var checkLogs = function(output, messages) { + for (var pos in messages) { + var message = messages[pos]; + if (output.indexOf(message) === -1) { + throw new Error('does not exist in logs: ' + message); + } + } +}; + +/****************************** + *Below are exit failure tests* + ******************************/ + +// assert authentication error for sauce labs +runProtractor = spawn('node', + ['bin/protractor', 'spec/errorTest/sauceLabNotAvailable.js']); +output = runProtractor.stdout.toString(); +messages = ['WebDriverError: Sauce Labs Authentication Error.', + 'Process exited with error code 135']; +checkLogs(output, messages); diff --git a/scripts/test.js b/scripts/test.js index 0985c321c..99c04331b 100755 --- a/scripts/test.js +++ b/scripts/test.js @@ -34,6 +34,7 @@ var passingTests = [ 'node built/cli.js spec/noGlobalsConf.js', 'node built/cli.js spec/angular2Conf.js', 'node scripts/attachSession.js', + 'node scripts/exitCodes.js', 'node scripts/interactive_tests/interactive_test.js', 'node scripts/interactive_tests/with_base_url.js', // Unit tests diff --git a/spec/errorTest/sauceLabNotAvailable.js b/spec/errorTest/sauceLabNotAvailable.js new file mode 100644 index 000000000..737423e5a --- /dev/null +++ b/spec/errorTest/sauceLabNotAvailable.js @@ -0,0 +1,23 @@ +var env = require('../environment.js'); + +exports.config = { + sauceUser: 'foobar', + sauceKey: process.env.SAUCE_ACCESS_KEY, + + framework: 'jasmine', + + specs: [ + '../../example/example_spec.js' + ], + + capabilities: { + 'browserName': 'chrome' + }, + + baseUrl: env.baseUrl + '/ng1/', + + jasmineNodeOpts: { + defaultTimeoutInterval: 90000 + } + +}; diff --git a/spec/unit/driverProviders/direct_test.js b/spec/unit/driverProviders/direct_test.js new file mode 100644 index 000000000..ad9c27a1d --- /dev/null +++ b/spec/unit/driverProviders/direct_test.js @@ -0,0 +1,68 @@ +var fs = require('fs'), + os = require('os'), + path = require('path'); +var BrowserError = require('../../../built/exitCodes').BrowserError, + Logger = require('../../../built/logger2').Logger, + WriteTo = require('../../../built/logger2').WriteTo; + +describe('direct connect', function() { + beforeEach(function() { + Logger.setWrite(WriteTo.NONE); + }); + + afterEach(function() { + Logger.setWrite(WriteTo.CONSOLE); + }); + + describe('without the chrome driver', function() { + it('should throw an error if no drivers are present', function() { + var config = { + directConnect: true, + capabilities: { browserName: 'chrome' }, + chromeDriver: '/foo/bar/chromedriver' + }; + var errorFound = false; + try { + webdriver = require('../../../built/driverProviders/direct')(config); + webdriver.getNewDriver(); + } catch(e) { + errorFound = true; + expect(e.code).toBe(BrowserError.CODE); + } + expect(errorFound).toBe(true); + }); + }); + + describe('with chromedriver drivers', function() { + var chromedriver = ''; + beforeEach(function() { + // add files to selenium folder + file = 'chromedriver'; + chromedriver = path.resolve(os.tmpdir(), file); + fs.openSync(chromedriver, 'w'); + }); + + afterEach(function() { + try { + fs.unlinkSync(chromedriver); + } catch(e) { + } + }); + it('should throw an error if the driver cannot be used', function() { + var config = { + directConnect: true, + capabilities: { browserName: 'foobar explorer' }, + chromeDriver: chromedriver + }; + var errorFound = false; + try { + webdriver = require('../../../built/driverProviders/direct')(config); + webdriver.getNewDriver(); + } catch(e) { + errorFound = true; + expect(e.code).toBe(BrowserError.CODE); + } + expect(errorFound).toBe(true); + }); + }); +}); diff --git a/spec/unit/driverProviders/local_test.js b/spec/unit/driverProviders/local_test.js new file mode 100644 index 000000000..8427223ac --- /dev/null +++ b/spec/unit/driverProviders/local_test.js @@ -0,0 +1,71 @@ +var fs = require('fs'), + os = require('os'), + path = require('path'); +var BrowserError = require('../../../built/exitCodes').BrowserError, + Logger = require('../../../built/logger2').Logger, + WriteTo = require('../../../built/logger2').WriteTo; + +describe('local connect', function() { + beforeEach(function() { + Logger.setWrite(WriteTo.NONE); + }); + + afterEach(function() { + Logger.setWrite(WriteTo.CONSOLE); + }); + + describe('without the selenium standalone jar', function() { + it('should throw an error jar file is not present', function() { + var config = { + directConnect: true, + capabilities: { browserName: 'chrome' }, + seleniumServerJar: '/foo/bar/selenium.jar' + }; + var errorFound = false; + try { + webdriver = require('../../../built/driverProviders/local')(config); + webdriver.setupEnv(); + } catch(e) { + errorFound = true; + expect(e.code).toBe(BrowserError.CODE); + } + expect(errorFound).toBe(true); + }); + }); + + describe('with the selenium standalone jar', function() { + it('should throw an error if the jar file does not work', function() { + var jarFile = ''; + beforeEach(function() { + // add files to selenium folder + file = 'selenium.jar'; + jarFile = path.resolve(os.tmpdir(), file); + fs.openSync(jarFile, 'w'); + }); + + afterEach(function() { + try { + fs.unlinkSync(jarFile); + } catch(e) { + } + }); + + it('should throw an error if the selenium sever jar cannot be used', function() { + var config = { + directConnect: true, + capabilities: { browserName: 'foobar explorer' }, + seleniumServerJar: jarFile + }; + var errorFound = false; + try { + webdriver = require('../../../built/driverProviders/local')(config); + webdriver.getNewDriver(); + } catch(e) { + errorFound = true; + expect(e.code).toBe(BrowserError.CODE); + } + expect(errorFound).toBe(true); + }); + }); + }); +});