Plato on Github
Report Home
index.js
Maintainability
70.59
Lines of code
447
Difficulty
31.47
Estimated Errors
2.04
Function weight
By Complexity
By SLOC
/*! * * Copyright (c) 2013 Sebastian Golasch * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ 'use strict'; // ext. libs var fs = require('fs'); var Q = require('q'); // int. libs var WD = require('dalek-internal-webdriver'); var Browser = require('./lib/browser'); /** * Initializes the sauce labs driver & the remote browser instance * * @param {object} opts Initializer options * @constructor */ var Sauce = function (opts) { // get the browser configuration & the browser module var browserConf = {name: null}; var browser = opts.browserMo; // prepare properties this._initializeProperties(opts); // create a new webdriver client instance this.webdriverClient = new WD(browser, this.events); // listen on browser events this._startBrowserEventListeners(browser); // assign the current browser name browserConf.name = this.browserName; // store desired capabilities of this session this.desiredCapabilities = browser.getDesiredCapabilities(this.browserName, this.config); this.browserDefaults = browser.driverDefaults; this.browserDefaults.status = browser.getStatusDefaults(this.desiredCapabilities); this.browserData = browser; // set auth data var driverConfig = this.config.get('driver.sauce'); browser.setAuth(driverConfig.user, driverConfig.key); // launch the browser & when the browser launch // promise is fullfilled, issue the driver:ready event // for the particular browser browser .launch(browserConf, this.reporterEvents, this.config) .then(this.events.emit.bind(this.events, 'driver:ready:sauce:' + this.browserName, browser)); }; /** * This module is a driver plugin for [DalekJS](//github.com/dalekjs/dalek). * It connects Daleks testsuite with the remote testing environment of [Sauce Labs](https://saucelabs.com). * * The driver can be installed with the following command: * * ```bash * $ npm install dalek-driver-sauce --save-dev * ``` * * You can use the driver by adding a config option to the your [Dalekfile](/docs/config.html) * * ```javascript * "driver": ["sauce"] * ``` * * Or you can tell Dalek that it should run your tests via sauces service via the command line: * * ```bash * $ dalek mytest.js -d sauce * ``` * * In order to run your tests within the Sauce Labs infrastructure, you must add your sauce username & key * to your dalek configuration. Those two parameters must be set in order to get this driver up & running. * You can specifiy them within your [Dalekfile](/docs/config.html) like so: * * ```javascript * "driver.sauce": { * "user": "dalekjs", * "key": "aaaaaa-1234-567a-1abc-1br6d9f68689" * } * ``` * * It is also possible to specify a set of other extra saucy parameters like `name` & `tags`: * * ```javascript * "driver.sauce": { * "user": "dalekjs", * "key": "aaaaaa-1234-567a-1abc-1br6d9f68689", * "name": "Guineapig", * "tags": ["dalek", "testproject"] * } * ``` * * If you would like to have a more control over the browser/OS combinations that are available, you are able * to configure you custom combinations: * * ```javascript * "browsers": [{ * "chrome": { * "platform": "OS X 10.6", * "actAs": "chrome", * "version": 27 * }, * "chromeWin": { * "platform": "Windows 7", * "actAs": "chrome", * "version": 27 * }, * "chromeLinux": { * "platform": "Linux", * "actAs": "chrome", * "version": 26 * } * ``` * * You can then call your custom browsers like so: * * ```bash * $ dalek mytest.js -d sauce -b chrome,chromeWin,chromeLinux * ``` * * or you can define them in your Dalekfile: * * ```javascript * "browser": ["chrome", "chromeWin", "chromeLinux"] * ``` * * A list of all available browser/OS combinations, can be found [here](https://saucelabs.com/docs/platforms). * * @module Driver * @class Sauce * @namespace Dalek * @part Sauce * @api */ Sauce.prototype = { /** * Initializes the driver properties * * @method _initializeProperties * @param {object} opts Options needed to kick off the driver * @chainable * @private */ _initializeProperties: function (opts) { // prepare properties this.actionQueue = []; this.config = opts.config; this.lastCalledUrl = null; this.driverStatus = {}; this.sessionStatus = {}; // store injcted options in object properties this.events = opts.events; this.reporterEvents = opts.reporter; this.browserName = opts.browser; return this; }, /** * Binds listeners on browser events * * @method _initializeProperties * @param {object} browser Browser module * @chainable * @private */ _startBrowserEventListeners: function (browser) { // issue the kill command to the browser, when all tests are completed this.events.on('tests:complete:sauce:' + this.browserName, browser.kill.bind(browser)); // clear the webdriver session, when all tests are completed this.events.on('tests:complete:sauce:' + this.browserName, this.webdriverClient.closeSession.bind(this.webdriverClient)); return this; }, /** * Checks if a webdriver session has already been established, * if not, create a new one * * @method start * @return {object} promise Driver promise */ start: function () { var deferred = Q.defer(); // store desired capabilities of this session this.desiredCapabilities = this.browserData.getDesiredCapabilities(this.browserName, this.config); this.browserDefaults = this.browserData.driverDefaults; this.browserDefaults.status = this.browserData.getStatusDefaults(this.desiredCapabilities); // start a browser session this._startBrowserSession(deferred, this.desiredCapabilities, this.browserDefaults); return deferred.promise; }, /** * Creates a new webdriver session * Gets the driver status * Gets the session status * Resolves the promise (e.g. let them tests run) * * @method _startBrowserSession * @param {object} deferred Browser session deferred * @chainable * @private */ _startBrowserSession: function (deferred, desiredCapabilities, defaults) { var viewport = this.config.get('viewport'); // start a session, transmit the desired capabilities var promise = this.webdriverClient.createSession({desiredCapabilities: desiredCapabilities}); // set the default viewport if supported by the browser if (defaults.viewport) { promise = promise.then(this.webdriverClient.setWindowSize.bind(this.webdriverClient, viewport.width, viewport.height)); } // get the driver status if supported by the browser if (defaults.status === true) { promise = promise .then(this.webdriverClient.status.bind(this.webdriverClient)) .then(this._driverStatus.bind(this)); } else { promise = promise.then(this._driverStatus.bind(this, JSON.stringify({value: defaults.status}))); } // get the session info if supported by the browser if (defaults.sessionInfo === true) { promise = promise .then(this.webdriverClient.sessionInfo.bind(this.webdriverClient)) .then(this._sessionStatus.bind(this)); } else { promise = promise.then(this._driverStatus.bind(this, JSON.stringify({value: defaults.sessionInfo}))); } // finally resolve the deferred promise.then(deferred.resolve); return this; }, /** * Starts to execution of a batch of tests * * @method end * @chainable */ end: function () { var result = Q.resolve(); // loop through all promises created by the remote methods // this is synchronous, so it waits if a method is finished before // the next one will be executed this.actionQueue.forEach(function (f) { result = result.then(f); }); // flush the queue & fire an event // when the queue finished its executions result.then(this.flushQueue.bind(this)); return this; }, /** * Flushes the action queue (e.g. commands that should be send to the wbdriver server) * * @method flushQueue * @chainable */ flushQueue: function () { // clear the action queue this.actionQueue = []; // kill the session var promise = this.webdriverClient.deleteSession(); // emit the run.complete event promise.then(function () { this.events.emit('driver:message', {key: 'run.complete', value: null}); }.bind(this)); return this; }, /** * Loads the browser session status * * @method _sessionStatus * @param {object} sessionInfo Session information * @return {object} promise Browser session promise * @private */ _sessionStatus: function (sessionInfo) { var defer = Q.defer(); this.sessionStatus = JSON.parse(sessionInfo).value; this.events.emit('driver:sessionStatus:sauce:' + this.browserName, this.sessionStatus); defer.resolve(); return defer.promise; }, /** * Loads the browser driver status * * @method _driverStatus * @param {object} statusInfo Driver status information * @return {object} promise Driver status promise * @private */ _driverStatus: function (statusInfo) { var defer = Q.defer(); this.driverStatus = JSON.parse(statusInfo).value; this.events.emit('driver:status:sauce:' + this.browserName, this.driverStatus); defer.resolve(); return defer.promise; }, /** * Creates an anonymus function that calls a webdriver * method that has no return value, emits an empty result event * if the function has been run * TODO: Name is weird, should be saner * * @method _createNonReturnee * @param {string} fnName Name of the webdriver function that should be called * @return {function} fn * @private */ _createNonReturnee: function (fnName) { return this._actionQueueNonReturneeTemplate.bind(this, fnName); }, /** * Generates a chain of webdriver calls for webdriver * methods that don't have a return value * TODO: Name is weird, should be saner * * @method _actionQueueNonReturneeTemplate * @param {string} fnName Name of the webdriver function that should be called * @param {string} hash Unique action hash * @param {string} uuid Unique action hash * @chainable * @private */ _actionQueueNonReturneeTemplate:function (fnName, hash, uuid) { this.actionQueue.push(this.webdriverClient[fnName].bind(this.webdriverClient)); this.actionQueue.push(this._generateDummyDriverMessageFn.bind(this, fnName, hash, uuid)); return this; }, /** * Creates a driver notification with an empty value * TODO: Name is weird, should be saner * * @method _generateDummyDriverMessageFn * @param {string} fnName Name of the webdriver function that should be called * @param {string} hash Unique action hash * @param {string} uuid Unique action hash * @return {object} promise Driver message promise * @private */ _generateDummyDriverMessageFn: function (fnName, hash, uuid) { var deferred = Q.defer(); this.events.emit('driver:message', {key: fnName, value: null, uuid: uuid, hash: hash}); deferred.resolve(); return deferred.promise; } }; /** * Determines if the driver is a "multi" browser driver, * e.g. can handle more than one browser * * @method isMultiBrowser * @return {bool} isMultiBrowser Driver can handle more than one browser */ module.exports.isMultiBrowser = function () { return true; }; /** * Verifies a browser request * TODO: Still a noop, need to add "verify the browser" logic * * @method verifyBrowser * @return {bool} isVerifiedBrowser Driver can handle this browser */ module.exports.verifyBrowser = function () { return true; }; /** * Determines if the driver comes with its own browsers bundled * * @method dummyBrowser * @return {bool} isDummyBrowser Driver does not rely on Dalek browser modules */ module.exports.dummyBrowser = function () { return true; }; /** * Returnes the browser that comes bundled with the driver * * @method getBrowser * @param {object} driver Driver instance * @return {object} Browser module */ module.exports.getBrowser = function (driver) { return {configuration: null, module: new Browser(driver)}; }; /** * Creates a new driver instance * * @method create * @param {object} opts Options needed to kick off the driver * @return {Sauce} driver */ module.exports.create = function (opts) { // load the remote command helper methods var dir = __dirname + '/lib/commands/'; fs.readdirSync(dir).forEach(function (file) { require(dir + file)(Sauce); }); return new Sauce(opts); };