8 font-family: Helvetica, sans-serif;
10 display: -webkit-flex;
11 -webkit-flex-direction: column;
29 tr:not(.results-row) td {
33 tr:not(.results-row) td:first-of-type {
37 td:not(:first-of-type) {
38 text-transform: lowercase;
50 -webkit-user-select: none;
51 -moz-user-select: none;
56 min-height: -webkit-min-content;
66 background-color: white;
69 .results-row iframe, .results-row img {
74 .results-row[data-expanded="false"] {
84 background-color: rgba(255, 255, 255, 0.85);
85 border: 1px solid silver;
90 background-color: white;
93 border: 1px solid gray;
94 display: inline-block;
104 .current .expand-button {
108 .expand-button-text {
118 tbody.flagged .flag {
122 .stopped-running-early-message {
123 border: 3px solid #d00;
125 display: inline-block;
130 display: inline-block;
131 border: 1px solid gray;
135 .result-container iframe, .result-container img {
144 background-color: silver;
147 .pixel-zoom-container {
154 display: -webkit-box;
156 pointer-events: none;
157 background-color: silver;
159 border: 1px solid gray;
160 box-shadow: 0 0 5px rgba(0, 0, 0, 0.75);
163 .pixel-zoom-container > * {
166 border: 1px solid black;
169 background-color: white;
172 .pixel-zoom-container .scaled-image-container {
179 .scaled-image-container > img {
183 image-rendering: -webkit-optimize-contrast;
192 #flagged-test-container h2 {
193 display: inline-block;
197 <style id="unexpected-pass-style"></style>
198 <style id="flaky-failures-style"></style>
199 <style id="stderr-style"></style>
200 <style id="unexpected-style"></style>
204 function globalState()
211 hasImageFailures: false,
212 hasTextFailures: false,
215 shouldToggleImages: true,
219 unexpectedPassTests: []
225 function ADD_RESULTS(input)
227 globalState().results = input;
231 <script src="failing_results.json"></script>
234 function stripExtension(test)
236 var index = test.lastIndexOf('.');
237 return test.substring(0, index);
240 function matchesSelector(node, selector)
242 if (node.webkitMatchesSelector)
243 return node.webkitMatchesSelector(selector);
245 if (node.mozMatchesSelector)
246 return node.mozMatchesSelector(selector);
249 function parentOfType(node, selector)
251 while (node = node.parentNode) {
252 if (matchesSelector(node, selector))
258 function remove(node)
260 node.parentNode.removeChild(node);
263 function forEach(nodeList, handler)
265 Array.prototype.forEach.call(nodeList, handler);
268 function resultIframe(src)
270 // FIXME: use audio tags for AUDIO tests?
271 var layoutTestsIndex = src.indexOf('LayoutTests');
273 if (layoutTestsIndex != -1) {
274 var hasTrac = src.indexOf('trac.webkit.org') != -1;
275 var prefix = hasTrac ? 'trac.webkit.org/.../' : '';
276 name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length);
278 var lastDashIndex = src.lastIndexOf('-pretty');
279 if (lastDashIndex == -1)
280 lastDashIndex = src.lastIndexOf('-');
281 name = src.substring(lastDashIndex + 1);
284 var tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img';
286 if (tagName != 'img')
287 src += '?format=txt';
288 return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="' + src + '"></' + tagName + '></div>';
291 function togglingImage(prefix)
293 return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' +
294 prefix + '"></img></div>';
297 function toggleExpectations(element)
299 var expandLink = element;
300 if (expandLink.className != 'expand-button-text')
301 expandLink = expandLink.querySelector('.expand-button-text');
303 if (expandLink.textContent == '+')
304 expandExpectations(expandLink);
306 collapseExpectations(expandLink);
309 function collapseExpectations(expandLink)
311 expandLink.textContent = '+';
312 var existingResultsRow = parentOfType(expandLink, 'tbody').querySelector('.results-row');
313 if (existingResultsRow)
314 updateExpandedState(existingResultsRow, false);
317 function updateExpandedState(row, isExpanded)
319 row.setAttribute('data-expanded', isExpanded);
320 updateImageTogglingTimer();
323 function appendHTML(node, html)
325 if (node.insertAdjacentHTML)
326 node.insertAdjacentHTML('beforeEnd', html);
328 node.innerHTML += html;
331 function expandExpectations(expandLink)
333 var row = parentOfType(expandLink, 'tr');
334 var parentTbody = row.parentNode;
335 var existingResultsRow = parentTbody.querySelector('.results-row');
337 var enDash = '\u2013';
338 expandLink.textContent = enDash;
339 if (existingResultsRow) {
340 updateExpandedState(existingResultsRow, true);
344 var newRow = document.createElement('tr');
345 newRow.className = 'results-row';
346 var newCell = document.createElement('td');
347 newCell.colSpan = row.querySelectorAll('td').length;
349 var resultLinks = row.querySelectorAll('.result-link');
350 var hasTogglingImages = false;
351 for (var i = 0; i < resultLinks.length; i++) {
352 var link = resultLinks[i];
354 if (link.textContent == 'images') {
355 hasTogglingImages = true;
356 result = togglingImage(link.getAttribute('data-prefix'));
358 result = resultIframe(link.href);
360 appendHTML(newCell, result);
363 newRow.appendChild(newCell);
364 parentTbody.appendChild(newRow);
366 updateExpandedState(newRow, true);
368 updateImageTogglingTimer();
371 function updateImageTogglingTimer()
373 var hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage');
374 if (!hasVisibleAnimatedImage) {
375 clearInterval(globalState().togglingImageInterval);
376 globalState().togglingImageInterval = null;
380 if (!globalState().togglingImageInterval) {
382 globalState().togglingImageInterval = setInterval(toggleImages, 2000);
386 function async(func, args)
388 setTimeout(function() { func.apply(null, args); }, 100);
391 function visibleTests(opt_container)
393 var container = opt_container || document;
394 if (onlyShowUnexpectedFailures())
395 return container.querySelectorAll('tbody:not(.expected)');
397 return container.querySelectorAll('tbody');
400 function visibleExpandLinks()
402 if (onlyShowUnexpectedFailures())
403 return document.querySelectorAll('tbody:not(.expected) .expand-button-text');
405 return document.querySelectorAll('.expand-button-text');
408 function expandAllExpectations()
410 var expandLinks = visibleExpandLinks();
411 for (var i = 0, len = expandLinks.length; i < len; i++)
412 async(expandExpectations, [expandLinks[i]]);
415 function collapseAllExpectations()
417 var expandLinks = visibleExpandLinks();
418 for (var i = 0, len = expandLinks.length; i < len; i++)
419 async(collapseExpectations, [expandLinks[i]]);
422 function shouldUseTracLinks()
424 return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0;
427 function testLink(test)
430 if (shouldUseTracLinks()) {
431 var revision = globalState().results.revision;
432 target = 'http://src.chromium.org/viewvc/blink/trunk/LayoutTests/' + test;
434 target += '?pathrev=' + revision;
437 target = globalState().results.layout_tests_dir + '/' + test;
438 return '<a class=test-link href="' + target + '">' + test + '</a><span class=flag onclick="unflag(this)"> \u2691</span>';
441 function unflag(flag)
443 var shouldFlag = false;
444 TestNavigator.flagTest(parentOfType(flag, 'tbody'), shouldFlag);
447 function testLinkWithExpandButton(test)
449 return '<span class=expand-button onclick="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testLink(test);
452 function resultLink(testPrefix, suffix, contents)
454 return '<a class=result-link href="' + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> ';
457 function processGlobalStateFor(testObject)
459 var test = testObject.name;
460 if (testObject.has_stderr)
461 globalState().testsWithStderr.push(testObject);
463 globalState().hasHttpTests = globalState().hasHttpTests || test.indexOf('http/') == 0;
465 var actual = testObject.actual;
466 var expected = testObject.expected || 'PASS';
468 if (actual == 'MISSING') {
469 // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for
470 // tests with MISSING results.
471 globalState().missingResults.push(testObject);
475 var actualTokens = actual.split(' ');
476 var passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE';
477 if (actualTokens[1] && actual.indexOf('PASS') != -1 || (!globalState().results.pixel_tests_enabled && passedWithImageOnlyFailureInRetry)) {
478 globalState().flakyPassTests.push(testObject);
482 if (actual == 'PASS' && expected != 'PASS') {
483 if (expected != 'IMAGE' || (globalState().results.pixel_tests_enabled || testObject.reftest_type)) {
484 globalState().unexpectedPassTests.push(testObject);
489 if (actual == 'CRASH') {
490 globalState().crashTests.push(testObject);
494 if (actual == 'TIMEOUT') {
495 globalState().timeoutTests.push(testObject);
499 globalState().failingTests.push(testObject);
502 function toggleImages()
504 var images = document.querySelectorAll('.animatedImage');
505 var imageTexts = document.querySelectorAll('.imageText');
506 for (var i = 0, len = images.length; i < len; i++) {
507 var image = images[i];
508 var text = imageTexts[i];
509 if (text.textContent == 'Expected Image') {
510 text.textContent = 'Actual Image';
511 image.src = image.getAttribute('data-prefix') + '-actual.png';
513 text.textContent = 'Expected Image';
514 image.src = image.getAttribute('data-prefix') + '-expected.png';
519 function textResultLinks(prefix)
521 var html = resultLink(prefix, '-expected.txt', 'expected') +
522 resultLink(prefix, '-actual.txt', 'actual') +
523 resultLink(prefix, '-diff.txt', 'diff');
525 if (globalState().results.has_pretty_patch)
526 html += resultLink(prefix, '-pretty-diff.html', 'pretty diff');
528 if (globalState().results.has_wdiff)
529 html += resultLink(prefix, '-wdiff.html', 'wdiff');
534 function imageResultsCell(testObject, testPrefix, actual) {
537 if (actual.indexOf('IMAGE') != -1) {
538 globalState().hasImageFailures = true;
540 if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1) {
541 row += resultLink(testPrefix, '-expected-mismatch.html', 'ref mismatch html');
542 row += resultLink(testPrefix, '-actual.png', 'actual');
544 if (testObject.reftest_type && testObject.reftest_type.indexOf('==') != -1) {
545 row += resultLink(testPrefix, '-expected.html', 'ref html');
547 if (globalState().shouldToggleImages) {
548 row += resultLink(testPrefix, '-diffs.html', 'images');
550 row += resultLink(testPrefix, '-expected.png', 'expected');
551 row += resultLink(testPrefix, '-actual.png', 'actual');
554 row += resultLink(testPrefix, '-diff.png', 'diff');
558 if (actual.indexOf('MISSING') != -1 && testObject.is_missing_image)
559 row += resultLink(testPrefix, '-actual.png', 'png result');
564 function tableRow(testObject)
566 var row = '<tbody class="' + (testObject.is_unexpected ? '' : 'expected') + '"';
567 if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1)
568 row += ' mismatchreftest=true';
571 row += '<td>' + testLinkWithExpandButton(testObject.name) + '</td>';
573 var testPrefix = stripExtension(testObject.name);
576 var actual = testObject.actual;
577 if (actual.indexOf('TEXT') != -1) {
578 globalState().hasTextFailures = true;
579 row += textResultLinks(testPrefix);
582 if (actual.indexOf('AUDIO') != -1) {
583 row += resultLink(testPrefix, '-expected.wav', 'expected audio');
584 row += resultLink(testPrefix, '-actual.wav', 'actual audio');
587 if (actual.indexOf('MISSING') != -1) {
588 if (testObject.is_missing_audio)
589 row += resultLink(testPrefix, '-actual.wav', 'audio result');
590 if (testObject.is_missing_text)
591 row += resultLink(testPrefix, '-actual.txt', 'result');
594 var actualTokens = actual.split(/\s+/);
595 var cell = imageResultsCell(testObject, testPrefix, actualTokens[0]);
596 if (!cell && actualTokens.length > 1)
597 cell = imageResultsCell(testObject, 'retries/' + testPrefix, actualTokens[1]);
599 row += '</td><td>' + cell + '</td>' +
600 '<td>' + actual + '</td>' +
601 '<td>' + (actual.indexOf('MISSING') == -1 ? testObject.expected : '') + '</td>' +
606 function forEachTest(handler, opt_tree, opt_prefix)
608 var tree = opt_tree || globalState().results.tests;
609 var prefix = opt_prefix || '';
611 for (var key in tree) {
612 var newPrefix = prefix ? (prefix + '/' + key) : key;
613 if ('actual' in tree[key]) {
614 var testObject = tree[key];
615 testObject.name = newPrefix;
618 forEachTest(handler, tree[key], newPrefix);
622 function hasUnexpected(tests)
624 return tests.some(function (test) { return test.is_unexpected; });
627 function updateTestlistCounts()
629 forEach(document.querySelectorAll('.test-list-count'), function(count) {
630 var container = parentOfType(count, 'div');
632 if (onlyShowUnexpectedFailures())
633 testContainers = container.querySelectorAll('tbody:not(.expected)');
635 testContainers = container.querySelectorAll('tbody');
637 count.textContent = testContainers.length;
641 function flagAll(headerLink)
643 var tests = visibleTests(parentOfType(headerLink, 'div'));
644 forEach(tests, function(test) {
645 var shouldFlag = true;
646 TestNavigator.flagTest(test, shouldFlag);
650 function testListHeaderHtml(header)
652 return '<h1>' + header + ' (<span class=test-list-count></span>): <a href="#" class=flag-all onclick="flagAll(this)">flag all</a></h1>';
655 function testList(tests, header, tableId)
659 var html = '<div' + ((!hasUnexpected(tests) && tableId != 'stderr-table') ? ' class=expected' : '') + ' id=' + tableId + '>' +
660 testListHeaderHtml(header) + '<table>';
662 // FIXME: Include this for all testLists.
663 if (tableId == 'passes-table')
664 html += '<thead><th>test</th><th>expected</th></thead>';
666 for (var i = 0; i < tests.length; i++) {
667 var testObject = tests[i];
668 var test = testObject.name;
669 html += '<tbody class="' + ((testObject.is_unexpected || tableId == 'stderr-table') ? '' : 'expected') + '"><tr><td>' +
670 ((tableId == 'passes-table') ? testLink(test) : testLinkWithExpandButton(test)) +
673 if (tableId == 'stderr-table')
674 html += resultLink(stripExtension(test), '-stderr.txt', 'stderr');
675 else if (tableId == 'passes-table')
676 html += testObject.expected;
677 else if (tableId == 'crash-tests-table') {
678 html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log');
679 html += resultLink(stripExtension(test), '-sample.txt', 'sample');
680 } else if (tableId == 'timeout-tests-table') {
681 // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests.
682 html += textResultLinks(stripExtension(test));
685 html += '</td></tr></tbody>';
687 html += '</table></div>';
691 function toArray(nodeList)
693 return Array.prototype.slice.call(nodeList);
696 function trim(string)
698 return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
701 // Just a namespace for code management.
702 var TableSorter = {};
704 TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>';
706 TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>';
708 TableSorter._sortedContents = function(header, arrow)
710 return arrow + ' ' + trim(header.textContent) + ' ' + arrow;
713 TableSorter._updateHeaderClassNames = function(newHeader)
715 var sortHeader = document.querySelector('.sortHeader');
717 if (sortHeader == newHeader) {
718 var isAlreadyReversed = sortHeader.classList.contains('reversed');
719 if (isAlreadyReversed)
720 sortHeader.classList.remove('reversed');
722 sortHeader.classList.add('reversed');
724 sortHeader.textContent = sortHeader.textContent;
725 sortHeader.classList.remove('sortHeader');
726 sortHeader.classList.remove('reversed');
730 newHeader.classList.add('sortHeader');
733 TableSorter._textContent = function(tbodyRow, column)
735 return tbodyRow.querySelectorAll('td')[column].textContent;
738 TableSorter._sortRows = function(newHeader, reversed)
740 var testsTable = document.getElementById('results-table');
741 var headers = toArray(testsTable.querySelectorAll('th'));
742 var sortColumn = headers.indexOf(newHeader);
744 var rows = toArray(testsTable.querySelectorAll('tbody'));
746 rows.sort(function(a, b) {
747 // Only need to support lexicographic sort for now.
748 var aText = TableSorter._textContent(a, sortColumn);
749 var bText = TableSorter._textContent(b, sortColumn);
751 // Forward sort equal values by test name.
752 if (sortColumn && aText == bText) {
753 var aTestName = TableSorter._textContent(a, 0);
754 var bTestName = TableSorter._textContent(b, 0);
755 if (aTestName == bTestName)
757 return aTestName < bTestName ? -1 : 1;
761 return aText < bText ? 1 : -1;
763 return aText < bText ? -1 : 1;
766 for (var i = 0; i < rows.length; i++)
767 testsTable.appendChild(rows[i]);
770 TableSorter.sortColumn = function(columnNumber)
772 var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
773 TableSorter._sort(newHeader);
776 TableSorter.handleClick = function(e)
778 var newHeader = e.target;
779 if (newHeader.localName != 'th')
781 TableSorter._sort(newHeader);
784 TableSorter._sort = function(newHeader)
786 TableSorter._updateHeaderClassNames(newHeader);
788 var reversed = newHeader.classList.contains('reversed');
789 var sortArrow = reversed ? TableSorter._backwardArrow : TableSorter._forwardArrow;
790 newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow);
792 TableSorter._sortRows(newHeader, reversed);
795 var PixelZoomer = {};
797 PixelZoomer.showOnDelay = true;
798 PixelZoomer._zoomFactor = 6;
800 var kResultWidth = 800;
801 var kResultHeight = 600;
803 var kZoomedResultWidth = kResultWidth * PixelZoomer._zoomFactor;
804 var kZoomedResultHeight = kResultHeight * PixelZoomer._zoomFactor;
806 PixelZoomer._zoomImageContainer = function(url)
808 var container = document.createElement('div');
809 container.className = 'zoom-image-container';
811 var title = url.match(/\-([^\-]*)\.png/)[1];
813 var label = document.createElement('div');
814 label.className = 'label';
815 label.appendChild(document.createTextNode(title));
816 container.appendChild(label);
818 var imageContainer = document.createElement('div');
819 imageContainer.className = 'scaled-image-container';
821 var image = new Image();
823 image.style.width = kZoomedResultWidth + 'px';
824 image.style.height = kZoomedResultHeight + 'px';
825 image.style.border = '1px solid black';
826 imageContainer.appendChild(image);
827 container.appendChild(imageContainer);
832 PixelZoomer._createContainer = function(e)
834 var tbody = parentOfType(e.target, 'tbody');
835 var row = tbody.querySelector('tr');
836 var imageDiffLinks = row.querySelectorAll('a[href$=".png"]');
838 var container = document.createElement('div');
839 container.className = 'pixel-zoom-container';
843 var togglingImageLink = row.querySelector('a[href$="-diffs.html"]');
844 if (togglingImageLink) {
845 var prefix = togglingImageLink.getAttribute('data-prefix');
846 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-expected.png'));
847 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-actual.png'));
850 for (var i = 0; i < imageDiffLinks.length; i++)
851 container.appendChild(PixelZoomer._zoomImageContainer(imageDiffLinks[i].href));
853 document.body.appendChild(container);
854 PixelZoomer._drawAll();
857 PixelZoomer._draw = function(imageContainer)
859 var image = imageContainer.querySelector('img');
860 var containerBounds = imageContainer.getBoundingClientRect();
861 image.style.left = (containerBounds.width / 2 - PixelZoomer._percentX * kZoomedResultWidth) + 'px';
862 image.style.top = (containerBounds.height / 2 - PixelZoomer._percentY * kZoomedResultHeight) + 'px';
865 PixelZoomer._drawAll = function()
867 forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), PixelZoomer._draw);
870 PixelZoomer.handleMouseOut = function(e)
872 if (e.relatedTarget && e.relatedTarget.tagName != 'IFRAME')
875 // If e.relatedTarget is null, we've moused out of the document.
876 var container = document.querySelector('.pixel-zoom-container');
881 PixelZoomer.handleMouseMove = function(e) {
882 if (PixelZoomer._mouseMoveTimeout)
883 clearTimeout(PixelZoomer._mouseMoveTimeout);
885 if (parentOfType(e.target, '.pixel-zoom-container'))
888 var container = document.querySelector('.pixel-zoom-container');
890 var resultContainer = (e.target.className == 'result-container') ?
891 e.target : parentOfType(e.target, '.result-container');
892 if (!resultContainer || !resultContainer.querySelector('img')) {
898 var targetLocation = e.target.getBoundingClientRect();
899 PixelZoomer._percentX = (e.clientX - targetLocation.left) / targetLocation.width;
900 PixelZoomer._percentY = (e.clientY - targetLocation.top) / targetLocation.height;
903 if (PixelZoomer.showOnDelay) {
904 PixelZoomer._mouseMoveTimeout = setTimeout(function() {
905 PixelZoomer._createContainer(e);
910 PixelZoomer._createContainer(e);
914 PixelZoomer._drawAll();
917 document.addEventListener('mousemove', PixelZoomer.handleMouseMove, false);
918 document.addEventListener('mouseout', PixelZoomer.handleMouseOut, false);
920 var TestNavigator = {};
922 TestNavigator.reset = function() {
923 TestNavigator.currentTestIndex = -1;
924 TestNavigator.flaggedTests = {};
925 TestNavigator._createFlaggedTestContainer();
928 TestNavigator.handleKeyEvent = function(event)
930 if (event.metaKey || event.shiftKey || event.ctrlKey)
933 switch (String.fromCharCode(event.charCode)) {
935 TestNavigator._scrollToFirstTest();
938 TestNavigator._scrollToNextTest();
941 TestNavigator._scrollToPreviousTest();
944 TestNavigator._scrollToLastTest();
947 TestNavigator._expandCurrentTest();
950 TestNavigator._collapseCurrentTest();
953 TestNavigator._toggleCurrentTest();
956 TestNavigator._toggleCurrentTestFlagged();
961 TestNavigator._scrollToFirstTest = function()
963 if (TestNavigator._setCurrentTest(0))
964 TestNavigator._scrollToCurrentTest();
967 TestNavigator._scrollToLastTest = function()
969 var links = visibleTests();
970 if (TestNavigator._setCurrentTest(links.length - 1))
971 TestNavigator._scrollToCurrentTest();
974 TestNavigator._scrollToNextTest = function()
976 if (TestNavigator.currentTestIndex == -1)
977 TestNavigator._scrollToFirstTest();
978 else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex + 1))
979 TestNavigator._scrollToCurrentTest();
982 TestNavigator._scrollToPreviousTest = function()
984 if (TestNavigator.currentTestIndex == -1)
985 TestNavigator._scrollToLastTest();
986 else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex - 1))
987 TestNavigator._scrollToCurrentTest();
990 TestNavigator._currentTestLink = function()
992 var links = visibleTests();
993 return links[TestNavigator.currentTestIndex];
996 TestNavigator._currentTestExpandLink = function()
998 return TestNavigator._currentTestLink().querySelector('.expand-button-text');
1001 TestNavigator._expandCurrentTest = function()
1003 expandExpectations(TestNavigator._currentTestExpandLink());
1006 TestNavigator._collapseCurrentTest = function()
1008 collapseExpectations(TestNavigator._currentTestExpandLink());
1011 TestNavigator._toggleCurrentTest = function()
1013 toggleExpectations(TestNavigator._currentTestExpandLink());
1016 TestNavigator._toggleCurrentTestFlagged = function()
1018 var testLink = TestNavigator._currentTestLink();
1019 TestNavigator.flagTest(testLink, !testLink.classList.contains('flagged'));
1022 // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something.
1023 TestNavigator.flagTest = function(testTbody, shouldFlag)
1025 var testName = testTbody.querySelector('.test-link').innerText;
1028 testTbody.classList.add('flagged');
1029 TestNavigator.flaggedTests[testName] = 1;
1031 testTbody.classList.remove('flagged');
1032 delete TestNavigator.flaggedTests[testName];
1035 TestNavigator.updateFlaggedTests();
1038 TestNavigator._createFlaggedTestContainer = function()
1040 var flaggedTestContainer = document.createElement('div');
1041 flaggedTestContainer.id = 'flagged-test-container';
1042 flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2>' +
1043 '<label title="Use newlines instead of spaces to separate flagged tests">' +
1044 '<input id="use-newlines" type=checkbox checked onchange="handleToggleUseNewlines()">Use newlines</input>' +
1046 '<pre id="flagged-tests" contentEditable></pre>';
1047 document.body.appendChild(flaggedTestContainer);
1050 TestNavigator.updateFlaggedTests = function()
1052 var flaggedTestTextbox = document.getElementById('flagged-tests');
1053 var flaggedTests = Object.keys(this.flaggedTests);
1054 flaggedTests.sort();
1055 var separator = document.getElementById('use-newlines').checked ? '\n' : ' ';
1056 flaggedTestTextbox.innerHTML = flaggedTests.join(separator);
1057 document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none';
1060 TestNavigator._setCurrentTest = function(testIndex)
1062 var links = visibleTests();
1063 if (testIndex < 0 || testIndex >= links.length)
1066 var currentTest = links[TestNavigator.currentTestIndex];
1068 currentTest.classList.remove('current');
1070 TestNavigator.currentTestIndex = testIndex;
1072 currentTest = links[TestNavigator.currentTestIndex];
1073 currentTest.classList.add('current');
1078 TestNavigator._scrollToCurrentTest = function()
1080 var targetLink = TestNavigator._currentTestLink();
1084 var rowRect = targetLink.getBoundingClientRect();
1085 var container = document.querySelector('.content-container');
1086 // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position.
1087 container.scrollTop += rowRect.top - 20;
1090 TestNavigator.onlyShowUnexpectedFailuresChanged = function()
1092 var currentTest = document.querySelector('.current');
1096 // If our currentTest became hidden, reset the currentTestIndex.
1097 if (onlyShowUnexpectedFailures() && currentTest.classList.contains('expected'))
1098 TestNavigator._scrollToFirstTest();
1100 // Recompute TestNavigator.currentTestIndex
1101 var links = visibleTests();
1102 TestNavigator.currentTestIndex = links.indexOf(currentTest);
1103 window.console.log('TestNavigator.currentTestIndex is ', TestNavigator.currentTestIndex)
1107 document.addEventListener('keypress', TestNavigator.handleKeyEvent, false);
1110 function onlyShowUnexpectedFailures()
1112 return !document.getElementById('show-expected-failures').checked;
1115 function handleStderrChange()
1117 OptionWriter.save();
1118 document.getElementById('stderr-style').textContent = document.getElementById('show-stderr').checked ?
1119 '' : '#stderr-table { display: none; }';
1122 function handleUnexpectedPassesChange()
1124 OptionWriter.save();
1125 document.getElementById('unexpected-pass-style').textContent = document.getElementById('show-unexpected-passes').checked ?
1126 '' : '#passes-table { display: none; }';
1129 function handleFlakyFailuresChange()
1131 OptionWriter.save();
1132 document.getElementById('flaky-failures-style').textContent = document.getElementById('show-flaky-failures').checked ?
1133 '' : '.flaky { display: none; }';
1136 function handleUnexpectedResultsChange()
1138 OptionWriter.save();
1139 updateExpectedFailures();
1142 function updateExpectedFailures()
1144 document.getElementById('unexpected-style').textContent = onlyShowUnexpectedFailures() ?
1145 '.expected { display: none; }' : '';
1147 updateTestlistCounts();
1148 TestNavigator.onlyShowUnexpectedFailuresChanged();
1151 var OptionWriter = {};
1153 OptionWriter._key = 'run-webkit-tests-options';
1155 OptionWriter.save = function()
1157 var options = document.querySelectorAll('label input');
1159 for (var i = 0, len = options.length; i < len; i++) {
1160 var option = options[i];
1161 data[option.id] = option.checked;
1163 localStorage.setItem(OptionWriter._key, JSON.stringify(data));
1166 OptionWriter.apply = function()
1168 var json = localStorage.getItem(OptionWriter._key);
1174 var data = JSON.parse(json);
1175 for (var id in data) {
1176 var input = document.getElementById(id);
1178 input.checked = data[id];
1183 function updateAllOptions()
1185 forEach(document.querySelectorAll('input'), function(input) { input.onchange(); });
1188 function handleToggleUseNewlines()
1190 OptionWriter.save();
1191 TestNavigator.updateFlaggedTests();
1194 function handleToggleImagesChange()
1196 OptionWriter.save();
1197 updateTogglingImages();
1200 function updateTogglingImages()
1202 var shouldToggle = document.getElementById('toggle-images').checked;
1203 globalState().shouldToggleImages = shouldToggle;
1206 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), convertToTogglingHandler(function(prefix) {
1207 return resultLink(prefix, '-diffs.html', 'images');
1209 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), convertToTogglingHandler(togglingImage));
1211 forEach(document.querySelectorAll('a[href$="-diffs.html"]'), convertToNonTogglingHandler(resultLink));
1212 forEach(document.querySelectorAll('.animatedImage'), convertToNonTogglingHandler(function (absolutePrefix, suffix) {
1213 return resultIframe(absolutePrefix + suffix);
1217 updateImageTogglingTimer();
1220 function getResultContainer(node)
1222 return (node.tagName == 'IMG') ? parentOfType(node, '.result-container') : node;
1225 function convertToTogglingHandler(togglingImageFunction)
1227 return function(node) {
1228 var url = (node.tagName == 'IMG') ? node.src : node.href;
1229 if (url.match('-expected.png$'))
1230 remove(getResultContainer(node));
1231 else if (url.match('-actual.png$')) {
1232 var name = parentOfType(node, 'tbody').querySelector('.test-link').textContent;
1233 getResultContainer(node).outerHTML = togglingImageFunction(stripExtension(name));
1238 function convertToNonTogglingHandler(resultFunction)
1240 return function(node) {
1241 var prefix = node.getAttribute('data-prefix');
1242 getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual');
1246 function failingTestsTable(tests, title, id)
1251 var numberofUnexpectedFailures = 0;
1252 var tableRowHtml = '';
1253 for (var i = 0; i < tests.length; i++){
1254 tableRowHtml += tableRow(tests[i]);
1255 if (tests[i].is_unexpected)
1256 numberofUnexpectedFailures++;
1261 className += id.split('-')[0];
1262 if (!hasUnexpected(tests))
1263 className += ' expected';
1265 var header = '<div';
1267 header += ' class="' + className + '"';
1269 header += '>' + testListHeaderHtml(title) +
1270 '<table id="' + id + '"><thead><tr>' +
1272 '<th id="text-results-header">results</th>' +
1273 '<th id="image-results-header">image results</th>' +
1275 '<th>expected</th>';
1277 if (id == 'flaky-tests-table')
1278 header += '<th>failures</th>';
1280 header += '</tr></thead>';
1283 return header + tableRowHtml + '</table></div>';
1287 function generatePage()
1289 forEachTest(processGlobalStateFor);
1291 var html = '<div class=content-container><div id=toolbar>' +
1292 '<div class="note">Use the i, j, k and l keys to navigate, e, c to expand and collapse, and f to flag</div>' +
1293 '<a href="javascript:void()" onclick="expandAllExpectations()">expand all</a> ' +
1294 '<a href="javascript:void()" onclick="collapseAllExpectations()">collapse all</a> ' +
1295 '<label><input id="toggle-images" type=checkbox checked onchange="handleToggleImagesChange()">Toggle images</label>' +
1296 '<div id=container>Show: '+
1297 '<label><input id="show-expected-failures" type=checkbox onchange="handleUnexpectedResultsChange()">expected failures</label>' +
1298 '<label><input id="show-flaky-failures" type=checkbox onchange="handleFlakyFailuresChange()">flaky failures</label>' +
1299 '<label><input id="show-unexpected-passes" type=checkbox onchange="handleUnexpectedPassesChange()">unexpected passes</label>' +
1300 '<label><input id="show-stderr" type=checkbox onchange="handleStderrChange()">stderr</label>' +
1303 if (globalState().results.interrupted)
1304 html += "<p class='stopped-running-early-message'>Testing exited early.</p>"
1306 if (globalState().crashTests.length)
1307 html += testList(globalState().crashTests, 'Tests that crashed', 'crash-tests-table');
1309 html += failingTestsTable(globalState().failingTests,
1310 'Tests that failed text/pixel/audio diff', 'results-table');
1312 html += failingTestsTable(globalState().missingResults,
1313 'Tests that had no expected results (probably new)', 'missing-table');
1315 if (globalState().timeoutTests.length)
1316 html += testList(globalState().timeoutTests, 'Tests that timed out', 'timeout-tests-table');
1318 if (globalState().testsWithStderr.length)
1319 html += testList(globalState().testsWithStderr, 'Tests that had stderr output', 'stderr-table');
1321 html += failingTestsTable(globalState().flakyPassTests,
1322 'Flaky tests (failed the first run and passed on retry)', 'flaky-tests-table');
1324 if (globalState().unexpectedPassTests.length)
1325 html += testList(globalState().unexpectedPassTests, 'Tests expected to fail but passed', 'passes-table');
1327 if (globalState().hasHttpTests) {
1328 html += '<p>httpd access log: <a href="access_log.txt">access_log.txt</a></p>' +
1329 '<p>httpd error log: <a href="error_log.txt">error_log.txt</a></p>';
1334 document.body.innerHTML = html;
1336 if (document.getElementById('results-table')) {
1337 document.getElementById('results-table').addEventListener('click', TableSorter.handleClick, false);
1338 TableSorter.sortColumn(0);
1339 if (!globalState().hasTextFailures)
1340 document.getElementById('text-results-header').textContent = '';
1341 if (!globalState().hasImageFailures) {
1342 document.getElementById('image-results-header').textContent = '';
1343 parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none';
1347 updateTestlistCounts();
1349 TestNavigator.reset();
1350 OptionWriter.apply();
1353 <!-- HACK: when json_results_test.js is included, loading this page runs the tests.
1354 It is not copied to the layout-test-results output directory. -->
1355 <script src="resources/results-test.js"></script>
1356 <body onload="generatePage()"></body>