diff --git a/package.json b/package.json index 58c808c..4a0a03c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,10 @@ "gulp-mocha": "^2.0.0", "mocha": "^2.4.5", "phantomjs-prebuilt": "^2.1.7", - "qunitjs": "^1.23.0" + "qunitjs": "^1.23.0", + "xml2js": "^0.4.16" + }, + "dependencies": { + "qunit-reporter-junit": "^1.0.2" } } diff --git a/runner-xml.js b/runner-xml.js new file mode 100644 index 0000000..35166c4 --- /dev/null +++ b/runner-xml.js @@ -0,0 +1,131 @@ +/*global phantom:false, require:false, console:false, window:false, QUnit:false */ + +(function () { + 'use strict'; + + var url, page, timeout, + args = require('system').args; + + // arg[0]: scriptName, args[1...]: arguments + if (args.length < 2) { + console.error('Usage:\n phantomjs [phantom arguments] runner-xml.js [url-of-your-qunit-testsuite] [timeout-in-seconds] [page-properties]'); + exit(1); + } + + url = args[1]; + + if (args[2] !== undefined) { + timeout = parseInt(args[2], 10); + } + + page = require('webpage').create(); + + if (args[3] !== undefined) { + try { + var pageProperties = JSON.parse(args[3]); + + if (pageProperties) { + for (var prop in pageProperties) { + if (pageProperties.hasOwnProperty(prop)) { + page[prop] = pageProperties[prop]; + } + } + } + } catch (e) { + console.error('Error parsing "' + args[3] + '": ' + e); + } + } + + // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`) + page.onConsoleMessage = function (msg) { + console.log(msg); + }; + + page.onInitialized = function () { + page.evaluate(addLogging); + }; + + page.onCallback = function (message) { + var result, + failed; + + if (message) { + if (message.name === 'QUnit.done') { + result = message.data; + failed = !result || !result.total || result.failed; + + if (!result.total) { + console.error('No tests were executed. Are you loading tests asynchronously?'); + } + + exit(failed ? 1 : 0); + } + } + }; + + page.open(url, function (status) { + if (status !== 'success') { + console.error('Unable to access network: ' + status); + exit(1); + } else { + // Cannot do this verification with the 'DOMContentLoaded' handler because it + // will be too late to attach it if a page does not have any script tags. + var qunitMissing = page.evaluate(function () { + return (typeof QUnit === 'undefined' || !QUnit); + }); + if (qunitMissing) { + console.error('The `QUnit` object is not present on this page.'); + exit(1); + } + + // Set a default timeout value if the user does not provide one + if (typeof timeout === 'undefined') { + timeout = 5; + } + + if (page.injectJs('./node_modules/qunit-reporter-junit/qunit-reporter-junit.js') === false) { + console.error('Could not inject `qunit-reporter-junit.js`.'); + exit(1); + } + + // Override default + page.evaluate(function () { + QUnit.jUnitReport = function (report) { + console.log(report.xml); + }; + }); + + // Set a timeout on the test running, otherwise tests with async problems will hang forever + setTimeout(function () { + console.error('The specified timeout of ' + timeout + ' seconds has expired. Aborting...'); + exit(1); + }, timeout * 1000); + + // Do nothing... the callback mechanism will handle everything! + } + }); + + function addLogging() { + window.document.addEventListener('DOMContentLoaded', function () { + + QUnit.done(function (result) { + if (typeof window.callPhantom === 'function') { + window.callPhantom({ + 'name': 'QUnit.done', + 'data': result + }); + } + }); + + }, false); + } + + function exit(code) { + if (page) { + page.close(); + } + setTimeout(function () { + phantom.exit(code); + }, 0); + } +})(); diff --git a/test/main.js b/test/main.js index d311d2c..3d5049a 100644 --- a/test/main.js +++ b/test/main.js @@ -95,7 +95,7 @@ describe('qunit-phantomjs-runner runner.js', function () { process.stdout.write = function (str) { //out(str); - assert.ok(/Usage:\n phantomjs \[phantom arguments\] runner.js \[url-of-your-qunit-testsuite\] \[timeout-in-seconds\]/.test(str)); + assert.ok(/Usage:\r?\n phantomjs \[phantom arguments\] runner.js \[url-of-your-qunit-testsuite\] \[timeout-in-seconds\]/.test(str)); process.stdout.write = out; cb(); }; @@ -376,3 +376,111 @@ describe('qunit-phantomjs-runner runner-json.js', function () { }; }); }); + +describe('qunit-phantomjs-runner runner-xml.js', function () { + this.timeout(5000); + + it('tests should pass', function (cb) { + + qunit('test/fixtures/passing.html', '../runner-xml.js'); + + process.stdout.write = function (str) { + //out(str); + + process.stdout.write = out; + require('xml2js').parseString(str, function (err, result) { + assert.equal(result.testsuites.$.tests, 10); + assert.equal(result.testsuites.$.failures, 0); + assert.equal(result.testsuites.$.errors, 0); + cb(); + }); + }; + }); + + it('tests should fail', function (cb) { + + qunit('test/fixtures/failing.html', '../runner-xml.js'); + + process.stdout.write = function (str) { + //out(str); + + process.stdout.write = out; + require('xml2js').parseString(str, function (err, result) { + assert.equal(result.testsuites.$.tests, 10); + assert.equal(result.testsuites.$.failures, 1); + assert.equal(result.testsuites.$.errors, 0); + cb(); + }); + }; + }); + + it('should error on no tests', function (cb) { + + qunit('test/fixtures/no-tests.html', '../runner-xml.js'); + + process.stdout.write = function (str) { + //out(str); + + assert.ok(/No tests were executed. Are you loading tests asynchronously?/.test(str)); + process.stdout.write = out; + cb(); + }; + }); + + it('should error when QUnit not found', function (cb) { + + qunit('test/fixtures/no-qunit.html', '../runner-xml.js'); + + process.stdout.write = function (str) { + //out(str); + + assert.ok(/The `QUnit` object is not present on this page./.test(str)); + process.stdout.write = out; + cb(); + }; + }); + + it('should time out', function (cb) { + this.timeout(10000); + + qunit('test/fixtures/async.html', '../runner-xml.js', 1); + + process.stdout.write = function (str) { + //out(str); + + assert.ok(/The specified timeout of 1 seconds has expired. Aborting.../.test(str)); + process.stdout.write = out; + cb(); + }; + }); + + it('should show unable to access network', function (cb) { + + qunit('test/fixtures/not-found.html', '../runner-xml.js'); + + process.stdout.write = function (str) { + //out(str); + + assert.ok(/Unable to access network:/.test(str)); + process.stdout.write = out; + cb(); + }; + }); + + it('should set custom viewport', function (cb) { + + qunit('test/fixtures/custom-viewport.html', '../runner-xml.js', 5, '', pageProperties); + + process.stdout.write = function (str) { + //out(str); + + process.stdout.write = out; + require('xml2js').parseString(str, function (err, result) { + assert(result.testsuites.$.tests, 1); + assert(result.testsuites.$.failures, 0); + assert(result.testsuites.$.errors, 0); + cb(); + }); + }; + }); +});