API Docs for: 0.0.9
Show:

File: lib/dalek/remote.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 Q = require('q');
var http = require('http');

/**
 * Mimics a real browser that runs in a remote dalek instance
 *
 * @module Remote
 * @class Remote
 * @namespace Dalek
 */

var Remote = {

  /**
   * Remote webdriver path
   *
   * @property path
   * @type {string}
   * @default ''
   */

  path: '',

  /**
   * Remote port
   *
   * @property port
   * @type {integer}
   * @default 9020
   */

  port: 9020,

  /**
   * Remote host
   *
   * @property host
   * @type {string} 
   * @default ''
   */

  host: '',

  /**
   * Url (with placeholders) to launch browsers on the remote instance
   *
   * @property defaultLaunchUrl
   * @type {string} 
   * @default http://{{host}}:{{port}}/dalek/launch/{{browser}}
   */

  defaultLaunchUrl: 'http://{{host}}:{{port}}/dalek/launch/{{browser}}',
  
  /**
   * Url (with placeholders) to kill browsers on the remote instance
   *
   * @property defaultKillUrl
   * @type {string} 
   * @default http://{{host}}:{{port}}/dalek/kill
   */

  defaultKillUrl: 'http://{{host}}:{{port}}/dalek/kill',

  /**
   * Url to start a specific remote browser session
   * 
   * @property launchUrl
   * @type {string} 
   * @default ''
   */

  launchUrl: '',

  /**
   * Url to kill a specific remote browser session
   * 
   * @property killUrl
   * @type {string} 
   * @default ''
   */

  killUrl: '',

  /**
   * Internal config name of the browser to start remotly
   * 
   * @property browser
   * @type {string} 
   * @default '
   */

  browser: '',

  /**
   * Remote browser alias to start a browser
   * (browser.name or browser.actAs or user input browser alias)
   * 
   * @property browserAlias
   * @type {string} 
   * @default '
   */

  browserAlias: '',

  /**
   * Driver defaults
   *
   * @property driverDefaults
   * @type {object}
   */

  driverDefaults: {},

  /**
   * Request secret or false when unsecure
   *
   * @param secret
   * @type {string|bool}
   * @default false
   */

  secret: false,

  /**
   * Stores & validates the incoming browser config
   *
   * @method launch
   * @param {object} configuration Browser configuration
   * @param {EventEmitter2} events EventEmitter (Reporter Emitter instance)
   * @param {Dalek.Internal.Config} config Dalek configuration class
   * @return {object} Browser promise
   */

  launch: function (configuration, events, config) {
    var deferred = Q.defer();

    // store injected configuration/log event handlers
    this.reporterEvents = events;
    this.configuration = configuration;
    this.config = config;

    // load configs
    this._loadConfigs(configuration, config);

    // fire up the remote browser
    var request = http.request(this.launchUrl, this._afterRemoteBrowserLaunched.bind(this, deferred));

    // set secret header if available
    if (this.secret) {
      request.setHeader('secret-token', this.secret);
    }

    // fire the request
    request.end();
    
    return deferred.promise;
  },

  /**
   * Kills the remote browser
   *
   * @method kill
   * @return {object} Promise
   */

  kill: function () {
    http.request(this.killUrl).end();
    return this;
  },

  /**
   * Injects the browser name
   *
   * @method setBrowser
   * @param {string} browser Browser to launch
   * @chainable
   */

  setBrowser: function (browser) {
    this.browser = browser;

    // generate kill & launch url
    this.launchUrl = this._replaceUrlPlaceholder(this.defaultLaunchUrl);
    this.killUrl = this._replaceUrlPlaceholder(this.defaultKillUrl);
    return this;
  },

  /**
   * Listens on the response of the initial browser launch call
   * and collects the response data, fires the _handshakeFinished call
   * after the response ended
   *
   * @method _afterRemoteBrowserLaunched
   * @param {object} deferred Promise
   * @param {object} response Browser launch response object
   * @chainable
   * @private
   */

  _afterRemoteBrowserLaunched: function (deferred, response) {
    // collect remote browser information and
    // start the test execution after the handshake finished
    var data = [];
    response.on('data', function (chunk) {
      data.push(chunk+'');
    }).on('end', this._handshakeFinished.bind(this, deferred, data));
    return this;
  },

  /**
   * Parses the response data of the initial browser handshake,
   * sets the longName, desiredCapabilities & driverDefaults,
   * emits the browser data (can be used by reporters & drivers)
   *
   * @method _handshakeFinished
   * @param {object} deferred Promise
   * @param {array} data Remote browser data (longName, desiredCapabilities, driverDefaults)
   * @chainable
   * @private
   */

  _handshakeFinished: function (deferred, data) {
    var br = JSON.parse(data);

    // check if an error happened
    if (!!br.error) {
      this.reporterEvents.emit('error', br.error);
      return this;
    }

    // update the desired capabilities & browser defaults in the remote instance
    this.longName = br.name;
    this.desiredCapabilities = br.caps;
    this.driverDefaults = br.defaults;

    // update the desired capabilities & browser defaults in the driver instance
    this.reporterEvents.emit('browser:notify:data:' + this.browser, {desiredCapabilities: this.desiredCapabilities, defaults: this.driverDefaults});

    deferred.resolve();
    return this;
  },

  /**
   * Sets the host & port of the remote instance,
   * extracts the remote browser to call,
   * generates the launch & kill objects for this session
   *
   * @method _loadConfigs
   * @param {object} configuration Browser session configuration
   * @param {object} config Dalek configuration data
   * @chainable
   * @private
   */

  _loadConfigs: function (configuration, config) {
    // set host & port
    this.host = configuration.host ? configuration.host : this.host;
    this.port = configuration.port ? configuration.port : this.port;

    // get the browser alias & secret
    this.browserAlias = this.browser;
    var browserConfig = config.get('browsers');
    if (browserConfig && browserConfig[0] && browserConfig[0][this.browser]) {
      this.browserAlias = browserConfig[0][this.browser].actAs ? browserConfig[0][this.browser].actAs : this.browserAlias;
      this.browserAlias = browserConfig[0][this.browser].name ? browserConfig[0][this.browser].name : this.browserAlias;
      this.secret = browserConfig[0][this.browser].secret ? browserConfig[0][this.browser].secret : this.secret;
    }

    // generate kill & launch url
    this.launchUrl = this._replaceUrlPlaceholder(this.defaultLaunchUrl);
    this.killUrl = this._replaceUrlPlaceholder(this.defaultKillUrl);

    return this;
  },

  /**
   * Replaces {{host}}, {{port}} & {{browser}} placeholders
   * in the given url with data from this.host, this.port & this.browserAlias
   *
   * @method _replaceUrlPlaceholder
   * @param {string} url Url with placeholder
   * @return {string} Url with replaced placeholders
   * @private
   */

  _replaceUrlPlaceholder: function (url) {
    url = url.replace('{{port}}', this.port).replace('{{host}}', this.host).replace('{{browser}}', this.browserAlias);
    return url;
  }
};

module.exports = Remote;