function runTests(mTests, bRealOData) {
		var oFirstFailedTest,
			iFrames,
			mLastRun = loadLastRun(),
			iRunningTests = 0,
			oSelectedTest,
			iStart = Date.now(),
			aTests = filterTests(mTests, isIntegrationOnly(), bRealOData),
			iTop = 10000,
			oTotal,
			bVisible;

		function createTest(sText, sUrl) {
			var oTest = {
					element : document.createElement("li"),
					text : sText,
					url : sUrl
				},
				oDiv = document.createElement("div");

			oTest.element.appendChild(oDiv);
			oTest.icon = document.createElement("div");
			oTest.icon.classList.add("status");
			oDiv.appendChild(oTest.icon);
			oDiv.addEventListener("click", select.bind(null, oTest));
			oDiv.appendChild(document.createTextNode(sText));
			oTest.infoNode = document.createTextNode("");
			oTest.element.appendChild(oTest.infoNode);
			document.getElementById("results").appendChild(oTest.element);
			oTest.last = mLastRun[oTest.text] || 0;
			return oTest;
		}

		// starts another test or shows the summary
		function next() {
			if (aTests.length) {
				runTest(aTests.shift());
			} else if (!iRunningTests) {
				oTotal.element.classList.remove("hidden");
				oTotal.element.firstChild.classList.add("running");
				summary(Date.now() - iStart);
				saveLastRun(mLastRun);
				if (oFirstFailedTest) {
					select(oFirstFailedTest);
				}
			}
		}

		// runs a test: creates its frame (out of sight), registers first at the frame's page, then
		// at the frame's QUnit to observe the progress and notice when it's finished
		function runTest(oTest) {
			// finishes the test: sets the CSS according to the status, removes the frame and starts
			// the next test
			function finish(bFailed) {
				oTest.element.firstChild.classList.remove("running");
				if (bFailed) {
					oTest.element.firstChild.classList.add("failed");
					oFirstFailedTest = oFirstFailedTest || oTest;
				} else if (!mParameters.keepResults) {
					// remove iframe in order to free memory
					document.body.removeChild(oTest.frame);
					oTest.frame = undefined;
				}
				if (bVisible && oTest === oSelectedTest) {
					select(oTest); // unselect the test to make it invisible
				}
				iRunningTests -= 1;
				next();
			}

			// observes QUnit within the test frame
			function observe(oQUnit) {
				// see https://github.com/js-reporters/js-reporters (@since QUnit 2)
				oQUnit.on("runStart", function (oDetails) {
					oTest.testCounts = oDetails.testCounts;
					progress(0);
				});
				oQUnit.on("testEnd", function () {
					progress(oTest.testCounts.finished + 1);
				});
				oQUnit.on("runEnd", function (oDetails) {
					oTest.testCounts = oDetails.testCounts;
					summary(oDetails.runtime, oTest);
					finish(oDetails.status === "failed");
				});
			}

			function onFrameLoad() {
				oTest.frame.removeEventListener("load", onFrameLoad);
				waitForQUnit(5);
			}

			function progress(iFinished) {
				oTest.testCounts.finished = iFinished;
				oTest.infoNode.data = ": " + iFinished + "/" + oTest.testCounts.total;
			}

			// searches QUnit within the test frame; waits iRetryCount times for one second if not
			// found
			function waitForQUnit(iRetryCount) {
				var oQUnit = oTest.frame.contentWindow.QUnit;

				if (oQUnit && oQUnit.on) {
					observe(oQUnit);
				} else if (iRetryCount) {
					// retry after a second
					setTimeout(waitForQUnit.bind(null, iRetryCount - 1), 1000);
				} else {
					oTest.infoNode.data = oQUnit ? " no QUnit V2 found" : " no QUnit found";
					finish(true);
				}
			}

			oTest.top = iTop;
			iTop += 1000;
			oTest.frame = document.createElement("iframe");
			oTest.frame.src = oTest.url;
			oTest.frame.setAttribute("height", "900");
			oTest.frame.setAttribute("width", "1600");
			oTest.frame.style.position = "fixed";
			oTest.frame.style.top = oTest.top + "px";
			document.body.appendChild(oTest.frame);
			oTest.element.firstChild.classList.add("running");
			iRunningTests += 1;
			oTest.frame.addEventListener("load", onFrameLoad);
			if (bVisible) {
				select(oTest);
			}
		}

		// handler for a click on a test title, showing or hiding the frame
		function select(oTest) {
			var bSelect = oTest !== oSelectedTest;

			if (oSelectedTest) {
				oSelectedTest.element.classList.remove("selected");
				if (oSelectedTest.frame) {
					oSelectedTest.frame.style.position = "fixed";
					oSelectedTest.frame.style.top = oSelectedTest.top + "px";
				}
				oSelectedTest = null;
			}
			if (bSelect) {
				oSelectedTest = oTest;
				oTest.element.classList.add("selected");
				if (oTest.frame) {
					oTest.frame.style.position = null;
					oTest.frame.style.top = null;
				}
			}
		}

		// Adds the summary for a test or the total when no test is given
		function summary(iRuntime, oTest) {
			var oElement, iMinutes, iSeconds, sText;

			function count(iCount, sWhat) {
				return iCount ? ", " + iCount + " " + sWhat : "";
			}

			if (oTest) {
				oTotal.testCounts.failed += oTest.testCounts.failed;
				oTotal.testCounts.skipped += oTest.testCounts.skipped;
				oTotal.testCounts.todo += oTest.testCounts.todo;
				oTotal.testCounts.total += oTest.testCounts.total;
			} else {
				oTest = oTotal;
			}
			iRuntime = Math.round(iRuntime / 1000);
			iMinutes = Math.floor(iRuntime / 60);
			iSeconds = iRuntime - iMinutes * 60;
			sText = ": " + oTest.testCounts.total + " tests in "
				+ (iMinutes ? iMinutes + " min " : "")
				+ iSeconds + " sec"
				+ count(oTest.testCounts.failed, "failed")
				+ count(oTest.testCounts.skipped, "skipped")
				+ count(oTest.testCounts.todo, "todo")
				+ " ";

			oTest.infoNode.data = sText;

			if (oTest.url) {
				oTest.icon.appendChild(
					document.createTextNode(oTest.testCounts.failed ? "\u2716" : "\u2714"));
				oElement = document.createElement("a");
				oElement.setAttribute("href", oTest.url);
				oElement.setAttribute("target", "_blank");
				oElement.appendChild(document.createTextNode("Rerun"));
				oTest.element.appendChild(oElement);
				if (!oTest.testCounts.failed || !mLastRun[oTest.text]) {
					mLastRun[oTest.text] = iRuntime;
				}
			}
		}

		setStatus();
		iFrames = getFrameCount();
		bVisible = !iFrames;
		document.getElementById("list").classList.remove("hidden");
		aTests = aTests.map(function (sSimplifiedUrl) {
			var sUrl = normalizeUrl(sSimplifiedUrl);

			if (sSimplifiedUrl.includes("Test.qunit.html")) {
				sSimplifiedUrl = sSimplifiedUrl
					.replace(/^.*Test.qunit.html\?testsuite=.*&test=/, "")
					.replace("&supportAssistant=true", "");
			}
			return createTest(sSimplifiedUrl, sUrl);
		});
		aTests.sort(function (oTest1, oTest2) { return oTest2.last - oTest1.last; });
		oTotal = createTest("Finished");
		oTotal.testCounts = {
			failed : 0,
			skipped : 0,
			todo : 0,
			total : 0
		};
		oTotal.element.classList.add("hidden");
		if (bVisible) {
			next();
		} else {
			while (iFrames) {
				next();
				iFrames -= 1;
			}
		}
	}
Runs the tests.

@param {object} mTests
  the map of test files to 'full', 'integration' or 'both'
@param {boolean} [bRealOData]
  if undefined, run all tests; if true, run only tests with "realOData=true"; if false, run
  all tests without realOData (it is stripped off)