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>&nbsp;
91    <span id="status"></span>&nbsp;
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