source from https://bitbucket.org/ms2ger/test-runner/ testJam
authorGopal Raghavan <gopal.raghavan@nokia.com>
Thu, 03 May 2012 14:58:30 -0700
branchtestJam
changeset 44 bb7c550da66e
parent 41 4c52b9a6b4c5
child 45 0c5859aace90
source from https://bitbucket.org/ms2ger/test-runner/
tests/testRunner/checkManifests.py
tests/testRunner/index.html
tests/testRunner/manifests.txt
tests/testRunner/parseManifest.py
tests/testRunner/reftest.html
tests/testRunner/runner.css
tests/testRunner/runner.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testRunner/checkManifests.py	Thu May 03 14:58:30 2012 -0700
@@ -0,0 +1,46 @@
+# Copyright (C) 2011-2012 Ms2ger
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os, sys
+import parseManifest
+
+def getDirsAndFiles(root):
+  files = ["MANIFEST"]
+  dirs, autotests, reftests, othertests, supportfiles = parseManifest.parseManifestFile(root + "/MANIFEST")
+  files.extend(autotests)
+  for r in reftests:
+    files.extend([r[1], r[2]])
+  files.extend(othertests)
+  files.extend(supportfiles)
+  return set(dirs), set(files)
+
+def checkDir(path):
+  for root, dirs, files in os.walk(path):
+    print "Checking " + root
+    mdirs, mfiles = getDirsAndFiles(root)
+    for real in dirs:
+      if not real in mdirs:
+        raise Exception(real + " not listed.")
+    for real in files:
+      if not real in mfiles:
+        raise Exception(real + " not listed.")
+
+if __name__ == "__main__":
+  checkDir(sys.argv[1])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testRunner/index.html	Thu May 03 14:58:30 2012 -0700
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang=en>
+<meta charset=UTF-8>
+<title>Web tests</title>
+<link rel=stylesheet href=runner.css>
+<script src=runner.js></script>
+<p><button value=/webappsec/tests/cors/submitted/cors1.0/>Run CORS 1.0 tests</button>
+<button value=/webappsec/tests/cors/submitted/opera/js/>Run CORS Opera tests</button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testRunner/manifests.txt	Thu May 03 14:58:30 2012 -0700
@@ -0,0 +1,10 @@
+This code relies on the existence of MANIFEST files to list the tests within
+a directory.  These files consist of LF-separated lines, each of which falls
+in one of categories:
+
+* Directory: "dir foo"
+* Support files: "support foo.html"
+* Reftests: "foo.html == foo-ref.html == foo-ref2.html != foo-notref.html"
+* Automated tests (testharness.js): "foo.html"
+* Manual tests: "manual foo.html"
+* HTML parser data files: "parser foo.dat"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testRunner/parseManifest.py	Thu May 03 14:58:30 2012 -0700
@@ -0,0 +1,64 @@
+# Copyright (C) 2011-2012 Ms2ger
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+def parseManifest(fd):
+  def parseReftestLine(chunks):
+    assert len(chunks) % 2 == 0
+    reftests = []
+    for i in range(2, len(chunks), 2):
+      if not chunks[i] in ["==", "!="]:
+        raise Exception("Misformatted reftest line " + line)
+      reftests.append([chunks[i], chunks[1], chunks[i + 1]])
+    return reftests
+
+  dirs = []
+  autotests = []
+  reftests = []
+  othertests = []
+  supportfiles = []
+  for fullline in fd:
+    line = fullline.strip()
+    if not line:
+      continue
+
+    chunks = line.split(" ")
+
+    if chunks[0] == "MANIFEST":
+      raise Exception("MANIFEST listed on line " + line)
+
+    if chunks[0] == "dir" or (chunks[0] == "support" and chunks[1] == "dir"):
+      dirs.append(chunks[1]);
+    elif chunks[0] == "ref":
+      if len(chunks) % 2:
+        raise Exception("Missing chunk in line " + line)
+      reftests.extend(parseReftestLine(chunks))
+    elif chunks[0] == "support":
+      supportfiles.append(chunks[1])
+    elif chunks[0] in ["manual", "parser"]:
+      othertests.append(chunks[1])
+    else: # automated
+      autotests.append(chunks[0])
+  return dirs, autotests, reftests, othertests, supportfiles
+
+def parseManifestFile(path):
+  fp = open(path)
+  dirs, autotests, reftests, othertests, supportfiles = parseManifest(fp)
+  fp.close()
+  return dirs, autotests, reftests, othertests, supportfiles
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testRunner/reftest.html	Thu May 03 14:58:30 2012 -0700
@@ -0,0 +1,91 @@
+<!doctype html>
+<title>Reftest harness</title>
+<style>
+iframe { width: 95%; height: 20em; }
+#test { border-color: blue; }
+#ref { border-color: green; }
+</style>
+<h1>Reftests</h1>
+<script>
+"use strict";
+function report(aResult, aMessage) {
+  pass &= aResult
+  var res = { status: +!aResult,
+              nodes: aResult ? [] : [document.createTextNode(aMessage)] }
+  results.push(res)
+  parent.result_callback(res)
+}
+function nextTest() {
+  if (!tests.length) {
+    parent.completion_callback(results, { status: +!pass })
+    return;
+  }
+  var test = tests.shift()
+  msgs.textContent = "These pages should " +
+                     (test[0] === "==" ? "match" : "not match") +
+                     "."
+  iframetest.src = path + test[1]
+  iframeref.src = path + test[2]
+}
+var pass = true
+var results = []
+var t = JSON.parse(decodeURIComponent(location.search.substring(1))), path = t[0], tests = t[1]
+var msgs = document.body.appendChild(document.createElement("p"))
+var p = document.body.appendChild(document.createElement("p"));
+p.textContent = "Look at the "
+var tButton = document.createElement("button")
+tButton.textContent = "Test"
+tButton.disabled = true
+tButton.onclick = function() {
+  tButton.disabled = true
+  rButton.disabled = false
+  iframetest.style.display = "inline"
+  iframeref.style.display = "none"
+}
+p.appendChild(tButton);
+p.appendChild(document.createTextNode(" "));
+var rButton = document.createElement("button")
+rButton.textContent = "Reference"
+rButton.onclick = function() {
+  rButton.disabled = true
+  tButton.disabled = false
+  iframetest.style.display = "none"
+  iframeref.style.display = "inline"
+}
+p.appendChild(rButton);
+
+var iframetest = document.body.appendChild(document.createElement("p"))
+                              .appendChild(document.createElement("iframe"))
+iframetest.id = "test"
+iframetest.style.display = "inline"
+var iframeref = document.body.lastChild
+                             .appendChild(document.createElement("iframe"))
+iframeref.id = "ref"
+iframeref.style.display = "none"
+
+p = document.body.appendChild(document.createElement("p"));
+p.textContent = "This test has: "
+var button = document.createElement("button")
+button.textContent = "Passed"
+button.onclick = function() {
+  report(true, "Tester clicked \"Pass\"");
+  nextTest()
+}
+p.appendChild(button);
+p.appendChild(document.createTextNode(" "));
+button = document.createElement("button")
+button.textContent = "Failed"
+button.onclick = function() {
+  report(false, "Tester clicked \"Fail\"");
+  nextTest()
+}
+p.appendChild(button);
+p.appendChild(document.createTextNode(" "));
+button = document.createElement("button")
+button.textContent = "Skip"
+button.onclick = function() {
+  nextTest()
+}
+p.appendChild(button);
+nextTest()
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testRunner/runner.css	Thu May 03 14:58:30 2012 -0700
@@ -0,0 +1,81 @@
[email protected] "UTF-8";
+html { margin: 0 8px; }
+body { margin: 0; }
+html.done { border: 2px solid limegreen; margin: 3px; padding: 3px; }
+
+html:not(.done) { height: 100%; }
+html:not(.done) body { height: 100%; }
+
+header { display: block; font-weight: bold; }
+html.done header { font-size: 1.5em; margin: 0 0 .66em; }
+html.done header p { height: 5em; margin: 0; }
+
+html:not(.done) header { border: thin solid; height: 1.5em;
+                         -moz-box-sizing: border-box;
+                         -webkit-box-sizing: border-box;
+                         box-sizing: border-box; }
+html:not(.done) header p { height: 100%; width: 15em; margin: 0; float: right; }
+
+header meter { display: block; height: 100%; width: 100%; }
+header dl, header dt, header dd { margin: 0; padding: 0; }
+header dt, header dd { display: inline; }
+header dt:after { content: ": "; }
+html.done header dd:after { content: "\a"; white-space: pre-line; }
+html:not(.done) header dd:after { content: "; "; }
+html:not(.done) header dd:last-child:after { content: "."; }
+html:not(.done) header dl { line-height: 1.5; }
+
+html:not(.done) #wrapper { height: 100%; margin-top: -1.5em; padding-top: 1.5em;
+                           -moz-box-sizing: border-box;
+                           -webkit-box-sizing: border-box;
+                           box-sizing: border-box; }
+
+html:not(.done) #results { width: 15em; height: 100%; float:left;
+                           overflow-x: hidden; overflow-y: scroll; }
+
+section { display: block; border: thin solid black; padding: 0.5em 0; }
+section h1 { margin: 0; font-size: 1em; }
+html.done section h1 { text-align: center; }
+section ol { padding: 0; margin: 0; list-style-position: inside; }
+html.done section ol { -moz-column-count: 3; -webkit-column-count: 3; column-count: 3; }
+section li { padding: 0.1em 0.5em; }
+section li.pass:nth-child(odd)  { background: #E5FFE5; }
+section li.pass:nth-child(even) { background: #DEF8DE; }
+section li.fail:nth-child(odd)  { background: #FFE5E5; }
+section li.fail:nth-child(even) { background: #F8DEDE; }
+section p { margin: 0; }
+html:not(.done) section { border-top: none; }
+html.done section + section { border-top: none; }
+
+html:not(.done) #iframewrapper { margin: 0 0 0 15em; padding: 2px 2px 3px; height: 100%;
+                                 -moz-box-sizing: border-box;
+                                 -webkit-box-sizing: border-box;
+                                 box-sizing: border-box; }
+html:not(.done) iframe { width: 100%; height: 100%; border-width: 2px; margin: -2px; }
+
+html:not(.done) body.manual #iframewrapper { height: 90%; }
+html:not(.done) body.manual #manualUI { display: block; margin: 0 0 0 15em;
+                                        height: 10%; }
+#manualUI { display: none; }
+
+body > p { text-align: center; }
+body > p > textarea { width: 90%; height: 20em; }
+
+meter::-webkit-meter-horizontal-bar {
+  background: -webkit-gradient(linear, 0% 0%, 0% 100%,
+                               from(#F77),
+                               color-stop(0.2, #FCC),
+                               color-stop(0.45, #D44),
+                               color-stop(0.55, #D44),
+                               to(#F77));
+}
+meter::-webkit-meter-horizontal-optimum-value,
+meter::-webkit-meter-horizontal-suboptimal-value,
+meter::-webkit-meter-horizontal-even-less-good-value {
+  background: -webkit-gradient(linear, 0% 0%, 0% 100%,
+                               from(#AD7),
+                               color-stop(0.2, #CEA),
+                               color-stop(0.45, #7A3),
+                               color-stop(0.55, #7A3),
+                               to(#AD7));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testRunner/runner.js	Thu May 03 14:58:30 2012 -0700
@@ -0,0 +1,530 @@
+"use strict";
+var runner;
+
+function Runner(aPath) {
+  this.mStartTime = (new Date()).getTime();
+  this.mPath = aPath;
+  this.mTimeouts = 0;
+  this.mToBeProcessed = 1;
+  this.mPasses = 0;
+  this.mFails = 0;
+  this.mIframe = document.createElement("iframe");
+  this.mMeter = null;
+  this.mScoreNode = null
+  this.mPassNode = null
+  this.mFailNode = null
+  this.mWrapper = null;
+  this.mSectionWrapper = null;
+  this.mSection = null;
+  this.mOl = null;
+  this.mTests = [];
+  this.mTestCount = -1;
+  this.mSkipManual = false;
+};
+Runner.prototype = {
+  "sTimeout": 10000, //ms
+
+  "sOrigTitle": document.title,
+
+  "sSupportsMeter": "value" in document.createElement("meter"),
+
+  "currentTest": function Runner_currentTest() {
+    return this.mTests[this.mTestCount];
+  },
+
+  "start": function Runner_start() {
+    var options = parseOptions();
+    this.mSkipManual = "skipmanual" in options && options["skipmanual"] === "1";
+
+    document.title = this.sOrigTitle;
+    document.documentElement.className = "";
+
+    clearBody();
+
+    var header = document.body.appendChild(document.createElement("header"));
+    this.mMeter = header.appendChild(document.createElement("p"))
+                        .appendChild(document.createElement("meter"));
+
+    if (!this.sSupportsMeter) {
+      this.mMeter.parentNode.style.background = "red";
+      this.mMeter.style.background = "limegreen";
+      this.mMeter.style.width = "67%";
+    }
+
+    var dl = header.appendChild(document.createElement("dl"));
+    dl.appendChild(document.createElement("dt"))
+      .appendChild(document.createTextNode("Score"));
+    this.mScoreNode = dl.appendChild(document.createElement("dd"))
+                        .appendChild(document.createTextNode("0%"));
+    dl.appendChild(document.createElement("dt"))
+      .appendChild(document.createTextNode("Pass"));
+    this.mPassNode = dl.appendChild(document.createElement("dd"))
+                       .appendChild(document.createTextNode("0"));
+    dl.appendChild(document.createElement("dt"))
+      .appendChild(document.createTextNode("Fail"));
+    this.mFailNode = dl.appendChild(document.createElement("dd"))
+                       .appendChild(document.createTextNode("0"));
+
+    this.mWrapper =
+      document.body.appendChild(document.createElement("div"));
+    this.mWrapper.id = "wrapper";
+
+    this.mSectionWrapper =
+      this.mWrapper.appendChild(document.createElement("div"));
+    this.mSectionWrapper.id = "results";
+
+    var p = this.mWrapper.appendChild(document.createElement("p"));
+    p.id = "iframewrapper";
+    p.appendChild(this.mIframe);
+
+    if (!this.mSkipManual) {
+      p = this.mWrapper.appendChild(document.createElement("p"));
+      p.id = "manualUI";
+      p.textContent = "This test has: "
+
+      var button = document.createElement("button")
+      button.textContent = "Passed"
+      button.onclick = function() {
+        report(true, "Tester clicked \"Pass\"");
+        runner.currentTest().passes = 1;
+        runner.finishTest();
+      }
+      p.appendChild(button);
+      p.appendChild(document.createTextNode(" "));
+      button = document.createElement("button")
+      button.textContent = "Failed"
+      button.onclick = function() {
+        report(false, "Tester clicked \"Fail\"");
+        runner.currentTest().fails = 1;
+        runner.finishTest();
+      }
+      p.appendChild(button);
+      p.appendChild(document.createTextNode(" "));
+      button = document.createElement("button")
+      button.textContent = "Skip"
+      button.onclick = function() {
+        runner.finishTest();
+      }
+      p.appendChild(button);
+    }
+
+    var xhr = new XMLHttpRequest(), self = this;
+    xhr.onreadystatechange = function onrsc() {
+      if (this.readyState !== 4 || !(this.status === 200 || this.status === 0))
+        return;
+      self.process(this.responseText, "");
+    };
+    xhr.open("GET", this.mPath + "MANIFEST");
+    xhr.send(null);//Fx 3
+  },
+
+  "process": function Runner_process(aManifest, aPath) {
+    --this.mToBeProcessed;
+    var dirs = this.parseManifest(aManifest.split("\n"), aPath);
+    for (var k = 0, kl = dirs.length; k < kl; ++k) {
+      var dir = dirs[k] + "/";
+      ++this.mToBeProcessed;
+      var xhr = new XMLHttpRequest(), self = this;
+      xhr.dataDir = dir;
+      xhr.onreadystatechange = function() {
+        if (this.readyState !== 4 || !(this.status === 200 || this.status === 0))
+          return;
+
+        self.process(this.responseText, this.dataDir);
+      };
+      xhr.open("GET", this.mPath + dir + "MANIFEST");
+      xhr.send(null);//Fx 3
+    }
+
+    if (!this.mToBeProcessed) {
+      this.runNextTest()
+    }
+  },
+
+  "parseManifest": function Runner_parseManifest(aLines, aPath) {
+    var dirs = [];
+    for (var i = 0, il = aLines.length; i < il; ++i) {
+      if (!aLines[i]) {
+        continue;
+      }
+
+      var chunks = aLines[i].split(" ");
+
+      switch (chunks[0]) {
+      case "support":
+      case "list":
+        break;
+
+      case "dir":
+        if (chunks[1]) {
+          dirs.push(aPath + chunks[1]);
+        }
+        break;
+
+      case "manual":
+        if (chunks[1]) {
+          this.mTests.push({ url: aPath + chunks[1], passes: 0, fails: 0, type: "manual" });
+        }
+        break;
+
+      case "ref":
+        if (chunks.length % 2) {
+          break;
+        }
+        var reftests = [];
+        for (var j = 2, jl = chunks.length; j < jl; j += 2) {
+          reftests.push([chunks[j], aPath + chunks[1], aPath + chunks[j + 1]]);
+        }
+        this.mTests.push({ type: "reftest", url: aPath + chunks[1], passes: 0, fails: 0, tests: reftests })
+        break;
+
+      default:
+        if (chunks.length > 1) {
+          break;
+        }
+        this.mTests.push({ url: aPath + chunks[0], passes: 0, fails: 0, type: "automated" });
+      }
+    }
+    return dirs;
+  },
+
+  /* ***** Running the test suite ***** */
+  "_report": function Runner__report(aPass, aMessageNodes) {
+    var li = document.createElement("li");
+    li.className = aPass ? "pass" : "fail";
+    li.appendChild(document.createTextNode(aPass ? "Pass" : "Fail: "));
+    for (var i = 0, il = aMessageNodes.length; i < il; ++i) {
+      li.appendChild(aMessageNodes[i]);
+    }
+    this.mOl.appendChild(li);
+  },
+
+  "fail": function Runner_fail(aMsg) {
+    ++this.mFails;
+    this._report(false, [document.createTextNode(aMsg)]);
+  },
+
+  "timedOut": function Runner_timedOut() {
+    this.fail("Timed out.");
+    ++this.mTimeouts;
+    clearTimeout(this.currentTest().timeout);
+    this.runNextTest();
+  },
+
+  "updateResults": function Runner_updateResults() {
+    if (!this.mPasses && !this.mFails)
+      return;
+    var score = this.mPasses / (this.mPasses + this.mFails);
+    var scorestr = (100 * score).toFixed(2) + "%";
+    if (this.sSupportsMeter)
+      this.mMeter.value = score;
+    else
+      this.mMeter.style.width = scorestr;
+    this.mScoreNode.data = scorestr;
+    this.mPassNode.data = this.mPasses;
+    this.mFailNode.data = this.mFails;
+
+    var metadata = this.mIframe && this.mIframe.contentWindow &&
+      this.mIframe.contentWindow.Test && this.mIframe.contentWindow.Test.meta;
+    if (!metadata)
+      return;
+
+    var p = this.mSection.appendChild(document.createElement("p"));
+
+    this.appendList("Created by", metadata.authors, p);
+    this.appendList("Reviewed by", metadata.reviewers, p);
+    this.appendList("Help:", metadata.helps, p);
+
+    if (metadata.assert) {
+      p.appendChild(document.createTextNode("Asserting that " + metadata.assert +
+                                            "."));
+    }
+  },
+
+  "createLink": function Runner_createLink(aLink) {
+    var a = document.createElement("a");
+    a.href = aLink.href;
+    a.appendChild(document.createTextNode(aLink.text));
+    return a;
+  },
+
+  "appendList": function Runner_appendList(aLabel, aList, aEl) {
+    if (!aList.length)
+      return;
+
+    aEl.appendChild(document.createTextNode(aLabel + " "));
+    for (var i = 0, il = aList.length; i < il; ++i) {
+      if (i)
+        aEl.appendChild(document.createTextNode(", "));
+      aEl.appendChild(this.createLink(aList[i]));
+    }
+    aEl.appendChild(document.createTextNode(". "));
+  },
+
+  "addTitle": function Runner_addTitle() {
+    this.mSection = document.createElement("section");
+    this.mSectionWrapper.appendChild(this.mSection);
+
+    var a = document.createElement("a");
+    a.appendChild(document.createTextNode(this.currentTest().url));
+    a.href = this.mPath + this.currentTest().url;
+
+    var h1 = document.createElement("h1");
+    h1.appendChild(a);
+    this.mSection.appendChild(h1);
+
+    this.mOl = this.mSection.appendChild(document.createElement("ol"));
+  },
+
+  "hideManualUI": function() {
+    document.body.className = ""
+  },
+
+  "showManualUI": function() {
+    document.body.className = "manual"
+  },
+
+  "runNextTest": function Runner_runNextTest() {
+    ++this.mTestCount;
+    if (!this.currentTest()) {
+      this.finish();
+      return;
+    }
+
+    this.addTitle();
+
+    switch (this.currentTest().type) {
+    case "automated":
+      if (!this.mSkipManual) {
+        this.hideManualUI()
+      }
+      this.currentTest().timeout =
+        setTimeout(function() { runner.timedOut() }, this.sTimeout);
+      this.mIframe.src = this.mPath + this.currentTest().url;
+      break;
+
+    case "reftest":
+      if (!this.mSkipManual) {
+        this.hideManualUI()
+      }
+      this.mIframe.src = "reftest.html?" +
+        JSON.stringify([this.mPath, this.currentTest().tests]);
+      break;
+
+    case "manual":
+      if (!this.mSkipManual) {
+        this.showManualUI()
+        this.mIframe.src = this.mPath + this.currentTest().url;
+      } else {
+        this.finishTest();
+      }
+      break;
+
+    default:
+      throw "Unrecognized test type";
+    }
+  },
+
+  "finishTest": function Report_finishTest() {
+    this.mPasses += this.currentTest().passes;
+    this.mFails += this.currentTest().fails;
+    this.updateResults();
+    clearTimeout(this.currentTest().timeout);
+    this.runNextTest();
+  },
+
+  /* ***** Finishing up ***** */
+  "getXMLReport": function Runner_getXMLReport() {
+    var container = document.createElement("div");
+    var tests = container
+      .appendChild(document.createElementNS(null, "testresults"))
+        .appendChild(document.createTextNode("\n "))
+      .parentNode
+        .appendChild(document.createElementNS(null, "browser"))
+          .appendChild(document.createTextNode("\n  "))
+        .parentNode
+          .appendChild(document.createElementNS(null, "ua"))
+            .appendChild(document.createTextNode(navigator.userAgent))
+        .parentNode.parentNode
+          .appendChild(document.createTextNode("\n  "))
+        .parentNode
+          .appendChild(document.createElementNS(null, "browsername"))
+            .appendChild(document.createTextNode("REPLACE WITH BROWSERNAME BEFORE PUSHING TO HG"))
+        .parentNode.parentNode
+          .appendChild(document.createTextNode("\n  "))
+        .parentNode
+          .appendChild(document.createElementNS(null, "dateran"))
+            .appendChild(document.createTextNode(Date()))
+        .parentNode.parentNode
+          .appendChild(document.createTextNode("\n "))
+      .parentNode.parentNode
+        .appendChild(document.createTextNode("\n "))
+      .parentNode
+        .appendChild(document.createElementNS(null, "tests"));
+
+    for (var i = 0, il = this.mTests.length; i < il; ++i) {
+      if (this.mTests[i].passes || this.mTests[i].fails) {
+        tests.appendChild(document.createTextNode("\n  "));
+        tests
+          .appendChild(document.createElementNS(null, "test"))
+            .appendChild(document.createTextNode("\n   "))
+          .parentNode
+            .appendChild(document.createElementNS(null, "uri"))
+              .appendChild(document.createTextNode(this.mTests[i].url))
+          .parentNode.parentNode
+            .appendChild(document.createTextNode("\n   "))
+          .parentNode
+            .appendChild(document.createElementNS(null, "result"))
+              .appendChild(document.createTextNode(
+                !this.mTests[i].fails ? "Pass" : "Fail"))
+          .parentNode.parentNode
+            .appendChild(document.createTextNode("\n  "));
+      }
+    }
+    container
+      .firstChild
+        .lastChild
+          .appendChild(document.createTextNode("\n "))
+      .parentNode.parentNode
+        .appendChild(document.createTextNode("\n"));
+    return container.innerHTML;
+  },
+
+  "finish": function Runner_finish() {
+    this.mWrapper.removeChild(this.mIframe.parentNode);
+    this.updateResults();
+    var time = (((new Date()).getTime() - this.mStartTime) / 1000).toFixed(0);
+    document.body.appendChild(document.createElement("p"))
+                 .appendChild(document.createTextNode("Application ran for "
+                                                      + time + " seconds."));
+    document.body.appendChild(document.createElement("p"))
+                 .appendChild(document.createTextNode(this.mTimeouts + " timeouts."));
+    var button = document.body.appendChild(document.createElement("p"))
+                              .appendChild(document.createElement("button"));
+    button.appendChild(document.createTextNode("Run again"));
+    button.value = this.mPath;
+    button.onclick = startRunning;
+    this.mMeter.onclick = function(e) {
+      if (e.altKey) {
+        document.open();
+        document.write("<pre>");
+        document.writeln("  &lt;dt>" + navigator.userAgent);
+        document.writeln("  &lt;dd>Pass " + this.mPasses);
+        document.writeln("  &lt;dd>Fail " + this.mFails);
+        document.writeln("  &lt;dd>Score "
+                         + (100 * this.mPasses / (this.mPasses + this.mFails)).toFixed(2) + "%");
+        document.write("</pre>");
+        document.close();
+      }
+    };
+    document.body.appendChild(document.createElement("p"))
+                 .appendChild(document.createElement("textarea"))
+                 .appendChild(document.createTextNode(this.getXMLReport()));
+    document.title = "Done \u2013 " + document.title;
+    document.documentElement.className = "done";
+  }
+};
+
+
+/* ***** Preparation ***** */
+function parseOptions() {
+  var optionstrings = location.search.substring(1).split("&");
+  var options = {};
+  for (var i = 0, il = optionstrings.length; i < il; ++i) {
+    var opt = optionstrings[i];
+    options[opt.substring(0, opt.indexOf("="))] =
+      opt.substring(opt.indexOf("=") + 1);
+  }
+  return options;
+}
+
+function setup() {
+  var options = parseOptions();
+  if (options["autorun"] === "1") {
+    runner = new Runner(options["path"] || "../html5/");
+    runner.start();
+    return;
+  }
+
+  if (options["path"]) {
+    clearBody();
+    var button = document.body.appendChild(document.createElement("p"))
+                              .appendChild(document.createElement("button"));
+    button.appendChild(document.createTextNode("Run tests"));
+    button.value = options["path"];
+    button.onclick = startRunning;
+    return;
+  }
+    
+  var buttons = document.getElementsByTagName("button");
+  for (var i = 0, il = buttons.length; i < il; ++i) {
+    buttons[i].onclick = startRunning;
+  }
+}
+
+function startRunning() {
+  runner = new Runner(this.value);
+  runner.start();
+}
+
+
+function report(aPass, aMessage) {
+  var nodes = [];
+  if (!aPass)
+    nodes.push(document.createTextNode(aMessage));
+  runner._report(aPass, nodes);
+}
+
+function result_callback(aTest) {
+  if (runner.currentTest().type === "manual" && aTest.status === aTest.TIMEOUT) {
+    return;
+  }
+  var nodes = [];
+  if (aTest.message) {
+    if (typeof aTest.message === "string") {
+      nodes = [document.createTextNode(aTest.message)];
+    } else {
+      var rendered = runner.mIframe.contentWindow.template.render(aTest.message);
+      if (rendered.length) {
+        nodes = nodes.concat(rendered);
+      } else {
+        nodes.push(rendered);
+      }
+    }
+  } else if (aTest.nodes) {
+    nodes = aTest.nodes;
+  }
+  runner._report(!aTest.status, nodes);
+  !aTest.status ? runner.currentTest().passes++ : runner.currentTest().fails++;
+}
+
+function separate() {
+}
+
+
+function finishTest() {
+  runner.currentTest().passes = runner.mIframe.contentWindow.Test.results.passes;
+  runner.currentTest().fails = runner.mIframe.contentWindow.Test.results.fails;
+  runner.finishTest();
+}
+
+function completion_callback(aTests, aStatus) {
+  if (runner.currentTest().type === "manual") {
+    for (var i = 0, il = aTests.length; i < il; ++i) {
+      var test = aTests[i];
+      if (test.status == test.TIMEOUT) {
+        return;
+      }
+    }
+  }
+  runner.finishTest();
+}
+
+function clearBody() {
+  var i = document.body.childNodes.length;
+  while (i--) {
+    document.body.removeChild(document.body.childNodes[i]);
+  }
+}
+
+window.onload = setup;