| <!DOCTYPE html><!-- webkit-test-runner [ experimental:WebAnimationsCSSIntegrationEnabled=false ] --> |
| <style> |
| body { |
| margin: 0; |
| font-family: Helvetica, sans-serif; |
| font-size: 11pt; |
| } |
| |
| body > * { |
| margin-left: 4px; |
| margin-top: 4px; |
| } |
| |
| h1 { |
| font-size: 14pt; |
| margin-top: 1.5em; |
| } |
| |
| p { |
| margin-bottom: 0.3em; |
| } |
| |
| tr:not(.results-row) td { |
| white-space: nowrap; |
| } |
| |
| tr:not(.results-row) td:first-of-type { |
| white-space: normal; |
| } |
| |
| td:not(:first-of-type) { |
| text-transform: lowercase; |
| } |
| |
| td { |
| padding: 1px 4px; |
| } |
| |
| th:empty, td:empty { |
| padding: 0; |
| } |
| |
| th { |
| -webkit-user-select: none; |
| -moz-user-select: none; |
| } |
| |
| .content-container { |
| min-height: 0; |
| } |
| |
| .note { |
| color: gray; |
| font-size: smaller; |
| } |
| |
| .results-row { |
| background-color: white; |
| } |
| |
| .results-row iframe, .results-row img { |
| width: 800px; |
| height: 600px; |
| } |
| |
| .results-row[data-expanded="false"] { |
| display: none; |
| } |
| |
| #toolbar { |
| position: fixed; |
| top: 2px; |
| right: 2px; |
| text-align: right; |
| } |
| |
| .floating-panel { |
| padding: 4px; |
| background-color: rgba(255, 255, 255, 0.9); |
| border: 1px solid silver; |
| border-radius: 4px; |
| } |
| |
| .expand-button { |
| background-color: white; |
| width: 11px; |
| height: 12px; |
| border: 1px solid gray; |
| display: inline-block; |
| margin: 0 3px 0 0; |
| position: relative; |
| cursor: default; |
| } |
| |
| .current { |
| color: red; |
| } |
| |
| .current .expand-button { |
| border-color: red; |
| } |
| |
| .expand-button-text { |
| position: absolute; |
| top: -0.3em; |
| left: 1px; |
| } |
| |
| tbody .flag { |
| display: none; |
| } |
| |
| tbody.flagged .flag { |
| display: inline; |
| } |
| |
| .stopped-running-early-message { |
| border: 3px solid #d00; |
| font-weight: bold; |
| display: inline-block; |
| padding: 3px; |
| } |
| |
| .result-container { |
| display: inline-block; |
| border: 1px solid gray; |
| margin: 4px; |
| } |
| |
| .result-container iframe, .result-container img { |
| border: 0; |
| vertical-align: top; |
| } |
| |
| #options { |
| background-color: white; |
| } |
| |
| #options-menu { |
| border: 1px solid; |
| margin-top: 1px; |
| padding: 2px 4px; |
| box-shadow: 2px 2px 2px #888; |
| -webkit-transition: opacity .2s; |
| text-align: left; |
| position: absolute; |
| right: 4px; |
| background-color: white; |
| } |
| |
| #options-menu label { |
| display: block; |
| } |
| |
| .hidden-menu { |
| pointer-events: none; |
| opacity: 0; |
| } |
| |
| .label { |
| padding-left: 3px; |
| font-weight: bold; |
| font-size: small; |
| background-color: silver; |
| } |
| |
| .pixel-zoom-container { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 96%; |
| margin: 10px; |
| padding: 10px; |
| display: -webkit-box; |
| display: -moz-box; |
| pointer-events: none; |
| background-color: silver; |
| border-radius: 20px; |
| border: 1px solid gray; |
| box-shadow: 0 0 5px rgba(0, 0, 0, 0.75); |
| } |
| |
| .pixel-zoom-container > * { |
| -webkit-box-flex: 1; |
| -moz-box-flex: 1; |
| border: 1px solid black; |
| margin: 4px; |
| overflow: hidden; |
| background-color: white; |
| } |
| |
| .pixel-zoom-container .scaled-image-container { |
| position: relative; |
| overflow: hidden; |
| width: 100%; |
| height: 400px; |
| } |
| |
| .scaled-image-container > img { |
| position: absolute; |
| top: 0; |
| left: 0; |
| image-rendering: -webkit-optimize-contrast; |
| } |
| |
| #flagged-test-container { |
| position: fixed; |
| bottom: 4px; |
| right: 4px; |
| width: 50%; |
| min-width: 400px; |
| background-color: rgba(255, 255, 255, 0.75); |
| } |
| |
| #flagged-test-container > h2 { |
| margin: 0 0 4px 0; |
| } |
| |
| #flagged-tests { |
| padding: 0 5px; |
| margin: 0; |
| height: 7em; |
| overflow-y: scroll; |
| } |
| </style> |
| <style id="unexpected-style"></style> |
| |
| <script> |
| if (window.testRunner) |
| testRunner.dumpAsText(); |
| |
| var g_state; |
| function globalState() |
| { |
| if (!g_state) { |
| g_state = { |
| crashTests: [], |
| crashOther: [], |
| flakyPassTests: [], |
| hasHttpTests: false, |
| hasImageFailures: false, |
| hasTextFailures: false, |
| missingResults: [], |
| results: {}, |
| shouldToggleImages: true, |
| failingTests: [], |
| testsWithStderr: [], |
| timeoutTests: [], |
| unexpectedPassTests: [] |
| } |
| } |
| return g_state; |
| } |
| |
| function ADD_RESULTS(input) |
| { |
| globalState().results = input; |
| } |
| </script> |
| |
| <script src="full_results.json"></script> |
| |
| <script> |
| function splitExtension(test) |
| { |
| var index = test.lastIndexOf('.'); |
| if (index == -1) { |
| return [test, ""]; |
| } |
| return [test.substring(0, index), test.substring(index + 1)]; |
| } |
| |
| function stripExtension(test) |
| { |
| // Temporary fix, also in Tools/Scripts/webkitpy/layout_tests/constrollers/test_result_writer.py, line 95. |
| // FIXME: Refactor to avoid confusing reference to both test and process names. |
| if (splitExtension(test)[1].length > 5) |
| return test; |
| return splitExtension(test)[0]; |
| } |
| |
| function matchesSelector(node, selector) |
| { |
| if (node.matches) |
| return node.matches(selector); |
| |
| if (node.webkitMatchesSelector) |
| return node.webkitMatchesSelector(selector); |
| |
| if (node.mozMatchesSelector) |
| return node.mozMatchesSelector(selector); |
| } |
| |
| function parentOfType(node, selector) |
| { |
| while (node = node.parentNode) { |
| if (matchesSelector(node, selector)) |
| return node; |
| } |
| return null; |
| } |
| |
| function remove(node) |
| { |
| node.parentNode.removeChild(node); |
| } |
| |
| function forEach(nodeList, handler) |
| { |
| Array.prototype.forEach.call(nodeList, handler); |
| } |
| |
| function resultIframe(src) |
| { |
| // FIXME: use audio tags for AUDIO tests? |
| var layoutTestsIndex = src.indexOf('LayoutTests'); |
| var name; |
| if (layoutTestsIndex != -1) { |
| var hasTrac = src.indexOf('trac.webkit.org') != -1; |
| var prefix = hasTrac ? 'trac.webkit.org/.../' : ''; |
| name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length); |
| } else { |
| var lastDashIndex = src.lastIndexOf('-pretty'); |
| if (lastDashIndex == -1) |
| lastDashIndex = src.lastIndexOf('-'); |
| name = src.substring(lastDashIndex + 1); |
| } |
| |
| var tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img'; |
| |
| if (tagName != 'img') |
| src += '?format=txt'; |
| return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="' + src + '"></' + tagName + '></div>'; |
| } |
| |
| function togglingImage(prefix) |
| { |
| return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' + |
| prefix + '"></img></div>'; |
| } |
| |
| function toggleExpectations(element) |
| { |
| var expandLink = element; |
| if (expandLink.className != 'expand-button-text') |
| expandLink = expandLink.querySelector('.expand-button-text'); |
| |
| if (expandLink.textContent == '+') |
| expandExpectations(expandLink); |
| else |
| collapseExpectations(expandLink); |
| } |
| |
| function collapseExpectations(expandLink) |
| { |
| expandLink.textContent = '+'; |
| var existingResultsRow = parentOfType(expandLink, 'tbody').querySelector('.results-row'); |
| if (existingResultsRow) |
| updateExpandedState(existingResultsRow, false); |
| } |
| |
| function updateExpandedState(row, isExpanded) |
| { |
| row.setAttribute('data-expanded', isExpanded); |
| updateImageTogglingTimer(); |
| } |
| |
| function appendHTML(node, html) |
| { |
| if (node.insertAdjacentHTML) |
| node.insertAdjacentHTML('beforeEnd', html); |
| else |
| node.innerHTML += html; |
| } |
| |
| function expandExpectations(expandLink) |
| { |
| var row = parentOfType(expandLink, 'tr'); |
| var parentTbody = row.parentNode; |
| var existingResultsRow = parentTbody.querySelector('.results-row'); |
| |
| var enDash = '\u2013'; |
| expandLink.textContent = enDash; |
| if (existingResultsRow) { |
| updateExpandedState(existingResultsRow, true); |
| return; |
| } |
| |
| var newRow = document.createElement('tr'); |
| newRow.className = 'results-row'; |
| var newCell = document.createElement('td'); |
| newCell.colSpan = row.querySelectorAll('td').length; |
| |
| var resultLinks = row.querySelectorAll('.result-link'); |
| var hasTogglingImages = false; |
| for (var i = 0; i < resultLinks.length; i++) { |
| var link = resultLinks[i]; |
| var result; |
| if (link.textContent == 'images') { |
| hasTogglingImages = true; |
| result = togglingImage(link.getAttribute('data-prefix')); |
| } else |
| result = resultIframe(link.href); |
| |
| appendHTML(newCell, result); |
| } |
| |
| newRow.appendChild(newCell); |
| parentTbody.appendChild(newRow); |
| |
| updateExpandedState(newRow, true); |
| |
| updateImageTogglingTimer(); |
| } |
| |
| function updateImageTogglingTimer() |
| { |
| var hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage'); |
| if (!hasVisibleAnimatedImage) { |
| clearInterval(globalState().togglingImageInterval); |
| globalState().togglingImageInterval = null; |
| return; |
| } |
| |
| if (!globalState().togglingImageInterval) { |
| toggleImages(); |
| globalState().togglingImageInterval = setInterval(toggleImages, 2000); |
| } |
| } |
| |
| function async(func, args) |
| { |
| setTimeout(function() { func.apply(null, args); }, 100); |
| } |
| |
| function visibleTests(opt_container) |
| { |
| var container = opt_container || document; |
| if (onlyShowUnexpectedFailures()) |
| return container.querySelectorAll('tbody:not(.expected)'); |
| else |
| return container.querySelectorAll('tbody'); |
| } |
| |
| function visibleExpandLinks() |
| { |
| if (onlyShowUnexpectedFailures()) |
| return document.querySelectorAll('tbody:not(.expected) .expand-button-text'); |
| else |
| return document.querySelectorAll('.expand-button-text'); |
| } |
| |
| function expandAllExpectations() |
| { |
| var expandLinks = visibleExpandLinks(); |
| for (var i = 0, len = expandLinks.length; i < len; i++) |
| async(expandExpectations, [expandLinks[i]]); |
| } |
| |
| function collapseAllExpectations() |
| { |
| var expandLinks = visibleExpandLinks(); |
| for (var i = 0, len = expandLinks.length; i < len; i++) |
| async(collapseExpectations, [expandLinks[i]]); |
| } |
| |
| function shouldUseTracLinks() |
| { |
| return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0; |
| } |
| |
| function layoutTestsBasePath() |
| { |
| var basePath; |
| if (shouldUseTracLinks()) { |
| var revision = globalState().results.revision; |
| basePath = 'http://trac.webkit.org'; |
| basePath += revision ? ('/export/' + revision) : '/browser'; |
| basePath += '/trunk/LayoutTests/'; |
| } else |
| basePath = globalState().results.layout_tests_dir + '/'; |
| return basePath; |
| } |
| |
| var mappings = { |
| "http/tests/ssl/": "https://127.0.0.1:8443/ssl/", |
| "http/tests/": "http://127.0.0.1:8000/", |
| "http/wpt/": "http://localhost:8800/WebKit/", |
| "imported/w3c/web-platform-tests/": "http://localhost:8800/" |
| } |
| |
| function testToURL(test, layoutTestsPath) |
| { |
| for (let key in mappings) { |
| if (test.startsWith(key)) |
| return mappings[key] + test.substring(key.length); |
| |
| } |
| return "file://" + layoutTestsPath + "/" + test |
| } |
| |
| function layoutTestURL(test) |
| { |
| if (shouldUseTracLinks()) |
| return layoutTestsBasePath() + test; |
| return testToURL(test, layoutTestsBasePath()); |
| } |
| |
| function checkServerIsRunning(event) |
| { |
| if (shouldUseTracLinks()) |
| return; |
| |
| var url = event.target.href; |
| if (url.startsWith("file://")) |
| return; |
| |
| event.preventDefault(); |
| fetch(url, {mode: "no-cors"}).then(() => { |
| window.location = url; |
| }, () => { |
| alert("HTTP server does not seem to be running, please use the run-webkit-httpd script"); |
| }); |
| } |
| |
| function testLink(test) |
| { |
| return '<a class=test-link onclick="checkServerIsRunning(event)" href="' + layoutTestURL(test) + '">' + test + '</a><span class=flag onclick="unflag(this)"> \u2691</span>'; |
| } |
| |
| function unflag(flag) |
| { |
| var shouldFlag = false; |
| TestNavigator.flagTest(parentOfType(flag, 'tbody'), shouldFlag); |
| } |
| |
| function testLinkWithExpandButton(test) |
| { |
| return '<span class=expand-button onclick="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testLink(test); |
| } |
| |
| function testWithExpandButton(test) |
| { |
| return '<span class=expand-button onclick="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + test; |
| } |
| |
| function resultLink(testPrefix, suffix, contents) |
| { |
| return '<a class=result-link href="' + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> '; |
| } |
| |
| function isFailureExpected(expected, actual) |
| { |
| var isExpected = true; |
| if (actual != 'SKIP') { |
| var expectedArray = expected.split(' '); |
| var actualArray = actual.split(' '); |
| for (var i = 0; i < actualArray.length; i++) { |
| var actualValue = actualArray[i]; |
| if (expectedArray.indexOf(actualValue) == -1 && |
| (expectedArray.indexOf('FAIL') == -1 || |
| (actualValue != 'TEXT' && actualValue != 'IMAGE+TEXT' && actualValue != 'AUDIO'))) |
| isExpected = false; |
| } |
| } |
| return isExpected; |
| } |
| |
| function processGlobalStateFor(testObject) |
| { |
| var test = testObject.name; |
| if (testObject.has_stderr) |
| globalState().testsWithStderr.push(testObject); |
| |
| globalState().hasHttpTests = globalState().hasHttpTests || test.indexOf('http/') == 0; |
| |
| var actual = testObject.actual; |
| var expected = testObject.expected || 'PASS'; |
| if (globalState().results.uses_expectations_file) |
| testObject.isExpected = isFailureExpected(expected, actual); |
| |
| if (actual == 'MISSING') { |
| // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for |
| // tests with MISSING results. |
| globalState().missingResults.push(testObject); |
| return; |
| } |
| |
| var actualTokens = actual.split(' '); |
| var passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE'; |
| if (actualTokens[1] && actual.indexOf('PASS') != -1 || (!globalState().results.pixel_tests_enabled && passedWithImageOnlyFailureInRetry)) { |
| globalState().flakyPassTests.push(testObject); |
| return; |
| } |
| |
| if (actual == 'PASS' && expected != 'PASS') { |
| if (expected != 'IMAGE' || (globalState().results.pixel_tests_enabled || testObject.reftest_type)) { |
| globalState().unexpectedPassTests.push(testObject); |
| } |
| return; |
| } |
| |
| if (actual == 'CRASH') { |
| globalState().crashTests.push(testObject); |
| return; |
| } |
| |
| if (actual == 'TIMEOUT') { |
| globalState().timeoutTests.push(testObject); |
| return; |
| } |
| |
| globalState().failingTests.push(testObject); |
| } |
| |
| function toggleImages() |
| { |
| var images = document.querySelectorAll('.animatedImage'); |
| var imageTexts = document.querySelectorAll('.imageText'); |
| for (var i = 0, len = images.length; i < len; i++) { |
| var image = images[i]; |
| var text = imageTexts[i]; |
| if (text.textContent == 'Expected Image') { |
| text.textContent = 'Actual Image'; |
| image.src = image.getAttribute('data-prefix') + '-actual.png'; |
| } else { |
| text.textContent = 'Expected Image'; |
| image.src = image.getAttribute('data-prefix') + '-expected.png'; |
| } |
| } |
| } |
| |
| function textResultLinks(prefix) |
| { |
| var html = resultLink(prefix, '-expected.txt', 'expected') + |
| resultLink(prefix, '-actual.txt', 'actual') + |
| resultLink(prefix, '-diff.txt', 'diff'); |
| |
| if (globalState().results.has_pretty_patch) |
| html += resultLink(prefix, '-pretty-diff.html', 'pretty diff'); |
| |
| if (globalState().results.has_wdiff) |
| html += resultLink(prefix, '-wdiff.html', 'wdiff'); |
| |
| return html; |
| } |
| |
| function imageResultsCell(testObject, testPrefix, actual) { |
| var row = ''; |
| |
| if (actual.indexOf('IMAGE') != -1) { |
| var testExtension = splitExtension(testObject.name)[1]; |
| globalState().hasImageFailures = true; |
| |
| if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1) { |
| row += resultLink(layoutTestsBasePath() + testPrefix, '-expected-mismatch.' + testExtension, 'ref mismatch'); |
| row += resultLink(testPrefix, '-actual.png', 'actual'); |
| } else { |
| if (testObject.reftest_type && testObject.reftest_type.indexOf('==') != -1) { |
| row += resultLink(layoutTestsBasePath() + testPrefix, '-expected.' + testExtension, 'reference'); |
| } |
| if (globalState().shouldToggleImages) { |
| row += resultLink(testPrefix, '-diffs.html', 'images'); |
| } else { |
| row += resultLink(testPrefix, '-expected.png', 'expected'); |
| row += resultLink(testPrefix, '-actual.png', 'actual'); |
| } |
| |
| var diff = testObject.image_diff_percent; |
| row += resultLink(testPrefix, '-diff.png', 'diff (' + diff + '%)'); |
| } |
| } |
| |
| if (actual.indexOf('MISSING') != -1 && testObject.is_missing_image) |
| row += resultLink(testPrefix, '-actual.png', 'png result'); |
| |
| return row; |
| } |
| |
| function flakinessDashboardURLForTests(testObjects) |
| { |
| var testList = ""; |
| for (var i = 0; i < testObjects.length; ++i) { |
| testList += testObjects[i].name; |
| |
| if (i != testObjects.length - 1) |
| testList += ","; |
| } |
| |
| return 'http://webkit-test-results.webkit.org/dashboards/flakiness_dashboard.html#showAllRuns=true&tests=' + encodeURIComponent(testList); |
| } |
| |
| function tableRow(testObject) |
| { |
| var row = '<tbody' |
| if (globalState().results.uses_expectations_file) |
| row += ' class="' + (testObject.isExpected ? 'expected' : '') + '"'; |
| if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1) |
| row += ' mismatchreftest=true'; |
| row += '><tr>'; |
| |
| row += '<td>' + testLinkWithExpandButton(testObject.name) + '</td>'; |
| |
| var testPrefix = stripExtension(testObject.name); |
| row += '<td>'; |
| |
| var actual = testObject.actual; |
| if (actual.indexOf('TEXT') != -1) { |
| globalState().hasTextFailures = true; |
| row += textResultLinks(testPrefix); |
| } |
| |
| if (actual.indexOf('AUDIO') != -1) { |
| row += resultLink(testPrefix, '-expected.wav', 'expected audio'); |
| row += resultLink(testPrefix, '-actual.wav', 'actual audio'); |
| row += resultLink(testPrefix, '-diff.txt', 'textual diff'); |
| } |
| |
| if (actual.indexOf('MISSING') != -1) { |
| if (testObject.is_missing_audio) |
| row += resultLink(testPrefix, '-actual.wav', 'audio result'); |
| if (testObject.is_missing_text) |
| row += resultLink(testPrefix, '-actual.txt', 'result'); |
| } |
| |
| var actualTokens = actual.split(/\s+/); |
| var cell = imageResultsCell(testObject, testPrefix, actualTokens[0]); |
| if (!cell && actualTokens.length > 1) |
| cell = imageResultsCell(testObject, 'retries/' + testPrefix, actualTokens[1]); |
| |
| row += '</td><td>' + cell + '</td>'; |
| |
| if (globalState().results.uses_expectations_file || actual.indexOf(' ') != -1) |
| row += '<td>' + actual + '</td>'; |
| |
| if (globalState().results.uses_expectations_file) |
| row += '<td>' + (actual.indexOf('MISSING') == -1 ? testObject.expected : '') + '</td>'; |
| |
| row += '<td><a href="' + flakinessDashboardURLForTests([testObject]) + '">history</a></td>'; |
| |
| row += '</tr></tbody>'; |
| return row; |
| } |
| |
| function forEachTest(handler, opt_tree, opt_prefix) |
| { |
| var tree = opt_tree || globalState().results.tests; |
| var prefix = opt_prefix || ''; |
| |
| for (var key in tree) { |
| var newPrefix = prefix ? (prefix + '/' + key) : key; |
| if ('actual' in tree[key]) { |
| var testObject = tree[key]; |
| testObject.name = newPrefix; |
| handler(testObject); |
| } else |
| forEachTest(handler, tree[key], newPrefix); |
| } |
| } |
| |
| function forOtherCrashes() |
| { |
| var tree = globalState().results.other_crashes; |
| for (var key in tree) { |
| var testObject = tree[key]; |
| testObject.name = key; |
| globalState().crashOther.push(testObject); |
| } |
| } |
| |
| function hasUnexpected(tests) |
| { |
| return tests.some(function (test) { return !test.isExpected; }); |
| } |
| |
| function updateTestlistCounts() |
| { |
| forEach(document.querySelectorAll('.test-list-count'), function(count) { |
| var container = parentOfType(count, 'div'); |
| var testContainers; |
| if (onlyShowUnexpectedFailures()) |
| testContainers = container.querySelectorAll('tbody:not(.expected)'); |
| else |
| testContainers = container.querySelectorAll('tbody'); |
| |
| count.textContent = testContainers.length; |
| }) |
| } |
| |
| function flagAll(headerLink) |
| { |
| var tests = visibleTests(parentOfType(headerLink, 'div')); |
| forEach(tests, function(tests) { |
| var shouldFlag = true; |
| TestNavigator.flagTest(tests, shouldFlag); |
| }) |
| } |
| |
| function testListHeaderHtml(header) |
| { |
| return '<h1>' + header + ' (<span class=test-list-count></span>): <a href="#" class=flag-all onclick="flagAll(this)">flag all</a></h1>'; |
| } |
| |
| function testList(tests, header, tableId) |
| { |
| tests.sort(function (a, b) { return a.name.localeCompare(b.name) }); |
| |
| var html = '<div' + ((!hasUnexpected(tests) && tableId != 'stderr-table') ? ' class=expected' : '') + ' id=' + tableId + '>' + |
| testListHeaderHtml(header) + '<table>'; |
| |
| // FIXME: add the expected failure column for all the test lists if globalState().results.uses_expectations_file |
| if (tableId == 'passes-table') |
| html += '<thead><th>test</th><th>expected failure</th></thead>'; |
| |
| for (var i = 0; i < tests.length; i++) { |
| var testObject = tests[i]; |
| var test = testObject.name; |
| html += '<tbody'; |
| if (globalState().results.uses_expectations_file) |
| html += ' class="' + ((testObject.isExpected && tableId != 'stderr-table') ? 'expected' : '') + '"'; |
| html += '><tr><td>'; |
| if (tableId == 'passes-table') |
| html += testLink(test); |
| else if (tableId == 'other-crash-tests-table') |
| html += testWithExpandButton(test); |
| else |
| html += testLinkWithExpandButton(test); |
| |
| html += '</td><td>'; |
| |
| if (tableId == 'stderr-table') |
| html += resultLink(stripExtension(test), '-stderr.txt', 'stderr'); |
| else if (tableId == 'passes-table') |
| html += testObject.expected; |
| else if (tableId == 'other-crash-tests-table') |
| html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log'); |
| else if (tableId == 'crash-tests-table') { |
| html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log'); |
| html += resultLink(stripExtension(test), '-sample.txt', 'sample'); |
| } else if (tableId == 'timeout-tests-table') { |
| // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests. |
| html += textResultLinks(stripExtension(test)); |
| } |
| |
| if (tableId != 'other-crash-tests-table') |
| html += '</td><td><a href="' + flakinessDashboardURLForTests([testObject]) + '">history</a></td>'; |
| |
| html += '</tr></tbody>'; |
| } |
| html += '</table></div>'; |
| return html; |
| } |
| |
| function toArray(nodeList) |
| { |
| return Array.prototype.slice.call(nodeList); |
| } |
| |
| function trim(string) |
| { |
| return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); |
| } |
| |
| // Just a namespace for code management. |
| var TableSorter = {}; |
| |
| TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>'; |
| |
| TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>'; |
| |
| TableSorter._sortedContents = function(header, arrow) |
| { |
| return arrow + ' ' + trim(header.textContent) + ' ' + arrow; |
| } |
| |
| TableSorter._updateHeaderClassNames = function(newHeader) |
| { |
| var sortHeader = document.querySelector('.sortHeader'); |
| if (sortHeader) { |
| if (sortHeader == newHeader) { |
| var isAlreadyReversed = sortHeader.classList.contains('reversed'); |
| if (isAlreadyReversed) |
| sortHeader.classList.remove('reversed'); |
| else |
| sortHeader.classList.add('reversed'); |
| } else { |
| sortHeader.textContent = sortHeader.textContent; |
| sortHeader.classList.remove('sortHeader'); |
| sortHeader.classList.remove('reversed'); |
| } |
| } |
| |
| newHeader.classList.add('sortHeader'); |
| } |
| |
| TableSorter._textContent = function(tbodyRow, column) |
| { |
| return tbodyRow.querySelectorAll('td')[column].textContent; |
| } |
| |
| TableSorter._sortRows = function(newHeader, reversed) |
| { |
| var testsTable = document.getElementById('results-table'); |
| var headers = toArray(testsTable.querySelectorAll('th')); |
| var sortColumn = headers.indexOf(newHeader); |
| |
| var rows = toArray(testsTable.querySelectorAll('tbody')); |
| |
| rows.sort(function(a, b) { |
| // Only need to support lexicographic sort for now. |
| var aText = TableSorter._textContent(a, sortColumn); |
| var bText = TableSorter._textContent(b, sortColumn); |
| |
| // Forward sort equal values by test name. |
| if (sortColumn && aText == bText) { |
| var aTestName = TableSorter._textContent(a, 0); |
| var bTestName = TableSorter._textContent(b, 0); |
| if (aTestName == bTestName) |
| return 0; |
| return aTestName < bTestName ? -1 : 1; |
| } |
| |
| if (reversed) |
| return aText < bText ? 1 : -1; |
| else |
| return aText < bText ? -1 : 1; |
| }); |
| |
| for (var i = 0; i < rows.length; i++) |
| testsTable.appendChild(rows[i]); |
| } |
| |
| TableSorter.sortColumn = function(columnNumber) |
| { |
| var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber]; |
| TableSorter._sort(newHeader); |
| } |
| |
| TableSorter.handleClick = function(e) |
| { |
| var newHeader = e.target; |
| if (newHeader.localName != 'th') |
| return; |
| TableSorter._sort(newHeader); |
| } |
| |
| TableSorter._sort = function(newHeader) |
| { |
| TableSorter._updateHeaderClassNames(newHeader); |
| |
| var reversed = newHeader.classList.contains('reversed'); |
| var sortArrow = reversed ? TableSorter._backwardArrow : TableSorter._forwardArrow; |
| newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow); |
| |
| TableSorter._sortRows(newHeader, reversed); |
| } |
| |
| var PixelZoomer = {}; |
| |
| PixelZoomer.showOnDelay = true; |
| PixelZoomer._zoomFactor = 6; |
| |
| var kResultWidth = 800; |
| var kResultHeight = 600; |
| |
| var kZoomedResultWidth = kResultWidth * PixelZoomer._zoomFactor; |
| var kZoomedResultHeight = kResultHeight * PixelZoomer._zoomFactor; |
| |
| PixelZoomer._zoomImageContainer = function(url) |
| { |
| var container = document.createElement('div'); |
| container.className = 'zoom-image-container'; |
| |
| var title = url.match(/\-([^\-]*)\.png/)[1]; |
| |
| var label = document.createElement('div'); |
| label.className = 'label'; |
| label.appendChild(document.createTextNode(title)); |
| container.appendChild(label); |
| |
| var imageContainer = document.createElement('div'); |
| imageContainer.className = 'scaled-image-container'; |
| |
| var image = new Image(); |
| image.src = url; |
| image.style.width = kZoomedResultWidth + 'px'; |
| image.style.height = kZoomedResultHeight + 'px'; |
| image.style.border = '1px solid black'; |
| imageContainer.appendChild(image); |
| container.appendChild(imageContainer); |
| |
| return container; |
| } |
| |
| PixelZoomer._createContainer = function(e) |
| { |
| var tbody = parentOfType(e.target, 'tbody'); |
| var row = tbody.querySelector('tr'); |
| var imageDiffLinks = row.querySelectorAll('a[href$=".png"]'); |
| |
| var container = document.createElement('div'); |
| container.className = 'pixel-zoom-container'; |
| |
| var html = ''; |
| |
| var togglingImageLink = row.querySelector('a[href$="-diffs.html"]'); |
| if (togglingImageLink) { |
| var prefix = togglingImageLink.getAttribute('data-prefix'); |
| container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-expected.png')); |
| container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-actual.png')); |
| } |
| |
| for (var i = 0; i < imageDiffLinks.length; i++) |
| container.appendChild(PixelZoomer._zoomImageContainer(imageDiffLinks[i].href)); |
| |
| document.body.appendChild(container); |
| PixelZoomer._drawAll(); |
| } |
| |
| PixelZoomer._draw = function(imageContainer) |
| { |
| var image = imageContainer.querySelector('img'); |
| var containerBounds = imageContainer.getBoundingClientRect(); |
| image.style.left = (containerBounds.width / 2 - PixelZoomer._percentX * kZoomedResultWidth) + 'px'; |
| image.style.top = (containerBounds.height / 2 - PixelZoomer._percentY * kZoomedResultHeight) + 'px'; |
| } |
| |
| PixelZoomer._drawAll = function() |
| { |
| forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), PixelZoomer._draw); |
| } |
| |
| PixelZoomer.handleMouseOut = function(e) |
| { |
| if (e.relatedTarget && e.relatedTarget.tagName != 'IFRAME') |
| return; |
| |
| // If e.relatedTarget is null, we've moused out of the document. |
| var container = document.querySelector('.pixel-zoom-container'); |
| if (container) |
| remove(container); |
| } |
| |
| PixelZoomer.handleMouseMove = function(e) { |
| if (PixelZoomer._mouseMoveTimeout) |
| clearTimeout(PixelZoomer._mouseMoveTimeout); |
| |
| if (parentOfType(e.target, '.pixel-zoom-container')) |
| return; |
| |
| var container = document.querySelector('.pixel-zoom-container'); |
| |
| var resultContainer = (e.target.className == 'result-container') ? |
| e.target : parentOfType(e.target, '.result-container'); |
| if (!resultContainer || !resultContainer.querySelector('img')) { |
| if (container) |
| remove(container); |
| return; |
| } |
| |
| var targetLocation = e.target.getBoundingClientRect(); |
| PixelZoomer._percentX = (e.clientX - targetLocation.left) / targetLocation.width; |
| PixelZoomer._percentY = (e.clientY - targetLocation.top) / targetLocation.height; |
| |
| if (!container) { |
| if (PixelZoomer.showOnDelay) { |
| PixelZoomer._mouseMoveTimeout = setTimeout(function() { |
| PixelZoomer._createContainer(e); |
| }, 400); |
| return; |
| } |
| |
| PixelZoomer._createContainer(e); |
| return; |
| } |
| |
| PixelZoomer._drawAll(); |
| } |
| |
| document.addEventListener('mousemove', PixelZoomer.handleMouseMove, false); |
| document.addEventListener('mouseout', PixelZoomer.handleMouseOut, false); |
| |
| var TestNavigator = {}; |
| |
| TestNavigator.reset = function() { |
| TestNavigator.currentTestIndex = -1; |
| TestNavigator.flaggedTests = {}; |
| } |
| |
| TestNavigator.handleKeyEvent = function(event) |
| { |
| if (event.metaKey || event.shiftKey || event.ctrlKey) |
| return; |
| |
| switch (String.fromCharCode(event.charCode)) { |
| case 'i': |
| TestNavigator._scrollToFirstTest(); |
| break; |
| case 'j': |
| TestNavigator._scrollToNextTest(); |
| break; |
| case 'k': |
| TestNavigator._scrollToPreviousTest(); |
| break; |
| case 'l': |
| TestNavigator._scrollToLastTest(); |
| break; |
| case 'e': |
| TestNavigator._expandCurrentTest(); |
| break; |
| case 'c': |
| TestNavigator._collapseCurrentTest(); |
| break; |
| case 't': |
| TestNavigator._toggleCurrentTest(); |
| break; |
| case 'f': |
| TestNavigator._toggleCurrentTestFlagged(); |
| break; |
| } |
| } |
| |
| TestNavigator._scrollToFirstTest = function() |
| { |
| if (TestNavigator._setCurrentTest(0)) |
| TestNavigator._scrollToCurrentTest(); |
| } |
| |
| TestNavigator._scrollToLastTest = function() |
| { |
| var links = visibleTests(); |
| if (TestNavigator._setCurrentTest(links.length - 1)) |
| TestNavigator._scrollToCurrentTest(); |
| } |
| |
| TestNavigator._scrollToNextTest = function() |
| { |
| if (TestNavigator.currentTestIndex == -1) |
| TestNavigator._scrollToFirstTest(); |
| else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex + 1)) |
| TestNavigator._scrollToCurrentTest(); |
| } |
| |
| TestNavigator._scrollToPreviousTest = function() |
| { |
| if (TestNavigator.currentTestIndex == -1) |
| TestNavigator._scrollToLastTest(); |
| else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex - 1)) |
| TestNavigator._scrollToCurrentTest(); |
| } |
| |
| TestNavigator._currentTestLink = function() |
| { |
| var links = visibleTests(); |
| return links[TestNavigator.currentTestIndex]; |
| } |
| |
| TestNavigator._currentTestExpandLink = function() |
| { |
| return TestNavigator._currentTestLink().querySelector('.expand-button-text'); |
| } |
| |
| TestNavigator._expandCurrentTest = function() |
| { |
| expandExpectations(TestNavigator._currentTestExpandLink()); |
| } |
| |
| TestNavigator._collapseCurrentTest = function() |
| { |
| collapseExpectations(TestNavigator._currentTestExpandLink()); |
| } |
| |
| TestNavigator._toggleCurrentTest = function() |
| { |
| toggleExpectations(TestNavigator._currentTestExpandLink()); |
| } |
| |
| TestNavigator._toggleCurrentTestFlagged = function() |
| { |
| var testLink = TestNavigator._currentTestLink(); |
| TestNavigator.flagTest(testLink, !testLink.classList.contains('flagged')); |
| } |
| |
| // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something. |
| TestNavigator.flagTest = function(testTbody, shouldFlag) |
| { |
| var testName = testTbody.querySelector('.test-link').innerText; |
| |
| if (shouldFlag) { |
| testTbody.classList.add('flagged'); |
| TestNavigator.flaggedTests[testName] = 1; |
| } else { |
| testTbody.classList.remove('flagged'); |
| delete TestNavigator.flaggedTests[testName]; |
| } |
| |
| TestNavigator.updateFlaggedTests(); |
| } |
| |
| TestNavigator.updateFlaggedTests = function() |
| { |
| var flaggedTestTextbox = document.getElementById('flagged-tests'); |
| if (!flaggedTestTextbox) { |
| var flaggedTestContainer = document.createElement('div'); |
| flaggedTestContainer.id = 'flagged-test-container'; |
| flaggedTestContainer.className = 'floating-panel'; |
| flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2><pre id="flagged-tests" contentEditable></pre>'; |
| document.body.appendChild(flaggedTestContainer); |
| |
| flaggedTestTextbox = document.getElementById('flagged-tests'); |
| } |
| |
| var flaggedTests = Object.keys(this.flaggedTests); |
| flaggedTests.sort(); |
| var separator = document.getElementById('use-newlines').checked ? '\n' : ' '; |
| flaggedTestTextbox.innerHTML = flaggedTests.join(separator); |
| document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none'; |
| } |
| |
| TestNavigator._setCurrentTest = function(testIndex) |
| { |
| var links = visibleTests(); |
| if (testIndex < 0 || testIndex >= links.length) |
| return false; |
| |
| var currentTest = links[TestNavigator.currentTestIndex]; |
| if (currentTest) |
| currentTest.classList.remove('current'); |
| |
| TestNavigator.currentTestIndex = testIndex; |
| |
| currentTest = links[TestNavigator.currentTestIndex]; |
| currentTest.classList.add('current'); |
| |
| return true; |
| } |
| |
| TestNavigator._scrollToCurrentTest = function() |
| { |
| var targetLink = TestNavigator._currentTestLink(); |
| if (!targetLink) |
| return; |
| |
| var rowRect = targetLink.getBoundingClientRect(); |
| // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position. |
| document.scrollingElement.scrollTop += rowRect.top; |
| } |
| |
| TestNavigator.onlyShowUnexpectedFailuresChanged = function() |
| { |
| var currentTest = document.querySelector('.current'); |
| if (!currentTest) |
| return; |
| |
| // If our currentTest became hidden, reset the currentTestIndex. |
| if (onlyShowUnexpectedFailures() && currentTest.classList.contains('expected')) |
| TestNavigator._scrollToFirstTest(); |
| else { |
| // Recompute TestNavigator.currentTestIndex |
| var links = visibleTests(); |
| TestNavigator.currentTestIndex = links.indexOf(currentTest); |
| } |
| } |
| |
| document.addEventListener('keypress', TestNavigator.handleKeyEvent, false); |
| |
| |
| function onlyShowUnexpectedFailures() |
| { |
| return document.getElementById('unexpected-results').checked; |
| } |
| |
| function handleUnexpectedResultsChange() |
| { |
| OptionWriter.save(); |
| updateExpectedFailures(); |
| } |
| |
| function updateExpectedFailures() |
| { |
| document.getElementById('unexpected-style').textContent = onlyShowUnexpectedFailures() ? |
| '.expected { display: none; }' : ''; |
| |
| updateTestlistCounts(); |
| TestNavigator.onlyShowUnexpectedFailuresChanged(); |
| } |
| |
| var OptionWriter = {}; |
| |
| OptionWriter._key = 'run-webkit-tests-options'; |
| |
| OptionWriter.save = function() |
| { |
| var options = document.querySelectorAll('label input'); |
| var data = {}; |
| for (var i = 0, len = options.length; i < len; i++) { |
| var option = options[i]; |
| data[option.id] = option.checked; |
| } |
| try { |
| localStorage.setItem(OptionWriter._key, JSON.stringify(data)); |
| } catch (err) { |
| if (err.name != "SecurityError") |
| throw err; |
| } |
| } |
| |
| OptionWriter.apply = function() |
| { |
| var json; |
| try { |
| json = localStorage.getItem(OptionWriter._key); |
| } catch (err) { |
| if (err.name != "SecurityError") |
| throw err; |
| } |
| |
| if (!json) { |
| updateAllOptions(); |
| return; |
| } |
| |
| var data = JSON.parse(json); |
| for (var id in data) { |
| var input = document.getElementById(id); |
| if (input) |
| input.checked = data[id]; |
| } |
| updateAllOptions(); |
| } |
| |
| function updateAllOptions() |
| { |
| forEach(document.querySelectorAll('#options-menu input'), function(input) { input.onchange(); }); |
| } |
| |
| function handleToggleUseNewlines() |
| { |
| OptionWriter.save(); |
| TestNavigator.updateFlaggedTests(); |
| } |
| |
| function handleToggleImagesChange() |
| { |
| OptionWriter.save(); |
| updateTogglingImages(); |
| } |
| |
| function updateTogglingImages() |
| { |
| var shouldToggle = document.getElementById('toggle-images').checked; |
| globalState().shouldToggleImages = shouldToggle; |
| |
| if (shouldToggle) { |
| forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), convertToTogglingHandler(function(prefix) { |
| return resultLink(prefix, '-diffs.html', 'images'); |
| })); |
| forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), convertToTogglingHandler(togglingImage)); |
| } else { |
| forEach(document.querySelectorAll('a[href$="-diffs.html"]'), convertToNonTogglingHandler(resultLink)); |
| forEach(document.querySelectorAll('.animatedImage'), convertToNonTogglingHandler(function (absolutePrefix, suffix) { |
| return resultIframe(absolutePrefix + suffix); |
| })); |
| } |
| |
| updateImageTogglingTimer(); |
| } |
| |
| function getResultContainer(node) |
| { |
| return (node.tagName == 'IMG') ? parentOfType(node, '.result-container') : node; |
| } |
| |
| function convertToTogglingHandler(togglingImageFunction) |
| { |
| return function(node) { |
| var url = (node.tagName == 'IMG') ? node.src : node.href; |
| if (url.match('-expected.png$')) |
| remove(getResultContainer(node)); |
| else if (url.match('-actual.png$')) { |
| var name = parentOfType(node, 'tbody').querySelector('.test-link').textContent; |
| getResultContainer(node).outerHTML = togglingImageFunction(stripExtension(name)); |
| } |
| } |
| } |
| |
| function convertToNonTogglingHandler(resultFunction) |
| { |
| return function(node) { |
| var prefix = node.getAttribute('data-prefix'); |
| getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual'); |
| } |
| } |
| |
| function toggleOptionsMenu() |
| { |
| var menu = document.getElementById('options-menu'); |
| menu.className = (menu.className == 'hidden-menu') ? '' : 'hidden-menu'; |
| } |
| |
| function handleMouseDown(e) |
| { |
| if (!parentOfType(e.target, '#options-menu') && e.target.id != 'options-link') |
| document.getElementById('options-menu').className = 'hidden-menu'; |
| } |
| |
| document.addEventListener('mousedown', handleMouseDown, false); |
| |
| function failingTestsTable(tests, title, id) |
| { |
| if (!tests.length) |
| return ''; |
| |
| var numberofUnexpectedFailures = 0; |
| var tableRowHtml = ''; |
| for (var i = 0; i < tests.length; i++){ |
| tableRowHtml += tableRow(tests[i]); |
| if (!tests[i].isExpected) |
| numberofUnexpectedFailures++; |
| } |
| |
| var header = '<div'; |
| if (!hasUnexpected(tests)) |
| header += ' class=expected'; |
| |
| header += '>' + testListHeaderHtml(title) + |
| '<table id="' + id + '"><thead><tr>' + |
| '<th>test</th>' + |
| '<th id="text-results-header">results</th>' + |
| '<th id="image-results-header">image results</th>'; |
| |
| if (globalState().results.uses_expectations_file) |
| header += '<th>actual failure</th><th>expected failure</th>'; |
| |
| header += '<th><a href="' + flakinessDashboardURLForTests(tests) + '">history</a></th>'; |
| |
| if (id == 'flaky-tests-table') |
| header += '<th>failures</th>'; |
| |
| header += '</tr></thead>'; |
| |
| return header + tableRowHtml + '</table></div>'; |
| } |
| |
| function updateTitle() |
| { |
| var dateString = globalState().results.date; |
| |
| var title = document.createElement('title'); |
| title.textContent = 'Layout Test Results from ' + dateString; |
| document.head.appendChild(title); |
| } |
| |
| function generatePage() |
| { |
| updateTitle(); |
| forEachTest(processGlobalStateFor); |
| forOtherCrashes(); |
| |
| var html = ""; |
| |
| if (globalState().results.interrupted) |
| html += "<p class='stopped-running-early-message'>Testing exited early.</p>" |
| |
| if (globalState().crashTests.length) |
| html += testList(globalState().crashTests, 'Tests that crashed', 'crash-tests-table'); |
| |
| if (globalState().crashOther.length) |
| html += testList(globalState().crashOther, 'Other Crashes', 'other-crash-tests-table'); |
| |
| html += failingTestsTable(globalState().failingTests, |
| 'Tests that failed text/pixel/audio diff', 'results-table'); |
| |
| html += failingTestsTable(globalState().missingResults, |
| 'Tests that had no expected results (probably new)', 'missing-table'); |
| |
| if (globalState().timeoutTests.length) |
| html += testList(globalState().timeoutTests, 'Tests that timed out', 'timeout-tests-table'); |
| |
| if (globalState().testsWithStderr.length) |
| html += testList(globalState().testsWithStderr, 'Tests that had stderr output', 'stderr-table'); |
| |
| html += failingTestsTable(globalState().flakyPassTests, |
| 'Flaky tests (failed the first run and passed on retry)', 'flaky-tests-table'); |
| |
| if (globalState().results.uses_expectations_file && globalState().unexpectedPassTests.length) |
| html += testList(globalState().unexpectedPassTests, 'Tests expected to fail but passed', 'passes-table'); |
| |
| if (globalState().hasHttpTests) { |
| html += '<p>httpd access log: <a href="access_log.txt">access_log.txt</a></p>' + |
| '<p>httpd error log: <a href="error_log.txt">error_log.txt</a></p>'; |
| } |
| |
| document.getElementById('main-content').innerHTML = html + '</div>'; |
| |
| if (document.getElementById('results-table')) { |
| document.getElementById('results-table').addEventListener('click', TableSorter.handleClick, false); |
| TableSorter.sortColumn(0); |
| if (!globalState().results.uses_expectations_file) |
| parentOfType(document.getElementById('unexpected-results'), 'label').style.display = 'none'; |
| if (!globalState().hasTextFailures) |
| document.getElementById('text-results-header').textContent = ''; |
| if (!globalState().hasImageFailures) { |
| document.getElementById('image-results-header').textContent = ''; |
| parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none'; |
| } |
| } |
| |
| updateTestlistCounts(); |
| |
| TestNavigator.reset(); |
| OptionWriter.apply(); |
| } |
| </script> |
| <body onload="generatePage()"> |
| |
| <div class="content-container"> |
| <div id="toolbar" class="floating-panel"> |
| <div class="note">Use the i, j, k and l keys to navigate, e, c to expand and collapse, and f to flag</div> |
| <a href="javascript:void()" onclick="expandAllExpectations()">expand all</a> |
| <a href="javascript:void()" onclick="collapseAllExpectations()">collapse all</a> |
| <a href="javascript:void()" id=options-link onclick="toggleOptionsMenu()">options</a> |
| <div id="options-menu" class="hidden-menu"> |
| <label><input id="unexpected-results" type="checkbox" checked onchange="handleUnexpectedResultsChange()">Only unexpected results</label> |
| <label><input id="toggle-images" type="checkbox" checked onchange="handleToggleImagesChange()">Toggle images</label> |
| <label title="Use newlines instead of spaces to separate flagged tests"><input id="use-newlines" type="checkbox" checked onchange="handleToggleUseNewlines()">Use newlines in flagged list</input> |
| </div> |
| </div> |
| |
| <div id="main-content"></div> |
| |
| </body> |