index.js

Maintainability

68.71

Lines of code

369

Created with Raphaël 2.1.002550751002016-1-9

2016-1-9
Maintainability: 68.71

Created with Raphaël 2.1.001002003004002016-1-9

2016-1-9
Lines of Code: 369

Difficulty

22.98

Estimated Errors

1.23

Function weight

By Complexity

Created with Raphaël 2.1.0_checkUserDefinedBinary4

By SLOC

Created with Raphaël 2.1.0_checkUserDefinedBinary22
1
/*!
2
 *
3
 * Copyright (c) 2013 Sebastian Golasch
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a
6
 * copy of this software and associated documentation files (the "Software"),
7
 * to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
 * and/or sell copies of the Software, and to permit persons to whom the
10
 * Software is furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included
13
 * in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
 * DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
 
25
'use strict';
26
 
27
// ext. libs
28
var Q = require('q');
29
var fs = require('fs');
30
var phantomjs = require('phantomjs');
31
var portscanner = require('portscanner');
32
var spawn = require('child_process').spawn;
33
 
34
/**
35
 * This module is a browser plugin for [DalekJS](//github.com/dalekjs/dalek).
36
 * It provides a browser launcher as well the PhantomJS browser itself.
37
 *
38
 * The browser plugin comes bundled with the DalekJS base framework.
39
 *
40
 * You can use the browser plugin beside others (it is the default)
41
 * by adding a config option to the your Dalekfile:
42
 *
43
 * ```javascript
44
 * "browser": ["phantomjs", "chrome"]
45
 * ```
46
 *
47
 * Or you can tell Dalek that it should test in this & another browser via the command line:
48
 *
49
 * ```bash
50
 * $ dalek mytest.js -b phantomjs,chrome
51
 * ```
52
 *
53
 * The Webdriver Server tries to open Port 9001 by default,
54
 * if this port is blocked, it tries to use a port between 9002 & 9091
55
 * You can specifiy a different port from within your [Dalekfile](/pages/config.html) like so:
56
 *
57
 * ```javascript
58
 * "browsers": {
59
 *   "phantomjs": {
60
 *     "port": 5555 
61
 *   }
62
 * }
63
 * ```
64
 *
65
 * It is also possible to specify a range of ports:
66
 *
67
 * ```javascript
68
 * "browsers": {
69
 *   "phantomjs": {
70
 *     "portRange": [6100, 6120] 
71
 *   }
72
 * }
73
 * ```
74
 *
75
 * If you would like to use a different Phantom version than the one that comes bundled with the driver,
76
 * your are able to specify its location in your [Dalekfile](/pages/config.html):
77
 *
78
 * ```javascript
79
 * "browsers": {
80
 *   "phantomjs": {
81
 *     "binary": "~/bin/phantomjs" 
82
 *   }
83
 * }
84
 * ```
85
 *
86
 * If you would like to preserve the ability to use the bundled version,
87
 * you can also add an additional browser launcher in your [Dalekfile](/pages/config.html).
88
 *
89
 * ```javascript
90
 * "browsers": {
91
 *   "phantomjs:1.9.1": {
92
 *     "binary": "~/bin/phantomjs" 
93
 *   }
94
 * }
95
 * ```
96
 *
97
 * And then launch it like this:
98
 * 
99
 * ```bash
100
 * $ dalek mytest.js -b phantomjs:1.9.1
101
 * ```
102
 * 
103
 * @module DalekJS
104
 * @class PhantomJSDriver
105
 * @namespace Browser
106
 * @part PhantomJS
107
 * @api
108
 */
109
 
110
var PhantomJSDriver = {
111
 
112
  /**
113
   * Verbose version of the browser name
114
   *
115
   * @property
116
   * @type string
117
   * @default PhantomJS
118
   */
119
 
120
  longName: 'PhantomJS',
121
 
122
  /**
123
   * Default port of the PhantomJSDriver
124
   * The port may change, cause the port conflict resultion
125
   * tool might pick another one, if the default one is blocked
126
   *
127
   * @property
128
   * @type integer
129
   * @default 9001
130
   */
131
 
132
  port: 9001,
133
 
134
  /**
135
   * Default maximum port of the Ghostdriver Server
136
   * The port is the highest port in the range that can be allocated
137
   * by the Ghostdriver Server
138
   *
139
   * @property maxPort
140
   * @type integer
141
   * @default 9091
142
   */
143
 
144
  maxPort: 9091,
145
 
146
  /**
147
   * Default host of the PhantomJSDriver
148
   * The host may be overriden with
149
   * a user configured value
150
   *
151
   * @property
152
   * @type string
153
   * @default localhost
154
   */
155
 
156
  host: 'localhost',
157
 
158
  /**
159
   * Root path of the PhantomJSDriver
160
   *
161
   * @property
162
   * @type string
163
   * @default /wd/hub
164
   */
165
 
166
  path: '/wd/hub',
167
 
168
  /**
169
   * Default desired capabilities that should be
170
   * transferred when the browser session gets requested
171
   *
172
   * @property desiredCapabilities
173
   * @type object
174
   */
175
 
176
  desiredCapabilities: {
177
    version: phantomjs.version,
178
    browserName: 'phantomjs'
179
  },
180
 
181
  /**
182
   * Driver defaults, what should the driver be able to access.
183
   *
184
   * @property driverDefaults
185
   * @type object
186
   */
187
 
188
  driverDefaults: {
189
    viewport: true,
190
    status: true,
191
    sessionInfo: true
192
  },
193
 
194
  /**
195
   * Child process instance of the PhantomJS browser
196
   *
197
   * @property
198
   * @type null|Object
199
   */
200
 
201
  spawned: null,
202
 
203
  /**
204
   * Resolves the driver port
205
   *
206
   * @method getPort
207
   * @return integer
208
   */
209
 
210
  getPort: function () {
211
    return this.port;
212
  },
213
 
214
  /**
215
   * Resolves the maximum range for the driver port
216
   *
217
   * @method getMaxPort
218
   * @return {integer} port Max WebDriver server port range
219
   */
220
 
221
  getMaxPort: function () {
222
    return this.maxPort;
223
  },
224
 
225
  /**
226
   * Returns the driver host
227
   *
228
   * @method getHost
229
   * @type string
230
   */
231
 
232
  getHost: function () {
233
    return this.host;
234
  },
235
 
236
  /**
237
   * Launches PhantomJS, negoatiates a port & checks for a user set binary
238
   *
239
   * @method launch
240
   * @param {object} configuration Browser configuration
241
   * @param {EventEmitter2} events EventEmitter (Reporter Emitter instance)
242
   * @param {Dalek.Internal.Config} config Dalek configuration class
243
   * @return {object} promise Browser promise
244
   */
245
 
246
  launch: function (configuration, events, config) {
247
    var deferred = Q.defer();
248
 
249
    // store injected configuration/log event handlers
250
    this.reporterEvents = events;
251
    this.configuration = configuration;
252
    this.config = config;
253
 
254
    // check for a user set port
255
    var browsers = this.config.get('browsers');
256
    if (browsers && Array.isArray(browsers)) {
257
      browsers.forEach(this._checkUserDefinedPorts.bind(this));
258
    }
259
 
260
    // check if the current port is in use, if so, scan for free ports
261
    portscanner.findAPortNotInUse(this.getPort(), this.getMaxPort(), this.getHost(), this._checkPorts.bind(this, deferred));
262
    return deferred.promise;
263
  },
264
 
265
  /**
266
   * Kills the PhantomJSDriver processe
267
   *
268
   * @method kill
269
   * @chainable
270
   */
271
 
272
  kill: function () {
273
    this.spawned.kill('SIGTERM');
274
    return this;
275
  },
276
 
277
  /**
278
   * Checks if the def. port is blocked & if we need to switch to another port
279
   * Kicks off the process manager (for closing the opened browsers after the run has been finished)
280
   * Also starts the chromedriver instance 
281
   *
282
   * @method _checkPorts
283
   * @param {object} deferred Promise
284
   * @param {null|object} error Error object
285
   * @param {integer} port Found open port
286
   * @private
287
   * @chainable
288
   */
289
 
290
  _checkPorts: function (deferred, error, port) {
291
    // check if the port was blocked & if we need to switch to another port
292
    if (this.port !== port) {
293
      this.reporterEvents.emit('report:log:system', 'dalek-browser-phantomjs: Switching to port: ' + port);
294
      this.port = port;
295
    }
296
 
297
    // check the binary
298
    var binary = this._checkUserDefinedBinary(this.configuration, phantomjs.path);
299
 
300
    // launch the browser process
301
    this.spawned = spawn(binary, ['--webdriver', this.getPort(), '--ignore-ssl-errors=true']);
302
    this.spawned.stdout.on('data', this._launch.bind(this, deferred));
303
    return this;
304
  },
305
 
306
  /**
307
   * Checks the data stream from the launched phantom process
308
   *
309
   * @method _launch
310
   * @param {object} deferred Promise
311
   * @param {buffer} data Console output from Ghostdriver
312
   * @chainable
313
   * @private
314
   */
315
 
316
  _launch: function (deferred, data) {
317
    var stream = data + '';
318
    
319
    // check if ghostdriver could be launched    
320
    if (stream.search('GhostDriver - Main - running') !== -1) {
321
      deferred.resolve();
322
    } else if (stream.search('Could not start Ghost Driver') !== -1) {
323
      this.reporterEvents.emit('error', 'dalek-browser-phantomjs: Could not start Ghost Driver');
324
      deferred.reject('Could not start Ghost Driver');
325
      process.exit(127);
326
    }
327
 
328
    return this;
329
  },
330
 
331
  /**
332
   * Process user defined ports
333
   *
334
   * @method _checkUserDefinedPorts
335
   * @param {object} browser Browser configuration
336
   * @chainable
337
   * @private
338
   */
339
 
340
  _checkUserDefinedPorts: function (browser) {
341
    // check for a single defined port
342
    if (browser.phantomjs && browser.phantomjs.port) {
343
      this.port = parseInt(browser.phantomjs.port, 10);
344
      this.maxPort = this.port + 90;
345
      this.reporterEvents.emit('report:log:system', 'dalek-browser-phantomjs: Switching to user defined port: ' + this.port);
346
    }
347
 
348
    // check for a port range
349
    if (browser.phantomjs && browser.phantomjs.portRange && browser.phantomjs.portRange.length === 2) {
350
      this.port = parseInt(browser.phantomjs.portRange[0], 10);
351
      this.maxPort = parseInt(browser.phantomjs.portRange[1], 10);
352
      this.reporterEvents.emit('report:log:system', 'dalek-browser-phantomjs: Switching to user defined port(s): ' + this.port + ' -> ' + this.maxPort);
353
    }
354
 
355
    return this;
356
  },
357
 
358
  /**
359
   * Checks if the binary exists,
360
   * when set manually by the user
361
   *
362
   * @method _checkUserDefinedBinary
363
   * @param {string} binary Path to the browser binary
364
   * @return {bool|string} Binary path if binary exists, else false
365
   * @private
366
   */
367
 
368
  _checkUserDefinedBinary: function (configuration, defaultBinary) {
369
    var binary = defaultBinary;
370
 
371
    // check if we have a user defined binary
372
    if (configuration && configuration.binary) {
373
      binary = configuration.binary;
374
    }
375
 
376
    // check if we need to replace the users home directory
377
    if (process.platform === 'darwin' && binary.trim()[0] === '~') {
378
      binary = binary.replace('~', process.env.HOME);
379
    }
380
 
381
    // check if the binary exists
382
    if (!fs.existsSync(binary)) {
383
      this.reporterEvents.emit('error', 'dalek-driver-phantomjs: Binary not found: ' + binary);
384
      process.exit(127);
385
      return false;
386
    }
387
 
388
    return binary;
389
  }
390
 
391
};
392
 
393
module.exports = PhantomJSDriver;