Loading Dependencies in PhantomJS without using a Webserver

We run our javascript QUnit unit tests “headless” using the amazing PhantomJS. Originally our dependency loading code was overridden as a no-op within the tests. But that began to cause problems and require tests to run in a certain order and other Bad Things.

Within the WebPage environment in PhantomJS it is possible to form <script> tags and load code from a webserver, but not so easy to keep things running entirely from the commandline. Especially when you also want to run the same tests in the browser in QA (not everything that passes in WebKit passes in IE8, as if you didn’t know that).

To make this work I overrode the dependency loading code so that it just stores strings of dependencies in a variable xq.required. Then:


...

// Load the tests.
for (var i = 0; i < sources.length; i++) {
    _.page.injectJs(sources[i]);
}

// Now that we've loaded some tests we will have registered some
// dependencies (with require calls in the page).  Load the
// dependencies, recursively if they themselves have dependencies.
_.loadDependencies(_.page);

...

// Loop over the known dependencies and load them.  Also load any of their
// dependencies and so on, turtles all the way down.
_.loadDependencies = function(page) {
    var loaded = [];
    while (true) {
        // Ask the page what dependencies it has
        toLoad = page.evaluate(function () {
            return window.xq.required; // executes inside page, not phantom
        });

        // Diff toLoad with loaded to get stuff that hasn't been loaded yet
        toLoad = toLoad.filter(function(i) { return (loaded.indexOf(i) < 0); });
        if (toLoad.length === 0) {
            break;
        }

        // Load the stuff that hasn't been loaded yet
        for (var i = 0; i < toLoad.length; i++) {
            _.loadByNamespace(page, toLoad[i]);
        }
        // Add what we just loaded to the list of stuff we've already loaded
        loaded = loaded.concat(toLoad);
    }
}

Where loadByNamespace converts its second argument to a file path and loads it into the first argument with page.injectJs.

Now all the dependencies are loaded we can run the tests and report the results.

Leave a Reply

Your email address will not be published.