Salient Solutions

wrasslin ones and nones for fun and profit - Sky Sanders' Blog
Get your own ranked flair here
posts - 92, comments - 103, trackbacks - 0

QUnit test runner - run multiple QUnit pages without modification

 

I had the need to run a bunch of QUnit test pages and get a page level visual with the ability to drill down into a page to examine the tests without hacking them to fit in a runner or adding/changing script references in each and every one.

A need is like an itch and I like to code, not scratch, so here is a little QUnit addon that you can safely tack on to the tail of your deployed qunit.js to enable a runner and to unobtrusively hook the referenced pages. You do not have to touch the test pages and they will run as usual standalone.

 See it in action here

qunit-runner.js - add to qunit.js

/*
// <copyright project "Salient.QualityControl" file="qunit.runner.js" company="Sky Sanders">
// This source is a Public Domain Dedication. 
// http://salientqc.codeplex.com
// Attribution is appreciated.
// </copyright> 
*/

/* this file can safely be added to the tail of your qunit.js to simplify deployment */

(function(window) {
    var runner = function(tests, sequential, done) {
        this.failures = 0;
        this.total = 0;
        this.currentIndex = 0;
        this.sequential = sequential;
        this.tests = tests;
        this.done = done || this.done;
        var that = this;
        $(document).ready(function() {
            $(".runner-test-page-header").live("click", function() { $(this).next(".runner-test-page-frame").slideToggle(100); });
            that.runPage();
        });
    }

    runner.prototype.nextPage = function() {
        if (this.currentIndex + 1 < this.tests.length) {
            this.currentIndex++;
            this.runPage();
        }
    }

    runner.prototype.runPage = function() {
        // update page counter
        $("#qunit-runner-userAgent").html('<span class="runner-test-page-counter">Test Page '
                + (this.currentIndex + 1) + " of " + this.tests.length + "</span><br/>" + window.navigator.userAgent);

        var test = this.tests[this.currentIndex];
        test.title = test.title || test.page;

        // load the test page in an iframe
        test.header = $('<p class="runner-test-page-header">' + test.title + "</p>").addClass("passed")[0];
        $("#runner-test-page-container").append(test.header);

        test.frame = $('<iframe class="runner-test-page-frame" src="' + test.page + '" width="100%" height="200"></iframe>')[0];
        $("#runner-test-page-container").append(test.frame);

        if (!this.sequential) {
            this.nextPage();
        }
    }

    runner.prototype.pageProgress = function(frame, failures, total, testName, isDone) {
        var that = this;
        $.grep(this.tests, function(test, index) {
            if (test.frame === frame) {
                $(test.header).removeClass("passed").addClass(failures > 0 ? "failed" : "passed")
                            .html(test.title + " " + testName
                            + (total == 0 ? "" : "  " + failures + " failed, " + total + " total"));
                if (isDone) {
                    test.complete = true;
                    that.failures += failures;
                    that.total += total;
                    // are all pages finished?
                    if ($.grep(that.tests, function(test, index) { return !test.complete; }).length == 0) {
                        $("#qunit-banner").addClass(that.failures > 0 ? "qunit-fail" : "qunit-pass");
                        that.done(that.failures, that.total);
                    }
                    else {
                        if (that.sequential) {
                            that.nextPage();
                        }
                    }
                }
            }
        });
    }

    // if you need to be notified the runner is finished..
    runner.prototype.done = function(failures, total) {
    }

    QUnit.run = function(tests, sequential, done) {
        /// <param name="tests" type="Array"></param>
        /// <param name="sequential" type="Boolean"></param>
        /// <param name="done" type="Function">Function(failures, total) will be called when all tests complete.</param>
        if (window.__qunit_runner) {
            throw new Error("One runner per page please.");
        }
        window.__qunit_runner = new runner(tests, sequential, done);
    }


    // runner test page hooks - if this page has a runner as parent
    // then set up the metric callbacks
    if (top.__qunit_runner) {
        var runner = top.__qunit_runner;
        QUnit.done = function(failures, total) {
            runner.pageProgress(window.frameElement, failures, total, "done", true);
        };
        QUnit.testStart = function(name) {
            runner.pageProgress(window.frameElement, 0, 0, name + " started");
        }
        QUnit.testDone = function(name, failures, total) {
            runner.pageProgress(window.frameElement, failures, total, name);
        }
    }
})(this);

 qunit-runner.css - deploy this to runner

/*
// <copyright project "Salient.QualityControl" file="qunit.runner.css" company="Sky Sanders">
// This source is a Public Domain Dedication. 
// http://salientqc.codeplex.com
// Attribution is appreciated.
// </copyright> 
*/

/* this file can be added to qunit.css to simplify deployment */

.runner-test-page-counter
{
    font-size: 110%;
}
.runner-test-page-header
{
    position: relative;
    font-family: "Helvetica Neue Light" , "HelveticaNeue-Light" , "Helvetica Neue" , Calibri, Helvetica, Arial;
    list-style-position: inside;
    font-size: small;
    padding: 5px 10px;
    cursor: pointer;
    margin: 1px;
}
.runner-test-page-header.failed
{
    background-color: #EE5757;
    color: #000000;
}
.runner-test-page-header.passed
{
    background-color: #D2E0E6;
    color: #528CE0;
}
.runner-test-page-frame
{
    background-color: #F4F4F8;
    display: none;
}
h2#qunit-runner-userAgent
{
    font-family: "Helvetica Neue Light" , "HelveticaNeue-Light" , "Helvetica Neue" , Calibri, Helvetica, Arial;
    background-color: #2b81af;
    margin: 0;
    padding: 0;
    color: #fff;
    font-size: small;
    padding: 0.5em 0 0.5em 2.5em;
    text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}

Usage - assuming the runner is rolled up into qunit.js 

runner.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <link href="scripts/qunit.css" rel="stylesheet" type="text/css" />
    <link href="scripts/qunit.runner.css" rel="stylesheet" type="text/css" />
    <script src="scripts/jquery-1.4.1.js" type="text/javascript"></script>
    <script src="scripts/qunit.js" type="text/javascript"></script>
    <script type="text/javascript">
        var tests = [
            { page: "TestPage01.htm", title: "Test Page 01 - click me to view test page" },
            { page: "TestPage02.htm" },
            { page: "TestPage01.htm" }
        ];
        var sequential = true; // false to run tests simultaneously
        var runnerDone = function(failures, total) {
            // get notified when runner completes
        }
        QUnit.run(tests, sequential, runnerDone);
    </script>
</head>
<body>
    <div>
        <h1 id="qunit-header">TEST RUNNER</h1>
        <h2 id="qunit-banner"></h2>
        <h2 id="qunit-runner-userAgent"></h2>
        <div id="runner-test-page-container"></div>
    </div>
</body>
</html>

 

TODO: I can see adding some more instrumentation if I get interested in it again, maybe a total footer.

Print | posted on Friday, February 26, 2010 7:45 PM |

Feedback

No comments posted yet.

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 7 and 5 and type the answer here:

Powered by: