I would like to use unit testing for JavaScript in TeamCity.
I am using QUnit, and I have seen a couple of places that suggest using phantomjs and QUnitTeamCityDriver. I just have not been able to get it to work...
I don't have a lot of experience in this, and can't seem to even get phantomjs to run unit tests in command line.
I literally copied the example from QUnitTeamCityDriver: simple_test.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>QUnit Example</title>
<link rel="stylesheet" href="test_tools/qunit-1.10.0.css">
<script type="text/javascript" src="resources/jquery-1.8.1.js"></script>
<script type="text/javascript" src="test_tools/qunit-1.10.0.js"></script>
<script type="text/javascript" src="qunit_teamcity_driver/QUnitTeamCityDriver.js"></script>
<script type="text/javascript" src="tests.js"></script>
</head>
<body>
<div id="qunit"></div>
<h1 id="qunit-header">QUnit example</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
</body>
</html>
tests.js has some simple tests that work or not, if I try it with the simple_test.html.
The other referenced files, in the html, are in the respective folders, of course.
phantomjs.exe, tests.js and simple_test.html are in the root of the directory I am calling from.
The directions for TeamCity build are:
Add a "Command Line" Build Step to your build in TeamCity which executes Tests.htm via PhantomJS
Command executable: C:\PhamtomJS\phantomjs.exe
Command parameters: \Scripts\QUnitTeamCityDriver.phantom.js Tests.htm
(which doesn't work, so I want to test on actual command line before putting in the command line inside TeamCity)
Some things I have tried:
phantomjs.exe tests.js
phantomjs.exe tests.js simple_test.html
phantomjs.exe simple_test.html
phantomjs.exe test_tools\qunit-1.10.0.js tests.js simple_test.html
phantomjs.exe qunit_teamcity_driver/QUnitTeamCityDriver.phantom.js simple_test.html
result: either Parse error or Can't find variable: test
Please, can someone point me in the right direction, give me an example, tell me what I am doing wrong ? Thank you very much.
If your technology stack is a match I've had success using Chutzpah.
Among other things it does the heavy lifting for you by dealing with the calls to and from phantomjs described by kabaros, as well as providing TeamCity and Visual Studio integration.
the short answer is, you need to run:
phantomjs.exe script-runner.js simple_test.html
The long answer:
You will need a javascript file that will call your tests, I wrote many variations of this, but the best one I found is this one with QUnitTeamCityDriver https://github.com/redbadger/QUnitTeamCityDriver . I've used with little changes even though I don't work with TeamCity.
Here are the contents of the file with some changes probably (I've used it in one of my recent projects), but you should find the original one on github (call it script-runner.js and put in the same folder as phantom.exe for convenience):
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function () {
if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if (!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
typeof (onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 250ms
};
var page = new WebPage();
// 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.open(phantom.args[0], function (status) {
if (status !== "success") {
console.log("Unable to access network");
phantom.exit(1);
} else {
waitFor(function () {
return page.evaluate(function () {
var el = document.getElementById('qunit-testresult');
if (el && el.innerText.match('completed')) {
return true;
}
return false;
});
}, function () {
phantom.exit();
}, 10000);
}
});
This will basically open your html page and parse it. This was one of the confusing part for me, phantomjs is a browser at the end, you should pass it this script that will eventually open an HTML page rather the Javascript file. So you call phantomjs like this:
phantomjs.exe script-runner.js [path to file containing tests]
so in your case it will be:
phantomjs.exe script-runner.js simple_test.html
Last bit of the puzzle is that in your html page that contains the test, you should add a reference to a javascript file, it is that file called QUnitTeamCityDriver.js in your references. This is what will hookup to qUnit events and channel them to phantom deciding what's a passed test and what's a failed one. The contents of the file (again you can probably find a better up-to-date version on the project page in github):
/*
Reference this file in the html files of the QUnit tests
Based on the work on this team city driver: https://github.com/redbadger/QUnitTeamCityDriver/
*/
if (navigator.userAgent.indexOf("PhantomJS") !== -1) {
String.prototype.format = function () {
var args = arguments;
return this.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != 'undefined'
? args[number]
: '{' + number + '}';
});
};
var suiteName = "QUnit Tests";
var currentTestName = "";
var hasBegun = false;
qunitBegin = function () {
console.log("[testSuiteStarted name='{0}']".format(suiteName));
};
/* QUnit.testStart({ name }) */
QUnit.testStart = function (args) {
if (!hasBegun) {
qunitBegin();
hasBegun = true;
}
currentTestName = args.name;
};
QUnit.moduleStart = function (args) {
console.log("Module started: {0}".format(args.name));
};
/* QUnit.log({ result, actual, expected, message }) */
QUnit.log = function (args) {
var currentAssertion = "{0} > {1}".format(currentTestName, args.message);
//console.log("[testStarted name='{0}']".format(currentAssertion));
if (!args.result) {
console.log("**[testFailed] type='comparisonFailure' name='{0}' details='expected={1}, actual={2}' expected='{1}' actual='{2}'".format(currentAssertion, args.expected, args.actual));
}
console.log("[testFinished] name='{0}'".format(currentAssertion));
};
/* QUnit.done({ failed, passed, total, runtime }) */
QUnit.done = function (args) {
console.log("[testSuiteFinished name='{0}']".format(suiteName));
};
}
This basically hooks to callbacks defined by QUnit and sends message to console which are shown by phantomjs. The QUnit callbacks documentation is found here: http://api.qunitjs.com/category/callbacks/
As for hooking with TeamCity, it is probably just a setting in the Build step definition to check for a certain string in the output (for example, presence of "[testFailed]" in the output indicates failure), I've done it with CruiseControl and also created an Nunit wrapper around it. At the end, the same concept applies, you just parse phatomjs output to check for the presence of [testFailed] or whatever string that will indicate a failure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With