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