API Docs for: 0.0.5
Show:

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

// export the driver base function
module.exports = function exportDriverBase(WebDriver) {

  /**
   * Native Webdriver base class
   *
   * @module DalekJS
   * @class Driver
   * @namespace Internal
   */

  var Driver = {

    /**
     * Parses an JSON Wire protocol dummy url
     *
     * @method parseUrl
     * @param {string} url URL with placeholders
     * @param {object} options List of url options
     * @return {string} url Parsed URL
     */

    parseUrl: function (url, options) {
      return Object.keys(options).reduce(this._replacePlaceholderInUrl.bind(this, options), url);
    },

    /**
     * Replaces placeholders in urls
     *
     * @method _replacePlaceholderInUrl
     * @param {object} options List of url options
     * @param {string} url URL with placeholders
     * @param {string} option Option to process
     * @return {string} url Parsed URL
     * @private
     */

    _replacePlaceholderInUrl: function (options, url, option) {
      return url.replace(':' + option, options[option]);
    },

    /**
     * Generates a set of params for the message body of the request
     *
     * @method generateParamset
     * @param {object|null} requestedParams Keys & placeholders for the paramset
     * @param {object} providedParams Values for the paramset
     * @return {object} params Params for the message body
     */

    generateParamset: function (requestedParams, providedParams) {
      return !requestedParams ? {} : requestedParams.reduce(this._mapParams.bind(this, providedParams), {});
    },

    /**
     * Mpas object values & keys of two objects
     *
     * @method _mapParams
     * @param {object} providedParams Values for the paramset
     * @param {object} params The object to be filled
     * @param {string} param The key of the output object
     * @param {integer} idx Index of the iteration
     * @return {object} params Params for the message body
     * @private
     */

    _mapParams: function (providedParams, params, param, idx) {
      params[param] = providedParams[idx];
      return params;
    },

    /**
     * Generates the message body for webdriver client requests of type POST
     *
     * @method generateBody
     * @param {object} options Browser options (name, bin path, etc.)
     * @param {function|undefined} cb Callback function that should be invoked to generate the message body
     * @param {Dalek.Internal.Webdriver} wd Webdriver base object
     * @param {object} params Parameters that should be part of the message body
     * @return {string} body Serialized JSON of body request data
     */

    generateBody: function (options, cb, wd, params) {
      // if no cb is given, generate a body with the `desiredCapabilities` object
      if (!cb) {
        // check if we have parameters set up
        if (Object.keys(params).length > 0) {
          return JSON.stringify(params);
        }
        return '';
      }

      // invoke the given callback & stringify
      var data = cb.call(wd, params);
      return data === null ? '{}' : JSON.stringify(data);
    },

    /**
     * Generates the request options for a webdriver client request
     *
     * @method generateRequestOptions
     * @param {string} hostname Hostname of the webdriver server
     * @param {integer} port Port of the webdriver server
     * @param {string} prefix Url address prefix of the webdriver endpoint
     * @param {string} url Url of the webdriver method
     * @param {string} method Request method e.g. (GET, POST, DELETE, PUT)
     * @param {string} body The message body of the request
     * @return {object} options Request options
     */

    generateRequestOptions: function (hostname, port, prefix, url, method, body, auth) {
      var options = {
        hostname: hostname,
        port: port,
        path: prefix + url,
        method: method,
        headers: {
          'Content-Type': 'application/json;charset=utf-8',
          'Content-Length': Buffer.byteLength(body, 'utf8')
        }
      };

      // check if auth information is available
      if (auth) {
        options.auth = auth;
      }

      return options;
    },

    /**
     * Generates a new webdriver client command
     * Takes a skeleton of obtions that will be converted
     * into a new function that can be invoked & will issue
     * a webdriver command to the webdriver server
     *
     * @method addCommand
     * @param {object} remote Object skeleton that will be turned into a webdriver client method
     * @chainable
     */

    addCommand: function (remote) {
      // assign the generated function to the webdriver prototype
      WebDriver.prototype[remote.name] = this._generateWebdriverCommand(remote, this);
      return this;
    },

    /**
     * Generates the webdriver callback function
     *
     * @method _generateWebdriverCommand
     * @param {object} remote Dummy request body (function name, url, method)
     * @param {DalekJs.Internal.Driver} driver Driver instance
     * @return {function} webdriverCommand Generated webdriver command function
     * @private
     */

    _generateWebdriverCommand: function (remote, driver) {
      return function webdriverCommand() {
        var deferred = Q.defer();
        // the request meta data
        var params = Driver.generateParamset(remote.params, arguments);
        var body = Driver.generateBody({}, remote.onRequest, this, params);
        var options = Driver.generateRequestOptions(this.opts.host, this.opts.port, this.opts.path, Driver.parseUrl(remote.url, this.options), remote.method, body, this.opts.auth);

        // generate the request, wait for response & fire the request
        var req = new http.ClientRequest(options);
        req.on('response', driver._onResponse.bind(this, driver, remote, options, deferred));
        req.end(body);

        return deferred.promise;
      };
    },

    /**
     * Response callback function
     *
     * @method _onResponse
     * @param {DalekJs.Internal.Driver} driver Driver instance
     * @param {object} remote Dummy request body (function name, url, method)
     * @param {object} options Request options (method, port, path, headers, etc.)
     * @param {object} deferred Webdriver command deferred
     * @param {object} response Response from the webdriver server
     * @chainable
     * @private
     */

    _onResponse: function (driver, remote, options, deferred, response) {
      this.data = '';
      response.on('data', driver._concatDataChunks.bind(this));
      response.on('end', driver._onResponseEnd.bind(this, driver, response, remote, options, deferred));
      return this;
    },

    /**
     * Concatenates chunks of strings
     *
     * @method _concatDataChunks
     * @param {string} chunk String to add
     * @return {string} data Concatenated string
     * @private
     */

    _concatDataChunks: function (chunk) {
      return this.data += chunk;
    },

    /**
     * Response end callback function
     *
     * @method _onResponseEnd
     * @param {DalekJs.Internal.Driver} driver Driver instance
     * @param {object} response Response from the webdriver server
     * @param {object} remote Dummy request body (function name, url, method)
     * @param {object} options Request options (method, port, path, headers, etc.)
     * @param {object} deferred Webdriver command deferred
     * @chainable
     * @priavte
     */

    _onResponseEnd: function (driver, response, remote, options, deferred) {
      return driver[(response.statusCode === 500 ? '_onError' : '_onSuccess')].bind(this)(driver, response, remote, options, deferred);
    },

    /**
     * On error callback function
     *
     * @method _onError
     * @param {DalekJs.Internal.Driver} driver Driver instance
     * @param {object} response Response from the webdriver server
     * @param {object} remote Dummy request body (function name, url, method)
     * @param {object} options Request options (method, port, path, headers, etc.)
     * @param {object} deferred Webdriver command deferred
     * @chainable
     * @private
     */

    _onError: function (driver, response, remote, options, deferred) {
      // Provide a default error handler to prevent hangs.
      if (!remote.onError) {
        remote.onError = function (request, remote, options, deferred, data) {
          data = JSON.parse(data);
          var value = -1;
          if (typeof data.value.message === 'string') {
            var msg = JSON.parse(data.value.message);
            value = msg.errorMessage;
          }
          deferred.resolve(JSON.stringify({'sessionId': data.sessionId, value: value}));
        };
      }
      remote.onError.call(this, response, remote, options, deferred, this.data);
      return this;
    },

    /**
     * On success callback function
     *
     * @method _onSuccess
     * @param {DalekJs.Internal.Driver} driver Driver instance
     * @param {object} response Response from the webdriver server
     * @param {object} remote Dummy request body (function name, url, method)
     * @param {object} options Request options (method, port, path, headers, etc.)
     * @param {object} deferred Webdriver command deferred
     * @chainable
     * @private
     */

    _onSuccess: function (driver, response, remote, options, deferred) {
      // log response data
      this.events.emit('driver:webdriver:response', {
        statusCode: response.statusCode,
        method: response.req.method,
        path: response.req.path,
        data: this.data
      });
      
      if (remote.onResponse) {
        remote.onResponse.call(this, response, remote, options, deferred, this.data);
      } else {
        deferred.resolve(this.data);
      }
      return this;
    }
  };

  return Driver;
};