Plato on Github
Report Home
index.js
Maintainability
64.72
Lines of code
322
Difficulty
47.41
Estimated Errors
1.90
Function weight
By Complexity
By SLOC
/*! * * Copyright (c) 2013 Sebastian Golasch * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ 'use strict'; // ext. libs var fs = require('fs'); var path = require('path'); var jsonxml = require('jsontoxml'); // int. global var reporter = null; /** * The jUnit reporter can produce a jUnit compatible file with the results of your testrun, * this reporter enables you to use daleks testresults within a CI environment like Jenkins. * * The reporter can be installed with the following command: * * ```bash * $ npm install dalek-reporter-junit --save-dev * ``` * * The file will follow the jUnit XML format: * * ```html * <?xml version="1.0" encoding="utf-8"?> * <resource name="DalekJSTest"> * <testsuite start="1375125067" name="Click - DalekJS guinea pig [Phantomjs]" end="1375125067" totalTests="1"> * <testcase start="1375125067" name="Can click a select option (OK, jQuery style, no message)" end="1375125067" result="pass"> * <variation start="1375125067" name="val" end="1375125067"> * <severity>pass</severity> * <description><![CDATA[David is the favourite]]></description> * <resource>DalekJSTest</resource> * </variation> * <variation start="1375125067" name="val" end="1375125067"> * <severity>pass</severity> * <description><![CDATA[Matt is now my favourite, bow ties are cool]]></description> * <resource>DalekJSTest</resource> * </variation> * </testcase> * </testsuite> * </resource> * ``` * * By default the file will be written to `report/dalek.xml`, * you can change this by adding a config option to the your Dalekfile * * ```javascript * "junit-reporter": { * "dest": "your/folder/your_file.xml" * } * ``` * * If you would like to use the reporter (in addition to the std. console reporter), * you can start dalek with a special command line argument * * ```bash * $ dalek your_test.js -r console,junit * ``` * * or you can add it to your Dalekfile * * ```javascript * "reporter": ["console", "junit"] * ``` * * @class Reporter * @constructor * @part JUnit * @api */ function Reporter (opts) { this.events = opts.events; this.config = opts.config; this.testCount = 0; this.testIdx = -1; this.variationCount = -1; this.data = {}; this.data.tests = []; this.browser = null; var defaultReportFolder = 'report'; this.dest = this.config.get('junit-reporter') && this.config.get('junit-reporter').dest ? this.config.get('junit-reporter').dest : defaultReportFolder; // prepare base xml this.xml = [ { name: 'resource', attrs: { name:'DalekJSTest' }, children: [] } ]; this.startListening(); } /** * @module Reporter */ module.exports = function (opts) { if (reporter === null) { reporter = new Reporter(opts); } return reporter; }; Reporter.prototype = { /** * Connects to all the event listeners * * @method startListening * @chainable */ startListening: function () { this.events.on('report:run:browser', this.runBrowser.bind(this)); this.events.on('report:assertion', this.assertion.bind(this)); this.events.on('report:test:started', this.testStarted.bind(this)); this.events.on('report:test:finished', this.testFinished.bind(this)); this.events.on('report:runner:finished', this.runnerFinished.bind(this)); this.events.on('report:testsuite:started', this.testsuiteStarted.bind(this)); //this.events.on('report:testsuite:finished', this.testsuiteFinished.bind(this)); return this; }, /** * Stores the current browser name * * @method runBrowser * @param {string} browser Browser name * @chainable */ runBrowser: function (browser) { this.browser = browser; return this; }, /** * Generates XML skeleton for testsuites * * @method testsuiteStarted * @param {string} name Testsuite name * @chainable */ testsuiteStarted: function (name) { this.testCount = 0; this.testIdx++; this.xml[0].children.push({ name: 'testsuite', children: [], attrs: { name: name + ' [' + this.browser + ']', } }); return this; }, /** * Finishes XML skeleton for testsuites * * @method testsuiteFinished * @chainable */ testsuiteFinished: function () { // this.xml[0].children[this.testIdx].attrs.end = Math.round(new Date().getTime() / 1000); return this; }, /** * Generates XML skeleton for an assertion * * @method assertion * @param {object} data Event data * @chainable */ assertion: function (data) { if (! data.success) { //var timestamp = Math.round(new Date().getTime() / 1000); this.xml[0].children[this.testIdx].children[this.testCount].children.push({ name: 'failure', attrs: { name: data.type, message: (data.message ? data.message : 'Expected: ' + data.expected + 'Actual: ' + data.value) } }); //if (this.variationCount > -1 && this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount]) { //this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs.end = timestamp; //} this.variationCount++; } return this; }, /** * Generates XML skeleton for a testcase * * @method testStarted * @param {object} data Event data * @chainable */ testStarted: function (data) { this.variationCount = -1; this.xml[0].children[this.testIdx].children.push({ name: 'testcase', children: [], attrs: { classname: this.xml[0].children[this.testIdx].attrs.name, name: data.name } }); return this; }, /** * Finishes XML skeleton for a testcase * * @method testFinished * @param {object} data Event data * @chainable */ testFinished: function () { //var timestamp = Math.round(new Date().getTime() / 1000); if (this._checkNodeAttributes(this.testIdx, this.testCount)) { this.xml[0].children[this.testIdx].children[this.testCount].attrs = {}; } //this.xml[0].children[this.testIdx].children[this.testCount].attrs.end = timestamp; //this.xml[0].children[this.testIdx].children[this.testCount].attrs.result = data.status ? 'Passed' : 'Failed'; if (this.variationCount > -1) { if (this._checkNodeAttributes(this.testIdx, this.testCount, this.variationCount)) { this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs = {}; } //this.xml[0].children[this.testIdx].children[this.testCount].children[this.variationCount].attrs.end = timestamp; } this.testCount++; this.variationCount = -1; return this; }, /** * Finishes XML and writes file to the file system * * @method runnerFinished * @param {object} data Event data * @chainable */ runnerFinished: function (data) { this.data.elapsedTime = data.elapsedTime; this.data.status = data.status; this.data.assertions = data.assertions; this.data.assertionsFailed = data.assertionsFailed; this.data.assertionsPassed = data.assertionsPassed; var contents = jsonxml(this.xml, {escape: true, removeIllegalNameCharacters: true, prettyPrint: true, xmlHeader: 'version="1.0" encoding="UTF-8"'}); if (path.extname(this.dest) !== '.xml') { this.dest = this.dest + '/dalek.xml'; } this.events.emit('report:written', {type: 'junit', dest: this.dest}); this._recursiveMakeDirSync(path.dirname(this.dest.replace(path.basename(this.dest, '')))); fs.writeFileSync(this.dest, contents, 'utf8'); }, /** * Helper method to generate deeper nested directory structures * * @method _recursiveMakeDirSync * @param {string} path PAth to create */ _recursiveMakeDirSync: function (path) { var pathSep = require('path').sep; var dirs = path.split(pathSep); var root = ''; while (dirs.length > 0) { var dir = dirs.shift(); if (dir === '') { root = pathSep; } if (!fs.existsSync(root + dir)) { fs.mkdirSync(root + dir); } root += dir + pathSep; } }, /** * Helper method to check if attributes should be set to an empty object literal * * @method _checkNodeAttributes * @param {string} testIdx Id of the test node * @param {string} testCount Id of the child node * @param {string} variationCount Id of the testCount child node */ _checkNodeAttributes: function (testIdx, testCount, variationCount) { if (variationCount === undefined) { return typeof this.xml[0].children[testIdx].children[testCount].attrs === 'undefined'; } return typeof this.xml[0].children[testIdx].children[testCount].children[variationCount].attrs === 'undefined'; } };