David Wheeler - Test.Simple-0.11

Documentation | Source

Name

Test.Harness.Browser - Run TAP standard JavaScript test scripts with statistics in a Browser

Synopsis

  <html>
  <head>
    <script type="text/javascript" src="../lib/Test/Harness.js"></script>
    <script type="text/javascript" src="../lib/Test/Harness/Browser.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      Test.Harness.Browser.runTests(
          'async.html',
          'bad_plan.html',
          'buffer.html',
          'builder.html'
      );
    </script>
  </body>
  </html>

Description

STOP! If all you want to do is write a test script, consider using Test.Simple. Test.Harness is the module that reads the output from Test.Simple, Test.More and other modules based on Test.Builder. You don't need to know about Test.Harness to use those modules.

Test.Harness.Browser runs JavaScript tests in a browser and expects to get the results from the TestResults attribute of the Test.Builder object constructed by each test script. These results conform to a format called TAP, the Test Anything Protocol. It is defined in http://search.cpan.org/dist/Test-Harness/lib/Test/Harness/TAP.pod. See Test.Harness for details on the output.

Class Methods

  Test.Harness.Browser.runTests('testone.html', 'testtwo.html');

Constructs a new Test.Harness.Browser object and calls its runTests() instance method, passing all arguments along.

Constructors

  var harness = new Test.Harness.Browser();

Constructs a new Test.Harness.Browser object.

Instance Methods

runTests

  harness.runTests('testone.html', 'testtwo.html');
This method runs all the given test files and divines whether they passed or failed based on the contents of the TestRusults attribute of their global Test.Builder.Test object. It prints out each individual test that failed along with a summary report and a how long it all took. When all tests have been run, a diagnostic message will be output. See Test.Harness for details on the output.

GET Options

An HTML file that uses Test.Harness.Browser will automatically process the GET its arguments, and these can be used to affect the behavior of the harness.

verbose

  index.html?verbose=1
Set the verbose option to a true value to have all of the output of all of the tests in the harness output to the browser window. By default, only failing tests display their output.

file

  index.html?file=foo.html,file=bar.html
Set the file option to override the list of files passed to runTest(). This option may be specified multiple times, and each file specified will be passed to runTest().

Bugs

Safari (and maybe KHTML?) has a number of bugs that affect how Test.Harness.Browser works. The most obvious is that it cannot run tests on a local disk. The harness only works in Safari if the tests are served by a Web server. The WebKit team is aware of the issue; expect it to be fixed in a future version.

Other Safari bugs I repoted while writing this module:

iFrame Doesn't seem to Respect a local "file://" src

http://bugzilla.opendarwin.org/show_bug.cgi?id=3593

Function.toString() Doesn't Stringify Constructors as Attributes

Some tests are skipped in tests/create.html, tests/harness.html, and tests/more.html to work around this bug.

http://bugzilla.opendarwin.org/show_bug.cgi?id=3537

WebKit JavaScript Does not Properly Support Circular References

One test is skipped in tests/circular_data.html to work around this bug.

http://bugzilla.opendarwin.org/show_bug.cgi?id=3539

iFrames Appear to be Cached

http://bugzilla.opendarwin.org/show_bug.cgi?id=3580

iFrames set to display:none are Missing from frames array

So the iframe used to run tests isn't hidden in Safari. Instead, it is set to "height: 0; widht: 0".

http://bugzilla.opendarwin.org/show_bug.cgi?id=3581

Add Support for the watch() method of Object

This would just be nice to have, so that we wouldn't have to set timeouts to check for test completion.

http://bugzilla.opendarwin.org/show_bug.cgi?id=3659

See Also

Test.Harness, the base class for this class.

Test.Simple and Test.More, modules with which to write tests.

Authors

David Wheeler <david@kineticode.com>.

Copyright

Copyright 2005 by David Wheeler <david@kineticode.com>

This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License or the GNU GPL.

See http://www.perl.com/perl/misc/Artistic.html and http://www.gnu.org/copyleft/gpl.html.

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 68:

You forgot a '=back' before '=head2'

// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $

if (typeof JSAN != 'undefined') new JSAN().use('Test.Harness');

Test.Harness.Browser = function () {};
Test.Harness.Browser.VERSION = '0.11';

Test.Harness.Browser.runTests = function () {
    var harness = new Test.Harness.Browser();
    harness.runTests.apply(harness, arguments);
};

Test.Harness.Browser.prototype = new Test.Harness();
Test.Harness.Browser.prototype.interval = 100;

Test.Harness.Browser.prototype._setupFrame = function () {
    // Setup the iFrame to run the tests.
    var node = document.getElementById('buffer');
    if (node) return node.contentWindow;
    node = document.createElement("iframe");
    node.setAttribute("id", "buffer");
    node.setAttribute("name", "buffer");
    // Safari makes it impossible to do anything with the iframe if it's set
    // to display:none. See:
    // http://www.quirksmode.org/bugreports/archives/2005/02/hidden_iframes.html
    if (/Safari/.test(navigator.userAgent)) {
        node.style.visibility = "hidden";
        node.style.height = "0"; 
        node.style.width = "0";
    } else
        node.style.display = "none";
    document.body.appendChild(node);
    return node.contentWindow;
};

Test.Harness.Browser.prototype._setupOutput = function () {
    // Setup the pre element for test output.
    var node = document.createElement("pre");
    node.setAttribute("id", "output");
    document.body.appendChild(node);
    return function (msg) {
        node.appendChild(document.createTextNode(msg));
        window.scrollTo(0, document.body.offsetHeight
                        || document.body.scrollHeight);
    };
};

Test.Harness.Browser.prototype._setupSummary = function () {
    // Setup the div for the summary.
    var node = document.createElement("div");
    node.setAttribute("id", "summary");
    node.setAttribute("style", "white-space:pre; font-family: Verdana,Arial,serif;");
    document.body.appendChild(node);
    return function (msg) {
        node.appendChild(document.createTextNode(msg));
        window.scrollTo(0, document.body.offsetHeight
                        || document.body.scrollHeight);
    };
};

Test.Harness.Browser.prototype.runTests = function () {
    var files = this.args.file
      ? typeof this.args.file == 'string' ? [this.args.file] : this.args.file
      : arguments;
    if (!files.length) return;
    var outfiles = this.outFileNames(files);
    var buffer = this._setupFrame();
    var harness = this;
    var ti = 0;
    var start;
    var node = document.getElementById('output');
    var output = this._setupOutput();
    var summaryOutput = this._setupSummary();
    // These depend on how we're watching for a test to finish.
    var finish = function () {}, runNext = function () {};

    // This function handles most of the work of outputting results and
    // running the next test, if there is one.
    var runner = function () {
        harness.outputResults(
            buffer.Test.Builder.Test,
            files[ti],
            output,
            harness.args
        );

        if (files[++ti]) {
            output(outfiles[ti] + (harness.args.verbose ? Test.Harness.LF : ''));
            buffer.location.href = files[ti];
            runNext();
        } else {
            harness.outputSummary(
                summaryOutput,
                new Date() - start
             );
            finish();
        }
    };

    if (Object.watch) {
        // We can use the cool watch method, and avoid setting timeouts!
        // We just need to unwatch() when all tests are finished.
        finish = function () { Test.Harness.unwatch('Done') };
        Test.Harness.watch('Done', function (attr, prev, next) {
            if (next < buffer.Test.Builder.Instances.length) return next;
            runner();
            return 0;
        });
    } else {
        // Damn. We have to set timeouts. :-(
        var wait = function () {
            // Check Test.Harness.Done. If it's non-zero, then we know that
            // the buffer is fully loaded, because it has incremented
            // Test.Harness.Done.
            if (Test.Harness.Done > 0
                && Test.Harness.Done >= buffer.Test.Builder.Instances.length)
            {
                Test.Harness.Done = 0;
                runner();
            } else {
                window.setTimeout(wait, harness.interval);
            }
        };
        // We'll just have to set a timeout for the next test.
        runNext = function () { window.setTimeout(wait, harness.interval); };
        window.setTimeout(wait, this.interval);
    }

    // Now start the first test.
    output(outfiles[ti] + (this.args.verbose ? Test.Harness.LF : ''));
    start = new Date();
    buffer.location.href = files[ti]; // replace() doesn't seem to work.
};

// From "JavaScript: The Difinitive Guide 4ed", p 214.
Test.Harness.Browser.prototype.args = {};
var pairs = location.search.substring(1).split(",");
for (var i = 0; i < pairs.length; i++) {
    var pos = pairs[i].indexOf('=');
    if (pos == -1) continue;
    var key = pairs[i].substring(0, pos);
    var val = pairs[i].substring(pos + 1); 
    if (Test.Harness.Browser.prototype.args[key]) {
        if (typeof Test.Harness.Browser.prototype.args[key] == 'string') {
            Test.Harness.Browser.prototype.args[key] =
                [Test.Harness.Browser.prototype.args[key]];
        }
        Test.Harness.Browser.prototype.args[key].push(unescape(val));
    } else {
        Test.Harness.Browser.prototype.args[key] = unescape(val);
    }
}
delete pairs;

Test.Harness.Browser.prototype.formatFailures = function (fn) {
    // XXX append new element for table and then populate it.
    var failedStr = "Failed Test";
    var middleStr = " Total Fail  Failed  ";
    var listStr = "List of Failed";
    var table = '<table style=""><tr><th>Failed Test</th><th>Total</th>'
      + '<th>Fail</th><th>Failed</th></tr>';
    for (var i = 0; i < this.failures.length; i++) {
        var track = this.failures[i];
        table += '<tr><td>' + track.fn + '</td>'
          + '<td>' + track.total + '</td>'
          + '<td>' + track.total - track.ok + '</td>'
          + '<td>' + this._failList(track.failList) + '</td></tr>'
    };
    table += '</table>' + Test.Harness.LF;
    var node = document.getElementById('summary');
    node.innerHTML += table;
    window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight);
};