1 | | /*! |
2 | | * |
3 | | * Copyright (c) 2013 Peter Foerger & 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 | | * Run browser tests with dalak |
26 | | * |
27 | | * ## Getting Started |
28 | | * This plugin requires Grunt `~0.4.1` |
29 | | * |
30 | | * If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: |
31 | | * |
32 | | * ```bash |
33 | | * npm install grunt-dalek --save-dev |
34 | | * ``` |
35 | | * |
36 | | * Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: |
37 | | * |
38 | | * ```javascript |
39 | | * grunt.loadNpmTasks('grunt-dalek'); |
40 | | * ``` |
41 | | * |
42 | | * ## The "dalek" task |
43 | | * |
44 | | * ### Overview |
45 | | * In your project's Gruntfile, add a section named `dalek` to the data object passed into `grunt.initConfig()`. |
46 | | * |
47 | | * ```javascript |
48 | | * grunt.initConfig({ |
49 | | * dalek: { |
50 | | * options: { |
51 | | * // Task-specific options go here. |
52 | | * }, |
53 | | * your_target: { |
54 | | * // Target-specific file lists and/or options go here. |
55 | | * }, |
56 | | * }, |
57 | | * }) |
58 | | * ``` |
59 | | * |
60 | | * ### Options |
61 | | * |
62 | | * #### options.dalekfile |
63 | | * Type: `Boolean` |
64 | | * Default: `true` |
65 | | * |
66 | | * Grunt should load the config options from your Dalekfile |
67 | | * |
68 | | * #### options.browser |
69 | | * Type: `Array` |
70 | | * Default: `['phantomjs']` |
71 | | * |
72 | | * The browsers you would like to test |
73 | | * Note: For other browsers than PhantomJS, you need to have the Dalek browser plugin installed. |
74 | | * |
75 | | * #### options.reporter |
76 | | * Type: `Array` |
77 | | * Default: `null` |
78 | | * |
79 | | * The reporters you would like to invoke |
80 | | * Note: For other reporters than the grunt console output, you need to have the corresponding Dalek reporter plugin installed. |
81 | | * |
82 | | * #### options.advanced |
83 | | * Type: `Object` |
84 | | * Default: `null` |
85 | | * |
86 | | * All the options you else would define in your Dalekfile. |
87 | | * This overwrites the contents of your Dalekfile. |
88 | | * |
89 | | * ## Examples |
90 | | * |
91 | | * ### Configuration Example |
92 | | * |
93 | | * Basic example of a Grunt config containing the dalek task. |
94 | | * |
95 | | * ```javascript |
96 | | * grunt.initConfig({ |
97 | | * dalek: { |
98 | | * dist: { |
99 | | * src: ['test/example/test-github.js'] |
100 | | * } |
101 | | * } |
102 | | * |
103 | | * }); |
104 | | * |
105 | | * // Loads tasks located in the tasks directory. |
106 | | * grunt.loadTasks('tasks'); |
107 | | * |
108 | | * grunt.registerTask('default', ['dalek']); |
109 | | * ``` |
110 | | * |
111 | | * ### Multiple Files |
112 | | * |
113 | | * Running dalekjs against multiple files. |
114 | | * |
115 | | * ```javascript |
116 | | * dalek: { |
117 | | * dist: { |
118 | | * src: ['test/example/test-dkd.js','test/example/test-github.js'] |
119 | | * } |
120 | | * } |
121 | | * ``` |
122 | | * |
123 | | * ### Specifying Options |
124 | | * |
125 | | * ```javascript |
126 | | * dalek: { |
127 | | * options: { |
128 | | * // invoke phantomjs, chrome & chrome canary |
129 | | * browser: ['phantomjs', 'chrome', 'chrome:canary'], |
130 | | * // generate an html & an jUnit report |
131 | | * reporter: ['html', 'junit'], |
132 | | * // don't load config from an Dalekfile |
133 | | * dalekfile: false, |
134 | | * // specify advanced options (that else would be in your Dalekfile) |
135 | | * advanced: { |
136 | | * // specify a port for chrome |
137 | | * browsers: [{ |
138 | | * chrome: { |
139 | | * port: 4000 |
140 | | * } |
141 | | * }] |
142 | | * } |
143 | | * } |
144 | | * } |
145 | | * ``` |
146 | | * |
147 | | * @part Grunt |
148 | | * @api |
149 | | */ |
150 | | |
151 | 1 | module.exports = function(grunt) { |
152 | 0 | 'use strict'; |
153 | | |
154 | 0 | grunt.registerMultiTask('dalek', 'dalekjs browser tests', function dalekMultiTask () { |
155 | | // tagging this task as async |
156 | 0 | var done = this.async(); |
157 | | |
158 | | // load dalek |
159 | 0 | var Dalek = require('dalekjs'); |
160 | | |
161 | | // yeah, globals, but stored for a good reason |
162 | 0 | var currentTest; |
163 | 0 | var currentBrowser; |
164 | 0 | var failedAssertions = []; |
165 | | |
166 | | // setting defaults for user set options |
167 | 0 | var options = this.options(); |
168 | 0 | var driver = []; |
169 | 0 | var reporter = []; |
170 | 0 | var browser = []; |
171 | | |
172 | | // setting defaults for advanced & plugin options |
173 | 0 | var loadDalekfile = true; |
174 | 0 | var advanced = {}; |
175 | | |
176 | | // check if options are available, |
177 | | // else use daleks defaults |
178 | 0 | if (options) { |
179 | 0 | driver = options.driver && Array.isArray(options.driver) ? options.driver : driver; |
180 | 0 | reporter = options.reporter && Array.isArray(options.reporter) ? options.reporter : reporter; |
181 | 0 | browser = options.browser && Array.isArray(options.browser) ? options.browser : browser; |
182 | | } |
183 | | |
184 | | // check if advanced options are available |
185 | 0 | if (options) { |
186 | 0 | loadDalekfile = (options.dalekfile || options.dalekfile === false) ? options.dalekfile : loadDalekfile; |
187 | 0 | if (options.advanced && typeof options.advanced === 'object') { |
188 | 0 | advanced = options.advanced; |
189 | | } |
190 | | |
191 | 0 | advanced.dalekfile = loadDalekfile; |
192 | | } |
193 | | |
194 | | // instanciate dalek with obligatory options |
195 | 0 | var dalek = new Dalek({ |
196 | | tests: this.filesSrc, |
197 | | driver: driver, |
198 | | reporter: reporter, |
199 | | browser: browser, |
200 | | logLevel: -1, |
201 | | noColors: true, |
202 | | noSymbols: true, |
203 | | advanced: advanced |
204 | | }); |
205 | | |
206 | | // grunt logging |
207 | | // ------------- |
208 | | |
209 | | // If options.force then log an error, otherwise exit with a warning |
210 | 0 | var warnUnlessForced = function (message) { |
211 | 0 | if (options && options.force) { |
212 | 0 | grunt.log.error(message); |
213 | | } else { |
214 | 0 | grunt.warn(message); |
215 | | } |
216 | | }; |
217 | | |
218 | | // Log failed assertions |
219 | 0 | var logFailedAssertions = function() { |
220 | 0 | var assertion; |
221 | | // Print each assertion error. |
222 | 0 | while (assertion = failedAssertions.shift()) { |
223 | 0 | grunt.verbose.or.error(assertion.testName); |
224 | 0 | grunt.log.error('Message: ' + formatMessage(assertion.message)); |
225 | 0 | if (assertion.actual !== assertion.expected) { |
226 | 0 | grunt.log.error('Actual: ' + formatMessage(assertion.actual)); |
227 | 0 | grunt.log.error('Expected: ' + formatMessage(assertion.expected)); |
228 | | } |
229 | 0 | if (assertion.source) { |
230 | 0 | grunt.log.error('Source:' + formatMessage(assertion.source.replace(/ {4}(at)/g, ' $1'))); |
231 | | } |
232 | 0 | grunt.log.error('Browser: ' + formatMessage(assertion.browserName)); |
233 | 0 | grunt.log.writeln(); |
234 | | } |
235 | | }; |
236 | | |
237 | | // Allow an error message to retain its color when split across multiple lines. |
238 | 0 | var formatMessage = function(str) { |
239 | 0 | return String(str).split('\n').map(function(s) { return s.magenta; }).join('\n'); |
240 | | }; |
241 | | |
242 | | // register browser launcher event |
243 | 0 | dalek.reporterEvents.on('report:run:browser', function reportRunBrowser (browserName) { |
244 | 0 | currentBrowser = browserName; |
245 | | }); |
246 | | |
247 | | // register logging for runner finished |
248 | 0 | dalek.reporterEvents.on('report:runner:finished', function reportRunnerFinished (data) { |
249 | 0 | var message = ''; |
250 | | // Print assertion errors here, if verbose mode is disabled. |
251 | 0 | if (!data.status) { |
252 | 0 | message = data.assertionsPassed + '/' + data.assertions + ' assertions passed ('; |
253 | 0 | message += data.elapsedTime.hours ? data.elapsedTime.hours + ' hours' : ''; |
254 | 0 | message += data.elapsedTime.minutes ? data.elapsedTime.minutes + ' min' : ''; |
255 | 0 | message += data.elapsedTime.seconds ? Math.round(data.elapsedTime.seconds * Math.pow(10, 2)) / Math.pow(10, 2) + ' sec' : ''; |
256 | 0 | message += ')'; |
257 | | |
258 | 0 | if (!grunt.option('verbose')) { |
259 | 0 | grunt.log.writeln(); |
260 | 0 | logFailedAssertions(); |
261 | | } |
262 | | |
263 | 0 | grunt.log.writeln(); |
264 | 0 | warnUnlessForced(message); |
265 | | |
266 | 0 | setTimeout(function () { |
267 | 0 | done(false); |
268 | | }, 250); |
269 | 0 | } else if (data.assertions === 0) { |
270 | | // create 0 assertions message |
271 | 0 | message = '0/0 assertions ran ('; |
272 | 0 | message += data.elapsedTime.hours ? data.elapsedTime.hours + ' hours' : ''; |
273 | 0 | message += data.elapsedTime.minutes ? data.elapsedTime.minutes + ' min' : ''; |
274 | 0 | message += data.elapsedTime.seconds ? Math.round(data.elapsedTime.seconds * Math.pow(10, 2)) / Math.pow(10, 2) + ' sec' : ''; |
275 | 0 | message += ')'; |
276 | | |
277 | | // timeout hack to enable shutdown of 3rd party processes |
278 | 0 | setTimeout(function () { |
279 | 0 | warnUnlessForced(message); |
280 | 0 | done(false); |
281 | | }, 250); |
282 | | } else { |
283 | | // create passed assertions message |
284 | 0 | message = data.assertions + '/' + data.assertions + ' assertions passed ('; |
285 | 0 | message += data.elapsedTime.hours ? data.elapsedTime.hours + ' hours' : ''; |
286 | 0 | message += data.elapsedTime.minutes ? data.elapsedTime.minutes + ' min' : ''; |
287 | 0 | message += data.elapsedTime.seconds ? Math.round(data.elapsedTime.seconds * Math.pow(10, 2)) / Math.pow(10, 2) + ' sec' : ''; |
288 | 0 | message += ')'; |
289 | | |
290 | 0 | setTimeout(function () { |
291 | 0 | grunt.log.writeln(''); |
292 | 0 | grunt.log.ok(message); |
293 | 0 | done(true); |
294 | | }, 250); |
295 | | } |
296 | | }); |
297 | | |
298 | | // log assertion |
299 | 0 | dalek.reporterEvents.on('report:assertion', function(data) { |
300 | 0 | if (!data.success) { |
301 | 0 | failedAssertions.push({ |
302 | | actual: data.value, |
303 | | expected: data.expected, |
304 | | message: (data.message || 'No message'), |
305 | | source: data.type, |
306 | | testName: currentTest, |
307 | | browserName: currentBrowser |
308 | | }); |
309 | | } |
310 | | }); |
311 | | |
312 | | // log test started |
313 | 0 | dalek.reporterEvents.on('report:test:started', function(data) { |
314 | 0 | currentTest = data.name; |
315 | 0 | grunt.verbose.write(currentTest + '...'); |
316 | | }); |
317 | | |
318 | | // log test finished |
319 | 0 | dalek.reporterEvents.on('report:test:finished', function(data) { |
320 | | // Log errors if necessary, otherwise success. |
321 | 0 | if (!data.status) { |
322 | | // list assertions |
323 | 0 | if (grunt.option('verbose')) { |
324 | 0 | grunt.log.error(); |
325 | 0 | logFailedAssertions(); |
326 | | } else { |
327 | 0 | grunt.log.write('F'.red); |
328 | | } |
329 | | } else { |
330 | 0 | grunt.verbose.ok().or.write('.'); |
331 | | } |
332 | | }); |
333 | | |
334 | | // All tests have been run. |
335 | | |
336 | | // log error |
337 | 0 | dalek.reporterEvents.on('error', function(message) { |
338 | 0 | setTimeout(function () { |
339 | 0 | if (options && options.force) { |
340 | 0 | grunt.log.error(message); |
341 | | } else { |
342 | 0 | grunt.warn(message); |
343 | | } |
344 | | |
345 | 0 | done(false); |
346 | | }, 250); |
347 | | }); |
348 | | |
349 | | // log warning |
350 | 0 | dalek.reporterEvents.on('warning', function(message) { |
351 | 0 | grunt.log.write('>> Warning: '.yellow + message + '\n'); |
352 | | }); |
353 | | |
354 | | // Re-broadcast dalek events on grunt.event. |
355 | 0 | dalek.reporterEvents.onAny(function() { |
356 | 0 | var args = [this.event].concat(grunt.util.toArray(arguments)); |
357 | 0 | args[0] = 'dalek:' + args[0]; |
358 | 0 | grunt.event.emit.apply(grunt.event, args); |
359 | | }); |
360 | | |
361 | | // start dalek |
362 | 0 | dalek.run(); |
363 | | }); |
364 | | }; |