1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7<link rel="import" href="/tracing/base/event.html"> 8<link rel="import" href="/tracing/base/unittest/constants.html"> 9<link rel="import" href="/tracing/base/utils.html"> 10<link rel="import" href="/tracing/base/xhr.html"> 11<link rel="import" href="/tracing/ui/base/ui.html"> 12<link rel="import" href="/tracing/ui/base/utils.html"> 13 14<style> 15 x-tr-b-unittest-test-results { 16 display: flex; 17 flex-direction: column; 18 flex: 0 0 auto; 19 } 20 21 x-tr-b-unittest-test-results > x-html-test-case-result.dark > #summary { 22 background-color: #eee; 23 } 24 25 x-html-test-case-result { 26 display: block; 27 } 28 x-html-test-case-result > #summary > #title, 29 x-html-test-case-result > #summary > #status, 30 x-html-test-case-result > #details > x-html-test-case-error > #message, 31 x-html-test-case-result > #details > x-html-test-case-error > #stack, 32 x-html-test-case-result > #details > x-html-test-case-error > #return-value, 33 x-html-test-case-result > #details > x-html-test-case-flaky > #message { 34 -webkit-user-select: auto; 35 } 36 37 x-html-test-case-result > #details > x-html-test-case-error, 38 x-html-test-case-result > #details > x-html-test-case-flaky { 39 display: block; 40 border: 1px solid grey; 41 border-radius: 5px; 42 font-family: monospace; 43 margin-bottom: 14px; 44 } 45 46 x-html-test-case-result > #details > x-html-test-case-error > #message, 47 x-html-test-case-result > #details > x-html-test-case-error > #stack, 48 x-html-test-case-result > #details > x-html-test-case-flaky > #message { 49 white-space: pre; 50 } 51 52 x-html-test-case-result > #details > x-html-test-case-html-result { 53 display: block; 54 } 55 56 .unittest-pending { 57 color: orange; 58 } 59 .unittest-running { 60 color: orange; 61 font-weight: bold; 62 } 63 64 .unittest-passed { 65 color: darkgreen; 66 } 67 68 .unittest-failed { 69 color: darkred; 70 font-weight: bold; 71 } 72 73 .unittest-flaky { 74 color: darkorange; 75 } 76 77 .unittest-exception { 78 color: red; 79 font-weight: bold; 80 } 81 82 .unittest-failure { 83 border: 1px solid grey; 84 border-radius: 5px; 85 padding: 5px; 86 } 87</style> 88<template id="x-html-test-case-result-template"> 89 <div id="summary"> 90 <span id="title"></span> 91 <span id="status"></span> 92 <span id="return-value"></span> 93 </div> 94 <div id="details"></div> 95</template> 96 97<template id="x-html-test-case-error-template"> 98 <div id="stack"></div> 99</template> 100 101<template id="x-html-test-case-flaky-template"> 102 <div id="message"></div> 103</template> 104 105<script> 106'use strict'; 107tr.exportTo('tr.b.unittest', function() { 108 var THIS_DOC = document.currentScript.ownerDocument; 109 110 var TestStatus = tr.b.unittest.TestStatus; 111 var TestTypes = tr.b.unittest.TestTypes; 112 113 /** 114 * @constructor 115 */ 116 var HTMLTestCaseResult = tr.ui.b.define('x-html-test-case-result'); 117 118 HTMLTestCaseResult.prototype = { 119 __proto__: HTMLUnknownElement.prototype, 120 121 decorate: function() { 122 this.appendChild(tr.ui.b.instantiateTemplate( 123 '#x-html-test-case-result-template', THIS_DOC)); 124 this.testCase_ = undefined; 125 this.testCaseHRef_ = undefined; 126 this.duration_ = undefined; 127 this.testStatus_ = TestStatus.PENDING; 128 this.testReturnValue_ = undefined; 129 this.showHTMLOutput_ = false; 130 this.updateColorAndStatus_(); 131 }, 132 133 get showHTMLOutput() { 134 return this.showHTMLOutput_; 135 }, 136 137 set showHTMLOutput(showHTMLOutput) { 138 this.showHTMLOutput_ = showHTMLOutput; 139 this.updateHTMLOutputDisplayState_(); 140 }, 141 142 get testCase() { 143 return this.testCase_; 144 }, 145 146 set testCase(testCase) { 147 this.testCase_ = testCase; 148 this.updateTitle_(); 149 }, 150 151 get testCaseHRef() { 152 return this.testCaseHRef_; 153 }, 154 155 set testCaseHRef(href) { 156 this.testCaseHRef_ = href; 157 this.updateTitle_(); 158 }, 159 updateTitle_: function() { 160 var titleEl = this.querySelector('#title'); 161 if (this.testCase_ === undefined) { 162 titleEl.textContent = ''; 163 return; 164 } 165 166 if (this.testCaseHRef_) { 167 titleEl.innerHTML = '<a href="' + this.testCaseHRef_ + '">' + 168 this.testCase_.fullyQualifiedName + '</a>'; 169 } else { 170 titleEl.textContent = this.testCase_.fullyQualifiedName; 171 } 172 }, 173 174 addError: function(normalizedException) { 175 var errorEl = document.createElement('x-html-test-case-error'); 176 errorEl.appendChild(tr.ui.b.instantiateTemplate( 177 '#x-html-test-case-error-template', THIS_DOC)); 178 errorEl.querySelector('#stack').textContent = normalizedException.stack; 179 this.querySelector('#details').appendChild(errorEl); 180 this.updateColorAndStatus_(); 181 }, 182 183 addFlaky: function() { 184 var flakyEl = document.createElement('x-html-test-case-flaky'); 185 flakyEl.appendChild(tr.ui.b.instantiateTemplate( 186 '#x-html-test-case-flaky-template', THIS_DOC)); 187 flakyEl.querySelector('#message').textContent = 'FLAKY'; 188 this.querySelector('#details').appendChild(flakyEl); 189 this.updateColorAndStatus_(); 190 }, 191 192 addHTMLOutput: function(element) { 193 var htmlResultEl = document.createElement('x-html-test-case-html-result'); 194 htmlResultEl.appendChild(element); 195 this.querySelector('#details').appendChild(htmlResultEl); 196 }, 197 198 updateHTMLOutputDisplayState_: function() { 199 var htmlResults = this.querySelectorAll('x-html-test-case-html-result'); 200 var display; 201 if (this.showHTMLOutput) 202 display = ''; 203 else 204 display = (this.testStatus_ == TestStatus.RUNNING) ? '' : 'none'; 205 for (var i = 0; i < htmlResults.length; i++) 206 htmlResults[i].style.display = display; 207 }, 208 209 get hadErrors() { 210 return !!this.querySelector('x-html-test-case-error'); 211 }, 212 213 get isFlaky() { 214 return !!this.querySelector('x-html-test-case-flaky'); 215 }, 216 217 get duration() { 218 return this.duration_; 219 }, 220 221 set duration(duration) { 222 this.duration_ = duration; 223 this.updateColorAndStatus_(); 224 }, 225 226 get testStatus() { 227 return this.testStatus_; 228 }, 229 230 set testStatus(testStatus) { 231 this.testStatus_ = testStatus; 232 this.updateColorAndStatus_(); 233 this.updateHTMLOutputDisplayState_(); 234 }, 235 236 updateColorAndStatus_: function() { 237 var colorCls; 238 var status; 239 if (this.hadErrors) { 240 colorCls = 'unittest-failed'; 241 status = 'failed'; 242 } else if (this.isFlaky) { 243 colorCls = 'unittest-flaky'; 244 status = 'flaky'; 245 } else if (this.testStatus_ == TestStatus.PENDING) { 246 colorCls = 'unittest-pending'; 247 status = 'pending'; 248 } else if (this.testStatus_ == TestStatus.RUNNING) { 249 colorCls = 'unittest-running'; 250 status = 'running'; 251 } else { // DONE_RUNNING and no errors 252 colorCls = 'unittest-passed'; 253 status = 'passed'; 254 } 255 256 var statusEl = this.querySelector('#status'); 257 if (this.duration_) 258 statusEl.textContent = status + ' (' + 259 this.duration_.toFixed(2) + 'ms)'; 260 else 261 statusEl.textContent = status; 262 statusEl.className = colorCls; 263 }, 264 265 get testReturnValue() { 266 return this.testReturnValue_; 267 }, 268 269 set testReturnValue(testReturnValue) { 270 this.testReturnValue_ = testReturnValue; 271 this.querySelector('#return-value').textContent = testReturnValue; 272 } 273 }; 274 275 276 277 278 /** 279 * @constructor 280 */ 281 var HTMLTestResults = tr.ui.b.define('x-tr-b-unittest-test-results'); 282 283 HTMLTestResults.prototype = { 284 __proto__: HTMLUnknownElement.prototype, 285 286 decorate: function() { 287 this.testCaseResultsByCaseGUID_ = {}; 288 this.currentTestCaseStartTime_ = undefined; 289 this.totalRunTime_ = 0; 290 this.numTestsThatPassed_ = 0; 291 this.numTestsThatFailed_ = 0; 292 this.numFlakyTests_ = 0; 293 this.showHTMLOutput_ = false; 294 this.showPendingAndPassedTests_ = false; 295 this.linkifyCallback_ = undefined; 296 this.headless_ = false; 297 }, 298 299 get headless() { 300 return this.headless_; 301 }, 302 303 set headless(headless) { 304 this.headless_ = headless; 305 }, 306 307 getHRefForTestCase: function(testCase) { 308 /* Override this to create custom links */ 309 return undefined; 310 }, 311 312 get showHTMLOutput() { 313 return this.showHTMLOutput_; 314 }, 315 316 set showHTMLOutput(showHTMLOutput) { 317 this.showHTMLOutput_ = showHTMLOutput; 318 var testCaseResults = this.querySelectorAll('x-html-test-case-result'); 319 for (var i = 0; i < testCaseResults.length; i++) 320 testCaseResults[i].showHTMLOutput = showHTMLOutput; 321 }, 322 323 get showPendingAndPassedTests() { 324 return this.showPendingAndPassedTests_; 325 }, 326 327 set showPendingAndPassedTests(showPendingAndPassedTests) { 328 this.showPendingAndPassedTests_ = showPendingAndPassedTests; 329 330 var testCaseResults = this.querySelectorAll('x-html-test-case-result'); 331 for (var i = testCaseResults.length - 1; i >= 0; i--) 332 this.updateDisplayStateForResult_(testCaseResults[i]); 333 }, 334 335 updateDisplayStateForResult_: function(res) { 336 var display; 337 if (this.showPendingAndPassedTests_) { 338 if (res.testStatus == TestStatus.RUNNING || 339 res.hadErrors) { 340 display = ''; 341 } else { 342 display = 'none'; 343 } 344 } else { 345 display = ''; 346 } 347 res.style.display = display; 348 }, 349 350 willRunTests: function(testCases) { 351 this.timeAtBeginningOfTest_ = window.performance.now(); 352 testCases.forEach(function(testCase, i) { 353 var testCaseResult = new HTMLTestCaseResult(); 354 testCaseResult.showHTMLOutput = this.showHTMLOutput_; 355 testCaseResult.testCase = testCase; 356 if ((i % 2) === 0) 357 testCaseResult.classList.add('dark'); 358 359 var href = this.getHRefForTestCase(testCase); 360 if (href) 361 testCaseResult.testCaseHRef = href; 362 testCaseResult.testStatus = TestStatus.PENDING; 363 this.testCaseResultsByCaseGUID_[testCase.guid] = testCaseResult; 364 this.appendChild(testCaseResult); 365 this.updateDisplayStateForResult_(testCaseResult); 366 }, this); 367 }, 368 369 willRunTest: function(testCase) { 370 this.currentTestCaseResult_ = this.testCaseResultsByCaseGUID_[ 371 testCase.guid]; 372 this.currentTestCaseStartTime_ = window.performance.now(); 373 this.currentTestCaseResult_.testStatus = TestStatus.RUNNING; 374 this.updateDisplayStateForResult_(this.currentTestCaseResult_); 375 this.log_(testCase.fullyQualifiedName + ': '); 376 }, 377 378 addErrorForCurrentTest: function(error) { 379 this.log_('\n'); 380 381 var normalizedException = tr.b.normalizeException(error); 382 this.log_('Exception: ' + normalizedException.message + '\n' + 383 normalizedException.stack); 384 385 this.currentTestCaseResult_.addError(normalizedException); 386 this.updateDisplayStateForResult_(this.currentTestCaseResult_); 387 if (this.headless_) 388 this.notifyTestResultToDevServer_('EXCEPT', normalizedException.stack); 389 }, 390 391 addHTMLOutputForCurrentTest: function(element) { 392 this.currentTestCaseResult_.addHTMLOutput(element); 393 this.updateDisplayStateForResult_(this.currentTestCaseResult_); 394 }, 395 396 setCurrentTestFlaky: function() { 397 this.currentTestCaseResult_.addFlaky(); 398 this.updateDisplayStateForResult_(this.currentTestCaseResult_); 399 }, 400 401 setReturnValueFromCurrentTest: function(returnValue) { 402 this.currentTestCaseResult_.testReturnValue = returnValue; 403 }, 404 405 didCurrentTestEnd: function() { 406 var now = window.performance.now(); 407 var testCaseResult = this.currentTestCaseResult_; 408 var testCaseDuration = now - this.currentTestCaseStartTime_; 409 this.currentTestCaseResult_.testStatus = TestStatus.DONE_RUNNING; 410 testCaseResult.duration = testCaseDuration; 411 this.totalRunTime_ = now - this.timeAtBeginningOfTest_; 412 var resultString; 413 if (testCaseResult.hadErrors) { 414 resultString = 'FAILED'; 415 this.numTestsThatFailed_ += 1; 416 tr.b.dispatchSimpleEvent(this, 'testfailed'); 417 } else if (testCaseResult.isFlaky) { 418 resultString = 'FLAKY'; 419 this.numFlakyTests_ += 1; 420 tr.b.dispatchSimpleEvent(this, 'testflaky'); 421 } else { 422 resultString = 'PASSED'; 423 this.numTestsThatPassed_ += 1; 424 tr.b.dispatchSimpleEvent(this, 'testpassed'); 425 } 426 this.log_('[' + resultString + ']\n'); 427 428 if (this.headless_) 429 this.notifyTestResultToDevServer_(resultString); 430 431 this.updateDisplayStateForResult_(this.currentTestCaseResult_); 432 this.currentTestCaseResult_ = undefined; 433 }, 434 435 didRunTests: function() { 436 this.log_('[DONE]\n'); 437 if (this.headless_) 438 this.notifyTestCompletionToDevServer_(); 439 }, 440 441 getStats: function() { 442 return { 443 numTestsThatPassed: this.numTestsThatPassed_, 444 numTestsThatFailed: this.numTestsThatFailed_, 445 numFlakyTests: this.numFlakyTests_, 446 totalRunTime: this.totalRunTime_ 447 }; 448 }, 449 450 notifyTestResultToDevServer_: function(result, extra_msg) { 451 var req = new XMLHttpRequest(); 452 var testName = this.currentTestCaseResult_.testCase.fullyQualifiedName; 453 var data = result + ' ' + testName + ' ' + (extra_msg || ''); 454 tr.b.postAsync('/tracing/notify_test_result', data); 455 }, 456 457 notifyTestCompletionToDevServer_: function() { 458 if (this.numTestsThatPassed_ + this.numTestsThatFailed_ + 459 this.numFlakyTests_ == 0) { 460 return; 461 } 462 var data = this.numTestsThatFailed_ == 0 ? 'ALL_PASSED' : 'HAD_FAILURES'; 463 data += '\nPassed tests: ' + this.numTestsThatPassed_ + 464 ' Failed tests: ' + this.numTestsThatFailed_ + 465 ' Flaky tests: ' + this.numFlakyTests_; 466 467 tr.b.postAsync('/tracing/notify_tests_completed', data); 468 }, 469 470 log_: function(msg) { 471 //this.textContent += msg; 472 tr.b.dispatchSimpleEvent(this, 'statschange'); 473 } 474 }; 475 476 return { 477 HTMLTestResults: HTMLTestResults 478 }; 479}); 480</script> 481