small musing on headless testing of javascript in perl apps
JavaScript Perl Shell
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Testing javascript in webpages

So, the problem we are always facing.. How do we test javascript? and user interface javascript at that?

In this new re-write we use PhantomJS, a headless WebKit, to provide us access to a propper DOM etc.

Give me the gist of it

Well, we provide a simple perl-function to run a javascript file against a content-string. That way, so long as you can get the content in a perl scalar, you can run javascript against it. You can for instance use Catalyst::Test, or Test::WWWW::Mechanize::Catalyst to do that, without needing to fork a server etc.

How do I test it?

There are a few things you need:

  • Working PhantomJS
  • clones or downloads of QUnit and qunit-tap.
  • a JSINC env var that points to directories containing the aforementioned files


For Mac OS X, a simple brew install phantomjs will do. For other platforms, there might be differences.


I just cloned it to ~/Projects/js/qunit/, and added ~/Projects/js/qunit/qunit to my JSINC.


I cloned this as well to ~/Projects/js/qunit-tap, and added ~/Projects/js/qunit-tap/lib to my `JSINC

I got all that installed, now what?

Once that is in place, you should be able to run prove in the normal way.

prove -l t/

If all goes well, you should see the regular test output!

Whoa, cool, can I do that with my app?

I sure hope so! Thats the point at least :p The perl-module Startsiden::Test::JavaScript only really provides two functions. js_test and js_live_test. The later does a lot more than the first one, so lets cover the first one first!


js_test can be run in three different ways:

use Startsiden::Test::JavaScript;


use Startsiden::Test::JavaScript;
js_test '<html><body><h1>This is some content</h1></body></html>'


use Startsiden::Test::JavaScript;
js_test 't/html/test.html'

The first one will have no HTML loaded into the DOM, but should still execute your javascript in a good environment.

The second will write the string to a tempfile, and use that as your web page.

The third one will load the given file as your webpage.

Lets say we named this file t/01.basic.t for instance. Then we would also create a t/01.basic.t.js, which will hold our javascript part of the test:

test("Finding class name works", function() {
    var s = document.getElementById('search');
    equals("hidden", s.className);

We let it know how many tests we plan to run, but this is optional. The tests are written using QUnit, but in the background we use Qunit-tap to provide us with TAP output.

As you can see, this test has access to the DOM of the content we passed it. That is a cool thing :)


Then js_live_test comes along! Changing the game completely! How you say? By making things easy to test live:

Again, the Perl-portion of the test is simple. Consider the folowing in t/03.ajax.t:

use Startsiden::Test::JavaScript;
js_live_test cat => 't::TestApp' => '/ajax';

Quite simple ehh? Let me explain: First we have a flag for app-type. We support two types at the moment, one being cat for Catalyst apps, the other being psgi for PSGI-apps.

The cat type will load the class, then enable the PSGI-engine on it, and get a PSGI-app out of that, while the psgi type will just load a psgi-file:

use Startsiden::Test::JavaScript;
js_live_test psgi => 'app.psgi' => '/';

Before we go on, let us also take a moment to look at the content that we run this test against (What /ajax of t::TestApp returns):

        <script type="text/javascript" src="/static/jq.js"></script>
        <script type="text/javascript">
            $().ready(function() {
                // lets try to replace some content as well!
                $('#button2').click(function() {
        <div id="content">a</div>
        <button id="button2">Click me!</button>

You see it links to jquery for instance, and then uses that in the next script-tag.

If we return to the interesting part, our t/03.ajax.t.js:

test("For real, check the ajax!", function() {
    var t = setTimeout(function() {

        equals($('#content').html(), 'C', "and then to C in ajax call");
    }, 1000);

If we start from the top, we see that we now have access to jquery from our test as well, since our test-page loads it. This makes it easy to write your tests in whatever javascript-library you usually use. The only thing we always load is QUnit and qunit-tap.

Line 1 simply simulates a click of a button. Then comes our qunit-test, which uses the async-capabilities of qunit (the stop() and start() functions). You see we also do a setTimeout. This is to give our DOM time to change, since AJAX is by definition Async.

And there you have it, all summed up nicely!