| 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 async = require('async'); |
| 29 | | |
| 30 | | // int. libs |
| 31 | 1 | var Testsuite = require('dalek-internal-testsuite'); |
| 32 | | |
| 33 | | /** |
| 34 | | * Configures the driver instance |
| 35 | | * |
| 36 | | * @constructor |
| 37 | | */ |
| 38 | | |
| 39 | 1 | var Driver = function (options) { |
| 40 | | // add configuration data to the driver instance |
| 41 | 0 | this.config = options.config; |
| 42 | 0 | this.browser = this.config.get('browser'); |
| 43 | 0 | this.files = this.config.get('tests'); |
| 44 | 0 | this.drivers = this.config.get('driver'); |
| 45 | | |
| 46 | | // flag if we use the canary driver builds |
| 47 | 0 | this.driverIsCanary = false; |
| 48 | | |
| 49 | | // link driver events |
| 50 | 0 | this.driverEmitter = options.driverEmitter; |
| 51 | 0 | this.reporterEvents = options.reporterEvents; |
| 52 | | }; |
| 53 | | |
| 54 | | /** |
| 55 | | * Generates & starts drivers & browsers |
| 56 | | * the tests will be run in |
| 57 | | * |
| 58 | | * @module DalekJS |
| 59 | | * @class Driver |
| 60 | | * @namespace Dalek |
| 61 | | * @part Driver |
| 62 | | * @api |
| 63 | | */ |
| 64 | | |
| 65 | 1 | Driver.prototype = { |
| 66 | | |
| 67 | | /** |
| 68 | | * Checks if the requested driver is available |
| 69 | | * |
| 70 | | * @method isDriver |
| 71 | | * @param {string} driver Name of the requested driver |
| 72 | | * @return {bool} isDriver Driver is availavle |
| 73 | | */ |
| 74 | | |
| 75 | | isDriver: function (driver) { |
| 76 | 0 | try { |
| 77 | 0 | require.resolve('dalek-driver-' + driver); |
| 78 | | } catch (e) { |
| 79 | 0 | try { |
| 80 | 0 | require.resolve('dalek-driver-' + driver + '-canary'); |
| 81 | | } catch (e) { |
| 82 | 0 | return false; |
| 83 | | } |
| 84 | 0 | this.driverIsCanary = true; |
| 85 | 0 | return true; |
| 86 | | } |
| 87 | 0 | return true; |
| 88 | | }, |
| 89 | | |
| 90 | | /** |
| 91 | | * Loads the requested driver |
| 92 | | * Emits an event to the reporter |
| 93 | | * |
| 94 | | * @method loadDriver |
| 95 | | * @param {string} driver Name of the requested driver |
| 96 | | * @return {object} driverModule Instance of the driver module |
| 97 | | */ |
| 98 | | |
| 99 | | loadDriver: function (driver) { |
| 100 | 0 | this.reporterEvents.emit('report:log:system', 'dalek-internal-driver: Loading driver: "' + driver + '"'); |
| 101 | 0 | return require('dalek-driver-' + driver + (this.driverIsCanary ? '-canary' : '')); |
| 102 | | }, |
| 103 | | |
| 104 | | /** |
| 105 | | * Returns a list with browser driver instances |
| 106 | | * |
| 107 | | * @method getDrivers |
| 108 | | * @return {array} verifiedDrivers |
| 109 | | */ |
| 110 | | |
| 111 | | getDrivers: function () { |
| 112 | 0 | return this.drivers.map(this.getVerifiedBrowser, this)[0]; |
| 113 | | }, |
| 114 | | |
| 115 | | /** |
| 116 | | * Returns a list with browser driver instances |
| 117 | | * |
| 118 | | * @method getVerifiedBrowser |
| 119 | | * @param {string} driver Name of the requested driver |
| 120 | | * @return {array} verifiedDrivers Array of dribver 'run' functions |
| 121 | | */ |
| 122 | | |
| 123 | | getVerifiedBrowser: function (driver) { |
| 124 | 0 | return this.browser.map(this.getVerifiedDriver.bind(this, this.loadDriver(driver), driver)); |
| 125 | | }, |
| 126 | | |
| 127 | | /** |
| 128 | | * Returns a scoped version of the driver run function |
| 129 | | * |
| 130 | | * @method getVerifiedDriver |
| 131 | | * @param {object} driverModule Instance of the used driver |
| 132 | | * @param {string} driver Name of ther used driver |
| 133 | | * @param {string} browser Name of the used browser |
| 134 | | * @return {function} run Function that kicks off execution of a testsuite chain in a browser |
| 135 | | */ |
| 136 | | |
| 137 | | getVerifiedDriver: function (driverModule, driver, browser) { |
| 138 | 0 | return this.run.bind(this, driver, driverModule, browser); |
| 139 | | }, |
| 140 | | |
| 141 | | /** |
| 142 | | * Loads a browser driver |
| 143 | | * |
| 144 | | * @method loadBrowserConfiguration |
| 145 | | * @param {string} browser Name of the requested browser driver |
| 146 | | * @param {object} browsers Configuration options for the requested browser |
| 147 | | * @return {object} browserConfiguration Browser driver isntance and configuration meta data |
| 148 | | */ |
| 149 | | |
| 150 | | loadBrowserConfiguration: function (browser, browsers, driver) { |
| 151 | 0 | var browserConfiguration; |
| 152 | | |
| 153 | 0 | if (driver.dummyBrowser && driver.dummyBrowser()) { |
| 154 | 0 | return driver.getBrowser(driver); |
| 155 | | } |
| 156 | | |
| 157 | 0 | try { |
| 158 | 0 | browserConfiguration = this.getDefaultBrowserConfiguration(browser, browsers); |
| 159 | | } catch (e) { |
| 160 | 0 | browserConfiguration = this.getUserBrowserConfiguration(browser, browsers); |
| 161 | | } |
| 162 | | |
| 163 | 0 | return browserConfiguration; |
| 164 | | }, |
| 165 | | |
| 166 | | /** |
| 167 | | * Loads the default browser driver |
| 168 | | * |
| 169 | | * @method getDefaultBrowserConfiguration |
| 170 | | * @param {string} browser Name of the requested browser driver |
| 171 | | * @param {object} browsers Configuration options for the requested browser |
| 172 | | * @return {object} browserConfiguration Browser driver isntance and configuration meta data |
| 173 | | */ |
| 174 | | |
| 175 | | getDefaultBrowserConfiguration: function (browser, browsers) { |
| 176 | 0 | var browserConfiguration = {configuration: null, module: null}; |
| 177 | | |
| 178 | 0 | try { |
| 179 | 0 | browserConfiguration.module = require('dalek-browser-' + browser); |
| 180 | | } catch (e) { |
| 181 | 0 | browserConfiguration.module = require('dalek-browser-' + browser + '-canary'); |
| 182 | | } |
| 183 | 0 | if (browsers[browser]) { |
| 184 | 0 | browserConfiguration.configuration = browsers[browser]; |
| 185 | | } |
| 186 | | |
| 187 | 0 | return browserConfiguration; |
| 188 | | }, |
| 189 | | |
| 190 | | /** |
| 191 | | * Loads a user configured browser driver |
| 192 | | * |
| 193 | | * @method getUserBrowserConfiguration |
| 194 | | * @param {string} browser Name of the requested browser driver |
| 195 | | * @param {object} browsers Configuration options for the requested browser |
| 196 | | * @return {object} browserConfiguration Browser driver isntance and configuration meta data |
| 197 | | */ |
| 198 | | |
| 199 | | getUserBrowserConfiguration: function (browser, browsers) { |
| 200 | 0 | var browserConfiguration = {configuration: null, module: null}; |
| 201 | | |
| 202 | 0 | if (browsers && browsers[browser] && browsers[browser].actAs) { |
| 203 | 0 | browserConfiguration.module = require('dalek-browser-' + browsers[browser].actAs); |
| 204 | 0 | browserConfiguration.configuration = browsers[browser]; |
| 205 | | } |
| 206 | | |
| 207 | 0 | if (!browserConfiguration.module && browser.search(':') !== -1) { |
| 208 | 0 | var args = browser.split(':'); |
| 209 | 0 | var extractedBrowser = args[0].trim(); |
| 210 | 0 | var browserType = args[1].trim().toLowerCase(); |
| 211 | 0 | browserConfiguration.module = require('dalek-browser-' + extractedBrowser); |
| 212 | | |
| 213 | 0 | if (browserConfiguration.module && browserConfiguration.module.browserTypes && browserConfiguration.module.browserTypes[browserType]) { |
| 214 | 0 | var binary = (process.platform === 'win32' ? browserConfiguration.module.browserTypes[browserType].win32 : browserConfiguration.module.browserTypes[browserType].darwin); |
| 215 | 0 | browserConfiguration.configuration = { |
| 216 | | binary: binary, |
| 217 | | type: browserType |
| 218 | | }; |
| 219 | | } |
| 220 | | } |
| 221 | | |
| 222 | 0 | return browserConfiguration; |
| 223 | | }, |
| 224 | | |
| 225 | | /** |
| 226 | | * Couple driver & session status events for the reporter |
| 227 | | * |
| 228 | | * @method coupleReporterEvents |
| 229 | | * @param {string} driverName Name of the requested driver |
| 230 | | * @param {string} browser Name of the requested browser |
| 231 | | * @chainable |
| 232 | | */ |
| 233 | | |
| 234 | | coupleReporterEvents: function (driverName, browser) { |
| 235 | 0 | this.driverEmitter.on('driver:sessionStatus:' + driverName + ':' + browser, this.reporterEvents.emit.bind(this.reporterEvents, 'report:driver:session')); |
| 236 | 0 | this.driverEmitter.on('driver:status:' + driverName + ':' + browser, this.reporterEvents.emit.bind(this.reporterEvents, 'report:driver:status')); |
| 237 | 0 | return this; |
| 238 | | }, |
| 239 | | |
| 240 | | /** |
| 241 | | * Returns a list of testsuite runner functions |
| 242 | | * |
| 243 | | * @method getTestsuiteInstances |
| 244 | | * @param {object} driverInstance Instance of the requested driver |
| 245 | | * @return {array} testsuiteRunners List of testsuites that should be run |
| 246 | | */ |
| 247 | | |
| 248 | | getTestsuiteInstances: function (driverInstance) { |
| 249 | 0 | return this.files.map(this.createTestsuiteInstance.bind(this, driverInstance)); |
| 250 | | }, |
| 251 | | |
| 252 | | /** |
| 253 | | * Creates a testsuite runner function |
| 254 | | * |
| 255 | | * @method createTestsuiteInstance |
| 256 | | * @param {object} driverInstance Instance of the requested driver |
| 257 | | * @param {string} file Filename of the testsuite |
| 258 | | * @return {function} testsuiteRunner Runner function from the testsuite |
| 259 | | */ |
| 260 | | |
| 261 | | createTestsuiteInstance: function (driverInstance, file) { |
| 262 | 0 | var suite = new Testsuite({numberOfSuites: this.files.length, file: file, driver: driverInstance, driverEmitter: this.driverEmitter, reporterEmitter: this.reporterEvents}); |
| 263 | 0 | return suite.run.bind(suite); |
| 264 | | }, |
| 265 | | |
| 266 | | /** |
| 267 | | * Generates a testsuite instance, emits the |
| 268 | | * browser running event & starts a new async() sesries execution |
| 269 | | * Will be called when the driver is ready |
| 270 | | * |
| 271 | | * @method _onDriverReadyclear |
| 272 | | * @param {string} browser Name of the requested browser |
| 273 | | * @param {string} driverName Name of the requested driver |
| 274 | | * @param {function} callback Asyncs next() callback function |
| 275 | | * @param {object} driverInstance Instance of the requested driver |
| 276 | | * @chainable |
| 277 | | * @private |
| 278 | | */ |
| 279 | | |
| 280 | | _onDriverReady: function (browser, driverName, callback, driverInstance) { |
| 281 | | // generate testsuite instance from test files |
| 282 | 0 | var testsuites = this.getTestsuiteInstances(driverInstance); |
| 283 | 0 | this.reporterEvents.emit('report:run:browser', driverInstance.webdriverClient.opts.longName); |
| 284 | 0 | async.series(testsuites, this._onTestsuiteComplete.bind(this, callback, driverName, browser)); |
| 285 | 0 | return this; |
| 286 | | }, |
| 287 | | |
| 288 | | /** |
| 289 | | * Emits a 'tests complete' event & calls asyncs next() callback |
| 290 | | * |
| 291 | | * @method _onTestsuiteComplete |
| 292 | | * @param {function} callback Asyncs next() callback function |
| 293 | | * @param {string} driverName Name of the requested driver |
| 294 | | * @param {string} browser Name of the requested browser |
| 295 | | * @chainable |
| 296 | | * @private |
| 297 | | */ |
| 298 | | |
| 299 | | _onTestsuiteComplete: function (callback, driverName, browser) { |
| 300 | 0 | this.driverEmitter.emit('tests:complete:' + driverName + ':' + browser); |
| 301 | 0 | callback(); |
| 302 | 0 | return this; |
| 303 | | }, |
| 304 | | |
| 305 | | /** |
| 306 | | * Driver runner function. |
| 307 | | * Registers event handlers for this run, |
| 308 | | * loads browser & driver configuration & instances, |
| 309 | | * emits the 'driver ready' event for the browser/driver combination |
| 310 | | * |
| 311 | | * @method run |
| 312 | | * @param {string} driverName Name of the requested driver |
| 313 | | * @param {object} driverModule Instance of the used driver module |
| 314 | | * @param {string} browser Name of the requested browser |
| 315 | | * @param {function} callback Asyncs next() callback function |
| 316 | | * @chainable |
| 317 | | */ |
| 318 | | |
| 319 | | run: function (driverName, driverModule, browser, callback) { |
| 320 | | // load browser configuration |
| 321 | 0 | var browsersRaw = this.config.get('browsers'); |
| 322 | 0 | var browsers = []; |
| 323 | | |
| 324 | | // Check if we have a valid browser conf, then get the data out |
| 325 | 0 | if (browsersRaw !== null) { |
| 326 | 0 | browsers = browsersRaw[0]; |
| 327 | | } |
| 328 | | |
| 329 | 0 | var browserConfiguration = this.loadBrowserConfiguration(browser, browsers, driverModule); |
| 330 | 0 | var driverInstance = driverModule.create({events: this.driverEmitter, reporter: this.reporterEvents, browser: browser, config: this.config, browserMo: browserConfiguration.module, browserConf: browserConfiguration.configuration}); |
| 331 | | // couple driver & session status events for the reporter |
| 332 | 0 | this.coupleReporterEvents(driverName, browser); |
| 333 | | |
| 334 | | // register shutdown handler |
| 335 | 0 | if (driverInstance.webdriverClient.opts.kill) { |
| 336 | 0 | this.driverEmitter.on('killAll', driverInstance.webdriverClient.opts.kill.bind(driverInstance.webdriverClient.opts)); |
| 337 | | } |
| 338 | | |
| 339 | | // dispatch some (web)driver events to the reporter |
| 340 | 0 | this.driverEmitter.on('driver:webdriver:response', function (res) { |
| 341 | 0 | this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.statusCode + ' ' + res.method + ' ' + res.path); |
| 342 | 0 | this.reporterEvents.emit('report:log:system:webdriver', 'webdriver: ' + res.data); |
| 343 | | }.bind(this)); |
| 344 | | |
| 345 | | // run the tests in the browser, when the driver is ready |
| 346 | | // emit the tests:complete event, when all tests have been run |
| 347 | 0 | this.driverEmitter.on('driver:ready:' + driverName + ':' + browser, this._onDriverReady.bind(this, browser, driverName, callback, driverInstance)); |
| 348 | 0 | return this; |
| 349 | | } |
| 350 | | }; |
| 351 | | |
| 352 | | // export driver module |
| 353 | 1 | module.exports = Driver; |
| 354 | | |