API Docs for: 0.0.9
Show:

File: lib/dalek/driver.js

/*!
 *
 * 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 async = require('async');

// int. libs
var Suite = require('./suite');

/**
 * Configures the driver instance
 *
 * @constructor
 */

var Driver = function (options) {
  // add configuration data to the driver instance
  this.config = options.config;
  this.browser = this.config.get('browser');
  this.files = this.config.get('tests');
  this.drivers = this.config.get('driver');

  // flag if we use the canary driver builds
  this.driverIsCanary = false;

  // link driver events
  this.driverEmitter = options.driverEmitter;
  this.reporterEvents = options.reporterEvents;
};

/**
 * Generates & starts drivers & browsers
 * the tests will be run in
 *
 * @module DalekJS
 * @class Driver
 * @namespace Dalek
 * @part Driver
 * @api
 */

Driver.prototype = {

  /**
   * Checks if the requested driver is available
   *
   * @method isDriver
   * @param {string} driver Name of the requested driver
   * @return {bool} isDriver Driver is availavle
   */

  isDriver: function (driver) {
    try {
      require.resolve('dalek-driver-' + driver);
    } catch (e) {
      try {
        require.resolve('dalek-driver-' + driver + '-canary');
      } catch (e) {
        return false;
      }
      this.driverIsCanary = true;
      return true;
    }
    return true;
  },

  /**
   * Loads the requested driver
   * Emits an event to the reporter
   *
   * @method loadDriver
   * @param {string} driver Name of the requested driver
   * @return {object} driverModule Instance of the driver module
   */

  loadDriver: function (driver) {
    this.reporterEvents.emit('report:log:system', 'dalek-internal-driver: Loading driver: "' + driver + '"');
    return require('dalek-driver-' + driver + (this.driverIsCanary ? '-canary' : ''));
  },

  /**
   * Returns a list with browser driver instances
   *
   * @method getDrivers
   * @return {array} verifiedDrivers
   */

  getDrivers: function () {
    return this.drivers.map(this.getVerifiedBrowser, this)[0];
  },

  /**
   * Returns a list with browser driver instances
   *
   * @method getVerifiedBrowser
   * @param {string} driver Name of the requested driver
   * @return {array} verifiedDrivers Array of dribver 'run' functions
   */

  getVerifiedBrowser: function (driver) {
    return this.browser.map(this.getVerifiedDriver.bind(this, this.loadDriver(driver), driver));
  },

  /**
   * Returns a scoped version of the driver run function
   *
   * @method getVerifiedDriver
   * @param {object} driverModule Instance of the used driver
   * @param {string} driver Name of ther used driver
   * @param {string} browser Name of the used browser
   * @return {function} run Function that kicks off execution of a testsuite chain in a browser
   */

  getVerifiedDriver: function (driverModule, driver, browser) {
    return this.run.bind(this, driver, driverModule, browser);
  },

  /**
   * Loads a browser driver
   *
   * @method loadBrowserConfiguration
   * @param {string} browser Name of the requested browser driver
   * @param {object} browsers Configuration options for the requested browser
   * @return {object} browserConfiguration Browser driver isntance and configuration meta data
   */

  loadBrowserConfiguration: function (browser, browsers, driver) {
    var browserConfiguration;

    if (driver.dummyBrowser && driver.dummyBrowser()) {
      return driver.getBrowser(driver);
    }

    try {
      browserConfiguration = this.getDefaultBrowserConfiguration(browser, browsers);
    } catch (e) {
      browserConfiguration = this.getUserBrowserConfiguration(browser, browsers);
    }

    return browserConfiguration;
  },

  /**
   * Loads the default browser driver
   *
   * @method getDefaultBrowserConfiguration
   * @param {string} browser Name of the requested browser driver
   * @param {object} browsers Configuration options for the requested browser
   * @return {object} browserConfiguration Browser driver isntance and configuration meta data
   */

  getDefaultBrowserConfiguration: function (browser, browsers) {
    var browserConfiguration = {configuration: null, module: null};

    // set browser configuration
    if (browsers[browser]) {
      browserConfiguration.configuration = browsers[browser];
    }

    // try to load `normal` browser modules first,
    // if that doesnt work, try canary builds
    try {
      // check if the browser is a remote instance
      // else, try to load the local browser
      if (browserConfiguration.configuration && browserConfiguration.configuration.type === 'remote') {
        browserConfiguration.module = require('./remote');
      } else {
        browserConfiguration.module = require('dalek-browser-' + browser);
      }
    } catch (e) {
      browserConfiguration.module = require('dalek-browser-' + browser + '-canary');
    }

    return browserConfiguration;
  },

  /**
   * Loads a user configured browser driver
   *
   * @method getUserBrowserConfiguration
   * @param {string} browser Name of the requested browser driver
   * @param {object} browsers Configuration options for the requested browser
   * @return {object} browserConfiguration Browser driver isntance and configuration meta data
   */

  getUserBrowserConfiguration: function (browser, browsers) {
    var browserConfiguration = {configuration: null, module: null};

    if (browsers && browsers[browser] && browsers[browser].actAs) {
      browserConfiguration.module = require('dalek-browser-' + browsers[browser].actAs);
      browserConfiguration.configuration = browsers[browser];
    }

    if (!browserConfiguration.module && browser.search(':') !== -1) {
      var args = browser.split(':');
      var extractedBrowser = args[0].trim();
      var browserType = args[1].trim().toLowerCase();
      browserConfiguration.module = require('dalek-browser-' + extractedBrowser);

      if (browserConfiguration.module && browserConfiguration.module.browserTypes && browserConfiguration.module.browserTypes[browserType]) {
        var binary = (process.platform === 'win32' ? browserConfiguration.module.browserTypes[browserType].win32 : browserConfiguration.module.browserTypes[browserType].darwin);
        browserConfiguration.configuration = {
          binary: binary,
          type: browserType
        };
      }
    }

    return browserConfiguration;
  },

  /**
   * Couple driver & session status events for the reporter
   *
   * @method coupleReporterEvents
   * @param {string} driverName Name of the requested driver
   * @param {string} browser Name of the requested browser
   * @chainable
   */

  coupleReporterEvents: function (driverName, browser) {
    this.driverEmitter.on('driver:sessionStatus:' + driverName + ':' + browser, this.reporterEvents.emit.bind(this.reporterEvents, 'report:driver:session'));
    this.driverEmitter.on('driver:status:' + driverName + ':' + browser, this.reporterEvents.emit.bind(this.reporterEvents, 'report:driver:status'));
    return this;
  },

  /**
   * Returns a list of testsuite runner functions
   *
   * @method getTestsuiteInstances
   * @param {object} driverInstance Instance of the requested driver
   * @return {array} testsuiteRunners List of testsuites that should be run
   */

  getTestsuiteInstances: function (driverInstance) {
    return this.files.map(this.createTestsuiteInstance.bind(this, driverInstance));
  },

  /**
   * Creates a testsuite runner function
   *
   * @method createTestsuiteInstance
   * @param {object} driverInstance Instance of the requested driver
   * @param {string} file Filename of the testsuite
   * @return {function} testsuiteRunner Runner function from the testsuite
   */

  createTestsuiteInstance: function (driverInstance, file) {
    var suite = new Suite({numberOfSuites: this.files.length, file: file, driver: driverInstance, driverEmitter: this.driverEmitter, reporterEmitter: this.reporterEvents});
    return suite.run.bind(suite);
  },

  /**
   * Generates a testsuite instance, emits the
   * browser running event & starts a new async() sesries execution
   * Will be called when the driver is ready
   *
   * @method _onDriverReady
   * @param {string} browser Name of the requested browser
   * @param {string} driverName Name of the requested driver
   * @param {function} callback Asyncs next() callback function
   * @param {object} driverInstance Instance of the requested driver
   * @chainable
   * @private
   */

  _onDriverReady: function (browser, driverName, callback, driverInstance) {
    // generate testsuite instance from test files
    var testsuites = this.getTestsuiteInstances(driverInstance);
    this.reporterEvents.emit('report:run:browser', driverInstance.webdriverClient.opts.longName);
    async.series(testsuites, this._onTestsuiteComplete.bind(this, callback, driverName, browser));
    return this;
  },

  /**
   * Emits a 'tests complete' event & calls async's next() callback
   *
   * @method _onTestsuiteComplete
   * @param {function} callback Async's next() callback function
   * @param {string} driverName Name of the requested driver
   * @param {string} browser Name of the requested browser
   * @chainable
   * @private
   */

  _onTestsuiteComplete: function (callback, driverName, browser) {
    this.driverEmitter.emit('tests:complete:' + driverName + ':' + browser);
    callback();
    return this;
  },

  /**
   * Driver runner function.
   * Registers event handlers for this run,
   * loads browser & driver configuration & instances,
   * emits the 'driver ready' event for the browser/driver combination
   *
   * @method run
   * @param {string} driverName Name of the requested driver
   * @param {object} driverModule Instance of the used driver module
   * @param {string} browser Name of the requested browser
   * @param {function} callback Asyncs next() callback function
   * @chainable
   */

  run: function (driverName, driverModule, browser, callback) {
    // load browser configuration
    var browsersRaw = this.config.get('browsers');
    var browsers = [];

    // Check if we have a valid browser conf, then get the data out
    if (browsersRaw !== null) {
      browsers = browsersRaw[0];
    }

    // init the browser configuration
    var browserConfiguration = this.loadBrowserConfiguration(browser, browsers, driverModule);

    // check if we need to inject the browser alias into the browser module
    if (browserConfiguration.module.setBrowser) {
      browserConfiguration.module.setBrowser(browser);
    }

    // init the driver instance
    var driverInstance = driverModule.create({events: this.driverEmitter, reporter: this.reporterEvents, browser: browser, config: this.config, browserMo: browserConfiguration.module, browserConf: browserConfiguration.configuration});
    // couple driver & session status events for the reporter
    this.coupleReporterEvents(driverName, browser);

    // register shutdown handler
    if (driverInstance.webdriverClient.opts && driverInstance.webdriverClient.opts.kill) {
      this.driverEmitter.on('killAll', driverInstance.webdriverClient.opts.kill.bind(driverInstance.webdriverClient.opts));
    }

    if (driverInstance.webdriverClient.quit) {
      this.driverEmitter.on('killAll', driverInstance.webdriverClient.quit.bind(driverInstance.webdriverClient));
    }

    // dispatch some (web)driver events to the reporter
    this.driverEmitter.on('driver:webdriver:response', function (res) {
      this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.statusCode + ' ' + res.method + ' ' + res.path);
      this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.data);
    }.bind(this));

    // run the tests in the browser, when the driver is ready
    // emit the tests:complete event, when all tests have been run
    this.driverEmitter.on('driver:ready:' + driverName + ':' + browser, this._onDriverReady.bind(this, browser, driverName, callback, driverInstance));
    return this;
  }
};

// export driver module
module.exports = Driver;