browser-harness.xhtml (11825B)
1 <?xml version="1.0"?> 2 <?xml-stylesheet href="chrome://global/skin" type="text/css"?> 3 <!-- This Source Code Form is subject to the terms of the Mozilla Public 4 - License, v. 2.0. If a copy of the MPL was not distributed with this 5 - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> 6 7 <window id="browserTestHarness" 8 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 9 onload="TestStart();" 10 title="Browser chrome tests" 11 width="1024"> 12 <script src="chrome://mochikit/content/tests/SimpleTest/MozillaLogger.js"/> 13 <script src="chrome://mochikit/content/tests/SimpleTest/LogController.js"/> 14 <script src="chrome://mochikit/content/tests/SimpleTest/TestRunner.js"/> 15 <script src="chrome://mochikit/content/chrome-harness.js"/> 16 <script src="chrome://mochikit/content/manifestLibrary.js" /> 17 <script src="chrome://mochikit/content/mochitestListingsUtils.js" /> 18 <script src="chrome://mochikit/content/chunkifyTests.js"/> 19 <style xmlns="http://www.w3.org/1999/xhtml"><![CDATA[ 20 #results { 21 margin: 5px; 22 background-color: window; 23 user-select: text; 24 } 25 26 #summary { 27 color: white; 28 border: 2px solid black; 29 } 30 31 #summary.success { 32 background-color: #0d0; 33 } 34 35 #summary.failure { 36 background-color: red; 37 } 38 39 #summary.todo { 40 background-color: orange; 41 } 42 43 .info { 44 color: grey; 45 } 46 47 .failed { 48 color: red; 49 font-weight: bold; 50 } 51 52 .testHeader { 53 margin-top: 1em; 54 } 55 56 p { 57 margin: 0.1em; 58 } 59 60 a { 61 color: blue; 62 text-decoration: underline; 63 } 64 ]]></style> 65 <script type="application/javascript"><![CDATA[ 66 var gConfig; 67 68 var gDumper = { 69 get fileLogger() { 70 let logger = null; 71 if (gConfig.logFile) { 72 try { 73 logger = new MozillaFileLogger(gConfig.logFile) 74 } catch (ex) { 75 dump("TEST-UNEXPECTED-FAIL | (browser-harness.xhtml) | " + 76 "Error trying to log to " + gConfig.logFile + ": " + ex + "\n"); 77 } 78 } 79 delete this.fileLogger; 80 return this.fileLogger = logger; 81 }, 82 structuredLogger: TestRunner.structuredLogger, 83 dump(str) { 84 this.structuredLogger.info(str); 85 86 if (this.fileLogger) 87 this.fileLogger.log(str); 88 }, 89 90 done() { 91 if (this.fileLogger) 92 this.fileLogger.close(); 93 } 94 } 95 96 function TestStart() { 97 gConfig = readConfig(); 98 99 // Update the title for --start-at and --end-at. 100 if (gConfig.startAt || gConfig.endAt) 101 document.getElementById("runTestsButton").label = 102 "Run subset of tests"; 103 104 if (gConfig.autorun) 105 setTimeout(runTests, 0); 106 } 107 108 var gErrorCount = 0; 109 110 function browserTest(aTestFile) { 111 this.path = aTestFile.url; 112 this.expected = aTestFile.expected; 113 this.https_first_disabled = aTestFile.https_first_disabled || false; 114 this.allow_xul_xbl = aTestFile.allow_xul_xbl || false; 115 this.dumper = gDumper; 116 this.results = []; 117 this.scope = null; 118 this.duration = 0; 119 this.unexpectedTimeouts = 0; 120 this.lastOutputTime = 0; 121 } 122 browserTest.prototype = { 123 get passCount() { 124 return this.results.filter(t => !t.info && !t.todo && t.pass).length; 125 }, 126 get todoCount() { 127 return this.results.filter(t => !t.info && t.todo && t.pass).length; 128 }, 129 get failCount() { 130 return this.results.filter(t => !t.info && !t.pass).length; 131 }, 132 get allowedFailureCount() { 133 return this.results.filter(t => t.allowedFailure).length; 134 }, 135 136 addResult: function addResult(result) { 137 this.lastOutputTime = Date.now(); 138 this.results.push(result); 139 140 if (result.info) { 141 if (result.msg) { 142 ChromeUtils.addProfilerMarker("TEST-INFO", {category: "Test"}, 143 result.msg); 144 this.dumper.structuredLogger.info(result.msg); 145 } 146 return; 147 } 148 149 // Use task name as subtest if available 150 let subtest = this.scope?.SimpleTest?._currentTaskName || null; 151 152 // Combine assertion name with exception details if present 153 let message = result.name; 154 if (result.msg) { 155 message += " - " + result.msg; 156 } 157 158 this.dumper.structuredLogger.testStatus(this.path, 159 subtest, 160 result.status, 161 result.expected, 162 message, 163 result.stack || null); 164 let markerName = "TEST-"; 165 if (result.pass) { 166 markerName += result.todo ? "KNOWN-FAIL" : "PASS"; 167 } 168 else { 169 markerName += "UNEXPECTED-" + result.status; 170 } 171 ChromeUtils.addProfilerMarker(markerName, {category: "Test"}, message); 172 }, 173 174 setDuration: function setDuration(duration) { 175 this.duration = duration; 176 }, 177 178 get htmlLog() { 179 let txtToHTML = Cc["@mozilla.org/txttohtmlconv;1"]. 180 getService(Ci.mozITXTToHTMLConv); 181 function _entityEncode(str) { 182 return txtToHTML.scanTXT(str, Ci.mozITXTToHTMLConv.kEntities); 183 } 184 var path = _entityEncode(this.path); 185 var html = this.results.map(function (t) { 186 var classname = "result "; 187 var result = "TEST-"; 188 if (t.info) { 189 classname = "info"; 190 result += "INFO"; 191 } 192 else if (t.pass) { 193 classname += "passed"; 194 if (t.todo) 195 result += "KNOWN-FAIL"; 196 else 197 result += "PASS"; 198 } 199 else { 200 classname += "failed"; 201 result += "UNEXPECTED-" + t.status; 202 } 203 var message = t.name + (t.msg ? " - " + t.msg : ""); 204 var text = result + " | " + path + " | " + _entityEncode(message); 205 if (!t.info && !t.pass) { 206 return '<p class="' + classname + '" id=\"ERROR' + (gErrorCount++) + '">' + 207 text + " <a href=\"javascript:scrollTo('ERROR" + gErrorCount + "')\">NEXT ERROR</a></p>"; 208 } 209 return '<p class="' + classname + '">' + text + "</p>"; 210 }).join("\n"); 211 if (this.duration) { 212 html += "<p class=\"info\">TEST-END | " + path + " | finished in " + 213 this.duration + " ms</p>"; 214 } 215 return html; 216 } 217 }; 218 219 // Returns an array of browserTest objects for all the selected tests 220 function runTests() { 221 gConfig.baseurl = "chrome://mochitests/content"; 222 getTestList(gConfig, loadTestList); 223 } 224 225 function loadTestList(links) { 226 if (!links) { 227 createTester({}); 228 return; 229 } 230 231 var fileNames = []; 232 var fileNameRegexp = /browser_.+\.js$/; 233 arrayOfTestFiles(links, fileNames, fileNameRegexp); 234 235 if (gConfig.startAt || gConfig.endAt) { 236 fileNames = skipTests(fileNames, gConfig.startAt, gConfig.endAt); 237 } 238 239 createTester(fileNames.map(function (f) { return new browserTest(f); })); 240 } 241 242 function setStatus(aStatusString) { 243 document.getElementById("status").value = aStatusString; 244 } 245 246 function createTester(links) { 247 var winType = null; 248 if (gConfig.testRoot == "browser") { 249 const IS_THUNDERBIRD = Services.appinfo.ID == "{3550f703-e582-4d05-9a08-453d09bdfdc6}"; 250 winType = IS_THUNDERBIRD ? "mail:3pane" : "navigator:browser"; 251 } 252 if (!winType) { 253 throw new Error("Unrecognized gConfig.testRoot: " + gConfig.testRoot); 254 } 255 var testWin = Services.wm.getMostRecentWindow(winType); 256 257 setStatus("Running..."); 258 259 // It's possible that the test harness window is not yet focused when this 260 // function runs (in which case testWin is already focused, and focusing it 261 // will be a no-op, and then the test harness window will steal focus later, 262 // which will mess up tests). So wait for the test harness window to be 263 // focused before trying to focus testWin. 264 waitForFocus(() => { 265 // Focus the test window and start tests. 266 waitForFocus(() => { 267 var Tester = new testWin.Tester(links, gDumper.structuredLogger, testsFinished); 268 Tester.start(); 269 }, testWin); 270 }, window); 271 } 272 273 function executeSoon(callback) { 274 Services.tm.dispatchToMainThread(callback); 275 } 276 277 function waitForFocus(callback, win) { 278 // If "win" is already focused, just call the callback. 279 if (Services.focus.focusedWindow == win) { 280 executeSoon(callback); 281 return; 282 } 283 284 // Otherwise focus it, and wait for the focus event. 285 win.addEventListener("focus", function listener() { 286 executeSoon(callback); 287 }, { capture: true, once: true}); 288 win.focus(); 289 } 290 291 function sum(a, b) { 292 return a + b; 293 } 294 295 function getHTMLLogFromTests(aTests) { 296 if (!aTests.length) 297 return "<div id=\"summary\" class=\"failure\">No tests to run." + 298 " Did you pass an invalid --test-path?</div>"; 299 300 var log = ""; 301 302 var passCount = aTests.map(f => f.passCount).reduce(sum); 303 var failCount = aTests.map(f => f.failCount).reduce(sum); 304 var todoCount = aTests.map(f => f.todoCount).reduce(sum); 305 log += "<div id=\"summary\" class=\""; 306 if (failCount != 0) { 307 log += "failure"; 308 } else { 309 log += passCount == 0 ? "todo" : "success"; 310 } 311 log += "\">\n<p>Passed: " + passCount + "</p>\n" + 312 "<p>Failed: " + failCount; 313 if (failCount > 0) 314 log += " <a href=\"javascript:scrollTo('ERROR0')\">NEXT ERROR</a>"; 315 log += "</p>\n" + 316 "<p>Todo: " + todoCount + "</p>\n</div>\n<div id=\"log\">\n"; 317 318 return log + aTests.map(function (f) { 319 return "<p class=\"testHeader\">Running " + f.path + "...</p>\n" + f.htmlLog; 320 }).join("\n") + "</div>"; 321 } 322 323 function testsFinished(aTests) { 324 if (gConfig.closeWhenDone) { 325 const {AppConstants} = ChromeUtils.importESModule( 326 "resource://gre/modules/AppConstants.sys.mjs" 327 ); 328 if ( 329 !AppConstants.RELEASE_OR_BETA && 330 !AppConstants.DEBUG && 331 !AppConstants.MOZ_CODE_COVERAGE && 332 !AppConstants.ASAN && 333 !AppConstants.TSAN 334 ) { 335 let filename = 336 Services.profiler.IsActive() && 337 Services.env.get("MOZ_PROFILER_SHUTDOWN"); 338 if (!filename) { 339 Cu.exitIfInAutomation(); 340 } 341 Services.profiler 342 .dumpProfileToFileAsync(filename) 343 .then(() => Services.profiler.StopProfiler()) 344 .then(() => Cu.exitIfInAutomation()); 345 } else { 346 Services.startup.quit(Ci.nsIAppStartup.eForceQuit); 347 } 348 return; 349 } 350 351 // Focus our window, to display the results 352 window.focus(); 353 354 // UI 355 // eslint-disable-next-line no-unsanitized/property 356 document.getElementById("results").innerHTML = getHTMLLogFromTests(aTests); 357 setStatus("Done."); 358 } 359 360 function scrollTo(id) { 361 var line = document.getElementById(id); 362 if (!line) 363 return; 364 365 line.scrollIntoView(); 366 } 367 ]]></script> 368 <button id="runTestsButton" oncommand="runTests();" label="Run All Tests"/> 369 <label id="status"/> 370 <scrollbox flex="1" style="overflow: auto" align="stretch"> 371 <div id="results" xmlns="http://www.w3.org/1999/xhtml" flex="1"/> 372 </scrollbox> 373 </window>