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 | 1 | 'use strict'; |
26 | | |
27 | | // ext. libs |
28 | 1 | var Q = require('q'); |
29 | 1 | var fs = require('fs'); |
30 | 1 | var cp = require('child_process'); |
31 | 1 | var portscanner = require('portscanner'); |
32 | | |
33 | | // int. libs |
34 | 1 | var chromedriver = require('./lib/chromedriver'); |
35 | | |
36 | | /** |
37 | | * This module is a browser plugin for [DalekJS](//github.com/dalekjs/dalek). |
38 | | * It provides all a WebDriverServer & browser launcher for Google Chrome. |
39 | | * |
40 | | * The browser plugin can be installed with the following command: |
41 | | * |
42 | | * ```bash |
43 | | * $ npm install dalek-browser-chrome --save-dev |
44 | | * ``` |
45 | | * |
46 | | * You can use the browser plugin by adding a config option to the your [Dalekfile](/pages/config.html) |
47 | | * |
48 | | * ```javascript |
49 | | * "browser": ["chrome"] |
50 | | * ``` |
51 | | * |
52 | | * Or you can tell Dalek that it should test in this browser via the command line: |
53 | | * |
54 | | * ```bash |
55 | | * $ dalek mytest.js -b chrome |
56 | | * ``` |
57 | | * |
58 | | * The Webdriver Server tries to open Port 9002 by default, |
59 | | * if this port is blocked, it tries to use a port between 9003 & 9092 |
60 | | * You can specifiy a different port from within your [Dalekfile](/pages/config.html) like so: |
61 | | * |
62 | | * ```javascript |
63 | | * "browsers": [{ |
64 | | * "chrome": { |
65 | | * "port": 5555 |
66 | | * } |
67 | | * }] |
68 | | * ``` |
69 | | * |
70 | | * It is also possible to specify a range of ports: |
71 | | * |
72 | | * ```javascript |
73 | | * "browsers": [{ |
74 | | * "chrome": { |
75 | | * "portRange": [6100, 6120] |
76 | | * } |
77 | | * }] |
78 | | * ``` |
79 | | * |
80 | | * If you would like to test Chrome Canary oder Chromium releases, you can simply apply a snd. argument, |
81 | | * which defines the browser type: |
82 | | * |
83 | | * ```bash |
84 | | * $ dalek mytest.js -b chrome:canary |
85 | | * ``` |
86 | | * |
87 | | * for canary, and if you would like to use chromium, just append `:chromium`: |
88 | | * |
89 | | * ```bash |
90 | | * $ dalek mytest.js -b chrome:chromium |
91 | | * ``` |
92 | | * |
93 | | * This will only work if you installed your browser in the default locations, |
94 | | * if the browsers binary is located in a non default location, you are able to specify |
95 | | * its location in your [Dalekfile](/pages/config.html): |
96 | | * |
97 | | * ```javascript |
98 | | * "browsers": [{ |
99 | | * "chrome": { |
100 | | * "binary": "/Applications/Custom Located Chrome.app/MacOS/Contents/Chrome" |
101 | | * } |
102 | | * }] |
103 | | * ``` |
104 | | * |
105 | | * This also works for the canary & chromium builds |
106 | | * |
107 | | * ```javascript |
108 | | * "browsers": [{ |
109 | | * "chrome": { |
110 | | * "binary": "/Applications/Custom Located Chrome.app/MacOS/Contents/Chrome" |
111 | | * } |
112 | | * }] |
113 | | * ``` |
114 | | * |
115 | | * @module DalekJS |
116 | | * @class ChromeDriver |
117 | | * @namespace Browser |
118 | | * @part Chrome |
119 | | * @api |
120 | | */ |
121 | | |
122 | 1 | var ChromeDriver = { |
123 | | |
124 | | /** |
125 | | * Verbose version of the browser name |
126 | | * |
127 | | * @property longName |
128 | | * @type string |
129 | | * @default Google Chrome |
130 | | */ |
131 | | |
132 | | longName: 'Google Chrome', |
133 | | |
134 | | /** |
135 | | * Default port of the ChromeWebDriverServer |
136 | | * The port may change, cause the port conflict resolution |
137 | | * tool might pick another one, if the default one is blocked |
138 | | * |
139 | | * @property port |
140 | | * @type integer |
141 | | * @default 9002 |
142 | | */ |
143 | | |
144 | | port: 9002, |
145 | | |
146 | | /** |
147 | | * Default maximum port of the ChromeWebDriverServer |
148 | | * The port is the highest port in the range that can be allocated |
149 | | * by the ChromeWebDriverServer |
150 | | * |
151 | | * @property maxPort |
152 | | * @type integer |
153 | | * @default 9092 |
154 | | */ |
155 | | |
156 | | maxPort: 9092, |
157 | | |
158 | | /** |
159 | | * Default host of the ChromeWebDriverServer |
160 | | * The host may be overridden with |
161 | | * a user configured value |
162 | | * |
163 | | * @property host |
164 | | * @type string |
165 | | * @default localhost |
166 | | */ |
167 | | |
168 | | host: 'localhost', |
169 | | |
170 | | /** |
171 | | * Default desired capabilities that should be |
172 | | * transferred when the browser session gets requested |
173 | | * |
174 | | * @property desiredCapabilities |
175 | | * @type object |
176 | | */ |
177 | | |
178 | | desiredCapabilities: { |
179 | | browserName: 'chrome', |
180 | | chromeOptions: {} |
181 | | }, |
182 | | |
183 | | /** |
184 | | * Driver defaults, what should the driver be able to access. |
185 | | * |
186 | | * @property driverDefaults |
187 | | * @type object |
188 | | */ |
189 | | |
190 | | driverDefaults: { |
191 | | viewport: true, |
192 | | status: true, |
193 | | sessionInfo: true |
194 | | }, |
195 | | |
196 | | /** |
197 | | * Root path of the ChromeWebDriverServer |
198 | | * |
199 | | * @property path |
200 | | * @type string |
201 | | * @default /wd/hub |
202 | | */ |
203 | | |
204 | | path: '/wd/hub', |
205 | | |
206 | | /** |
207 | | * Child process instance of the Chrome browser |
208 | | * |
209 | | * @property spawned |
210 | | * @type null|Object |
211 | | * @default null |
212 | | */ |
213 | | |
214 | | spawned: null, |
215 | | |
216 | | /** |
217 | | * Chrome processes that are running on startup, |
218 | | * and therefor shouldn`t be closed |
219 | | * |
220 | | * @property openProcesses |
221 | | * @type array |
222 | | * @default [] |
223 | | */ |
224 | | |
225 | | openProcesses: [], |
226 | | |
227 | | /** |
228 | | * Name of the process (platform dependent) |
229 | | * that represents the browser itself |
230 | | * |
231 | | * @property processName |
232 | | * @type string |
233 | | * @default chrome.exe / Chrome |
234 | | */ |
235 | | |
236 | | processName: (process.platform === 'win32' ? 'chrome.exe' : 'Chrome'), |
237 | | |
238 | | /** |
239 | | * Different browser types (Canary / Chromium) that can be controlled |
240 | | * via the Chromedriver |
241 | | * |
242 | | * @property browserTypes |
243 | | * @type object |
244 | | */ |
245 | | |
246 | | browserTypes: { |
247 | | |
248 | | /** |
249 | | * Chrome Canary |
250 | | * |
251 | | * @property canary |
252 | | * @type object |
253 | | */ |
254 | | |
255 | | canary: { |
256 | | name: 'Chrome Canary', |
257 | | linux: 'google-chrome-canary', |
258 | | darwin: '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', |
259 | | win32: process.env.LOCALAPPDATA + '\\Google\\Chrome SxS\\Application\\chrome.exe' |
260 | | }, |
261 | | |
262 | | /** |
263 | | * Chromium |
264 | | * |
265 | | * @property chromium |
266 | | * @type object |
267 | | */ |
268 | | |
269 | | chromium: { |
270 | | name: 'Chromium', |
271 | | process: (process.platform === 'win32' ? 'chromium.exe' : 'Chromium'), |
272 | | linux: 'chromium-browser', |
273 | | darwin: '/Applications/Chromium.app/Contents/MacOS/Chromium', |
274 | | win32: process.env.LOCALAPPDATA + '\\Google\\Chrome SxS\\Application\\chrome.exe' |
275 | | } |
276 | | }, |
277 | | |
278 | | /** |
279 | | * Resolves the driver port |
280 | | * |
281 | | * @method getPort |
282 | | * @return {integer} port WebDriver server port |
283 | | */ |
284 | | |
285 | | getPort: function () { |
286 | 0 | return this.port; |
287 | | }, |
288 | | |
289 | | /** |
290 | | * Resolves the maximum range for the driver port |
291 | | * |
292 | | * @method getMaxPort |
293 | | * @return {integer} port Max WebDriver server port range |
294 | | */ |
295 | | |
296 | | getMaxPort: function () { |
297 | 0 | return this.maxPort; |
298 | | }, |
299 | | |
300 | | /** |
301 | | * Returns the driver host |
302 | | * |
303 | | * @method getHost |
304 | | * @return {string} host WebDriver server hostname |
305 | | */ |
306 | | |
307 | | getHost: function () { |
308 | 0 | return this.host; |
309 | | }, |
310 | | |
311 | | /** |
312 | | * Launches the ChromeWebDriverServer |
313 | | * (which implicitly launches Chrome itself) |
314 | | * and checks for an available port |
315 | | * |
316 | | * @method launch |
317 | | * @param {object} configuration Browser configuration |
318 | | * @param {EventEmitter2} events EventEmitter (Reporter Emitter instance) |
319 | | * @param {Dalek.Internal.Config} config Dalek configuration class |
320 | | * @return {object} promise Browser promise |
321 | | */ |
322 | | |
323 | | launch: function (configuration, events, config) { |
324 | 0 | var deferred = Q.defer(); |
325 | | |
326 | | // store injected configuration/log event handlers |
327 | 0 | this.reporterEvents = events; |
328 | 0 | this.configuration = configuration; |
329 | 0 | this.config = config; |
330 | | |
331 | | // check for a user set port |
332 | 0 | var browsers = this.config.get('browsers'); |
333 | 0 | if (browsers && Array.isArray(browsers)) { |
334 | 0 | browsers.forEach(this._checkUserDefinedPorts.bind(this)); |
335 | | } |
336 | | |
337 | 0 | if (configuration) { |
338 | 0 | if (configuration.chromeOptions) { |
339 | 0 | this.desiredCapabilities.chromeOptions = configuration.chromeOptions; |
340 | | } |
341 | | |
342 | | // check for a user defined binary |
343 | 0 | if (configuration.binary) { |
344 | 0 | var binaryExists = this._checkUserDefinedBinary(configuration.binary); |
345 | 0 | if (binaryExists) { |
346 | | // check for new verbose & process name |
347 | 0 | this.longName = this._modifyVerboseBrowserName(configuration); |
348 | 0 | this.processName = this._fetchProcessName(configuration); |
349 | | } |
350 | | } |
351 | | } |
352 | | |
353 | | // check if the current port is in use, if so, scan for free ports |
354 | 0 | portscanner.findAPortNotInUse(this.getPort(), this.getMaxPort(), this.getHost(), this._checkPorts.bind(this, deferred)); |
355 | 0 | return deferred.promise; |
356 | | }, |
357 | | |
358 | | /** |
359 | | * Kills the ChromeWebDriverServer |
360 | | * & Chrome browser processes |
361 | | * |
362 | | * @method kill |
363 | | * @chainable |
364 | | */ |
365 | | |
366 | | kill: function () { |
367 | 0 | this._processes(process.platform, this._checkProcesses.bind(this)); |
368 | 0 | return this; |
369 | | }, |
370 | | |
371 | | /** |
372 | | * Modifies the verbose browser name |
373 | | * |
374 | | * @method _modifyVerboseBrowserName |
375 | | * @param {object} configuration User configuration |
376 | | * @return {string} Verbose browser name |
377 | | * @private |
378 | | */ |
379 | | |
380 | | _modifyVerboseBrowserName: function (configuration) { |
381 | 0 | if (configuration.type && this.browserTypes[configuration.type]) { |
382 | 0 | return this.browserTypes[configuration.type].name + ' (' + this.longName + ')'; |
383 | | } |
384 | | |
385 | 0 | return this.longName; |
386 | | }, |
387 | | |
388 | | /** |
389 | | * Change the process name for browser instances like Canary & Chromium |
390 | | * |
391 | | * @method _fetchProcessName |
392 | | * @param {object} configuration User configuration |
393 | | * @return {string} Verbose browser name |
394 | | * @private |
395 | | */ |
396 | | |
397 | | _fetchProcessName: function (configuration) { |
398 | | // check if the process name must be overridden (to shut down the browser) |
399 | 0 | if (this.browserTypes[configuration.type] && this.browserTypes[configuration.type].process) { |
400 | 0 | return this.browserTypes[configuration.type].process; |
401 | | } |
402 | | |
403 | 0 | return this.processName; |
404 | | }, |
405 | | |
406 | | /** |
407 | | * Process user defined ports |
408 | | * |
409 | | * @method _checkUserDefinedPorts |
410 | | * @param {object} browser Browser configuration |
411 | | * @chainable |
412 | | * @private |
413 | | */ |
414 | | |
415 | | _checkUserDefinedPorts: function (browser) { |
416 | | // check for a single defined port |
417 | 0 | if (browser.chrome && browser.chrome.port) { |
418 | 0 | this.port = parseInt(browser.chrome.port, 10); |
419 | 0 | this.maxPort = this.port + 90; |
420 | 0 | this.reporterEvents.emit('report:log:system', 'dalek-browser-chrome: Switching to user defined port: ' + this.port); |
421 | | } |
422 | | |
423 | | // check for a port range |
424 | 0 | if (browser.chrome && browser.chrome.portRange && browser.chrome.portRange.length === 2) { |
425 | 0 | this.port = parseInt(browser.chrome.portRange[0], 10); |
426 | 0 | this.maxPort = parseInt(browser.chrome.portRange[1], 10); |
427 | 0 | this.reporterEvents.emit('report:log:system', 'dalek-browser-chrome: Switching to user defined port(s): ' + this.port + ' -> ' + this.maxPort); |
428 | | } |
429 | | |
430 | 0 | return this; |
431 | | }, |
432 | | |
433 | | /** |
434 | | * Checks if the binary exists, |
435 | | * when set manually by the user |
436 | | * |
437 | | * @method _checkUserDefinedBinary |
438 | | * @param {string} binary Path to the browser binary |
439 | | * @return {bool} Binary exists |
440 | | * @private |
441 | | */ |
442 | | |
443 | | _checkUserDefinedBinary: function (binary) { |
444 | | // check if we need to replace the users home directory |
445 | 0 | if (process.platform === 'darwin' && binary.trim()[0] === '~') { |
446 | 0 | binary = binary.replace('~', process.env.HOME); |
447 | | } |
448 | | |
449 | | // check if the binary exists |
450 | 0 | if (!fs.existsSync(binary)) { |
451 | 0 | this.reporterEvents.emit('error', 'dalek-driver-chrome: Binary not found: ' + binary); |
452 | 0 | process.exit(127); |
453 | 0 | return false; |
454 | | } |
455 | | |
456 | | // add the binary to the desired capabilities |
457 | 0 | this.desiredCapabilities.chromeOptions.binary = binary; |
458 | | |
459 | 0 | return true; |
460 | | }, |
461 | | |
462 | | /** |
463 | | * Checks if the def. port is blocked & if we need to switch to another port |
464 | | * Kicks off the process manager (for closing the opened browsers after the run has been finished) |
465 | | * Also starts the chromedriver instance |
466 | | * |
467 | | * @method _checkPorts |
468 | | * @param {object} deferred Promise |
469 | | * @param {null|object} error Error object |
470 | | * @param {integer} port Found open port |
471 | | * @private |
472 | | * @chainable |
473 | | */ |
474 | | |
475 | | _checkPorts: function(deferred, error, port) { |
476 | | // check if the port was blocked & if we need to switch to another port |
477 | 0 | if (this.port !== port) { |
478 | 0 | this.reporterEvents.emit('report:log:system', 'dalek-browser-chrome: Switching to port: ' + port); |
479 | 0 | this.port = port; |
480 | | } |
481 | | |
482 | | // get the currently running processes & invoke the chromedriver afterwards |
483 | 0 | this._processes(process.platform, this._startChromedriver.bind(this, deferred)); |
484 | 0 | return this; |
485 | | }, |
486 | | |
487 | | /** |
488 | | * Spawns an instance of Chromedriver |
489 | | * |
490 | | * @method _startChromedriver |
491 | | * @param {object} deferred Promise |
492 | | * @param {null|object} error Error object |
493 | | * @param {string} result List of open chrome processes BEFORE the test browser has been launched |
494 | | * @private |
495 | | * @chainable |
496 | | */ |
497 | | |
498 | | _startChromedriver: function (deferred, err, result) { |
499 | 0 | var args = ['--port=' + this.getPort(), '--url-base=' + this.path]; |
500 | 0 | this.spawned = cp.spawn(chromedriver.path, args); |
501 | 0 | this.openProcesses = result; |
502 | 0 | this.spawned.stdout.on('data', this._catchDriverLogs.bind(this, deferred)); |
503 | 0 | return this; |
504 | | }, |
505 | | |
506 | | /** |
507 | | * Watches the chromedriver console output to capture the starting message |
508 | | * |
509 | | * @method _catchDriverLogs |
510 | | * @param {object} deferred Promise |
511 | | * @param {buffer} data Chromedriver console output |
512 | | * @private |
513 | | * @chainable |
514 | | */ |
515 | | |
516 | | _catchDriverLogs: function (deferred, data) { |
517 | 0 | var dataStr = data + ''; |
518 | 0 | var timeout = null; |
519 | | |
520 | | // timeout to catch if chromedriver couldnt be launched |
521 | 0 | if (dataStr.search('DVFreeThread') === -1) { |
522 | 0 | timeout = setTimeout(function () { |
523 | 0 | deferred.reject(); |
524 | 0 | this.reporterEvents.emit('error', 'Chromedriver: ' + dataStr.trim()); |
525 | 0 | this.reporterEvents.emit('error', 'dalek-driver-chrome: Could not launch Chromedriver'); |
526 | 0 | process.exit(127); |
527 | | }.bind(this), 2000); |
528 | | } |
529 | | |
530 | | // look for the success message |
531 | 0 | if (dataStr.search('Starting ChromeDriver') !== -1) { |
532 | 0 | this.reporterEvents.emit('report:log:system', 'dalek-browser-chrome: Started ChromeDriver'); |
533 | 0 | if (timeout !== null) { |
534 | 0 | clearTimeout(timeout); |
535 | | } |
536 | 0 | deferred.resolve(); |
537 | | } |
538 | | |
539 | 0 | return this; |
540 | | }, |
541 | | |
542 | | /** |
543 | | * Remove the chromedriver log that is written to the current working directory |
544 | | * |
545 | | * @method _unlinkChromedriverLog |
546 | | * @param {bool} retry Delete has been tried min 1 time before |
547 | | * @private |
548 | | * @chainable |
549 | | */ |
550 | | |
551 | | _unlinkChromedriverLog: function (retry) { |
552 | 0 | var logfile = process.cwd() + '/chromedriver.log'; |
553 | 0 | try { |
554 | 0 | if (fs.existsSync(logfile)) { |
555 | 0 | fs.unlinkSync(logfile); |
556 | | } |
557 | | } catch (e) { |
558 | 0 | if (!retry) { |
559 | 0 | setTimeout(this._unlinkChromedriverLog.bind(this, true), 1000); |
560 | | } |
561 | | } |
562 | | |
563 | 0 | return this; |
564 | | }, |
565 | | |
566 | | /** |
567 | | * Tracks running browser processes for chrome on mac & linux |
568 | | * |
569 | | * @method _processes |
570 | | * @param {string} platform Current OS |
571 | | * @param {function} fn Callback |
572 | | * @chainable |
573 | | * @private |
574 | | */ |
575 | | |
576 | | _processes: function (platform, fn) { |
577 | 0 | if (platform === 'win32') { |
578 | 0 | this._processesWin(fn); |
579 | 0 | return this; |
580 | | } |
581 | | |
582 | 0 | this._processesNix(fn); |
583 | 0 | return this; |
584 | | }, |
585 | | |
586 | | /** |
587 | | * Kills all associated processes |
588 | | * |
589 | | * @method _checkProcesses |
590 | | * @param {object|null} err Error object or null |
591 | | * @param {array} result List of running processes |
592 | | * @chainable |
593 | | * @private |
594 | | */ |
595 | | |
596 | | _checkProcesses: function (err, result) { |
597 | | // log that the driver shutdown process has been initiated |
598 | 0 | this.reporterEvents.emit('report:log:system', 'dalek-browser-chrome: Shutting down ChromeDriver'); |
599 | | // kill leftover chrome browser processes |
600 | 0 | result.forEach(this[(process.platform === 'win32' ? '_killWindows' : '_killNix')].bind(this)); |
601 | | // kill chromedriver binary |
602 | 0 | this.spawned.kill('SIGTERM'); |
603 | | // clean up the file mess the chromedriver leaves us behind |
604 | 0 | this._unlinkChromedriverLog(); |
605 | 0 | return this; |
606 | | }, |
607 | | |
608 | | // UNIX ONLY |
609 | | // --------- |
610 | | |
611 | | /** |
612 | | * Kills a process |
613 | | * |
614 | | * @method _killNix |
615 | | * @param {integer} processID Process ID |
616 | | * @chainable |
617 | | * @private |
618 | | */ |
619 | | |
620 | | _killNix: function (processID) { |
621 | 0 | var kill = true; |
622 | 0 | this.openProcesses.forEach(function (pid) { |
623 | 0 | if (pid === processID) { |
624 | 0 | kill = false; |
625 | | } |
626 | | }); |
627 | | |
628 | 0 | if (kill === true) { |
629 | 0 | var killer = cp.spawn; |
630 | 0 | killer('kill', [processID]); |
631 | | } |
632 | | |
633 | 0 | return this; |
634 | | }, |
635 | | |
636 | | /** |
637 | | * Lists all chrome processes on *NIX systems |
638 | | * |
639 | | * @method _processesNix |
640 | | * @param {function} fn Calback |
641 | | * @chainable |
642 | | * @private |
643 | | */ |
644 | | |
645 | | _processesNix: function (fn) { |
646 | 0 | var processName = process.platform === 'darwin' ? this.processName : this.processName.toLowerCase(); |
647 | 0 | var cmd = ['ps -ax', '|', 'grep ' + processName]; |
648 | 0 | cp.exec(cmd.join(' '), this._processListNix.bind(this, fn)); |
649 | 0 | return this; |
650 | | }, |
651 | | |
652 | | /** |
653 | | * Deserializes a process list on nix |
654 | | * |
655 | | * @method _processListNix |
656 | | * @param {function} fn Calback |
657 | | * @param {object|null} err Error object |
658 | | * @param {string} stdout Output of the process list shell command |
659 | | * @chainable |
660 | | * @private |
661 | | */ |
662 | | |
663 | | _processListNix: function(fn, err, stdout) { |
664 | 0 | var result = []; |
665 | 0 | stdout.split('\n').forEach(this._splitProcessListNix.bind(this, result)); |
666 | 0 | fn(err, result); |
667 | 0 | return this; |
668 | | }, |
669 | | |
670 | | /** |
671 | | * Reformats the process list output on *NIX systems |
672 | | * |
673 | | * @method _splitProcessListNix |
674 | | * @param {array} result Reference to the process list |
675 | | * @param {string} line Single process in text representation |
676 | | * @chainable |
677 | | * @private |
678 | | */ |
679 | | |
680 | | _splitProcessListNix: function(result, line) { |
681 | 0 | var data = line.split(' '); |
682 | 0 | data = data.filter(this._filterProcessItemsNix.bind(this)); |
683 | 0 | result.push(data[0]); |
684 | 0 | return this; |
685 | | }, |
686 | | |
687 | | /** |
688 | | * Filters empty process list entries on *NIX |
689 | | * |
690 | | * @method _filterProcessItemsNix |
691 | | * @param {string} item Item to check |
692 | | * @return {string|bool} Item or falsy |
693 | | * @private |
694 | | */ |
695 | | |
696 | | _filterProcessItemsNix: function (item) { |
697 | 0 | if (item !== '') { |
698 | 0 | return item; |
699 | | } |
700 | 0 | return false; |
701 | | }, |
702 | | |
703 | | // WIN ONLY |
704 | | // -------- |
705 | | |
706 | | /** |
707 | | * Lists all running processes (win only) |
708 | | * |
709 | | * @method _processesWin |
710 | | * @param {Function} callback Receives the process object as the only callback argument |
711 | | * @chainable |
712 | | * @private |
713 | | */ |
714 | | |
715 | | _processesWin: function (callback) { |
716 | 0 | cp.exec('tasklist /FO CSV', this._processListWin.bind(this, callback)); |
717 | 0 | return this; |
718 | | }, |
719 | | |
720 | | /** |
721 | | * Deserializes the process list on win |
722 | | * |
723 | | * @method _processListWin |
724 | | * @param {function} callback Callback to be executed after the list has been transformed |
725 | | * @param {object|null} err Error if error, else null |
726 | | * @param {string} stdout Output of the process list command |
727 | | * @chainable |
728 | | * @private |
729 | | */ |
730 | | |
731 | | _processListWin: function (callback, err, stdout) { |
732 | 0 | var p = []; |
733 | 0 | stdout.split('\r\n').forEach(this._splitProcessListWin.bind(this, p)); |
734 | 0 | var proc = []; |
735 | 0 | var head = null; |
736 | 0 | while (p.length > 1) { |
737 | 0 | var rec = p.shift(); |
738 | 0 | rec = rec.replace(/\"\,/gi,'";').replace(/\"|\'/gi,'').split(';'); |
739 | 0 | if (head === null){ |
740 | 0 | head = rec; |
741 | 0 | for (var i=0;i<head.length;i++){ |
742 | 0 | head[i] = head[i].replace(/ /gi,''); |
743 | | } |
744 | | } else { |
745 | 0 | var tmp = {}; |
746 | 0 | for (var j=0;j<rec.length;j++){ |
747 | 0 | tmp[head[j]] = rec[j].replace(/\"|\'/gi,''); |
748 | | } |
749 | 0 | proc.push(tmp); |
750 | | } |
751 | | } |
752 | | |
753 | 0 | var result = []; |
754 | 0 | proc.forEach(this._filterProcessItemsWin.bind(this, result)); |
755 | 0 | callback(null, result); |
756 | 0 | return this; |
757 | | }, |
758 | | |
759 | | /** |
760 | | * Processes (transforms) the list of processes |
761 | | * |
762 | | * @method _filterProcessItemsWin |
763 | | * @param {array} result Reference to the result process list |
764 | | * @param {object} process Single piece of process information |
765 | | * @chainable |
766 | | * @private |
767 | | */ |
768 | | |
769 | | _filterProcessItemsWin: function (result, process) { |
770 | 0 | Object.keys(process).forEach(function (key) { |
771 | 0 | if (process[key] === this.processName) { |
772 | 0 | result.push(process.PID); |
773 | | } |
774 | | }.bind(this)); |
775 | 0 | return this; |
776 | | }, |
777 | | |
778 | | /** |
779 | | * Filters empty lines out of the process result |
780 | | * |
781 | | * @method _splitProcessListWin |
782 | | * @param {array} p Reference to the process list |
783 | | * @param {string} line Process item |
784 | | * @chainable |
785 | | * @private |
786 | | */ |
787 | | |
788 | | _splitProcessListWin: function (p, line) { |
789 | 0 | if (line.trim().length !== 0) { |
790 | 0 | p.push(line); |
791 | | } |
792 | 0 | return this; |
793 | | }, |
794 | | |
795 | | /** |
796 | | * Kills a process (based on a PID) |
797 | | * that was not opened BEFORE Dalek has |
798 | | * been started |
799 | | * |
800 | | * @method _killWindows |
801 | | * @param {integer} pid Process id |
802 | | * @chainable |
803 | | * @private |
804 | | */ |
805 | | |
806 | | _killWindows: function (pid) { |
807 | 0 | var kill = true; |
808 | | |
809 | | // check if the process was running BEFORE we started dalek |
810 | 0 | this.openProcesses.forEach(function (opid) { |
811 | 0 | if (opid === pid) { |
812 | 0 | kill = false; |
813 | | } |
814 | | }); |
815 | | |
816 | | // kill the browser process |
817 | 0 | if (kill === true) { |
818 | 0 | cp.exec('taskkill /PID ' + pid + ' /f', function(){}); |
819 | | } |
820 | | |
821 | 0 | return this; |
822 | | } |
823 | | |
824 | | }; |
825 | | |
826 | | // expose the module |
827 | 1 | module.exports = ChromeDriver; |
828 | | |