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 if (testObject.is_testharness_test) {
580 row += resultLink(testPrefix, '-actual.txt', 'actual');
582 row += textResultLinks(testPrefix);
586 if (actual.indexOf('AUDIO') != -1) {
587 row += resultLink(testPrefix, '-expected.wav', 'expected audio');
588 row += resultLink(testPrefix, '-actual.wav', 'actual audio');
591 if (actual.indexOf('MISSING') != -1) {
592 if (testObject.is_missing_audio)
593 row += resultLink(testPrefix, '-actual.wav', 'audio result');
594 if (testObject.is_missing_text)
595 row += resultLink(testPrefix, '-actual.txt', 'result');
598 var actualTokens = actual.split(/\s+/);
599 var cell = imageResultsCell(testObject, testPrefix, actualTokens[0]);
600 if (!cell && actualTokens.length > 1)
601 cell = imageResultsCell(testObject, 'retries/' + testPrefix, actualTokens[1]);
603 row += '</td><td>' + cell + '</td>' +
604 '<td>' + actual + '</td>' +
605 '<td>' + (actual.indexOf('MISSING') == -1 ? testObject.expected : '') + '</td>' +
610 function forEachTest(handler, opt_tree, opt_prefix)
612 var tree = opt_tree || globalState().results.tests;
613 var prefix = opt_prefix || '';
615 for (var key in tree) {
616 var newPrefix = prefix ? (prefix + '/' + key) : key;
617 if ('actual' in tree[key]) {
618 var testObject = tree[key];
619 testObject.name = newPrefix;
622 forEachTest(handler, tree[key], newPrefix);
626 function hasUnexpected(tests)
628 return tests.some(function (test) { return test.is_unexpected; });
631 function updateTestlistCounts()
633 forEach(document.querySelectorAll('.test-list-count'), function(count) {
634 var container = parentOfType(count, 'div');
636 if (onlyShowUnexpectedFailures())
637 testContainers = container.querySelectorAll('tbody:not(.expected)');
639 testContainers = container.querySelectorAll('tbody');
641 count.textContent = testContainers.length;
645 function flagAll(headerLink)
647 var tests = visibleTests(parentOfType(headerLink, 'div'));
648 forEach(tests, function(test) {
649 var shouldFlag = true;
650 TestNavigator.flagTest(test, shouldFlag);
654 function testListHeaderHtml(header)
656 return '<h1>' + header + ' (<span class=test-list-count></span>): <a href="#" class=flag-all onclick="flagAll(this)">flag all</a></h1>';
659 function testList(tests, header, tableId)
663 var html = '<div' + ((!hasUnexpected(tests) && tableId != 'stderr-table') ? ' class=expected' : '') + ' id=' + tableId + '>' +
664 testListHeaderHtml(header) + '<table>';
666 // FIXME: Include this for all testLists.
667 if (tableId == 'passes-table')
668 html += '<thead><th>test</th><th>expected</th></thead>';
670 for (var i = 0; i < tests.length; i++) {
671 var testObject = tests[i];
672 var test = testObject.name;
673 html += '<tbody class="' + ((testObject.is_unexpected || tableId == 'stderr-table') ? '' : 'expected') + '"><tr><td>' +
674 ((tableId == 'passes-table') ? testLink(test) : testLinkWithExpandButton(test)) +
677 if (tableId == 'stderr-table')
678 html += resultLink(stripExtension(test), '-stderr.txt', 'stderr');
679 else if (tableId == 'passes-table')
680 html += testObject.expected;
681 else if (tableId == 'crash-tests-table') {
682 html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log');
683 html += resultLink(stripExtension(test), '-sample.txt', 'sample');
684 } else if (tableId == 'timeout-tests-table') {
685 // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests.
686 html += textResultLinks(stripExtension(test));
689 html += '</td></tr></tbody>';
691 html += '</table></div>';
695 function toArray(nodeList)
697 return Array.prototype.slice.call(nodeList);
700 function trim(string)
702 return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
705 // Just a namespace for code management.
706 var TableSorter = {};
708 TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>';
710 TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>';
712 TableSorter._sortedContents = function(header, arrow)
714 return arrow + ' ' + trim(header.textContent) + ' ' + arrow;
717 TableSorter._updateHeaderClassNames = function(newHeader)
719 var sortHeader = document.querySelector('.sortHeader');
721 if (sortHeader == newHeader) {
722 var isAlreadyReversed = sortHeader.classList.contains('reversed');
723 if (isAlreadyReversed)
724 sortHeader.classList.remove('reversed');
726 sortHeader.classList.add('reversed');
728 sortHeader.textContent = sortHeader.textContent;
729 sortHeader.classList.remove('sortHeader');
730 sortHeader.classList.remove('reversed');
734 newHeader.classList.add('sortHeader');
737 TableSorter._textContent = function(tbodyRow, column)
739 return tbodyRow.querySelectorAll('td')[column].textContent;
742 TableSorter._sortRows = function(newHeader, reversed)
744 var testsTable = document.getElementById('results-table');
745 var headers = toArray(testsTable.querySelectorAll('th'));
746 var sortColumn = headers.indexOf(newHeader);
748 var rows = toArray(testsTable.querySelectorAll('tbody'));
750 rows.sort(function(a, b) {
751 // Only need to support lexicographic sort for now.
752 var aText = TableSorter._textContent(a, sortColumn);
753 var bText = TableSorter._textContent(b, sortColumn);
755 // Forward sort equal values by test name.
756 if (sortColumn && aText == bText) {
757 var aTestName = TableSorter._textContent(a, 0);
758 var bTestName = TableSorter._textContent(b, 0);
759 if (aTestName == bTestName)
761 return aTestName < bTestName ? -1 : 1;
765 return aText < bText ? 1 : -1;
767 return aText < bText ? -1 : 1;
770 for (var i = 0; i < rows.length; i++)
771 testsTable.appendChild(rows[i]);
774 TableSorter.sortColumn = function(columnNumber)
776 var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
777 TableSorter._sort(newHeader);
780 TableSorter.handleClick = function(e)
782 var newHeader = e.target;
783 if (newHeader.localName != 'th')
785 TableSorter._sort(newHeader);
788 TableSorter._sort = function(newHeader)
790 TableSorter._updateHeaderClassNames(newHeader);
792 var reversed = newHeader.classList.contains('reversed');
793 var sortArrow = reversed ? TableSorter._backwardArrow : TableSorter._forwardArrow;
794 newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow);
796 TableSorter._sortRows(newHeader, reversed);
799 var PixelZoomer = {};
801 PixelZoomer.showOnDelay = true;
802 PixelZoomer._zoomFactor = 6;
804 var kResultWidth = 800;
805 var kResultHeight = 600;
807 var kZoomedResultWidth = kResultWidth * PixelZoomer._zoomFactor;
808 var kZoomedResultHeight = kResultHeight * PixelZoomer._zoomFactor;
810 PixelZoomer._zoomImageContainer = function(url)
812 var container = document.createElement('div');
813 container.className = 'zoom-image-container';
815 var title = url.match(/\-([^\-]*)\.png/)[1];
817 var label = document.createElement('div');
818 label.className = 'label';
819 label.appendChild(document.createTextNode(title));
820 container.appendChild(label);
822 var imageContainer = document.createElement('div');
823 imageContainer.className = 'scaled-image-container';
825 var image = new Image();
827 image.style.display = 'none';
829 var canvas = document.createElement('canvas');
831 imageContainer.appendChild(image);
832 imageContainer.appendChild(canvas);
833 container.appendChild(imageContainer);
838 PixelZoomer._createContainer = function(e)
840 var tbody = parentOfType(e.target, 'tbody');
841 var row = tbody.querySelector('tr');
842 var imageDiffLinks = row.querySelectorAll('a[href$=".png"]');
844 var container = document.createElement('div');
845 container.className = 'pixel-zoom-container';
849 var togglingImageLink = row.querySelector('a[href$="-diffs.html"]');
850 if (togglingImageLink) {
851 var prefix = togglingImageLink.getAttribute('data-prefix');
852 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-expected.png'));
853 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-actual.png'));
856 for (var i = 0; i < imageDiffLinks.length; i++)
857 container.appendChild(PixelZoomer._zoomImageContainer(imageDiffLinks[i].href));
859 document.body.appendChild(container);
860 PixelZoomer._drawAll();
863 PixelZoomer._draw = function(imageContainer)
865 var image = imageContainer.querySelector('img');
866 var canvas = imageContainer.querySelector('canvas');
868 if (!image.complete) {
869 image.onload = function() {
870 PixelZoomer._draw(imageContainer);
875 canvas.width = imageContainer.clientWidth;
876 canvas.height = imageContainer.clientHeight;
878 var ctx = canvas.getContext('2d');
879 ctx.webkitImageSmoothingEnabled = false;
880 ctx.mozImageSmoothingEnabled = false;
881 ctx.imageSmoothingEnabled = false;
882 ctx.translate(imageContainer.clientWidth / 2, imageContainer.clientHeight / 2);
883 ctx.translate(-PixelZoomer._percentX * kZoomedResultWidth, -PixelZoomer._percentY * kZoomedResultHeight);
884 ctx.strokeRect(-1.5, -1.5, kZoomedResultWidth + 2, kZoomedResultHeight + 2);
885 ctx.scale(PixelZoomer._zoomFactor, PixelZoomer._zoomFactor);
886 ctx.drawImage(image, 0, 0);
889 PixelZoomer._drawAll = function()
891 forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), PixelZoomer._draw);
894 PixelZoomer.handleMouseOut = function(e)
896 if (e.relatedTarget && e.relatedTarget.tagName != 'IFRAME')
899 // If e.relatedTarget is null, we've moused out of the document.
900 var container = document.querySelector('.pixel-zoom-container');
905 PixelZoomer.handleMouseMove = function(e) {
906 if (PixelZoomer._mouseMoveTimeout)
907 clearTimeout(PixelZoomer._mouseMoveTimeout);
909 if (parentOfType(e.target, '.pixel-zoom-container'))
912 var container = document.querySelector('.pixel-zoom-container');
914 var resultContainer = (e.target.className == 'result-container') ?
915 e.target : parentOfType(e.target, '.result-container');
916 if (!resultContainer || !resultContainer.querySelector('img')) {
922 var targetLocation = e.target.getBoundingClientRect();
923 PixelZoomer._percentX = (e.clientX - targetLocation.left) / targetLocation.width;
924 PixelZoomer._percentY = (e.clientY - targetLocation.top) / targetLocation.height;
927 if (PixelZoomer.showOnDelay) {
928 PixelZoomer._mouseMoveTimeout = setTimeout(function() {
929 PixelZoomer._createContainer(e);
934 PixelZoomer._createContainer(e);
938 PixelZoomer._drawAll();
941 document.addEventListener('mousemove', PixelZoomer.handleMouseMove, false);
942 document.addEventListener('mouseout', PixelZoomer.handleMouseOut, false);
944 var TestNavigator = {};
946 TestNavigator.reset = function() {
947 TestNavigator.currentTestIndex = -1;
948 TestNavigator.flaggedTests = {};
949 TestNavigator._createFlaggedTestContainer();
952 TestNavigator.handleKeyEvent = function(event)
954 if (event.metaKey || event.shiftKey || event.ctrlKey)
957 switch (String.fromCharCode(event.charCode)) {
959 TestNavigator._scrollToFirstTest();
962 TestNavigator._scrollToNextTest();
965 TestNavigator._scrollToPreviousTest();
968 TestNavigator._scrollToLastTest();
971 TestNavigator._expandCurrentTest();
974 TestNavigator._collapseCurrentTest();
977 TestNavigator._toggleCurrentTest();
980 TestNavigator._toggleCurrentTestFlagged();
985 TestNavigator._scrollToFirstTest = function()
987 if (TestNavigator._setCurrentTest(0))
988 TestNavigator._scrollToCurrentTest();
991 TestNavigator._scrollToLastTest = function()
993 var links = visibleTests();
994 if (TestNavigator._setCurrentTest(links.length - 1))
995 TestNavigator._scrollToCurrentTest();
998 TestNavigator._scrollToNextTest = function()
1000 if (TestNavigator.currentTestIndex == -1)
1001 TestNavigator._scrollToFirstTest();
1002 else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex + 1))
1003 TestNavigator._scrollToCurrentTest();
1006 TestNavigator._scrollToPreviousTest = function()
1008 if (TestNavigator.currentTestIndex == -1)
1009 TestNavigator._scrollToLastTest();
1010 else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex - 1))
1011 TestNavigator._scrollToCurrentTest();
1014 TestNavigator._currentTestLink = function()
1016 var links = visibleTests();
1017 return links[TestNavigator.currentTestIndex];
1020 TestNavigator._currentTestExpandLink = function()
1022 return TestNavigator._currentTestLink().querySelector('.expand-button-text');
1025 TestNavigator._expandCurrentTest = function()
1027 expandExpectations(TestNavigator._currentTestExpandLink());
1030 TestNavigator._collapseCurrentTest = function()
1032 collapseExpectations(TestNavigator._currentTestExpandLink());
1035 TestNavigator._toggleCurrentTest = function()
1037 toggleExpectations(TestNavigator._currentTestExpandLink());
1040 TestNavigator._toggleCurrentTestFlagged = function()
1042 var testLink = TestNavigator._currentTestLink();
1043 TestNavigator.flagTest(testLink, !testLink.classList.contains('flagged'));
1046 // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something.
1047 TestNavigator.flagTest = function(testTbody, shouldFlag)
1049 var testName = testTbody.querySelector('.test-link').innerText;
1052 testTbody.classList.add('flagged');
1053 TestNavigator.flaggedTests[testName] = 1;
1055 testTbody.classList.remove('flagged');
1056 delete TestNavigator.flaggedTests[testName];
1059 TestNavigator.updateFlaggedTests();
1062 TestNavigator._createFlaggedTestContainer = function()
1064 var flaggedTestContainer = document.createElement('div');
1065 flaggedTestContainer.id = 'flagged-test-container';
1066 flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2>' +
1067 '<label title="Use newlines instead of spaces to separate flagged tests">' +
1068 '<input id="use-newlines" type=checkbox checked onchange="handleToggleUseNewlines()">Use newlines</input>' +
1070 '<pre id="flagged-tests" contentEditable></pre>';
1071 document.body.appendChild(flaggedTestContainer);
1074 TestNavigator.updateFlaggedTests = function()
1076 var flaggedTestTextbox = document.getElementById('flagged-tests');
1077 var flaggedTests = Object.keys(this.flaggedTests);
1078 flaggedTests.sort();
1079 var separator = document.getElementById('use-newlines').checked ? '\n' : ' ';
1080 flaggedTestTextbox.innerHTML = flaggedTests.join(separator);
1081 document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none';
1084 TestNavigator._setCurrentTest = function(testIndex)
1086 var links = visibleTests();
1087 if (testIndex < 0 || testIndex >= links.length)
1090 var currentTest = links[TestNavigator.currentTestIndex];
1092 currentTest.classList.remove('current');
1094 TestNavigator.currentTestIndex = testIndex;
1096 currentTest = links[TestNavigator.currentTestIndex];
1097 currentTest.classList.add('current');
1102 TestNavigator._scrollToCurrentTest = function()
1104 var targetLink = TestNavigator._currentTestLink();
1108 var rowRect = targetLink.getBoundingClientRect();
1109 var container = document.querySelector('.content-container');
1110 // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position.
1111 container.scrollTop += rowRect.top - 20;
1114 TestNavigator.onlyShowUnexpectedFailuresChanged = function()
1116 var currentTest = document.querySelector('.current');
1120 // If our currentTest became hidden, reset the currentTestIndex.
1121 if (onlyShowUnexpectedFailures() && currentTest.classList.contains('expected'))
1122 TestNavigator._scrollToFirstTest();
1124 // Recompute TestNavigator.currentTestIndex
1125 var links = visibleTests();
1126 TestNavigator.currentTestIndex = links.indexOf(currentTest);
1127 window.console.log('TestNavigator.currentTestIndex is ', TestNavigator.currentTestIndex)
1131 document.addEventListener('keypress', TestNavigator.handleKeyEvent, false);
1134 function onlyShowUnexpectedFailures()
1136 return !document.getElementById('show-expected-failures').checked;
1139 function handleStderrChange()
1141 OptionWriter.save();
1142 document.getElementById('stderr-style').textContent = document.getElementById('show-stderr').checked ?
1143 '' : '#stderr-table { display: none; }';
1146 function handleUnexpectedPassesChange()
1148 OptionWriter.save();
1149 document.getElementById('unexpected-pass-style').textContent = document.getElementById('show-unexpected-passes').checked ?
1150 '' : '#passes-table { display: none; }';
1153 function handleFlakyFailuresChange()
1155 OptionWriter.save();
1156 document.getElementById('flaky-failures-style').textContent = document.getElementById('show-flaky-failures').checked ?
1157 '' : '.flaky { display: none; }';
1160 function handleUnexpectedResultsChange()
1162 OptionWriter.save();
1163 updateExpectedFailures();
1166 function updateExpectedFailures()
1168 document.getElementById('unexpected-style').textContent = onlyShowUnexpectedFailures() ?
1169 '.expected { display: none; }' : '';
1171 updateTestlistCounts();
1172 TestNavigator.onlyShowUnexpectedFailuresChanged();
1175 var OptionWriter = {};
1177 OptionWriter._key = 'run-webkit-tests-options';
1179 OptionWriter.save = function()
1181 var options = document.querySelectorAll('label input');
1183 for (var i = 0, len = options.length; i < len; i++) {
1184 var option = options[i];
1185 data[option.id] = option.checked;
1187 localStorage.setItem(OptionWriter._key, JSON.stringify(data));
1190 OptionWriter.apply = function()
1192 var json = localStorage.getItem(OptionWriter._key);
1198 var data = JSON.parse(json);
1199 for (var id in data) {
1200 var input = document.getElementById(id);
1202 input.checked = data[id];
1207 function updateAllOptions()
1209 forEach(document.querySelectorAll('input'), function(input) { input.onchange(); });
1212 function handleToggleUseNewlines()
1214 OptionWriter.save();
1215 TestNavigator.updateFlaggedTests();
1218 function handleToggleImagesChange()
1220 OptionWriter.save();
1221 updateTogglingImages();
1224 function updateTogglingImages()
1226 var shouldToggle = document.getElementById('toggle-images').checked;
1227 globalState().shouldToggleImages = shouldToggle;
1230 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), convertToTogglingHandler(function(prefix) {
1231 return resultLink(prefix, '-diffs.html', 'images');
1233 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), convertToTogglingHandler(togglingImage));
1235 forEach(document.querySelectorAll('a[href$="-diffs.html"]'), convertToNonTogglingHandler(resultLink));
1236 forEach(document.querySelectorAll('.animatedImage'), convertToNonTogglingHandler(function (absolutePrefix, suffix) {
1237 return resultIframe(absolutePrefix + suffix);
1241 updateImageTogglingTimer();
1244 function getResultContainer(node)
1246 return (node.tagName == 'IMG') ? parentOfType(node, '.result-container') : node;
1249 function convertToTogglingHandler(togglingImageFunction)
1251 return function(node) {
1252 var url = (node.tagName == 'IMG') ? node.src : node.href;
1253 if (url.match('-expected.png$'))
1254 remove(getResultContainer(node));
1255 else if (url.match('-actual.png$')) {
1256 var name = parentOfType(node, 'tbody').querySelector('.test-link').textContent;
1257 getResultContainer(node).outerHTML = togglingImageFunction(stripExtension(name));
1262 function convertToNonTogglingHandler(resultFunction)
1264 return function(node) {
1265 var prefix = node.getAttribute('data-prefix');
1266 getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual');
1270 function failingTestsTable(tests, title, id)
1275 var numberofUnexpectedFailures = 0;
1276 var tableRowHtml = '';
1277 for (var i = 0; i < tests.length; i++){
1278 tableRowHtml += tableRow(tests[i]);
1279 if (tests[i].is_unexpected)
1280 numberofUnexpectedFailures++;
1285 className += id.split('-')[0];
1286 if (!hasUnexpected(tests))
1287 className += ' expected';
1289 var header = '<div';
1291 header += ' class="' + className + '"';
1293 header += '>' + testListHeaderHtml(title) +
1294 '<table id="' + id + '"><thead><tr>' +
1296 '<th id="text-results-header">results</th>' +
1297 '<th id="image-results-header">image results</th>' +
1299 '<th>expected</th>';
1301 if (id == 'flaky-tests-table')
1302 header += '<th>failures</th>';
1304 header += '</tr></thead>';
1307 return header + tableRowHtml + '</table></div>';
1311 function generatePage()
1313 forEachTest(processGlobalStateFor);
1315 var html = '<div class=content-container><div id=toolbar>' +
1316 '<div class="note">Use the i, j, k and l keys to navigate, e, c to expand and collapse, and f to flag</div>' +
1317 '<a href="javascript:void()" onclick="expandAllExpectations()">expand all</a> ' +
1318 '<a href="javascript:void()" onclick="collapseAllExpectations()">collapse all</a> ' +
1319 '<label><input id="toggle-images" type=checkbox checked onchange="handleToggleImagesChange()">Toggle images</label>' +
1320 '<div id=container>Show: '+
1321 '<label><input id="show-expected-failures" type=checkbox onchange="handleUnexpectedResultsChange()">expected failures</label>' +
1322 '<label><input id="show-flaky-failures" type=checkbox onchange="handleFlakyFailuresChange()">flaky failures</label>' +
1323 '<label><input id="show-unexpected-passes" type=checkbox onchange="handleUnexpectedPassesChange()">unexpected passes</label>' +
1324 '<label><input id="show-stderr" type=checkbox onchange="handleStderrChange()">stderr</label>' +
1327 if (globalState().results.interrupted)
1328 html += "<p class='stopped-running-early-message'>Testing exited early.</p>"
1330 if (globalState().crashTests.length)
1331 html += testList(globalState().crashTests, 'Tests that crashed', 'crash-tests-table');
1333 html += failingTestsTable(globalState().failingTests,
1334 'Tests that failed text/pixel/audio diff', 'results-table');
1336 html += failingTestsTable(globalState().missingResults,
1337 'Tests that had no expected results (probably new)', 'missing-table');
1339 if (globalState().timeoutTests.length)
1340 html += testList(globalState().timeoutTests, 'Tests that timed out', 'timeout-tests-table');
1342 if (globalState().testsWithStderr.length)
1343 html += testList(globalState().testsWithStderr, 'Tests that had stderr output', 'stderr-table');
1345 html += failingTestsTable(globalState().flakyPassTests,
1346 'Flaky tests (failed the first run and passed on retry)', 'flaky-tests-table');
1348 if (globalState().unexpectedPassTests.length)
1349 html += testList(globalState().unexpectedPassTests, 'Tests expected to fail but passed', 'passes-table');
1351 if (globalState().hasHttpTests) {
1352 html += '<p>httpd access log: <a href="access_log.txt">access_log.txt</a></p>' +
1353 '<p>httpd error log: <a href="error_log.txt">error_log.txt</a></p>';
1358 document.body.innerHTML = html;
1360 if (document.getElementById('results-table')) {
1361 document.getElementById('results-table').addEventListener('click', TableSorter.handleClick, false);
1362 TableSorter.sortColumn(0);
1363 if (!globalState().hasTextFailures)
1364 document.getElementById('text-results-header').textContent = '';
1365 if (!globalState().hasImageFailures) {
1366 document.getElementById('image-results-header').textContent = '';
1367 parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none';
1371 updateTestlistCounts();
1373 TestNavigator.reset();
1374 OptionWriter.apply();
1377 <!-- HACK: when json_results_test.js is included, loading this page runs the tests.
1378 It is not copied to the layout-test-results output directory. -->
1379 <script src="resources/results-test.js"></script>
1380 <body onload="generatePage()"></body>