#

API Docs for: 0.0.2
Show: Inherited Protected Private Deprecated

# File: index.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 cp = require('child_process');
    var portscanner = require('portscanner');

    // int. libs
    var iedriver = require('./lib/iedriver');

    /**
     * This module is a browser plugin for [DalekJS](//github.com/dalekjs/dalek).
     * It provides all a WebDriverServer & browser launcher for Internet Explorer.
     *
     * The browser plugin can be installed with the following command:
     *
     * ```bash
     * $ npm install dalek-browser-ie --save-dev
     * ```
     *
     * You can use the browser plugin by adding a config option to the your Dalekfile
     *
     * ```javascript
     * "browsers": ["IE"]
     * ```
     *
     * Or you can tell Dalek that it should test in this browser via the command line:
     *
     * ```bash
     * $ dalek mytest.js -b IE
     * ```
     *
     * The Webdriver Server tries to open Port 5555 by default,
     * if this port is blocked, it tries to use a port between 5555 & 5564
     * You can specifiy a different port from within your [Dalekfile](/pages/config.html) like so:
     *
     * ```javascript
     * "browsers": {
     *   "ie": {
     *     "port": 6555
     *   }
     * }
     * ```
     *
     * It is also possible to specify a range of ports:
     *
     * ```javascript
     * "browsers": {
     *   "ie": {
     *     "portRange": [6100, 6120]
     *   }
     * }
     * ```
     *
     * @module DalekJS
     * @class InternetExplorer
     * @namespace Browser
     * @part InternetExplorer
     * @api
     */

    var InternetExplorer = {

      /**
       * Verbose version of the browser name
       *
       * @property longName
       * @type string
       * @default Internet Explorer
       * @api
       */

      longName: 'Internet Explorer',

      /**
       * Default port of the IEDriverServer
       * The port may change, cause the port conflict resultion
       * tool might pick another one, if the default one is blocked
       *
       * @property port
       * @type integer
       * @default 5555
       */

      port: 5555,

      /**
       * Default maximum port of the IEDriverServer
       * The port is the highest port in the range that can be allocated
       * by the IEDriverServer
       *
       * @property maxPort
       * @type integer
       * @default 5654
       */

      maxPort: 5654,

      /**
       * Default host of the IEDriverServer
       * The host may be overridden with
       * a user configured value
       *
       * @property host
       * @type string
       * @default localhost
       */

      host: 'localhost',

      /**
       * Default desired capabilities that should be
       * transferred when the browser session gets requested
       *
       * @property desiredCapabilities
       * @type object
       */

      desiredCapabilities: {
        browserName: 'InternetExplorer'
      },

      /**
       * Driver defaults, what should the driver be able to access.
       *
       * @property driverDefaults
       * @type object
       */

      driverDefaults: {
        viewport: true,
        status: true,
        sessionInfo: true
      },

      /**
       * Path to the IEDriverServer.exe file
       *
       * @property path
       * @type string
       * @default /
       */

      path: '/',

      /**
       * Child process instance of the IEDriverServer
       *
       * @property spawned
       * @type null|Object
       */

      spawned: null,

      /**
       * IE processes that are running on startup,
       * and therefor shouldn`t be closed
       *
       * @property openProcesses
       * @type array
       * @default []
       */

      openProcesses: [],

      /**
       * Resolves the driver port
       *
       * @method getPort
       * @return {integer} port WebDriver server port
       */

      getPort: function () {
        return this.port;
      },

      /**
       * Returns the driver host
       *
       * @method getHost
       * @return {string} host WebDriver server hostname
       */

      getHost: function () {
        return this.host;
      },

      /**
       * Resolves the maximum range for the driver port
       *
       * @method getMaxPort
       * @return {integer} port Max WebDriver server port range
       */

      getMaxPort: function () {
        return this.maxPort;
      },

      /**
       * Launches the driver
       * (the driver takes care of launching the browser)
       *
       * @method launch
       * @return {object} promise 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;

        // check for a user set port
        var browsers = this.config.get('browsers');
        if (browsers && Array.isArray(browsers)) {
          browsers.forEach(this._checkUserDefinedPorts.bind(this));
        }

        // check if the current port is in use, if so, scan for free ports
        portscanner.findAPortNotInUse(this.getPort(), this.getMaxPort(), this.getHost(), this._checkPorts.bind(this, deferred));
        return deferred.promise;
      },

      /**
       * Kills the driver & browser processes
       *
       * @method kill
       * @chainable
       */

      kill: function () {
        // get a list of all running processes
        this._list(function(svc){
          // filter out the browser process
          svc.forEach(function (item, idx) {
            Object.keys(item).forEach(function (key) {
              if(svc[idx][key] === 'iexplore.exe') {
                // kill the browser process
                this._kill(svc[idx].PID);
              }
            }.bind(this));
          }.bind(this));
        }.bind(this),true);

        // kill the driver process
        this.spawned.kill('SIGTERM');
        return this;
      },

      _checkPorts: function (deferred, err, port) {
        // check if the port was blocked & if we need to switch to another port
        if (this.port !== port) {
          this.reporterEvents.emit('report:log:system', 'dalek-browser-ie: Switching to port: ' + port);
          this.port = port;
        }

        // invoke the ie driver afterwards
        this._startIEdriver(deferred);
        return this;
      },

      _startIEdriver: function (deferred) {
        var stream = '';
        this.spawned = cp.spawn(iedriver.path, ['--port=' + this.getPort()]);

        this.spawned.stdout.on('data', function (data) {
          var dataStr = data + '';
          stream += dataStr;
          if (stream.search('Listening on port') !== -1) {
            deferred.resolve();
          }
        });
        return this;
      },

      /**
       * Process user defined ports
       *
       * @method _checkUserDefinedPorts
       * @param {object} browser Browser configuration
       * @chainable
       * @private
       */

      _checkUserDefinedPorts: function (browser) {
        // check for a single defined port
        if (browser.ie && browser.ie.port) {
          this.port = parseInt(browser.ie.port, 10);
          this.maxPort = this.port + 90;
          this.reporterEvents.emit('report:log:system', 'dalek-browser-ie: Switching to user defined port: ' + this.port);
        }

        // check for a port range
        if (browser.ie && browser.ie.portRange && browser.ie.portRange.length === 2) {
          this.port = parseInt(browser.ie.portRange[0], 10);
          this.maxPort = parseInt(browser.ie.portRange[1], 10);
          this.reporterEvents.emit('report:log:system', 'dalek-browser-ie: Switching to user defined port(s): ' + this.port + ' -> ' + this.maxPort);
        }

        return this;
      },

      /**
       * Lists all running processes (win only)
       *
       * @method _list
       * @param {Function} callback Receives the process object as the only callback argument
       * @param {Boolean} [verbose=false] Verbose output
       * @chainable
       * @private
       */

      _list: function(callback, verbose) {
        verbose = typeof verbose === 'boolean' ? verbose : false;
        cp.exec('tasklist /FO CSV' + (verbose === true ? ' /V' : ''), function (err, stdout) {
          var pi = stdout.split('\r\n');
          var p = [];

          pi.forEach(function (line) {
            if (line.trim().length !== 0) {
              p.push(line);
            }
          });

          var proc = [];
          var head = null;
          while (p.length > 1) {
            var rec = p.shift();
            rec = rec.replace(/\"\,/gi,'";').replace(/\"|\'/gi,'').split(';');
            if (head === null){
              head = rec;
              for (var i=0;i<head.length;i++){
                head[i] = head[i].replace(/ /gi,'');
              }
            } else {
              var tmp = {};
              for (var j=0;j<rec.length;j++){
                tmp[head[j]] = rec[j].replace(/\"|\'/gi,'');
              }
              proc.push(tmp);
            }
          }
          callback(proc);
        });

        return this;
      },

      /**
       * Kill a specific process (win only)
       *
       * @method _kill
       * @param {Number} PID Process ID
       * @param {Boolean} [force=false] Force close the process.
       * @param {Function} [callback] Callback after process has been killed
       * @chainable
       * @private
       */

      _kill: function(pid, force, callback) {
        if (!pid){
          throw new Error('PID is required for the kill operation.');
        }
        callback = callback || function(){};
        if (typeof force === 'function'){
          callback = force;
          force = false;
        }
        cp.exec('taskkill /PID ' + pid + (force === true ? ' /f' : ''),callback);
        return this;
      }

    };

    // expose the module
    module.exports = InternetExplorer;