Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow Jasmine unit tests to be run from the Maven build
It uses PhantomJS to execute SpecRunner.html and collects the results in target/surefire-reports. SpecRunner.html can also still be run directly from the browser as before. Right now it's still a in a separate profile called 'jasmine-test'. The integration uses jasmine.phantomjs-reporter.js, core.js and phantomjs_jasminexml_runner.js from the https://github.com/detro/phantomjs-jasminexml-example project. The author has given kind permission to use his code for other projects: https://github.com/detro/phantomjs-jasminexml-example/issues/2#issuecomment-10619232
- Loading branch information
1 parent
ee2fbf1
commit 465c558
Showing
5 changed files
with
341 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
204 changes: 204 additions & 0 deletions
204
hawtio-web/src/test/specs/lib/jasmine-reporters/jasmine.phantomjs-reporter.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
(function() { | ||
|
||
if (! jasmine) { | ||
throw new Exception("jasmine library does not exist in global namespace!"); | ||
} | ||
|
||
function elapsed(startTime, endTime) { | ||
return (endTime - startTime)/1000; | ||
} | ||
|
||
function ISODateString(d) { | ||
function pad(n) { return n < 10 ? '0'+n : n; } | ||
|
||
return d.getFullYear() + '-' | ||
+ pad(d.getMonth()+1) +'-' | ||
+ pad(d.getDate()) + 'T' | ||
+ pad(d.getHours()) + ':' | ||
+ pad(d.getMinutes()) + ':' | ||
+ pad(d.getSeconds()); | ||
} | ||
|
||
function trim(str) { | ||
return str.replace(/^\s+/, "" ).replace(/\s+$/, "" ); | ||
} | ||
|
||
function escapeInvalidXmlChars(str) { | ||
return str.replace(/\&/g, "&") | ||
.replace(/</g, "<") | ||
.replace(/\>/g, ">") | ||
.replace(/\"/g, """) | ||
.replace(/\'/g, "'"); | ||
} | ||
|
||
/** | ||
* PhantomJS Reporter generates JUnit XML for the given spec run. | ||
* Allows the test results to be used in java based CI. | ||
* It appends some DOM elements/containers, so that a PhantomJS script can pick that up. | ||
* | ||
* @param {boolean} consolidate whether to save nested describes within the | ||
* same file as their parent; default: true | ||
* @param {boolean} useDotNotation whether to separate suite names with | ||
* dots rather than spaces (ie "Class.init" not | ||
* "Class init"); default: true | ||
*/ | ||
var PhantomJSReporter = function(consolidate, useDotNotation) { | ||
this.consolidate = consolidate === jasmine.undefined ? true : consolidate; | ||
this.useDotNotation = useDotNotation === jasmine.undefined ? true : useDotNotation; | ||
}; | ||
|
||
PhantomJSReporter.prototype = { | ||
reportRunnerStarting: function(runner) { | ||
this.log("Runner Started."); | ||
}, | ||
|
||
reportSpecStarting: function(spec) { | ||
spec.startTime = new Date(); | ||
|
||
if (! spec.suite.startTime) { | ||
spec.suite.startTime = spec.startTime; | ||
} | ||
|
||
this.log(spec.suite.description + ' : ' + spec.description + ' ... '); | ||
}, | ||
|
||
reportSpecResults: function(spec) { | ||
var results = spec.results(); | ||
spec.didFail = !results.passed(); | ||
spec.status = spec.didFail ? 'Failed.' : 'Passed.'; | ||
if (results.skipped) { | ||
spec.status = 'Skipped.'; | ||
} | ||
this.log(spec.status); | ||
|
||
spec.duration = elapsed(spec.startTime, new Date()); | ||
spec.output = '<testcase classname="' + this.getFullName(spec.suite) + | ||
'" name="' + escapeInvalidXmlChars(spec.description) + '" time="' + spec.duration + '">'; | ||
|
||
var failure = ""; | ||
var failures = 0; | ||
var resultItems = results.getItems(); | ||
for (var i = 0; i < resultItems.length; i++) { | ||
var result = resultItems[i]; | ||
|
||
if (result.type == 'expect' && result.passed && !result.passed()) { | ||
failures += 1; | ||
failure += (failures + ": " + escapeInvalidXmlChars(result.message) + " "); | ||
} | ||
} | ||
if (failure) { | ||
spec.output += "<failure>" + trim(failure) + "</failure>"; | ||
} | ||
spec.output += "</testcase>"; | ||
}, | ||
|
||
reportSuiteResults: function(suite) { | ||
var results = suite.results(); | ||
var specs = suite.specs(); | ||
var specOutput = ""; | ||
// for JUnit results, let's only include directly failed tests (not nested suites') | ||
var failedCount = 0; | ||
|
||
suite.status = results.passed() ? 'Passed.' : 'Failed.'; | ||
suite.statusPassed = results.passed(); | ||
if (results.totalCount === 0) { // todo: change this to check results.skipped | ||
suite.status = 'Skipped.'; | ||
} | ||
|
||
// if a suite has no (active?) specs, reportSpecStarting is never called | ||
// and thus the suite has no startTime -- account for that here | ||
suite.startTime = suite.startTime || new Date(); | ||
suite.duration = elapsed(suite.startTime, new Date()); | ||
|
||
for (var i = 0; i < specs.length; i++) { | ||
failedCount += specs[i].didFail ? 1 : 0; | ||
specOutput += "\n " + specs[i].output; | ||
} | ||
suite.output = '\n<testsuite name="' + this.getFullName(suite) + | ||
'" errors="0" tests="' + specs.length + '" failures="' + failedCount + | ||
'" time="' + suite.duration + '" timestamp="' + ISODateString(suite.startTime) + '">'; | ||
suite.output += specOutput; | ||
suite.output += "\n</testsuite>"; | ||
this.log(suite.description + ": " + results.passedCount + " of " + results.totalCount + " expectations passed."); | ||
}, | ||
|
||
reportRunnerResults: function(runner) { | ||
this.log("Runner Finished."); | ||
var suites = runner.suites(), | ||
passed = true; | ||
for (var i = 0; i < suites.length; i++) { | ||
var suite = suites[i], | ||
filename = 'TEST-' + this.getFullName(suite, true) + '.xml', | ||
output = '<?xml version="1.0" encoding="UTF-8" ?>'; | ||
|
||
passed = !suite.statusPassed ? false : passed; | ||
|
||
// if we are consolidating, only write out top-level suites | ||
if (this.consolidate && suite.parentSuite) { | ||
continue; | ||
} | ||
else if (this.consolidate) { | ||
output += "\n<testsuites>"; | ||
output += this.getNestedOutput(suite); | ||
output += "\n</testsuites>"; | ||
this.createSuiteResultContainer(filename, output); | ||
} | ||
else { | ||
output += suite.output; | ||
this.createSuiteResultContainer(filename, output); | ||
} | ||
} | ||
this.createTestFinishedContainer(passed); | ||
}, | ||
|
||
getNestedOutput: function(suite) { | ||
var output = suite.output; | ||
for (var i = 0; i < suite.suites().length; i++) { | ||
output += this.getNestedOutput(suite.suites()[i]); | ||
} | ||
return output; | ||
}, | ||
|
||
createSuiteResultContainer: function(filename, xmloutput) { | ||
jasmine.phantomjsXMLReporterResults = jasmine.phantomjsXMLReporterResults || []; | ||
jasmine.phantomjsXMLReporterResults.push({ | ||
"xmlfilename" : filename, | ||
"xmlbody" : xmloutput | ||
}); | ||
}, | ||
|
||
createTestFinishedContainer: function(passed) { | ||
jasmine.phantomjsXMLReporterPassed = passed | ||
}, | ||
|
||
getFullName: function(suite, isFilename) { | ||
var fullName; | ||
if (this.useDotNotation) { | ||
fullName = suite.description; | ||
for (var parentSuite = suite.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { | ||
fullName = parentSuite.description + '.' + fullName; | ||
} | ||
} | ||
else { | ||
fullName = suite.getFullName(); | ||
} | ||
|
||
// Either remove or escape invalid XML characters | ||
if (isFilename) { | ||
return fullName.replace(/[^\w]/g, ""); | ||
} | ||
return escapeInvalidXmlChars(fullName); | ||
}, | ||
|
||
log: function(str) { | ||
var console = jasmine.getGlobal().console; | ||
|
||
if (console && console.log) { | ||
console.log(str); | ||
} | ||
} | ||
}; | ||
|
||
// export public | ||
jasmine.PhantomJSReporter = PhantomJSReporter; | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* Collection of Core JavaScript utility functionalities. | ||
*/ | ||
|
||
// Namespace "utils.core" | ||
var utils = utils || {}; | ||
utils.core = utils.core || {}; | ||
|
||
/** | ||
* Wait until the test condition is true or a timeout occurs. Useful for waiting | ||
* on a server response or for a ui change (fadeIn, etc.) to occur. | ||
* | ||
* @param check javascript condition that evaluates to a boolean. | ||
* @param onTestPass what to do when 'check' condition is fulfilled. | ||
* @param onTimeout what to do when 'check' condition is not fulfilled and 'timeoutMs' has passed | ||
* @param timeoutMs the max amount of time to wait. Default value is 3 seconds | ||
* @param freqMs how frequently to repeat 'check'. Default value is 250 milliseconds | ||
*/ | ||
utils.core.waitfor = function(check, onTestPass, onTimeout, timeoutMs, freqMs) { | ||
var timeoutMs = timeoutMs || 3000, //< Default Timeout is 3s | ||
freqMs = freqMs || 250, //< Default Freq is 250ms | ||
start = Date.now(), | ||
condition = false, | ||
timer = setTimeout(function() { | ||
var elapsedMs = Date.now() - start; | ||
if ((elapsedMs < timeoutMs) && !condition) { | ||
// If not time-out yet and condition not yet fulfilled | ||
condition = check(elapsedMs); | ||
timer = setTimeout(arguments.callee, freqMs); | ||
} else { | ||
clearTimeout(timer); //< house keeping | ||
if (!condition) { | ||
// If condition still not fulfilled (timeout but condition is 'false') | ||
onTimeout(elapsedMs); | ||
} else { | ||
// Condition fulfilled (timeout and/or condition is 'true') | ||
onTestPass(elapsedMs); | ||
} | ||
} | ||
}, freqMs); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Based on this project https://github.com/detro/phantomjs-jasminexml-example | ||
// Used with kind permission. | ||
var htmlrunner, | ||
resultdir, | ||
page, | ||
fs; | ||
|
||
phantom.injectJs("lib/utils/core.js") | ||
|
||
if ( phantom.args.length !== 2 ) { | ||
console.log("Usage: phantom_test_runner.js HTML_RUNNER RESULT_DIR"); | ||
phantom.exit(); | ||
} else { | ||
htmlrunner = phantom.args[0]; | ||
resultdir = phantom.args[1]; | ||
page = require("webpage").create(); | ||
fs = require("fs"); | ||
|
||
// Echo the output of the tests to the Standard Output | ||
page.onConsoleMessage = function(msg, source, linenumber) { | ||
console.log(msg); | ||
}; | ||
|
||
page.open(htmlrunner, function(status) { | ||
if (status === "success") { | ||
utils.core.waitfor(function() { // wait for this to be true | ||
return page.evaluate(function() { | ||
return typeof(jasmine.phantomjsXMLReporterPassed) !== "undefined"; | ||
}); | ||
}, function() { // once done... | ||
// Retrieve the result of the tests | ||
var f = null, i, len; | ||
suitesResults = page.evaluate(function(){ | ||
return jasmine.phantomjsXMLReporterResults; | ||
}); | ||
|
||
// Save the result of the tests in files | ||
for ( i = 0, len = suitesResults.length; i < len; ++i ) { | ||
try { | ||
f = fs.open(resultdir + '/' + suitesResults[i]["xmlfilename"], "w"); | ||
f.write(suitesResults[i]["xmlbody"]); | ||
f.close(); | ||
} catch (e) { | ||
console.log(e); | ||
console.log("phantomjs> Unable to save result of Suite '"+ suitesResults[i]["xmlfilename"] +"'"); | ||
} | ||
} | ||
|
||
// Return the correct exit status. '0' only if all the tests passed | ||
phantom.exit(page.evaluate(function(){ | ||
return jasmine.phantomjsXMLReporterPassed ? 0 : 1; //< exit(0) is success, exit(1) is failure | ||
})); | ||
}, function() { // or, once it timesout... | ||
phantom.exit(1); | ||
}); | ||
} else { | ||
console.log("phantomjs> Could not load '" + htmlrunner + "'."); | ||
phantom.exit(1); | ||
} | ||
}); | ||
} |