Engineer in Tokyo

Phantom QUnit Test Runner

image

I have been using PhantomJS and QUnit for a while now to run JavaScript automated tests. I like QUnit because has a decent in-browser UI but also has a nice extension API so you can create plugins and output in different formats if you like.

However, I’ve been spoiled by test runners like the Python test runners that create output that is easy to parse with the eyes, shows you tracebacks, and lets you specify filters to limit which tests get run. Most JavaScript test runners aren’t very flexable. QUnit allows you to filter tests, and this can be done easily by adding a simple filter parameter to the URL of your test html page. But the test runners people have written for the console aren’t that friendly.

I would also like to do is output test results in different formats like JUnit. Most QUnit test runners I’ve seen only output in one format and aren’t very flexible.

Another problem I’ve found with other test runners, is that they don’t handle errors in a robust way. PhantomJS allows you to set an error handler by passing a function to the page.onError() method. But if you modify the window.onerror callback like QUnit does, the page.onError() handler doesn’t get called. This can be annoying because your test runner may not be able to know when something has gone wrong and it needs to quit.

Given all these problems I thought that, as with many other things in JavaScript land, the only get all the things I wanted was to write it myself. So I wrote a nice little PhantomJS QUnit test runner script.

Because phantomjs’ command line interface isn’t terribly friendly I included a simple python wrapper that is a bit easier to use. It takes the URL or path to your QUnit HTML file and accepts a number of options:

$ ./runner.py --help
Usage: runner.py FILE_OR_URL [filter] [options]

Run QUnit tests.

Options:
  -h, --help        show this help message and exit
  --output=OUT      The test output format. [console, junit, tap] (Default:
                    console)
  --errorcode=CODE  The error code to use when the test fails.
  --noglobals       Invoke QUnit with the noglobals setting.
  --notrycatch      Invoke QUnit with the notrycatch setting.
  --abbrev          Abbreviated console output.

Console Output

The default console output takes inspiration from mocha.js test runner and the Python test runner. Console output for a test run looks like the this:

$ ./runner.py example/index.html
Tests Started

mymodule

     Equal Test
        ✓ ok() check (0.001s)
        ✓ equal() check (0.001s)
        ✓ Passed Test (0.001s)
     OK Test
        ✓ ok() check (0.001s)

othermodule

     OK Test
        ✓ ok() check (0s)

----------------------------------------------------------------------
Ran 5 tests in 0.099 secs

OK

A failed test run produces a failure report including tracebacks:

$ ./runner.py example/index.html mymodule
Tests Started

mymodule

     Equal Test
        ✓ ok() check (0.001s)
        ✓ equal() check (0.001s)
        ☓ Failed Test (0.001s)
     OK Test
        ✓ ok() check (0s)

======================================================================
FAIL: Equal Test(mymodule)
----------------------------------------------------------------------
Failed Test
    at file:///home/ian/src/phantomjs-qunit/example/qunit.js:435
    at file:///home/ian/src/phantomjs-qunit/example/mymodule.test.js:9
    at file:///home/ian/src/phantomjs-qunit/example/qunit.js:136
    at file:///home/ian/src/phantomjs-qunit/example/qunit.js:279
    at process (file:///home/ian/src/phantomjs-qunit/example/qunit.js:1277)
    at file:///home/ian/src/phantomjs-qunit/example/qunit.js:383
----------------------------------------------------------------------
Ran 4 tests in 0.023 secs

FAILED (failures=1)

Output Formats

With just a simple option change you can get JUnit XML output:

$ ./runner.py example/index.html --output=junit
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="testsuites">
    <testsuite name="mymodule" errors="0" failures="1" tests="4" time="0.002">
        <testcase classname="mymodule" name="Equal Test" time="0.002">
            <failure type="failed" message="Failed Test"/>
        </testcase>
        <testcase classname="mymodule" name="OK Test" time="0"/>
    </testsuite>
    <testsuite name="othermodule" errors="0" failures="0" tests="1" time="0">
        <testcase classname="othermodule" name="OK Test" time="0"/>
    </testsuite>
</testsuites>

… or TAP output:

$ ./runner.py example/index.html --output=tap
# module: mymodule
# test: Equal Test
ok 1 - ok() check
ok 2 - equal() check
not ok 3 - Failed Test
# test: OK Test
ok 4 - ok() check
# module: othermodule
# test: OK Test
ok 5 - ok() check
1..5

Filters

You can apply filters to the input to determine which tests get run:

$ ./runner.py example/index.html othermodule
Tests Started

othermodule

     OK Test
        ✓ ok() check (0s)

----------------------------------------------------------------------
Ran 1 tests in 0.024 secs

OK

QUnit Tests

The test runner doesn’t require you to include any extra code in order to work. Any QUnit test HTML should do. Simply create your test page and save it to an file. You can then give the path to this file to the test runner.

Your HTML may look as follows

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>QUnit Example</title>
    <link rel="stylesheet" href="qunit.css" type="text/css" media="screen" />
    <script type="text/javascript" src="qunit.js"></script>

    <script type="text/javascript" src="mymodule.test.js"></script>
  </head>
  <body>
    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
  </body>
</html>

Code

The code is up on Github here: https://github.com/IanLewis/phantomjs-qunit

The JUnit output is based on this gist by Eric Wendelin. The TAP output is based off of qunit-tap by Takuto Wada.

Future

Some things to think about for the future are getting saving coverage information using JSCoverage.