Modified: trunk/LayoutTests/fast/harness/results.html (234992 => 234993)
--- trunk/LayoutTests/fast/harness/results.html 2018-08-17 18:55:39 UTC (rev 234992)
+++ trunk/LayoutTests/fast/harness/results.html 2018-08-17 18:56:45 UTC (rev 234993)
@@ -20,6 +20,12 @@
margin-bottom: 0.3em;
}
+a.clickable {
+ color: blue;
+ cursor: pointer;
+ margin-left: 0.2em;
+}
+
tr:not(.results-row) td {
white-space: nowrap;
}
@@ -32,7 +38,7 @@
text-transform: lowercase;
}
-td {
+th, td {
padding: 1px 4px;
}
@@ -75,7 +81,7 @@
}
.floating-panel {
- padding: 4px;
+ padding: 6px;
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid silver;
border-radius: 4px;
@@ -137,11 +143,12 @@
}
#options-menu {
- border: 1px solid;
+ border: 1px solid gray;
+ border-radius: 4px;
margin-top: 1px;
padding: 2px 4px;
- box-shadow: 2px 2px 2px #888;
- -webkit-transition: opacity .2s;
+ box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.6);
+ transition: opacity .2s;
text-align: left;
position: absolute;
right: 4px;
@@ -229,1301 +236,1674 @@
if (window.testRunner)
testRunner.dumpAsText();
-var g_state;
-function globalState()
+class Utils
{
- if (!g_state) {
- g_state = {
- crashTests: [],
- crashOther: [],
- flakyPassTests: [],
- hasHttpTests: false,
- hasImageFailures: false,
- hasTextFailures: false,
- missingResults: [],
- results: {},
- shouldToggleImages: true,
- failingTests: [],
- testsWithStderr: [],
- timeoutTests: [],
- unexpectedPassTests: []
+ static matchesSelector(node, selector)
+ {
+ if (node.matches)
+ return node.matches(selector);
+
+ if (node.webkitMatchesSelector)
+ return node.webkitMatchesSelector(selector);
+
+ if (node.mozMatchesSelector)
+ return node.mozMatchesSelector(selector);
+ }
+
+ static parentOfType(node, selector)
+ {
+ while (node = node.parentNode) {
+ if (Utils.matchesSelector(node, selector))
+ return node;
}
+ return null;
}
- return g_state;
-}
-function ADD_RESULTS(input)
-{
- globalState().results = input;
-}
-</script>
+ static stripExtension(testName)
+ {
+ // 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 (Utils.splitExtension(testName)[1].length > 5)
+ return testName;
+ return Utils.splitExtension(testName)[0];
+ }
-<script src=""
+ static splitExtension(testName)
+ {
+ let index = testName.lastIndexOf('.');
+ if (index == -1) {
+ return [testName, ''];
+ }
+ return [testName.substring(0, index), testName.substring(index + 1)];
+ }
-<script>
-function splitExtension(test)
-{
- var index = test.lastIndexOf('.');
- if (index == -1) {
- return [test, ""];
+ static forEach(nodeList, handler)
+ {
+ Array.prototype.forEach.call(nodeList, handler);
}
- 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];
-}
+ static toArray(nodeList)
+ {
+ return Array.prototype.slice.call(nodeList);
+ }
-function matchesSelector(node, selector)
-{
- if (node.matches)
- return node.matches(selector);
+ static trim(string)
+ {
+ return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+ }
- if (node.webkitMatchesSelector)
- return node.webkitMatchesSelector(selector);
+ static async(func, args)
+ {
+ setTimeout(() => { func.apply(null, args); }, 50);
+ }
- if (node.mozMatchesSelector)
- return node.mozMatchesSelector(selector);
-}
+ static appendHTML(node, html)
+ {
+ if (node.insertAdjacentHTML)
+ node.insertAdjacentHTML('beforeEnd', html);
+ else
+ node.innerHTML += html;
+ }};
-function parentOfType(node, selector)
+class TestResult
{
- while (node = node.parentNode) {
- if (matchesSelector(node, selector))
- return node;
+ constructor(info, name)
+ {
+ this.name = name;
+ this.info = info; // FIXME: make this private.
}
- return null;
-}
-function remove(node)
-{
- node.parentNode.removeChild(node);
-}
+ isFailureExpected()
+ {
+ let actual = this.info.actual;
+ let expected = this.info.expected || 'PASS';
-function forEach(nodeList, handler)
-{
- Array.prototype.forEach.call(nodeList, handler);
-}
+ if (actual != 'SKIP') {
+ let expectedArray = expected.split(' ');
+ let actualArray = actual.split(' ');
+ for (let actualValue of actualArray) {
+ if (expectedArray.indexOf(actualValue) == -1 && (expectedArray.indexOf('FAIL') == -1 || (actualValue != 'TEXT' && actualValue != 'IMAGE+TEXT' && actualValue != 'AUDIO')))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ isMissing()
+ {
+ return this.info.actual.indexOf('MISSING') != -1;
+ }
+
+ isFlakey(pixelTestsEnabled)
+ {
+ let actualTokens = this.info.actual.split(' ');
+ let passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE';
+ if (actualTokens[1] && this.info.actual.indexOf('PASS') != -1 || (!pixelTestsEnabled && passedWithImageOnlyFailureInRetry))
+ return true;
+
+ return false;
+ }
+
+ isPass()
+ {
+ return this.info.actual == 'PASS';
+ }
-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);
+ isTextFailure()
+ {
+ return this.info.actual.indexOf('TEXT') != -1;
}
- var tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img';
+ isImageFailure()
+ {
+ return this.info.actual.indexOf('IMAGE') != -1;
+ }
- if (tagName != 'img')
- src += '?format=txt';
- return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="" + src + '"></' + tagName + '></div>';
-}
+ isAudioFailure()
+ {
+ return this.info.actual.indexOf('AUDIO') != -1;
+ }
-function togglingImage(prefix)
-{
- return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' +
- prefix + '"></img></div>';
-}
+ isCrash()
+ {
+ return this.info.actual == 'CRASH';
+ }
+
+ isTimeout()
+ {
+ return this.info.actual == 'TIMEOUT';
+ }
+
+ isUnexpectedPass(pixelTestsEnabled)
+ {
+ if (this.info.actual == 'PASS' && this.info.expected != 'PASS') {
+ if (this.info.expected != 'IMAGE' || (pixelTestsEnabled || this.isRefTest()))
+ return true;
+ }
+
+ return false;
+ }
+
+ isRefTest()
+ {
+ return !!this.info.reftest_type;
+ }
-function toggleExpectations(element)
-{
- var expandLink = element;
- if (expandLink.className != 'expand-button-text')
- expandLink = expandLink.querySelector('.expand-button-text');
+ isMismatchRefTest()
+ {
+ return this.isRefTest() && this.info.reftest_type.indexOf('!=') != -1;
+ }
- if (expandLink.textContent == '+')
- expandExpectations(expandLink);
- else
- collapseExpectations(expandLink);
-}
+ isMatchRefTest()
+ {
+ return this.isRefTest() && this.info.reftest_type.indexOf('==') != -1;
+ }
+
+ isMissingImage()
+ {
+ return this.info.is_missing_image;
+ }
+
+ hasStdErr()
+ {
+ return this.info.has_stderr;
+ }
+};
-function collapseExpectations(expandLink)
+class TestResults
{
- expandLink.textContent = '+';
- var existingResultsRow = parentOfType(expandLink, 'tbody').querySelector('.results-row');
- if (existingResultsRow)
- updateExpandedState(existingResultsRow, false);
-}
+ constructor(results)
+ {
+ this._results = results;
-function updateExpandedState(row, isExpanded)
-{
- row.setAttribute('data-expanded', isExpanded);
- updateImageTogglingTimer();
-}
+ this.crashTests = [];
+ this.crashOther = [];
+ this.missingResults = [];
+ this.failingTests = [];
+ this.testsWithStderr = [];
+ this.timeoutTests = [];
+ this.unexpectedPassTests = [];
+ this.flakyPassTests = [];
-function appendHTML(node, html)
-{
- if (node.insertAdjacentHTML)
- node.insertAdjacentHTML('beforeEnd', html);
- else
- node.innerHTML += html;
-}
+ this.hasHttpTests = false;
+ this.hasImageFailures = false;
+ this.hasTextFailures = false;
-function expandExpectations(expandLink)
-{
- var row = parentOfType(expandLink, 'tr');
- var parentTbody = row.parentNode;
- var existingResultsRow = parentTbody.querySelector('.results-row');
+ this._forEachTest(this._results.tests, '');
+ this._forOtherCrashes(this._results.other_crashes);
+ }
- var enDash = '\u2013';
- expandLink.textContent = enDash;
- if (existingResultsRow) {
- updateExpandedState(existingResultsRow, true);
- return;
+ date()
+ {
+ return this._results.date;
}
+
+ layoutTestsDir()
+ {
+ return this._results.layout_tests_dir;
+ }
- var newRow = document.createElement('tr');
- newRow.className = 'results-row';
- var newCell = document.createElement('td');
- newCell.colSpan = row.querySelectorAll('td').length;
+ usesExpectationsFile()
+ {
+ return this._results.uses_expectations_file;
+ }
+
+ resultForTest(testName)
+ {
+ return this._resultsByTest[testName];
+ }
+
+ wasInterrupted()
+ {
+ return this._results.interrupted;
+ }
- 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);
+ hasPrettyPatch()
+ {
+ return this._results.has_pretty_patch;
}
+
+ hasWDiff()
+ {
+ return this._results.has_wdiff;
+ }
- newRow.appendChild(newCell);
- parentTbody.appendChild(newRow);
+ _processResultForTest(testResult)
+ {
+ let test = testResult.name;
+ if (testResult.hasStdErr())
+ this.testsWithStderr.push(testResult);
- updateExpandedState(newRow, true);
+ this.hasHttpTests |= test.indexOf('http/') == 0;
- updateImageTogglingTimer();
-}
+ if (this.usesExpectationsFile())
+ testResult.isExpected = testResult.isFailureExpected();
+
+ if (testResult.isTextFailure())
+ this.hasTextFailures = true;
-function updateImageTogglingTimer()
-{
- var hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage');
- if (!hasVisibleAnimatedImage) {
- clearInterval(globalState().togglingImageInterval);
- globalState().togglingImageInterval = null;
- return;
- }
+ if (testResult.isImageFailure())
+ this.hasImageFailures = true;
- if (!globalState().togglingImageInterval) {
- toggleImages();
- globalState().togglingImageInterval = setInterval(toggleImages, 2000);
- }
-}
+ if (testResult.isMissing()) {
+ // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for tests with MISSING results.
+ this.missingResults.push(testResult);
+ return;
+ }
-function async(func, args)
-{
- setTimeout(function() { func.apply(null, args); }, 100);
-}
+ if (testResult.isFlakey(this._results.pixel_tests_enabled)) {
+ this.flakyPassTests.push(testResult);
+ return;
+ }
-function visibleTests(opt_container)
-{
- var container = opt_container || document;
- if (onlyShowUnexpectedFailures())
- return container.querySelectorAll('tbody:not(.expected)');
- else
- return container.querySelectorAll('tbody');
-}
+ if (testResult.isPass()) {
+ if (testResult.isUnexpectedPass(this._results.pixel_tests_enabled))
+ this.unexpectedPassTests.push(testResult);
+ return;
+ }
-function visibleExpandLinks()
-{
- if (onlyShowUnexpectedFailures())
- return document.querySelectorAll('tbody:not(.expected) .expand-button-text');
- else
- return document.querySelectorAll('.expand-button-text');
-}
+ if (testResult.isCrash()) {
+ this.crashTests.push(testResult);
+ return;
+ }
-function expandAllExpectations()
-{
- var expandLinks = visibleExpandLinks();
- for (var i = 0, len = expandLinks.length; i < len; i++)
- async(expandExpectations, [expandLinks[i]]);
-}
+ if (testResult.isTimeout()) {
+ this.timeoutTests.push(testResult);
+ return;
+ }
+
+ this.failingTests.push(testResult);
+ }
+
+ _forEachTest(tree, prefix)
+ {
+ for (let key in tree) {
+ let newPrefix = prefix ? (prefix + '/' + key) : key;
+ if ('actual' in tree[key]) {
+ let testObject = new TestResult(tree[key], newPrefix);
+ this._processResultForTest(testObject);
+ } else
+ this._forEachTest(tree[key], newPrefix);
+ }
+ }
-function collapseAllExpectations()
-{
- var expandLinks = visibleExpandLinks();
- for (var i = 0, len = expandLinks.length; i < len; i++)
- async(collapseExpectations, [expandLinks[i]]);
-}
+ _forOtherCrashes(tree)
+ {
+ for (let key in tree) {
+ let testObject = new TestResult(tree[key], key);
+ this.crashOther.push(testObject);
+ }
+ }
+
+ static sortByName(tests)
+ {
+ tests.sort(function (a, b) { return a.name.localeCompare(b.name) });
+ }
-function shouldUseTracLinks()
-{
- return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0;
-}
+ static hasUnexpectedResult(tests)
+ {
+ return tests.some(function (test) { return !test.isExpected; });
+ }
+};
-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;
-}
+class TestResultsController
+{
+ constructor(containerElement, testResults)
+ {
+ this.containerElement = containerElement;
+ this.testResults = testResults;
-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/"
-}
+ this.shouldToggleImages = true;
+ this._togglingImageInterval = null;
+
+ this._updatePageTitle();
-function testToURL(test, layoutTestsPath)
-{
- for (let key in mappings) {
- if (test.startsWith(key))
- return mappings[key] + test.substring(key.length);
-
+ this.buildResultsTables();
+ this.hideNonApplicableUI();
+ this.setupSorting();
+ this.setupOptions();
}
- return "file://" + layoutTestsPath + "/" + test
-}
+
+ buildResultsTables()
+ {
+ if (this.testResults.wasInterrupted()) {
+ let interruptionMessage = document.createElement('p');
+ interruptionMessage.textContent = 'Testing exited early';
+ interruptionMessage.classList.add('stopped-running-early-message');
+ this.containerElement.appendChild(interruptionMessage);
+ }
-function layoutTestURL(test)
-{
- if (shouldUseTracLinks())
- return layoutTestsBasePath() + test;
- return testToURL(test, layoutTestsBasePath());
-}
+ if (this.testResults.crashTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.crashTests, CrashingTestsSectionBuilder));
-function checkServerIsRunning(event)
-{
- if (shouldUseTracLinks())
- return;
+ if (this.testResults.crashOther.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.crashOther, OtherCrashesSectionBuilder));
- var url = ""
- if (url.startsWith("file://"))
- return;
+ if (this.testResults.failingTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.failingTests, FailingTestsSectionBuilder));
- 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");
- });
-}
+ if (this.testResults.missingResults.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.missingResults, TestsWithMissingResultsSectionBuilder));
-function testLink(test)
-{
- return '<a class=test-link _onclick_="checkServerIsRunning(event)" href="" + layoutTestURL(test) + '">' + test + '</a><span class=flag _onclick_="unflag(this)"> \u2691</span>';
-}
+ if (this.testResults.timeoutTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.timeoutTests, TimedOutTestsSectionBuilder));
-function unflag(flag)
-{
- var shouldFlag = false;
- TestNavigator.flagTest(parentOfType(flag, 'tbody'), shouldFlag);
-}
+ if (this.testResults.testsWithStderr.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.testsWithStderr, TestsWithStdErrSectionBuilder));
-function testLinkWithExpandButton(test)
-{
- return '<span class=expand-button _onclick_="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testLink(test);
-}
+ if (this.testResults.flakyPassTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.flakyPassTests, FlakyPassTestsSectionBuilder));
-function testWithExpandButton(test)
-{
- return '<span class=expand-button _onclick_="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + test;
-}
+ if (this.testResults.usesExpectationsFile() && this.testResults.unexpectedPassTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.unexpectedPassTests, UnexpectedPassTestsSectionBuilder));
-function resultLink(testPrefix, suffix, contents)
-{
- return '<a class=result-link href="" + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> ';
-}
+ if (this.testResults.hasHttpTests) {
+ let httpdAccessLogLink = document.createElement('p');
+ httpdAccessLogLink.innerHTML = 'httpd access log: <a href=""
-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;
+ let httpdErrorLogLink = document.createElement('p');
+ httpdErrorLogLink.innerHTML = 'httpd error log: <a href=""
+
+ this.containerElement.appendChild(httpdAccessLogLink);
+ this.containerElement.appendChild(httpdErrorLogLink);
}
+
+ this.updateTestlistCounts();
}
- return isExpected;
-}
+
+ setupSorting()
+ {
+ let resultsTable = document.getElementById('results-table');
+ if (!resultsTable)
+ return;
+
+ // FIXME: Make all the tables sortable. Maybe SectionBuilder should put a TableSorter on each table.
+ resultsTable.addEventListener('click', TableSorter.handleClick, false);
+ TableSorter.sortColumn(0);
+ }
+
+ hideNonApplicableUI()
+ {
+ // FIXME: do this all through body classnames.
+ if (!this.testResults.hasTextFailures) {
+ let textResultsHeader = document.getElementById('text-results-header');
+ if (textResultsHeader)
+ textResultsHeader.textContent = '';
+ }
-function processGlobalStateFor(testObject)
-{
- var test = testObject.name;
- if (testObject.has_stderr)
- globalState().testsWithStderr.push(testObject);
+ if (!this.testResults.hasImageFailures) {
+ let imageResultsHeader = document.getElementById('image-results-header');
+ if (imageResultsHeader)
+ imageResultsHeader.textContent = '';
- globalState().hasHttpTests = globalState().hasHttpTests || test.indexOf('http/') == 0;
+ Utils.parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none';
+ }
+ }
+
+ setupOptions()
+ {
+ // FIXME: do this all through body classnames.
+ if (!this.testResults.usesExpectationsFile())
+ Utils.parentOfType(document.getElementById('unexpected-results'), 'label').style.display = 'none';
+ }
- var actual = testObject.actual;
- var expected = testObject.expected || 'PASS';
- if (globalState().results.uses_expectations_file)
- testObject.isExpected = isFailureExpected(expected, actual);
+ buildOneSection(tests, sectionBuilderClass)
+ {
+ TestResults.sortByName(tests);
+
+ let sectionBuilder = new sectionBuilderClass(tests, this);
+ return sectionBuilder.build();
+ }
- 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;
+ updateTestlistCounts()
+ {
+ // FIXME: do this through the data model, not through the DOM.
+ let _onlyShowUnexpectedFailures_ = this.onlyShowUnexpectedFailures();
+ Utils.forEach(document.querySelectorAll('.test-list-count'), count => {
+ let container = Utils.parentOfType(count, 'section');
+ let testContainers;
+ if (onlyShowUnexpectedFailures)
+ testContainers = container.querySelectorAll('tbody:not(.expected)');
+ else
+ testContainers = container.querySelectorAll('tbody');
+
+ count.textContent = testContainers.length;
+ })
}
+
+ flagAll(headerLink)
+ {
+ let tests = this.visibleTests(Utils.parentOfType(headerLink, 'section'));
+ Utils.forEach(tests, tests => {
+ let shouldFlag = true;
+ testNavigator.flagTest(tests, shouldFlag);
+ })
+ }
- 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;
+ unflag(flag)
+ {
+ const shouldFlag = false;
+ testNavigator.flagTest(Utils.parentOfType(flag, 'tbody'), shouldFlag);
}
- if (actual == 'PASS' && expected != 'PASS') {
- if (expected != 'IMAGE' || (globalState().results.pixel_tests_enabled || testObject.reftest_type)) {
- globalState().unexpectedPassTests.push(testObject);
- }
- return;
+ visibleTests(opt_container)
+ {
+ let container = opt_container || document;
+ if (this.onlyShowUnexpectedFailures())
+ return container.querySelectorAll('tbody:not(.expected)');
+ else
+ return container.querySelectorAll('tbody');
}
- if (actual == 'CRASH') {
- globalState().crashTests.push(testObject);
- return;
+ // FIXME: this is confusing. Flip the sense around.
+ onlyShowUnexpectedFailures()
+ {
+ return document.getElementById('unexpected-results').checked;
}
- if (actual == 'TIMEOUT') {
- globalState().timeoutTests.push(testObject);
- return;
+ static _testListHeader(title)
+ {
+ let header = document.createElement('h1');
+ header.innerHTML = title + ' (<span class=test-list-count></span>): <a href="" class=flag-all _onclick_="controller.flagAll(this)">flag all</a>';
+ return header;
}
-
- 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 = "" + '-actual.png';
- } else {
- text.textContent = 'Expected Image';
- image.src = "" + '-expected.png';
+ testToURL(testResult, layoutTestsPath)
+ {
+ const 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/"
+ };
+
+ for (let key in mappings) {
+ if (testResult.name.startsWith(key))
+ return mappings[key] + testResult.name.substring(key.length);
+
}
+ return "file://" + layoutTestsPath + "/" + testResult.name;
}
-}
-function textResultLinks(prefix)
-{
- var html = resultLink(prefix, '-expected.txt', 'expected') +
- resultLink(prefix, '-actual.txt', 'actual') +
- resultLink(prefix, '-diff.txt', 'diff');
+ layoutTestURL(testResult)
+ {
+ if (this.shouldUseTracLinks())
+ return this.layoutTestsBasePath() + testResult.name;
- if (globalState().results.has_pretty_patch)
- html += resultLink(prefix, '-pretty-diff.html', 'pretty diff');
+ return this.testToURL(testResult, this.layoutTestsBasePath());
+ }
- if (globalState().results.has_wdiff)
- html += resultLink(prefix, '-wdiff.html', 'wdiff');
+ layoutTestsBasePath()
+ {
+ let basePath;
+ if (this.shouldUseTracLinks()) {
+ let revision = this.testResults.revision;
+ basePath = 'http://trac.webkit.org';
+ basePath += revision ? ('/export/' + revision) : '/browser';
+ basePath += '/trunk/LayoutTests/';
+ } else
+ basePath = this.testResults.layoutTestsDir() + '/';
- return html;
-}
+ return basePath;
+ }
-function imageResultsCell(testObject, testPrefix, actual) {
- var row = '';
+ shouldUseTracLinks()
+ {
+ return !this.testResults.layoutTestsDir() || !location.toString().indexOf('file://') == 0;
+ }
- if (actual.indexOf('IMAGE') != -1) {
- var testExtension = splitExtension(testObject.name)[1];
- globalState().hasImageFailures = true;
+ checkServerIsRunning(event)
+ {
+ if (this.shouldUseTracLinks())
+ return;
- 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');
- }
+ let url = ""
+ if (url.startsWith("file://"))
+ return;
- var diff = testObject.image_diff_percent;
- row += resultLink(testPrefix, '-diff.png', 'diff (' + diff + '%)');
- }
+ 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");
+ });
}
- if (actual.indexOf('MISSING') != -1 && testObject.is_missing_image)
- row += resultLink(testPrefix, '-actual.png', 'png result');
+ testLink(testResult)
+ {
+ return '<a class=test-link _onclick_="controller.checkServerIsRunning(event)" href="" + this.layoutTestURL(testResult) + '">' + testResult.name + '</a><span class=flag _onclick_="controller.unflag(this)"> \u2691</span>';
+ }
+
+ static resultLink(testPrefix, suffix, contents)
+ {
+ return '<a class=result-link href="" + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> ';
+ }
- return row;
-}
+ textResultLinks(prefix)
+ {
+ let html = TestResultsController.resultLink(prefix, '-expected.txt', 'expected') +
+ TestResultsController.resultLink(prefix, '-actual.txt', 'actual') +
+ TestResultsController.resultLink(prefix, '-diff.txt', 'diff');
-function flakinessDashboardURLForTests(testObjects)
-{
- var testList = "";
- for (var i = 0; i < testObjects.length; ++i) {
- testList += testObjects[i].name;
+ if (this.testResults.hasPrettyPatch())
+ html += TestResultsController.resultLink(prefix, '-pretty-diff.html', 'pretty diff');
- if (i != testObjects.length - 1)
- testList += ",";
+ if (this.testResults.hasWDiff())
+ html += TestResultsController.resultLink(prefix, '-wdiff.html', 'wdiff');
+
+ return html;
}
- return 'http://webkit-test-results.webkit.org/dashboards/flakiness_dashboard.html#showAllRuns=true&tests=' + encodeURIComponent(testList);
-}
+ flakinessDashboardURLForTests(testObjects)
+ {
+ // FIXME: just map and join here.
+ let testList = '';
+ for (let i = 0; i < testObjects.length; ++i) {
+ testList += testObjects[i].name;
-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>';
+ if (i != testObjects.length - 1)
+ testList += ',';
+ }
- row += '<td>' + testLinkWithExpandButton(testObject.name) + '</td>';
+ return 'http://webkit-test-results.webkit.org/dashboards/flakiness_dashboard.html#showAllRuns=true&tests=' + encodeURIComponent(testList);
+ }
- var testPrefix = stripExtension(testObject.name);
- row += '<td>';
-
- var actual = testObject.actual;
- if (actual.indexOf('TEXT') != -1) {
- globalState().hasTextFailures = true;
- row += textResultLinks(testPrefix);
+ _updatePageTitle()
+ {
+ let dateString = this.testResults.date();
+ let title = document.createElement('title');
+ title.textContent = 'Layout Test Results from ' + dateString;
+ document.head.appendChild(title);
}
- 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');
+ // Options handling. FIXME: move to a separate class?
+ updateAllOptions()
+ {
+ Utils.forEach(document.querySelectorAll('#options-menu input'), input => { input.onchange() });
}
- 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');
+ toggleOptionsMenu()
+ {
+ let menu = document.getElementById('options-menu');
+ menu.className = (menu.className == 'hidden-menu') ? '' : 'hidden-menu';
}
- var actualTokens = actual.split(/\s+/);
- var cell = imageResultsCell(testObject, testPrefix, actualTokens[0]);
- if (!cell && actualTokens.length > 1)
- cell = imageResultsCell(testObject, 'retries/' + testPrefix, actualTokens[1]);
+ handleToggleUseNewlines()
+ {
+ OptionWriter.save();
+ testNavigator.updateFlaggedTests();
+ }
- row += '</td><td>' + cell + '</td>';
+ handleUnexpectedResultsChange()
+ {
+ OptionWriter.save();
+ this._updateExpectedFailures();
+ }
- 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);
+ expandAllExpectations()
+ {
+ let expandLinks = this._visibleExpandLinks();
+ for (let link of expandLinks)
+ Utils.async(link => { controller.expandExpectations(link) }, [ link ]);
}
-}
-function forOtherCrashes()
-{
- var tree = globalState().results.other_crashes;
- for (var key in tree) {
- var testObject = tree[key];
- testObject.name = key;
- globalState().crashOther.push(testObject);
+ collapseAllExpectations()
+ {
+ let expandLinks = this._visibleExpandLinks();
+ for (let link of expandLinks)
+ Utils.async(link => { controller.collapseExpectations(link) }, [ link ]);
}
-}
-function hasUnexpected(tests)
-{
- return tests.some(function (test) { return !test.isExpected; });
-}
+ expandExpectations(expandLink)
+ {
+ let row = Utils.parentOfType(expandLink, 'tr');
+ let parentTbody = row.parentNode;
+ let existingResultsRow = parentTbody.querySelector('.results-row');
+
+ const enDash = '\u2013';
+ expandLink.textContent = enDash;
+ if (existingResultsRow) {
+ this._updateExpandedState(existingResultsRow, true);
+ return;
+ }
+
+ let newRow = document.createElement('tr');
+ newRow.className = 'results-row';
+ let newCell = document.createElement('td');
+ newCell.colSpan = row.querySelectorAll('td').length;
-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');
+ let resultLinks = row.querySelectorAll('.result-link');
+ let hasTogglingImages = false;
+ for (let link of resultLinks) {
+ let result;
+ if (link.textContent == 'images') {
+ hasTogglingImages = true;
+ result = TestResultsController._togglingImage(link.getAttribute('data-prefix'));
+ } else
+ result = TestResultsController._resultIframe(link.href);
- count.textContent = testContainers.length;
- })
-}
+ Utils.appendHTML(newCell, result);
+ }
-function flagAll(headerLink)
-{
- var tests = visibleTests(parentOfType(headerLink, 'div'));
- forEach(tests, function(tests) {
- var shouldFlag = true;
- TestNavigator.flagTest(tests, shouldFlag);
- })
-}
+ newRow.appendChild(newCell);
+ parentTbody.appendChild(newRow);
-function testListHeaderHtml(header)
-{
- return '<h1>' + header + ' (<span class=test-list-count></span>): <a href="" class=flag-all _onclick_="flagAll(this)">flag all</a></h1>';
-}
+ this._updateExpandedState(newRow, true);
-function testList(tests, header, tableId)
-{
- tests.sort(function (a, b) { return a.name.localeCompare(b.name) });
+ this._updateImageTogglingTimer();
+ }
- var html = '<div' + ((!hasUnexpected(tests) && tableId != 'stderr-table') ? ' class=expected' : '') + ' id=' + tableId + '>' +
- testListHeaderHtml(header) + '<table>';
+ collapseExpectations(expandLink)
+ {
+ expandLink.textContent = '+';
+ let existingResultsRow = Utils.parentOfType(expandLink, 'tbody').querySelector('.results-row');
+ if (existingResultsRow)
+ this._updateExpandedState(existingResultsRow, false);
+ }
- // 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>';
+ toggleExpectations(element)
+ {
+ let expandLink = element;
+ if (expandLink.className != 'expand-button-text')
+ expandLink = expandLink.querySelector('.expand-button-text');
- 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);
+ if (expandLink.textContent == '+')
+ this.expandExpectations(expandLink);
else
- html += testLinkWithExpandButton(test);
+ this.collapseExpectations(expandLink);
+ }
- html += '</td><td>';
+ _updateExpandedState(row, isExpanded)
+ {
+ row.setAttribute('data-expanded', isExpanded);
+ this._updateImageTogglingTimer();
+ }
- 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));
- }
+ handleToggleImagesChange()
+ {
+ OptionWriter.save();
+ this._updateTogglingImages();
+ }
- if (tableId != 'other-crash-tests-table')
- html += '</td><td><a href="" + flakinessDashboardURLForTests([testObject]) + '">history</a></td>';
+ _visibleExpandLinks()
+ {
+ if (this.onlyShowUnexpectedFailures())
+ return document.querySelectorAll('tbody:not(.expected) .expand-button-text');
+ else
+ return document.querySelectorAll('.expand-button-text');
+ }
- html += '</tr></tbody>';
+ static _togglingImage(prefix)
+ {
+ return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' + prefix + '"></img></div>';
}
- html += '</table></div>';
- return html;
-}
-function toArray(nodeList)
-{
- return Array.prototype.slice.call(nodeList);
-}
+ _updateTogglingImages()
+ {
+ this.shouldToggleImages = document.getElementById('toggle-images').checked;
-function trim(string)
-{
- return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
-}
+ // FIXME: this is all pretty confusing. Simplify.
+ if (this.shouldToggleImages) {
+ Utils.forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), TestResultsController._convertToTogglingHandler(function(prefix) {
+ return TestResultsController.resultLink(prefix, '-diffs.html', 'images');
+ }));
+ Utils.forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), TestResultsController._convertToTogglingHandler(TestResultsController._togglingImage));
+ } else {
+ Utils.forEach(document.querySelectorAll('a[href$="-diffs.html"]'), element => {
+ TestResultsController._convertToNonTogglingHandler(element);
+ });
+ Utils.forEach(document.querySelectorAll('.animatedImage'), TestResultsController._convertToNonTogglingHandler(function (absolutePrefix, suffix) {
+ return TestResultsController._resultIframe(absolutePrefix + suffix);
+ }));
+ }
-// Just a namespace for code management.
-var TableSorter = {};
+ this._updateImageTogglingTimer();
+ }
-TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>';
+ _updateExpectedFailures()
+ {
+ // Gross to do this by setting stylesheet text. Use a body class!
+ document.getElementById('unexpected-style').textContent = this.onlyShowUnexpectedFailures() ? '.expected { display: none; }' : '';
-TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>';
+ this.updateTestlistCounts();
+ testNavigator.onlyShowUnexpectedFailuresChanged();
+ }
-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');
+ static _resultIframe(src)
+ {
+ // FIXME: use audio tags for AUDIO tests?
+ let layoutTestsIndex = src.indexOf('LayoutTests');
+ let name;
+ if (layoutTestsIndex != -1) {
+ let hasTrac = src.indexOf('trac.webkit.org') != -1;
+ let prefix = hasTrac ? 'trac.webkit.org/.../' : '';
+ name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length);
} else {
- sortHeader.textContent = sortHeader.textContent;
- sortHeader.classList.remove('sortHeader');
- sortHeader.classList.remove('reversed');
+ let lastDashIndex = src.lastIndexOf('-pretty');
+ if (lastDashIndex == -1)
+ lastDashIndex = src.lastIndexOf('-');
+ name = src.substring(lastDashIndex + 1);
}
- }
- newHeader.classList.add('sortHeader');
-}
+ let tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img';
-TableSorter._textContent = function(tbodyRow, column)
-{
- return tbodyRow.querySelectorAll('td')[column].textContent;
-}
+ if (tagName != 'img')
+ src += '?format=txt';
+ return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="" + src + '"></' + tagName + '></div>';
+ }
-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'));
+ static _toggleImages()
+ {
+ let images = document.querySelectorAll('.animatedImage');
+ let imageTexts = document.querySelectorAll('.imageText');
+ for (let i = 0, len = images.length; i < len; i++) {
+ let image = images[i];
+ let text = imageTexts[i];
+ if (text.textContent == 'Expected Image') {
+ text.textContent = 'Actual Image';
+ image.src = "" + '-actual.png';
+ } else {
+ text.textContent = 'Expected Image';
+ image.src = "" + '-expected.png';
+ }
+ }
+ }
- 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;
+ _updateImageTogglingTimer()
+ {
+ let hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage');
+ if (!hasVisibleAnimatedImage) {
+ clearInterval(this._togglingImageInterval);
+ this._togglingImageInterval = null;
+ return;
}
- if (reversed)
- return aText < bText ? 1 : -1;
- else
- return aText < bText ? -1 : 1;
- });
+ if (!this._togglingImageInterval) {
+ TestResultsController._toggleImages();
+ this._togglingImageInterval = setInterval(TestResultsController._toggleImages, 2000);
+ }
+ }
+
+ static _getResultContainer(node)
+ {
+ return (node.tagName == 'IMG') ? Utils.parentOfType(node, '.result-container') : node;
+ }
- for (var i = 0; i < rows.length; i++)
- testsTable.appendChild(rows[i]);
-}
+ static _convertToTogglingHandler(togglingImageFunction)
+ {
+ return function(node) {
+ let url = "" == 'IMG') ? node.src : node.href;
+ if (url.match('-expected.png$'))
+ TestResultsController._getResultContainer(node).remove();
+ else if (url.match('-actual.png$')) {
+ let name = Utils.parentOfType(node, 'tbody').querySelector('.test-link').textContent;
+ TestResultsController._getResultContainer(node).outerHTML = togglingImageFunction(Utils.stripExtension(name));
+ }
+ }
+ }
+
+ static _convertToNonTogglingHandler(resultFunction)
+ {
+ return function(node) {
+ let prefix = node.getAttribute('data-prefix');
+ TestResultsController._getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual');
+ }
+ }
+};
-TableSorter.sortColumn = function(columnNumber)
-{
- var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
- TableSorter._sort(newHeader);
-}
+class SectionBuilder {
+
+ constructor(tests, resultsController)
+ {
+ this._tests = tests;
+ this._table = null;
+ this._resultsController = resultsController;
+ }
-TableSorter.handleClick = function(e)
-{
- var newHeader = e.target;
- if (newHeader.localName != 'th')
- return;
- TableSorter._sort(newHeader);
-}
+ build()
+ {
+ TestResults.sortByName(this._tests);
+
+ let section = document.createElement('section');
+ section.appendChild(TestResultsController._testListHeader(this.sectionTitle()));
+ if (this.hideWhenShowingUnexpectedResultsOnly())
+ section.classList.add('expected');
-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);
-}
+ this._table = document.createElement('table');
+ this._table.id = this.tableID();
+ this.addTableHeader();
-var PixelZoomer = {};
+ let visibleResultsCount = 0;
+ for (let testResult of this._tests) {
+ let tbody = this.createTableRow(testResult);
+ this._table.appendChild(tbody);
+
+ if (!this._resultsController.onlyShowUnexpectedFailures() || testResult.isExpected)
+ ++visibleResultsCount;
+ }
+
+ section.querySelector('.test-list-count').textContent = visibleResultsCount;
+ section.appendChild(this._table);
+ return section;
+ }
-PixelZoomer.showOnDelay = true;
-PixelZoomer._zoomFactor = 6;
+ createTableRow(testResult)
+ {
+ let tbody = document.createElement('tbody');
+ if (testResult.isExpected)
+ tbody.classList.add('expected');
+
+ let row = document.createElement('tr');
+ tbody.appendChild(row);
+
+ let testNameCell = document.createElement('td');
+ this.fillTestCell(testResult, testNameCell);
+ row.appendChild(testNameCell);
-var kResultWidth = 800;
-var kResultHeight = 600;
+ let resultCell = document.createElement('td');
+ this.fillTestResultCell(testResult, resultCell);
+ row.appendChild(resultCell);
-var kZoomedResultWidth = kResultWidth * PixelZoomer._zoomFactor;
-var kZoomedResultHeight = kResultHeight * PixelZoomer._zoomFactor;
+ let historyCell = this.createHistoryCell(testResult);
+ if (historyCell)
+ row.appendChild(historyCell);
-PixelZoomer._zoomImageContainer = function(url)
-{
- var container = document.createElement('div');
- container.className = 'zoom-image-container';
-
- var title = url.match(/\-([^\-]*)\.png/)[1];
+ return tbody;
+ }
- var label = document.createElement('div');
- label.className = 'label';
- label.appendChild(document.createTextNode(title));
- container.appendChild(label);
+ hideWhenShowingUnexpectedResultsOnly()
+ {
+ return !TestResults.hasUnexpectedResult(this._tests);
+ }
- var imageContainer = document.createElement('div');
- imageContainer.className = 'scaled-image-container';
+ addTableHeader()
+ {
+ }
- var image = new Image();
- image.src = ""
- image.style.width = kZoomedResultWidth + 'px';
- image.style.height = kZoomedResultHeight + 'px';
- image.style.border = '1px solid black';
- imageContainer.appendChild(image);
- container.appendChild(imageContainer);
-
- return container;
-}
+ fillTestCell(testResult, cell)
+ {
+ cell.innerHTML = '<span class=expand-button _onclick_="controller.toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + this._resultsController.testLink(testResult);
+ }
-PixelZoomer._createContainer = function(e)
-{
- var tbody = parentOfType(e.target, 'tbody');
- var row = tbody.querySelector('tr');
- var imageDiffLinks = row.querySelectorAll('a[href$=".png"]');
+ fillTestResultCell(testResult, cell)
+ {
+ }
- var container = document.createElement('div');
- container.className = 'pixel-zoom-container';
+ createHistoryCell(testResult)
+ {
+ let historyCell = document.createElement('td');
+ historyCell.innerHTML = '<a href="" + this._resultsController.flakinessDashboardURLForTests([testResult]) + '">history</a>'
+ return historyCell;
+ }
- var html = '';
+ tableID() { return ''; }
+ sectionTitle() { return ''; }
+};
+
+class FailuresSectionBuilder extends SectionBuilder {
- 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'));
+ addTableHeader()
+ {
+ let header = document.createElement('thead');
+ let html = '<th>test</th><th id="text-results-header">results</th><th id="image-results-header">image results</th>';
+
+ if (this._resultsController.testResults.usesExpectationsFile())
+ html += '<th>actual failure</th><th>expected failure</th>';
+
+ html += '<th><a href="" + this._resultsController.flakinessDashboardURLForTests(this._tests) + '">history</a></th>';
+
+ if (this.tableID() == 'flaky-tests-table') // FIXME: use the classes, Luke!
+ html += '<th>failures</th>';
+
+ header.innerHTML = html;
+ this._table.appendChild(header);
}
- for (var i = 0; i < imageDiffLinks.length; i++)
- container.appendChild(PixelZoomer._zoomImageContainer(imageDiffLinks[i].href));
+ createTableRow(testResult)
+ {
+ let tbody = document.createElement('tbody');
+ if (testResult.isExpected)
+ tbody.classList.add('expected');
+
+ if (testResult.isMismatchRefTest())
+ tbody.setAttribute('mismatchreftest', 'true');
- document.body.appendChild(container);
- PixelZoomer._drawAll();
-}
+ let row = document.createElement('tr');
+ tbody.appendChild(row);
+
+ let testNameCell = document.createElement('td');
+ this.fillTestCell(testResult, testNameCell);
+ row.appendChild(testNameCell);
-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';
-}
+ let resultCell = document.createElement('td');
+ this.fillTestResultCell(testResult, resultCell);
+ row.appendChild(resultCell);
-PixelZoomer._drawAll = function()
-{
- forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), PixelZoomer._draw);
-}
+ if (testResult.isTextFailure())
+ this.appendTextFailureLinks(testResult, resultCell);
-PixelZoomer.handleMouseOut = function(e)
-{
- if (e.relatedTarget && e.relatedTarget.tagName != 'IFRAME')
- return;
+ if (testResult.isAudioFailure())
+ this.appendAudioFailureLinks(testResult, resultCell);
+
+ if (testResult.isMissing())
+ this.appendActualOnlyLinks(testResult, resultCell);
- // If e.relatedTarget is null, we've moused out of the document.
- var container = document.querySelector('.pixel-zoom-container');
- if (container)
- remove(container);
-}
+ let actualTokens = testResult.info.actual.split(/\s+/);
-PixelZoomer.handleMouseMove = function(e) {
- if (PixelZoomer._mouseMoveTimeout)
- clearTimeout(PixelZoomer._mouseMoveTimeout);
+ let testPrefix = Utils.stripExtension(testResult.name);
+ let imageResults = this.imageResultLinks(testResult, testPrefix, actualTokens[0]);
+ if (!imageResults && actualTokens.length > 1)
+ imageResults = this.imageResultLinks(testResult, 'retries/' + testPrefix, actualTokens[1]);
- if (parentOfType(e.target, '.pixel-zoom-container'))
- return;
+ let imageResultsCell = document.createElement('td');
+ imageResultsCell.innerHTML = imageResults;
+ row.appendChild(imageResultsCell);
- 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;
- }
+ if (this._resultsController.testResults.usesExpectationsFile() || actualTokens.length) {
+ let actualCell = document.createElement('td');
+ actualCell.textContent = testResult.info.actual;
+ row.appendChild(actualCell);
+ }
- 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;
+ if (this._resultsController.testResults.usesExpectationsFile()) {
+ let expectedCell = document.createElement('td');
+ expectedCell.textContent = testResult.isMissing() ? '' : testResult.info.expected;
+ row.appendChild(expectedCell);
}
- PixelZoomer._createContainer(e);
- return;
+ let historyCell = this.createHistoryCell(testResult);
+ if (historyCell)
+ row.appendChild(historyCell);
+
+ return tbody;
}
+
+ appendTextFailureLinks(testResult, cell)
+ {
+ cell.innerHTML += this._resultsController.textResultLinks(Utils.stripExtension(testResult.name));
+ }
- PixelZoomer._drawAll();
-}
+ appendAudioFailureLinks(testResult, cell)
+ {
+ let prefix = Utils.stripExtension(testResult.name);
+ cell.innerHTML += TestResultsController.resultLink(prefix, '-expected.wav', 'expected audio')
+ + TestResultsController.resultLink(prefix, '-actual.wav', 'actual audio')
+ + TestResultsController.resultLink(prefix, '-diff.txt', 'textual diff');
+ }
+
+ appendActualOnlyLinks(testResult, cell)
+ {
+ if (testResult.info.is_missing_audio)
+ cell.innerHTML += TestResultsController.resultLink(prefix, '-actual.wav', 'audio result');
-document.addEventListener('mousemove', PixelZoomer.handleMouseMove, false);
-document.addEventListener('mouseout', PixelZoomer.handleMouseOut, false);
+ if (testResult.info.is_missing_text)
+ cell.innerHTML += TestResultsController.resultLink(prefix, '-actual.txt', 'result');
+ }
-var TestNavigator = {};
+ imageResultLinks(testResult, testPrefix, resultToken)
+ {
+ let result = '';
+ if (resultToken.indexOf('IMAGE') != -1) {
+ let testExtension = Utils.splitExtension(testResult.name)[1];
-TestNavigator.reset = function() {
- TestNavigator.currentTestIndex = -1;
- TestNavigator.flaggedTests = {};
-}
+ if (testResult.isMismatchRefTest()) {
+ result += TestResultsController.resultLink(this._resultsController.layoutTestsBasePath() + testPrefix, '-expected-mismatch.' + testExtension, 'ref mismatch');
+ result += TestResultsController.resultLink(testPrefix, '-actual.png', 'actual');
+ } else {
+ if (testResult.isMatchRefTest())
+ result += TestResultsController.resultLink(this._resultsController.layoutTestsBasePath() + testPrefix, '-expected.' + testExtension, 'reference');
-TestNavigator.handleKeyEvent = function(event)
-{
- if (event.metaKey || event.shiftKey || event.ctrlKey)
- return;
+ if (this._resultsController.shouldToggleImages)
+ result += TestResultsController.resultLink(testPrefix, '-diffs.html', 'images');
+ else {
+ result += TestResultsController.resultLink(testPrefix, '-expected.png', 'expected');
+ result += TestResultsController.resultLink(testPrefix, '-actual.png', 'actual');
+ }
- 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;
+ let diff = testResult.info.image_diff_percent;
+ result += TestResultsController.resultLink(testPrefix, '-diff.png', 'diff (' + diff + '%)');
+ }
+ }
+
+ if (testResult.isMissing() && testResult.isMissingImage())
+ result += TestResultsController.resultLink(testPrefix, '-actual.png', 'png result');
+
+ return result;
}
-}
+};
-TestNavigator._scrollToFirstTest = function()
-{
- if (TestNavigator._setCurrentTest(0))
- TestNavigator._scrollToCurrentTest();
-}
+class FailingTestsSectionBuilder extends FailuresSectionBuilder {
+ tableID() { return 'results-table'; }
+ sectionTitle() { return 'Tests that failed text/pixel/audio diff'; }
+};
-TestNavigator._scrollToLastTest = function()
-{
- var links = visibleTests();
- if (TestNavigator._setCurrentTest(links.length - 1))
- TestNavigator._scrollToCurrentTest();
-}
+class TestsWithMissingResultsSectionBuilder extends FailuresSectionBuilder {
+ tableID() { return 'missing-table'; }
+ sectionTitle() { return 'Tests that had no expected results (probably new)'; }
+};
-TestNavigator._scrollToNextTest = function()
-{
- if (TestNavigator.currentTestIndex == -1)
- TestNavigator._scrollToFirstTest();
- else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex + 1))
- TestNavigator._scrollToCurrentTest();
-}
+class FlakyPassTestsSectionBuilder extends FailuresSectionBuilder {
+ tableID() { return 'flaky-tests-table'; }
+ sectionTitle() { return 'Flaky tests (failed the first run and passed on retry)'; }
+};
-TestNavigator._scrollToPreviousTest = function()
-{
- if (TestNavigator.currentTestIndex == -1)
- TestNavigator._scrollToLastTest();
- else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex - 1))
- TestNavigator._scrollToCurrentTest();
-}
+class UnexpectedPassTestsSectionBuilder extends SectionBuilder {
+ tableID() { return 'passes-table'; }
+ sectionTitle() { return 'Tests expected to fail but passed'; }
-TestNavigator._currentTestLink = function()
-{
- var links = visibleTests();
- return links[TestNavigator.currentTestIndex];
-}
+ addTableHeader()
+ {
+ let header = document.createElement('thead');
+ header.innerHTML = '<th>test</th><th>expected failure</th><th>history</th>';
+ this._table.appendChild(header);
+ }
+
+ fillTestCell(testResult, cell)
+ {
+ cell.innerHTML = '<a class=test-link _onclick_="controller.checkServerIsRunning(event)" href="" + this._resultsController.layoutTestURL(testResult) + '">' + testResult.name + '</a><span class=flag _onclick_="controller.unflag(this)"> \u2691</span>';
+ }
-TestNavigator._currentTestExpandLink = function()
-{
- return TestNavigator._currentTestLink().querySelector('.expand-button-text');
-}
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = testResult.info.expected;
+ }
+};
-TestNavigator._expandCurrentTest = function()
-{
- expandExpectations(TestNavigator._currentTestExpandLink());
-}
-TestNavigator._collapseCurrentTest = function()
-{
- collapseExpectations(TestNavigator._currentTestExpandLink());
-}
+class TestsWithStdErrSectionBuilder extends SectionBuilder {
+ tableID() { return 'stderr-table'; }
+ sectionTitle() { return 'Tests that had stderr output'; }
+ hideWhenShowingUnexpectedResultsOnly() { return false; }
-TestNavigator._toggleCurrentTest = function()
-{
- toggleExpectations(TestNavigator._currentTestExpandLink());
-}
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-stderr.txt', 'stderr');
+ }
+};
-TestNavigator._toggleCurrentTestFlagged = function()
-{
- var testLink = TestNavigator._currentTestLink();
- TestNavigator.flagTest(testLink, !testLink.classList.contains('flagged'));
-}
+class TimedOutTestsSectionBuilder extends SectionBuilder {
+ tableID() { return 'timeout-tests-table'; }
+ sectionTitle() { return 'Tests that timed out'; }
-// 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];
+ fillTestResultCell(testResult, cell)
+ {
+ // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests.
+ cell.innerHTML = this._resultsController.textResultLinks(Utils.stripExtension(testResult.name));
}
+};
- TestNavigator.updateFlaggedTests();
-}
+class CrashingTestsSectionBuilder extends SectionBuilder {
+ tableID() { return 'crash-tests-table'; }
+ sectionTitle() { return 'Tests that crashed'; }
-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);
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-crash-log.txt', 'crash log')
+ + TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-sample.txt', 'sample');
+ }
+};
- flaggedTestTextbox = document.getElementById('flagged-tests');
+class OtherCrashesSectionBuilder extends SectionBuilder {
+ tableID() { return 'other-crash-tests-table'; }
+ sectionTitle() { return 'Other crashes'; }
+ fillTestCell(testResult, cell)
+ {
+ cell.innerHTML = '<span class=expand-button _onclick_="controller.toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testResult.name;
}
- 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';
-}
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-crash-log.txt', 'crash log');
+ }
-TestNavigator._setCurrentTest = function(testIndex)
-{
- var links = visibleTests();
- if (testIndex < 0 || testIndex >= links.length)
- return false;
+ createHistoryCell(testResult)
+ {
+ return null;
+ }
+};
- var currentTest = links[TestNavigator.currentTestIndex];
- if (currentTest)
- currentTest.classList.remove('current');
+class PixelZoomer {
+ constructor()
+ {
+ this.showOnDelay = true;
+ this._zoomFactor = 6;
- TestNavigator.currentTestIndex = testIndex;
+ this._resultWidth = 800;
+ this._resultHeight = 600;
+
+ this._percentX = 0;
+ this._percentY = 0;
- currentTest = links[TestNavigator.currentTestIndex];
- currentTest.classList.add('current');
+ document.addEventListener('mousemove', this, false);
+ document.addEventListener('mouseout', this, false);
+ }
- return true;
-}
+ _zoomedResultWidth()
+ {
+ return this._resultWidth * this._zoomFactor;
+ }
+
+ _zoomedResultHeight()
+ {
+ return this._resultHeight * this._zoomFactor;
+ }
+
+ _zoomImageContainer(url)
+ {
+ let container = document.createElement('div');
+ container.className = 'zoom-image-container';
-TestNavigator._scrollToCurrentTest = function()
-{
- var targetLink = TestNavigator._currentTestLink();
- if (!targetLink)
- return;
+ let title = url.match(/\-([^\-]*)\.png/)[1];
+
+ let label = document.createElement('div');
+ label.className = 'label';
+ label.appendChild(document.createTextNode(title));
+ container.appendChild(label);
+
+ let imageContainer = document.createElement('div');
+ imageContainer.className = 'scaled-image-container';
+
+ let image = new Image();
+ image.src = ""
+ image.style.width = this._zoomedResultWidth() + 'px';
+ image.style.height = this._zoomedResultHeight() + 'px';
+ image.style.border = '1px solid black';
+ imageContainer.appendChild(image);
+ container.appendChild(imageContainer);
+
+ return container;
+ }
- 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.body.scrollTop += rowRect.top;
-}
+ _createContainer(e)
+ {
+ let tbody = Utils.parentOfType(e.target, 'tbody');
+ let row = tbody.querySelector('tr');
+ let imageDiffLinks = row.querySelectorAll('a[href$=".png"]');
+
+ let container = document.createElement('div');
+ container.className = 'pixel-zoom-container';
+
+ let html = '';
+
+ let togglingImageLink = row.querySelector('a[href$="-diffs.html"]');
+ if (togglingImageLink) {
+ let prefix = togglingImageLink.getAttribute('data-prefix');
+ container.appendChild(this._zoomImageContainer(prefix + '-expected.png'));
+ container.appendChild(this._zoomImageContainer(prefix + '-actual.png'));
+ }
+
+ for (let link of imageDiffLinks)
+ container.appendChild(this._zoomImageContainer(link.href));
-TestNavigator._onlyShowUnexpectedFailuresChanged_ = function()
-{
- var currentTest = document.querySelector('.current');
- if (!currentTest)
- return;
+ document.body.appendChild(container);
+ this._drawAll();
+ }
- // 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);
+ _draw(imageContainer)
+ {
+ let image = imageContainer.querySelector('img');
+ let containerBounds = imageContainer.getBoundingClientRect();
+ image.style.left = (containerBounds.width / 2 - this._percentX * this._zoomedResultWidth()) + 'px';
+ image.style.top = (containerBounds.height / 2 - this._percentY * this._zoomedResultHeight()) + 'px';
}
-}
-document.addEventListener('keypress', TestNavigator.handleKeyEvent, false);
+ _drawAll()
+ {
+ Utils.forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), element => { this._draw(element) });
+ }
+
+ handleEvent(event)
+ {
+ if (event.type == 'mousemove') {
+ this._handleMouseMove(event);
+ return;
+ }
+ if (event.type == 'mouseout') {
+ this._handleMouseOut(event);
+ return;
+ }
+ }
-function onlyShowUnexpectedFailures()
-{
- return document.getElementById('unexpected-results').checked;
-}
+ _handleMouseOut(event)
+ {
+ if (event.relatedTarget && event.relatedTarget.tagName != 'IFRAME')
+ return;
-function handleUnexpectedResultsChange()
-{
- OptionWriter.save();
- updateExpectedFailures();
-}
+ // If e.relatedTarget is null, we've moused out of the document.
+ let container = document.querySelector('.pixel-zoom-container');
+ if (container)
+ container.remove();
+ }
-function updateExpectedFailures()
-{
- document.getElementById('unexpected-style').textContent = onlyShowUnexpectedFailures() ?
- '.expected { display: none; }' : '';
+ _handleMouseMove(event)
+ {
+ if (this._mouseMoveTimeout) {
+ clearTimeout(this._mouseMoveTimeout);
+ this._mouseMoveTimeout = 0;
+ }
- updateTestlistCounts();
- TestNavigator.onlyShowUnexpectedFailuresChanged();
-}
+ if (Utils.parentOfType(event.target, '.pixel-zoom-container'))
+ return;
-var OptionWriter = {};
+ let container = document.querySelector('.pixel-zoom-container');
+
+ let resultContainer = (event.target.className == 'result-container') ? event.target : Utils.parentOfType(event.target, '.result-container');
+ if (!resultContainer || !resultContainer.querySelector('img')) {
+ if (container)
+ container.remove();
+ return;
+ }
-OptionWriter._key = 'run-webkit-tests-options';
+ let targetLocation = event.target.getBoundingClientRect();
+ this._percentX = (event.clientX - targetLocation.left) / targetLocation.width;
+ this._percentY = (event.clientY - targetLocation.top) / targetLocation.height;
-OptionWriter.save = function()
+ if (!container) {
+ if (this.showOnDelay) {
+ this._mouseMoveTimeout = setTimeout(() => {
+ this._createContainer(event);
+ }, 400);
+ return;
+ }
+
+ this._createContainer(event);
+ return;
+ }
+
+ this._drawAll();
+ }
+};
+
+class TableSorter
{
- 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;
+ static _forwardArrow()
+ {
+ return '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>';
}
- try {
- localStorage.setItem(OptionWriter._key, JSON.stringify(data));
- } catch (err) {
- if (err.name != "SecurityError")
- throw err;
+
+ static _backwardArrow()
+ {
+ return '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>';
}
-}
-OptionWriter.apply = function()
-{
- var json;
- try {
- json = localStorage.getItem(OptionWriter._key);
- } catch (err) {
- if (err.name != "SecurityError")
- throw err;
+ static _sortedContents(header, arrow)
+ {
+ return arrow + ' ' + Utils.trim(header.textContent) + ' ' + arrow;
}
- if (!json) {
- updateAllOptions();
- return;
+ static _updateHeaderClassNames(newHeader)
+ {
+ let sortHeader = document.querySelector('.sortHeader');
+ if (sortHeader) {
+ if (sortHeader == newHeader) {
+ let 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');
}
- var data = ""
- for (var id in data) {
- var input = document.getElementById(id);
- if (input)
- input.checked = data[id];
+ static _textContent(tbodyRow, column)
+ {
+ return tbodyRow.querySelectorAll('td')[column].textContent;
}
- updateAllOptions();
-}
-function updateAllOptions()
-{
- forEach(document.querySelectorAll('#options-menu input'), function(input) { input.onchange(); });
-}
+ static _sortRows(newHeader, reversed)
+ {
+ let testsTable = document.getElementById('results-table');
+ let headers = Utils.toArray(testsTable.querySelectorAll('th'));
+ let sortColumn = headers.indexOf(newHeader);
-function handleToggleUseNewlines()
-{
- OptionWriter.save();
- TestNavigator.updateFlaggedTests();
-}
+ let rows = Utils.toArray(testsTable.querySelectorAll('tbody'));
-function handleToggleImagesChange()
-{
- OptionWriter.save();
- updateTogglingImages();
-}
+ rows.sort(function(a, b) {
+ // Only need to support lexicographic sort for now.
+ let aText = TableSorter._textContent(a, sortColumn);
+ let bText = TableSorter._textContent(b, sortColumn);
+
+ // Forward sort equal values by test name.
+ if (sortColumn && aText == bText) {
+ let aTestName = TableSorter._textContent(a, 0);
+ let bTestName = TableSorter._textContent(b, 0);
+ if (aTestName == bTestName)
+ return 0;
+ return aTestName < bTestName ? -1 : 1;
+ }
-function updateTogglingImages()
-{
- var shouldToggle = document.getElementById('toggle-images').checked;
- globalState().shouldToggleImages = shouldToggle;
+ if (reversed)
+ return aText < bText ? 1 : -1;
+ else
+ return aText < bText ? -1 : 1;
+ });
+
+ for (let row of rows)
+ testsTable.appendChild(row);
+ }
+
+ static sortColumn(columnNumber)
+ {
+ let newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
+ TableSorter._sort(newHeader);
+ }
+
+ static handleClick(e)
+ {
+ let newHeader = e.target;
+ if (newHeader.localName != 'th')
+ return;
+ TableSorter._sort(newHeader);
+ }
+
+ static _sort(newHeader)
+ {
+ TableSorter._updateHeaderClassNames(newHeader);
- 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);
- }));
+ let reversed = newHeader.classList.contains('reversed');
+ let sortArrow = reversed ? TableSorter._backwardArrow() : TableSorter._forwardArrow();
+ newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow);
+
+ TableSorter._sortRows(newHeader, reversed);
+ }
+};
+
+class OptionWriter {
+ static save()
+ {
+ let options = document.querySelectorAll('label input');
+ let data = ""
+ for (let option of options)
+ data[option.id] = option.checked;
+
+ try {
+ localStorage.setItem(OptionWriter._key, JSON.stringify(data));
+ } catch (err) {
+ if (err.name != "SecurityError")
+ throw err;
+ }
}
- updateImageTogglingTimer();
-}
+ static apply()
+ {
+ let json;
+ try {
+ json = localStorage.getItem(OptionWriter._key);
+ } catch (err) {
+ if (err.name != "SecurityError")
+ throw err;
+ }
-function getResultContainer(node)
-{
- return (node.tagName == 'IMG') ? parentOfType(node, '.result-container') : node;
-}
+ if (!json) {
+ controller.updateAllOptions();
+ return;
+ }
-function convertToTogglingHandler(togglingImageFunction)
-{
- return function(node) {
- var url = "" == '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));
+ let data = ""
+ for (let id in data) {
+ let input = document.getElementById(id);
+ if (input)
+ input.checked = data[id];
}
+ controller.updateAllOptions();
}
-}
-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');
+ static get _key()
+ {
+ return 'run-webkit-tests-options';
}
-}
+};
-function toggleOptionsMenu()
+let testResults;
+function ADD_RESULTS(input)
{
- var menu = document.getElementById('options-menu');
- menu.className = (menu.className == 'hidden-menu') ? '' : 'hidden-menu';
+ testResults = new TestResults(input);
}
+</script>
-function handleMouseDown(e)
-{
- if (!parentOfType(e.target, '#options-menu') && e.target.id != 'options-link')
- document.getElementById('options-menu').className = 'hidden-menu';
-}
+<script src=""
-document.addEventListener('mousedown', handleMouseDown, false);
+<script>
-function failingTestsTable(tests, title, id)
+class TestNavigator
{
- if (!tests.length)
- return '';
+ constructor() {
+ this.currentTestIndex = -1;
+ this.flaggedTests = {};
+ document.addEventListener('keypress', this, false);
+ }
+
+ handleEvent(event)
+ {
+ if (event.type == 'keypress') {
+ this.handleKeyEvent(event);
+ return;
+ }
+ }
- var numberofUnexpectedFailures = 0;
- var tableRowHtml = '';
- for (var i = 0; i < tests.length; i++){
- tableRowHtml += tableRow(tests[i]);
- if (!tests[i].isExpected)
- numberofUnexpectedFailures++;
+ handleKeyEvent(event)
+ {
+ if (event.metaKey || event.shiftKey || event.ctrlKey)
+ return;
+
+ switch (String.fromCharCode(event.charCode)) {
+ case 'i':
+ this._scrollToFirstTest();
+ break;
+ case 'j':
+ this._scrollToNextTest();
+ break;
+ case 'k':
+ this._scrollToPreviousTest();
+ break;
+ case 'l':
+ this._scrollToLastTest();
+ break;
+ case 'e':
+ this._expandCurrentTest();
+ break;
+ case 'c':
+ this._collapseCurrentTest();
+ break;
+ case 't':
+ this._toggleCurrentTest();
+ break;
+ case 'f':
+ this._toggleCurrentTestFlagged();
+ break;
+ }
}
- var header = '<div';
- if (!hasUnexpected(tests))
- header += ' class=expected';
+ _scrollToFirstTest()
+ {
+ if (this._setCurrentTest(0))
+ this._scrollToCurrentTest();
+ }
- 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>';
+ _scrollToLastTest()
+ {
+ let links = controller.visibleTests();
+ if (this._setCurrentTest(links.length - 1))
+ this._scrollToCurrentTest();
+ }
- if (globalState().results.uses_expectations_file)
- header += '<th>actual failure</th><th>expected failure</th>';
+ _scrollToNextTest()
+ {
+ if (this.currentTestIndex == -1)
+ this._scrollToFirstTest();
+ else if (this._setCurrentTest(this.currentTestIndex + 1))
+ this._scrollToCurrentTest();
+ }
- header += '<th><a href="" + flakinessDashboardURLForTests(tests) + '">history</a></th>';
+ _scrollToPreviousTest()
+ {
+ if (this.currentTestIndex == -1)
+ this._scrollToLastTest();
+ else if (this._setCurrentTest(this.currentTestIndex - 1))
+ this._scrollToCurrentTest();
+ }
- if (id == 'flaky-tests-table')
- header += '<th>failures</th>';
+ _currentTestLink()
+ {
+ let links = controller.visibleTests();
+ return links[this.currentTestIndex];
+ }
- header += '</tr></thead>';
+ _currentTestExpandLink()
+ {
+ return this._currentTestLink().querySelector('.expand-button-text');
+ }
- return header + tableRowHtml + '</table></div>';
-}
+ _expandCurrentTest()
+ {
+ controller.expandExpectations(this._currentTestExpandLink());
+ }
-function updateTitle()
-{
- var dateString = globalState().results.date;
-
- var title = document.createElement('title');
- title.textContent = 'Layout Test Results from ' + dateString;
- document.head.appendChild(title);
-}
+ _collapseCurrentTest()
+ {
+ controller.collapseExpectations(this._currentTestExpandLink());
+ }
-function generatePage()
-{
- updateTitle();
- forEachTest(processGlobalStateFor);
- forOtherCrashes();
+ _toggleCurrentTest()
+ {
+ controller.toggleExpectations(this._currentTestExpandLink());
+ }
- var html = "";
+ _toggleCurrentTestFlagged()
+ {
+ let testLink = this._currentTestLink();
+ this.flagTest(testLink, !testLink.classList.contains('flagged'));
+ }
- if (globalState().results.interrupted)
- html += "<p class='stopped-running-early-message'>Testing exited early.</p>"
+ // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something.
+ // FIXME: Batch flagging (avoid updateFlaggedTests on each test).
+ flagTest(testTbody, shouldFlag)
+ {
+ let testName = testTbody.querySelector('.test-link').innerText;
+
+ if (shouldFlag) {
+ testTbody.classList.add('flagged');
+ this.flaggedTests[testName] = 1;
+ } else {
+ testTbody.classList.remove('flagged');
+ delete this.flaggedTests[testName];
+ }
- if (globalState().crashTests.length)
- html += testList(globalState().crashTests, 'Tests that crashed', 'crash-tests-table');
+ this.updateFlaggedTests();
+ }
- if (globalState().crashOther.length)
- html += testList(globalState().crashOther, 'Other Crashes', 'other-crash-tests-table');
+ updateFlaggedTests()
+ {
+ let flaggedTestTextbox = document.getElementById('flagged-tests');
+ if (!flaggedTestTextbox) {
+ let 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);
- html += failingTestsTable(globalState().failingTests,
- 'Tests that failed text/pixel/audio diff', 'results-table');
+ flaggedTestTextbox = document.getElementById('flagged-tests');
+ }
- html += failingTestsTable(globalState().missingResults,
- 'Tests that had no expected results (probably new)', 'missing-table');
+ let flaggedTests = Object.keys(this.flaggedTests);
+ flaggedTests.sort();
+ let separator = document.getElementById('use-newlines').checked ? '\n' : ' ';
+ flaggedTestTextbox.innerHTML = flaggedTests.join(separator);
+ document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none';
+ }
- if (globalState().timeoutTests.length)
- html += testList(globalState().timeoutTests, 'Tests that timed out', 'timeout-tests-table');
+ _setCurrentTest(testIndex)
+ {
+ let links = controller.visibleTests();
+ if (testIndex < 0 || testIndex >= links.length)
+ return false;
- if (globalState().testsWithStderr.length)
- html += testList(globalState().testsWithStderr, 'Tests that had stderr output', 'stderr-table');
+ let currentTest = links[this.currentTestIndex];
+ if (currentTest)
+ currentTest.classList.remove('current');
- html += failingTestsTable(globalState().flakyPassTests,
- 'Flaky tests (failed the first run and passed on retry)', 'flaky-tests-table');
+ this.currentTestIndex = testIndex;
- if (globalState().results.uses_expectations_file && globalState().unexpectedPassTests.length)
- html += testList(globalState().unexpectedPassTests, 'Tests expected to fail but passed', 'passes-table');
+ currentTest = links[this.currentTestIndex];
+ currentTest.classList.add('current');
- if (globalState().hasHttpTests) {
- html += '<p>httpd access log: <a href="" +
- '<p>httpd error log: <a href=""
+ return true;
}
- document.getElementById('main-content').innerHTML = html + '</div>';
+ _scrollToCurrentTest()
+ {
+ let targetLink = this._currentTestLink();
+ if (!targetLink)
+ return;
- 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';
+ let 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.body.scrollTop += rowRect.top;
+ }
+
+ onlyShowUnexpectedFailuresChanged()
+ {
+ let currentTest = document.querySelector('.current');
+ if (!currentTest)
+ return;
+
+ // If our currentTest became hidden, reset the currentTestIndex.
+ if (controller.onlyShowUnexpectedFailures() && currentTest.classList.contains('expected'))
+ this._scrollToFirstTest();
+ else {
+ // Recompute this.currentTestIndex
+ let links = controller.visibleTests();
+ this.currentTestIndex = links.indexOf(currentTest);
}
}
+};
- updateTestlistCounts();
+function handleMouseDown(e)
+{
+ if (!Utils.parentOfType(e.target, '#options-menu') && e.target.id != 'options-link')
+ document.getElementById('options-menu').className = 'hidden-menu';
+}
- TestNavigator.reset();
+document.addEventListener('mousedown', handleMouseDown, false);
+
+let controller;
+let pixelZoomer;
+let testNavigator;
+
+function generatePage()
+{
+ let container = document.getElementById('main-content');
+
+ controller = new TestResultsController(container, testResults);
+ pixelZoomer = new PixelZoomer();
+ testNavigator = new TestNavigator();
+
OptionWriter.apply();
}
+
+window.addEventListener('load', generatePage, false);
+
</script>
-<body _onload_="generatePage()">
+<body>
<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="" _onclick_="expandAllExpectations()">expand all</a>
- <a href="" _onclick_="collapseAllExpectations()">collapse all</a>
- <a href="" id=options-link _onclick_="toggleOptionsMenu()">options</a>
+ <a class="clickable" _onclick_="controller.expandAllExpectations()">expand all</a>
+ <a class="clickable" _onclick_="controller.collapseAllExpectations()">collapse all</a>
+ <a class="clickable" id=options-link _onclick_="controller.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>
+ <label><input id="unexpected-results" type="checkbox" checked _onchange_="controller.handleUnexpectedResultsChange()">Only unexpected results</label>
+ <label><input id="toggle-images" type="checkbox" checked _onchange_="controller.handleToggleImagesChange()">Toggle images</label>
+ <label title="Use newlines instead of spaces to separate flagged tests"><input id="use-newlines" type="checkbox" checked _onchange_="controller.handleToggleUseNewlines()">Use newlines in flagged list</label>
</div>
</div>