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 | | |