Changeset 234993 in webkit
- Timestamp:
- Aug 17, 2018, 11:56:45 AM (7 years ago)
- Location:
- trunk/LayoutTests
- Files:
-
- 3 edited
-
ChangeLog (modified) (1 diff)
-
fast/harness/results-expected.txt (modified) (2 diffs)
-
fast/harness/results.html (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r234974 r234993 1 2018-08-17 Simon Fraser <simon.fraser@apple.com> 2 3 Modernize results.html 4 https://bugs.webkit.org/show_bug.cgi?id=188690 5 6 Reviewed by Alexey Proskuryakov. 7 8 results.html, which is used to show layout test results, had some very old-school 9 HTML string building to create the tables of test results, making it hard to hack on. 10 11 Modernize it, using ES6 classes for the major actors, and using DOM API to build most 12 of the content. 13 14 The page is functionally the same (other than the addition of a missing 'History" column header). 15 16 * fast/harness/results-expected.txt: 17 * fast/harness/results.html: 18 1 19 2018-08-16 Devin Rousso <drousso@apple.com> 2 20 -
trunk/LayoutTests/fast/harness/results-expected.txt
r234928 r234993 6 6 7 7 +http/tests/contentextensions/top-url.html crash log sample history 8 Other Crashes (2): flag all8 Other crashes (2): flag all 9 9 10 10 +DumpRenderTree-54888 crash log … … 32 32 Tests expected to fail but passed (4): flag all 33 33 34 test expected failure 34 test expected failure history 35 35 canvas/philip/tests/2d.gradient.interpolate.solid.html fail history 36 36 editing/spelling/spelling-marker-includes-hyphen.html image history -
trunk/LayoutTests/fast/harness/results.html
r217470 r234993 21 21 } 22 22 23 a.clickable { 24 color: blue; 25 cursor: pointer; 26 margin-left: 0.2em; 27 } 28 23 29 tr:not(.results-row) td { 24 30 white-space: nowrap; … … 33 39 } 34 40 35 t d {41 th, td { 36 42 padding: 1px 4px; 37 43 } … … 76 82 77 83 .floating-panel { 78 padding: 4px;84 padding: 6px; 79 85 background-color: rgba(255, 255, 255, 0.9); 80 86 border: 1px solid silver; … … 138 144 139 145 #options-menu { 140 border: 1px solid; 146 border: 1px solid gray; 147 border-radius: 4px; 141 148 margin-top: 1px; 142 149 padding: 2px 4px; 143 box-shadow: 2px 2px 2px #888;144 -webkit-transition: opacity .2s;150 box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.6); 151 transition: opacity .2s; 145 152 text-align: left; 146 153 position: absolute; … … 230 237 testRunner.dumpAsText(); 231 238 232 var g_state; 233 function globalState() 239 class Utils 234 240 { 235 if (!g_state) { 236 g_state = { 237 crashTests: [], 238 crashOther: [], 239 flakyPassTests: [], 240 hasHttpTests: false, 241 hasImageFailures: false, 242 hasTextFailures: false, 243 missingResults: [], 244 results: {}, 245 shouldToggleImages: true, 246 failingTests: [], 247 testsWithStderr: [], 248 timeoutTests: [], 249 unexpectedPassTests: [] 250 } 251 } 252 return g_state; 253 } 254 241 static matchesSelector(node, selector) 242 { 243 if (node.matches) 244 return node.matches(selector); 245 246 if (node.webkitMatchesSelector) 247 return node.webkitMatchesSelector(selector); 248 249 if (node.mozMatchesSelector) 250 return node.mozMatchesSelector(selector); 251 } 252 253 static parentOfType(node, selector) 254 { 255 while (node = node.parentNode) { 256 if (Utils.matchesSelector(node, selector)) 257 return node; 258 } 259 return null; 260 } 261 262 static stripExtension(testName) 263 { 264 // Temporary fix, also in Tools/Scripts/webkitpy/layout_tests/constrollers/test_result_writer.py, line 95. 265 // FIXME: Refactor to avoid confusing reference to both test and process names. 266 if (Utils.splitExtension(testName)[1].length > 5) 267 return testName; 268 return Utils.splitExtension(testName)[0]; 269 } 270 271 static splitExtension(testName) 272 { 273 let index = testName.lastIndexOf('.'); 274 if (index == -1) { 275 return [testName, '']; 276 } 277 return [testName.substring(0, index), testName.substring(index + 1)]; 278 } 279 280 static forEach(nodeList, handler) 281 { 282 Array.prototype.forEach.call(nodeList, handler); 283 } 284 285 static toArray(nodeList) 286 { 287 return Array.prototype.slice.call(nodeList); 288 } 289 290 static trim(string) 291 { 292 return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); 293 } 294 295 static async(func, args) 296 { 297 setTimeout(() => { func.apply(null, args); }, 50); 298 } 299 300 static appendHTML(node, html) 301 { 302 if (node.insertAdjacentHTML) 303 node.insertAdjacentHTML('beforeEnd', html); 304 else 305 node.innerHTML += html; 306 }}; 307 308 class TestResult 309 { 310 constructor(info, name) 311 { 312 this.name = name; 313 this.info = info; // FIXME: make this private. 314 } 315 316 isFailureExpected() 317 { 318 let actual = this.info.actual; 319 let expected = this.info.expected || 'PASS'; 320 321 if (actual != 'SKIP') { 322 let expectedArray = expected.split(' '); 323 let actualArray = actual.split(' '); 324 for (let actualValue of actualArray) { 325 if (expectedArray.indexOf(actualValue) == -1 && (expectedArray.indexOf('FAIL') == -1 || (actualValue != 'TEXT' && actualValue != 'IMAGE+TEXT' && actualValue != 'AUDIO'))) 326 return false; 327 } 328 } 329 return true; 330 } 331 332 isMissing() 333 { 334 return this.info.actual.indexOf('MISSING') != -1; 335 } 336 337 isFlakey(pixelTestsEnabled) 338 { 339 let actualTokens = this.info.actual.split(' '); 340 let passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE'; 341 if (actualTokens[1] && this.info.actual.indexOf('PASS') != -1 || (!pixelTestsEnabled && passedWithImageOnlyFailureInRetry)) 342 return true; 343 344 return false; 345 } 346 347 isPass() 348 { 349 return this.info.actual == 'PASS'; 350 } 351 352 isTextFailure() 353 { 354 return this.info.actual.indexOf('TEXT') != -1; 355 } 356 357 isImageFailure() 358 { 359 return this.info.actual.indexOf('IMAGE') != -1; 360 } 361 362 isAudioFailure() 363 { 364 return this.info.actual.indexOf('AUDIO') != -1; 365 } 366 367 isCrash() 368 { 369 return this.info.actual == 'CRASH'; 370 } 371 372 isTimeout() 373 { 374 return this.info.actual == 'TIMEOUT'; 375 } 376 377 isUnexpectedPass(pixelTestsEnabled) 378 { 379 if (this.info.actual == 'PASS' && this.info.expected != 'PASS') { 380 if (this.info.expected != 'IMAGE' || (pixelTestsEnabled || this.isRefTest())) 381 return true; 382 } 383 384 return false; 385 } 386 387 isRefTest() 388 { 389 return !!this.info.reftest_type; 390 } 391 392 isMismatchRefTest() 393 { 394 return this.isRefTest() && this.info.reftest_type.indexOf('!=') != -1; 395 } 396 397 isMatchRefTest() 398 { 399 return this.isRefTest() && this.info.reftest_type.indexOf('==') != -1; 400 } 401 402 isMissingImage() 403 { 404 return this.info.is_missing_image; 405 } 406 407 hasStdErr() 408 { 409 return this.info.has_stderr; 410 } 411 }; 412 413 class TestResults 414 { 415 constructor(results) 416 { 417 this._results = results; 418 419 this.crashTests = []; 420 this.crashOther = []; 421 this.missingResults = []; 422 this.failingTests = []; 423 this.testsWithStderr = []; 424 this.timeoutTests = []; 425 this.unexpectedPassTests = []; 426 this.flakyPassTests = []; 427 428 this.hasHttpTests = false; 429 this.hasImageFailures = false; 430 this.hasTextFailures = false; 431 432 this._forEachTest(this._results.tests, ''); 433 this._forOtherCrashes(this._results.other_crashes); 434 } 435 436 date() 437 { 438 return this._results.date; 439 } 440 441 layoutTestsDir() 442 { 443 return this._results.layout_tests_dir; 444 } 445 446 usesExpectationsFile() 447 { 448 return this._results.uses_expectations_file; 449 } 450 451 resultForTest(testName) 452 { 453 return this._resultsByTest[testName]; 454 } 455 456 wasInterrupted() 457 { 458 return this._results.interrupted; 459 } 460 461 hasPrettyPatch() 462 { 463 return this._results.has_pretty_patch; 464 } 465 466 hasWDiff() 467 { 468 return this._results.has_wdiff; 469 } 470 471 _processResultForTest(testResult) 472 { 473 let test = testResult.name; 474 if (testResult.hasStdErr()) 475 this.testsWithStderr.push(testResult); 476 477 this.hasHttpTests |= test.indexOf('http/') == 0; 478 479 if (this.usesExpectationsFile()) 480 testResult.isExpected = testResult.isFailureExpected(); 481 482 if (testResult.isTextFailure()) 483 this.hasTextFailures = true; 484 485 if (testResult.isImageFailure()) 486 this.hasImageFailures = true; 487 488 if (testResult.isMissing()) { 489 // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for tests with MISSING results. 490 this.missingResults.push(testResult); 491 return; 492 } 493 494 if (testResult.isFlakey(this._results.pixel_tests_enabled)) { 495 this.flakyPassTests.push(testResult); 496 return; 497 } 498 499 if (testResult.isPass()) { 500 if (testResult.isUnexpectedPass(this._results.pixel_tests_enabled)) 501 this.unexpectedPassTests.push(testResult); 502 return; 503 } 504 505 if (testResult.isCrash()) { 506 this.crashTests.push(testResult); 507 return; 508 } 509 510 if (testResult.isTimeout()) { 511 this.timeoutTests.push(testResult); 512 return; 513 } 514 515 this.failingTests.push(testResult); 516 } 517 518 _forEachTest(tree, prefix) 519 { 520 for (let key in tree) { 521 let newPrefix = prefix ? (prefix + '/' + key) : key; 522 if ('actual' in tree[key]) { 523 let testObject = new TestResult(tree[key], newPrefix); 524 this._processResultForTest(testObject); 525 } else 526 this._forEachTest(tree[key], newPrefix); 527 } 528 } 529 530 _forOtherCrashes(tree) 531 { 532 for (let key in tree) { 533 let testObject = new TestResult(tree[key], key); 534 this.crashOther.push(testObject); 535 } 536 } 537 538 static sortByName(tests) 539 { 540 tests.sort(function (a, b) { return a.name.localeCompare(b.name) }); 541 } 542 543 static hasUnexpectedResult(tests) 544 { 545 return tests.some(function (test) { return !test.isExpected; }); 546 } 547 }; 548 549 class TestResultsController 550 { 551 constructor(containerElement, testResults) 552 { 553 this.containerElement = containerElement; 554 this.testResults = testResults; 555 556 this.shouldToggleImages = true; 557 this._togglingImageInterval = null; 558 559 this._updatePageTitle(); 560 561 this.buildResultsTables(); 562 this.hideNonApplicableUI(); 563 this.setupSorting(); 564 this.setupOptions(); 565 } 566 567 buildResultsTables() 568 { 569 if (this.testResults.wasInterrupted()) { 570 let interruptionMessage = document.createElement('p'); 571 interruptionMessage.textContent = 'Testing exited early'; 572 interruptionMessage.classList.add('stopped-running-early-message'); 573 this.containerElement.appendChild(interruptionMessage); 574 } 575 576 if (this.testResults.crashTests.length) 577 this.containerElement.appendChild(this.buildOneSection(this.testResults.crashTests, CrashingTestsSectionBuilder)); 578 579 if (this.testResults.crashOther.length) 580 this.containerElement.appendChild(this.buildOneSection(this.testResults.crashOther, OtherCrashesSectionBuilder)); 581 582 if (this.testResults.failingTests.length) 583 this.containerElement.appendChild(this.buildOneSection(this.testResults.failingTests, FailingTestsSectionBuilder)); 584 585 if (this.testResults.missingResults.length) 586 this.containerElement.appendChild(this.buildOneSection(this.testResults.missingResults, TestsWithMissingResultsSectionBuilder)); 587 588 if (this.testResults.timeoutTests.length) 589 this.containerElement.appendChild(this.buildOneSection(this.testResults.timeoutTests, TimedOutTestsSectionBuilder)); 590 591 if (this.testResults.testsWithStderr.length) 592 this.containerElement.appendChild(this.buildOneSection(this.testResults.testsWithStderr, TestsWithStdErrSectionBuilder)); 593 594 if (this.testResults.flakyPassTests.length) 595 this.containerElement.appendChild(this.buildOneSection(this.testResults.flakyPassTests, FlakyPassTestsSectionBuilder)); 596 597 if (this.testResults.usesExpectationsFile() && this.testResults.unexpectedPassTests.length) 598 this.containerElement.appendChild(this.buildOneSection(this.testResults.unexpectedPassTests, UnexpectedPassTestsSectionBuilder)); 599 600 if (this.testResults.hasHttpTests) { 601 let httpdAccessLogLink = document.createElement('p'); 602 httpdAccessLogLink.innerHTML = 'httpd access log: <a href="access_log.txt">access_log.txt</a>'; 603 604 let httpdErrorLogLink = document.createElement('p'); 605 httpdErrorLogLink.innerHTML = 'httpd error log: <a href="error_log.txt">error_log.txt</a>'; 606 607 this.containerElement.appendChild(httpdAccessLogLink); 608 this.containerElement.appendChild(httpdErrorLogLink); 609 } 610 611 this.updateTestlistCounts(); 612 } 613 614 setupSorting() 615 { 616 let resultsTable = document.getElementById('results-table'); 617 if (!resultsTable) 618 return; 619 620 // FIXME: Make all the tables sortable. Maybe SectionBuilder should put a TableSorter on each table. 621 resultsTable.addEventListener('click', TableSorter.handleClick, false); 622 TableSorter.sortColumn(0); 623 } 624 625 hideNonApplicableUI() 626 { 627 // FIXME: do this all through body classnames. 628 if (!this.testResults.hasTextFailures) { 629 let textResultsHeader = document.getElementById('text-results-header'); 630 if (textResultsHeader) 631 textResultsHeader.textContent = ''; 632 } 633 634 if (!this.testResults.hasImageFailures) { 635 let imageResultsHeader = document.getElementById('image-results-header'); 636 if (imageResultsHeader) 637 imageResultsHeader.textContent = ''; 638 639 Utils.parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none'; 640 } 641 } 642 643 setupOptions() 644 { 645 // FIXME: do this all through body classnames. 646 if (!this.testResults.usesExpectationsFile()) 647 Utils.parentOfType(document.getElementById('unexpected-results'), 'label').style.display = 'none'; 648 } 649 650 buildOneSection(tests, sectionBuilderClass) 651 { 652 TestResults.sortByName(tests); 653 654 let sectionBuilder = new sectionBuilderClass(tests, this); 655 return sectionBuilder.build(); 656 } 657 658 updateTestlistCounts() 659 { 660 // FIXME: do this through the data model, not through the DOM. 661 let onlyShowUnexpectedFailures = this.onlyShowUnexpectedFailures(); 662 Utils.forEach(document.querySelectorAll('.test-list-count'), count => { 663 let container = Utils.parentOfType(count, 'section'); 664 let testContainers; 665 if (onlyShowUnexpectedFailures) 666 testContainers = container.querySelectorAll('tbody:not(.expected)'); 667 else 668 testContainers = container.querySelectorAll('tbody'); 669 670 count.textContent = testContainers.length; 671 }) 672 } 673 674 flagAll(headerLink) 675 { 676 let tests = this.visibleTests(Utils.parentOfType(headerLink, 'section')); 677 Utils.forEach(tests, tests => { 678 let shouldFlag = true; 679 testNavigator.flagTest(tests, shouldFlag); 680 }) 681 } 682 683 unflag(flag) 684 { 685 const shouldFlag = false; 686 testNavigator.flagTest(Utils.parentOfType(flag, 'tbody'), shouldFlag); 687 } 688 689 visibleTests(opt_container) 690 { 691 let container = opt_container || document; 692 if (this.onlyShowUnexpectedFailures()) 693 return container.querySelectorAll('tbody:not(.expected)'); 694 else 695 return container.querySelectorAll('tbody'); 696 } 697 698 // FIXME: this is confusing. Flip the sense around. 699 onlyShowUnexpectedFailures() 700 { 701 return document.getElementById('unexpected-results').checked; 702 } 703 704 static _testListHeader(title) 705 { 706 let header = document.createElement('h1'); 707 header.innerHTML = title + ' (<span class=test-list-count></span>): <a href="#" class=flag-all onclick="controller.flagAll(this)">flag all</a>'; 708 return header; 709 } 710 711 testToURL(testResult, layoutTestsPath) 712 { 713 const mappings = { 714 "http/tests/ssl/": "https://127.0.0.1:8443/ssl/", 715 "http/tests/": "http://127.0.0.1:8000/", 716 "http/wpt/": "http://localhost:8800/WebKit/", 717 "imported/w3c/web-platform-tests/": "http://localhost:8800/" 718 }; 719 720 for (let key in mappings) { 721 if (testResult.name.startsWith(key)) 722 return mappings[key] + testResult.name.substring(key.length); 723 724 } 725 return "file://" + layoutTestsPath + "/" + testResult.name; 726 } 727 728 layoutTestURL(testResult) 729 { 730 if (this.shouldUseTracLinks()) 731 return this.layoutTestsBasePath() + testResult.name; 732 733 return this.testToURL(testResult, this.layoutTestsBasePath()); 734 } 735 736 layoutTestsBasePath() 737 { 738 let basePath; 739 if (this.shouldUseTracLinks()) { 740 let revision = this.testResults.revision; 741 basePath = 'http://trac.webkit.org'; 742 basePath += revision ? ('/export/' + revision) : '/browser'; 743 basePath += '/trunk/LayoutTests/'; 744 } else 745 basePath = this.testResults.layoutTestsDir() + '/'; 746 747 return basePath; 748 } 749 750 shouldUseTracLinks() 751 { 752 return !this.testResults.layoutTestsDir() || !location.toString().indexOf('file://') == 0; 753 } 754 755 checkServerIsRunning(event) 756 { 757 if (this.shouldUseTracLinks()) 758 return; 759 760 let url = event.target.href; 761 if (url.startsWith("file://")) 762 return; 763 764 event.preventDefault(); 765 fetch(url, { mode: "no-cors" }).then(() => { 766 window.location = url; 767 }, () => { 768 alert("HTTP server does not seem to be running, please use the run-webkit-httpd script"); 769 }); 770 } 771 772 testLink(testResult) 773 { 774 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>'; 775 } 776 777 static resultLink(testPrefix, suffix, contents) 778 { 779 return '<a class=result-link href="' + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> '; 780 } 781 782 textResultLinks(prefix) 783 { 784 let html = TestResultsController.resultLink(prefix, '-expected.txt', 'expected') + 785 TestResultsController.resultLink(prefix, '-actual.txt', 'actual') + 786 TestResultsController.resultLink(prefix, '-diff.txt', 'diff'); 787 788 if (this.testResults.hasPrettyPatch()) 789 html += TestResultsController.resultLink(prefix, '-pretty-diff.html', 'pretty diff'); 790 791 if (this.testResults.hasWDiff()) 792 html += TestResultsController.resultLink(prefix, '-wdiff.html', 'wdiff'); 793 794 return html; 795 } 796 797 flakinessDashboardURLForTests(testObjects) 798 { 799 // FIXME: just map and join here. 800 let testList = ''; 801 for (let i = 0; i < testObjects.length; ++i) { 802 testList += testObjects[i].name; 803 804 if (i != testObjects.length - 1) 805 testList += ','; 806 } 807 808 return 'http://webkit-test-results.webkit.org/dashboards/flakiness_dashboard.html#showAllRuns=true&tests=' + encodeURIComponent(testList); 809 } 810 811 _updatePageTitle() 812 { 813 let dateString = this.testResults.date(); 814 let title = document.createElement('title'); 815 title.textContent = 'Layout Test Results from ' + dateString; 816 document.head.appendChild(title); 817 } 818 819 // Options handling. FIXME: move to a separate class? 820 updateAllOptions() 821 { 822 Utils.forEach(document.querySelectorAll('#options-menu input'), input => { input.onchange() }); 823 } 824 825 toggleOptionsMenu() 826 { 827 let menu = document.getElementById('options-menu'); 828 menu.className = (menu.className == 'hidden-menu') ? '' : 'hidden-menu'; 829 } 830 831 handleToggleUseNewlines() 832 { 833 OptionWriter.save(); 834 testNavigator.updateFlaggedTests(); 835 } 836 837 handleUnexpectedResultsChange() 838 { 839 OptionWriter.save(); 840 this._updateExpectedFailures(); 841 } 842 843 expandAllExpectations() 844 { 845 let expandLinks = this._visibleExpandLinks(); 846 for (let link of expandLinks) 847 Utils.async(link => { controller.expandExpectations(link) }, [ link ]); 848 } 849 850 collapseAllExpectations() 851 { 852 let expandLinks = this._visibleExpandLinks(); 853 for (let link of expandLinks) 854 Utils.async(link => { controller.collapseExpectations(link) }, [ link ]); 855 } 856 857 expandExpectations(expandLink) 858 { 859 let row = Utils.parentOfType(expandLink, 'tr'); 860 let parentTbody = row.parentNode; 861 let existingResultsRow = parentTbody.querySelector('.results-row'); 862 863 const enDash = '\u2013'; 864 expandLink.textContent = enDash; 865 if (existingResultsRow) { 866 this._updateExpandedState(existingResultsRow, true); 867 return; 868 } 869 870 let newRow = document.createElement('tr'); 871 newRow.className = 'results-row'; 872 let newCell = document.createElement('td'); 873 newCell.colSpan = row.querySelectorAll('td').length; 874 875 let resultLinks = row.querySelectorAll('.result-link'); 876 let hasTogglingImages = false; 877 for (let link of resultLinks) { 878 let result; 879 if (link.textContent == 'images') { 880 hasTogglingImages = true; 881 result = TestResultsController._togglingImage(link.getAttribute('data-prefix')); 882 } else 883 result = TestResultsController._resultIframe(link.href); 884 885 Utils.appendHTML(newCell, result); 886 } 887 888 newRow.appendChild(newCell); 889 parentTbody.appendChild(newRow); 890 891 this._updateExpandedState(newRow, true); 892 893 this._updateImageTogglingTimer(); 894 } 895 896 collapseExpectations(expandLink) 897 { 898 expandLink.textContent = '+'; 899 let existingResultsRow = Utils.parentOfType(expandLink, 'tbody').querySelector('.results-row'); 900 if (existingResultsRow) 901 this._updateExpandedState(existingResultsRow, false); 902 } 903 904 toggleExpectations(element) 905 { 906 let expandLink = element; 907 if (expandLink.className != 'expand-button-text') 908 expandLink = expandLink.querySelector('.expand-button-text'); 909 910 if (expandLink.textContent == '+') 911 this.expandExpectations(expandLink); 912 else 913 this.collapseExpectations(expandLink); 914 } 915 916 _updateExpandedState(row, isExpanded) 917 { 918 row.setAttribute('data-expanded', isExpanded); 919 this._updateImageTogglingTimer(); 920 } 921 922 handleToggleImagesChange() 923 { 924 OptionWriter.save(); 925 this._updateTogglingImages(); 926 } 927 928 _visibleExpandLinks() 929 { 930 if (this.onlyShowUnexpectedFailures()) 931 return document.querySelectorAll('tbody:not(.expected) .expand-button-text'); 932 else 933 return document.querySelectorAll('.expand-button-text'); 934 } 935 936 static _togglingImage(prefix) 937 { 938 return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' + prefix + '"></img></div>'; 939 } 940 941 _updateTogglingImages() 942 { 943 this.shouldToggleImages = document.getElementById('toggle-images').checked; 944 945 // FIXME: this is all pretty confusing. Simplify. 946 if (this.shouldToggleImages) { 947 Utils.forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), TestResultsController._convertToTogglingHandler(function(prefix) { 948 return TestResultsController.resultLink(prefix, '-diffs.html', 'images'); 949 })); 950 Utils.forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), TestResultsController._convertToTogglingHandler(TestResultsController._togglingImage)); 951 } else { 952 Utils.forEach(document.querySelectorAll('a[href$="-diffs.html"]'), element => { 953 TestResultsController._convertToNonTogglingHandler(element); 954 }); 955 Utils.forEach(document.querySelectorAll('.animatedImage'), TestResultsController._convertToNonTogglingHandler(function (absolutePrefix, suffix) { 956 return TestResultsController._resultIframe(absolutePrefix + suffix); 957 })); 958 } 959 960 this._updateImageTogglingTimer(); 961 } 962 963 _updateExpectedFailures() 964 { 965 // Gross to do this by setting stylesheet text. Use a body class! 966 document.getElementById('unexpected-style').textContent = this.onlyShowUnexpectedFailures() ? '.expected { display: none; }' : ''; 967 968 this.updateTestlistCounts(); 969 testNavigator.onlyShowUnexpectedFailuresChanged(); 970 } 971 972 static _resultIframe(src) 973 { 974 // FIXME: use audio tags for AUDIO tests? 975 let layoutTestsIndex = src.indexOf('LayoutTests'); 976 let name; 977 if (layoutTestsIndex != -1) { 978 let hasTrac = src.indexOf('trac.webkit.org') != -1; 979 let prefix = hasTrac ? 'trac.webkit.org/.../' : ''; 980 name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length); 981 } else { 982 let lastDashIndex = src.lastIndexOf('-pretty'); 983 if (lastDashIndex == -1) 984 lastDashIndex = src.lastIndexOf('-'); 985 name = src.substring(lastDashIndex + 1); 986 } 987 988 let tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img'; 989 990 if (tagName != 'img') 991 src += '?format=txt'; 992 return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="' + src + '"></' + tagName + '></div>'; 993 } 994 995 996 static _toggleImages() 997 { 998 let images = document.querySelectorAll('.animatedImage'); 999 let imageTexts = document.querySelectorAll('.imageText'); 1000 for (let i = 0, len = images.length; i < len; i++) { 1001 let image = images[i]; 1002 let text = imageTexts[i]; 1003 if (text.textContent == 'Expected Image') { 1004 text.textContent = 'Actual Image'; 1005 image.src = image.getAttribute('data-prefix') + '-actual.png'; 1006 } else { 1007 text.textContent = 'Expected Image'; 1008 image.src = image.getAttribute('data-prefix') + '-expected.png'; 1009 } 1010 } 1011 } 1012 1013 _updateImageTogglingTimer() 1014 { 1015 let hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage'); 1016 if (!hasVisibleAnimatedImage) { 1017 clearInterval(this._togglingImageInterval); 1018 this._togglingImageInterval = null; 1019 return; 1020 } 1021 1022 if (!this._togglingImageInterval) { 1023 TestResultsController._toggleImages(); 1024 this._togglingImageInterval = setInterval(TestResultsController._toggleImages, 2000); 1025 } 1026 } 1027 1028 static _getResultContainer(node) 1029 { 1030 return (node.tagName == 'IMG') ? Utils.parentOfType(node, '.result-container') : node; 1031 } 1032 1033 static _convertToTogglingHandler(togglingImageFunction) 1034 { 1035 return function(node) { 1036 let url = (node.tagName == 'IMG') ? node.src : node.href; 1037 if (url.match('-expected.png$')) 1038 TestResultsController._getResultContainer(node).remove(); 1039 else if (url.match('-actual.png$')) { 1040 let name = Utils.parentOfType(node, 'tbody').querySelector('.test-link').textContent; 1041 TestResultsController._getResultContainer(node).outerHTML = togglingImageFunction(Utils.stripExtension(name)); 1042 } 1043 } 1044 } 1045 1046 static _convertToNonTogglingHandler(resultFunction) 1047 { 1048 return function(node) { 1049 let prefix = node.getAttribute('data-prefix'); 1050 TestResultsController._getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual'); 1051 } 1052 } 1053 }; 1054 1055 class SectionBuilder { 1056 1057 constructor(tests, resultsController) 1058 { 1059 this._tests = tests; 1060 this._table = null; 1061 this._resultsController = resultsController; 1062 } 1063 1064 build() 1065 { 1066 TestResults.sortByName(this._tests); 1067 1068 let section = document.createElement('section'); 1069 section.appendChild(TestResultsController._testListHeader(this.sectionTitle())); 1070 if (this.hideWhenShowingUnexpectedResultsOnly()) 1071 section.classList.add('expected'); 1072 1073 this._table = document.createElement('table'); 1074 this._table.id = this.tableID(); 1075 this.addTableHeader(); 1076 1077 let visibleResultsCount = 0; 1078 for (let testResult of this._tests) { 1079 let tbody = this.createTableRow(testResult); 1080 this._table.appendChild(tbody); 1081 1082 if (!this._resultsController.onlyShowUnexpectedFailures() || testResult.isExpected) 1083 ++visibleResultsCount; 1084 } 1085 1086 section.querySelector('.test-list-count').textContent = visibleResultsCount; 1087 section.appendChild(this._table); 1088 return section; 1089 } 1090 1091 createTableRow(testResult) 1092 { 1093 let tbody = document.createElement('tbody'); 1094 if (testResult.isExpected) 1095 tbody.classList.add('expected'); 1096 1097 let row = document.createElement('tr'); 1098 tbody.appendChild(row); 1099 1100 let testNameCell = document.createElement('td'); 1101 this.fillTestCell(testResult, testNameCell); 1102 row.appendChild(testNameCell); 1103 1104 let resultCell = document.createElement('td'); 1105 this.fillTestResultCell(testResult, resultCell); 1106 row.appendChild(resultCell); 1107 1108 let historyCell = this.createHistoryCell(testResult); 1109 if (historyCell) 1110 row.appendChild(historyCell); 1111 1112 return tbody; 1113 } 1114 1115 hideWhenShowingUnexpectedResultsOnly() 1116 { 1117 return !TestResults.hasUnexpectedResult(this._tests); 1118 } 1119 1120 addTableHeader() 1121 { 1122 } 1123 1124 fillTestCell(testResult, cell) 1125 { 1126 cell.innerHTML = '<span class=expand-button onclick="controller.toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + this._resultsController.testLink(testResult); 1127 } 1128 1129 fillTestResultCell(testResult, cell) 1130 { 1131 } 1132 1133 createHistoryCell(testResult) 1134 { 1135 let historyCell = document.createElement('td'); 1136 historyCell.innerHTML = '<a href="' + this._resultsController.flakinessDashboardURLForTests([testResult]) + '">history</a>' 1137 return historyCell; 1138 } 1139 1140 tableID() { return ''; } 1141 sectionTitle() { return ''; } 1142 }; 1143 1144 class FailuresSectionBuilder extends SectionBuilder { 1145 1146 addTableHeader() 1147 { 1148 let header = document.createElement('thead'); 1149 let html = '<th>test</th><th id="text-results-header">results</th><th id="image-results-header">image results</th>'; 1150 1151 if (this._resultsController.testResults.usesExpectationsFile()) 1152 html += '<th>actual failure</th><th>expected failure</th>'; 1153 1154 html += '<th><a href="' + this._resultsController.flakinessDashboardURLForTests(this._tests) + '">history</a></th>'; 1155 1156 if (this.tableID() == 'flaky-tests-table') // FIXME: use the classes, Luke! 1157 html += '<th>failures</th>'; 1158 1159 header.innerHTML = html; 1160 this._table.appendChild(header); 1161 } 1162 1163 createTableRow(testResult) 1164 { 1165 let tbody = document.createElement('tbody'); 1166 if (testResult.isExpected) 1167 tbody.classList.add('expected'); 1168 1169 if (testResult.isMismatchRefTest()) 1170 tbody.setAttribute('mismatchreftest', 'true'); 1171 1172 let row = document.createElement('tr'); 1173 tbody.appendChild(row); 1174 1175 let testNameCell = document.createElement('td'); 1176 this.fillTestCell(testResult, testNameCell); 1177 row.appendChild(testNameCell); 1178 1179 let resultCell = document.createElement('td'); 1180 this.fillTestResultCell(testResult, resultCell); 1181 row.appendChild(resultCell); 1182 1183 if (testResult.isTextFailure()) 1184 this.appendTextFailureLinks(testResult, resultCell); 1185 1186 if (testResult.isAudioFailure()) 1187 this.appendAudioFailureLinks(testResult, resultCell); 1188 1189 if (testResult.isMissing()) 1190 this.appendActualOnlyLinks(testResult, resultCell); 1191 1192 let actualTokens = testResult.info.actual.split(/\s+/); 1193 1194 let testPrefix = Utils.stripExtension(testResult.name); 1195 let imageResults = this.imageResultLinks(testResult, testPrefix, actualTokens[0]); 1196 if (!imageResults && actualTokens.length > 1) 1197 imageResults = this.imageResultLinks(testResult, 'retries/' + testPrefix, actualTokens[1]); 1198 1199 let imageResultsCell = document.createElement('td'); 1200 imageResultsCell.innerHTML = imageResults; 1201 row.appendChild(imageResultsCell); 1202 1203 if (this._resultsController.testResults.usesExpectationsFile() || actualTokens.length) { 1204 let actualCell = document.createElement('td'); 1205 actualCell.textContent = testResult.info.actual; 1206 row.appendChild(actualCell); 1207 } 1208 1209 if (this._resultsController.testResults.usesExpectationsFile()) { 1210 let expectedCell = document.createElement('td'); 1211 expectedCell.textContent = testResult.isMissing() ? '' : testResult.info.expected; 1212 row.appendChild(expectedCell); 1213 } 1214 1215 let historyCell = this.createHistoryCell(testResult); 1216 if (historyCell) 1217 row.appendChild(historyCell); 1218 1219 return tbody; 1220 } 1221 1222 appendTextFailureLinks(testResult, cell) 1223 { 1224 cell.innerHTML += this._resultsController.textResultLinks(Utils.stripExtension(testResult.name)); 1225 } 1226 1227 appendAudioFailureLinks(testResult, cell) 1228 { 1229 let prefix = Utils.stripExtension(testResult.name); 1230 cell.innerHTML += TestResultsController.resultLink(prefix, '-expected.wav', 'expected audio') 1231 + TestResultsController.resultLink(prefix, '-actual.wav', 'actual audio') 1232 + TestResultsController.resultLink(prefix, '-diff.txt', 'textual diff'); 1233 } 1234 1235 appendActualOnlyLinks(testResult, cell) 1236 { 1237 if (testResult.info.is_missing_audio) 1238 cell.innerHTML += TestResultsController.resultLink(prefix, '-actual.wav', 'audio result'); 1239 1240 if (testResult.info.is_missing_text) 1241 cell.innerHTML += TestResultsController.resultLink(prefix, '-actual.txt', 'result'); 1242 } 1243 1244 imageResultLinks(testResult, testPrefix, resultToken) 1245 { 1246 let result = ''; 1247 if (resultToken.indexOf('IMAGE') != -1) { 1248 let testExtension = Utils.splitExtension(testResult.name)[1]; 1249 1250 if (testResult.isMismatchRefTest()) { 1251 result += TestResultsController.resultLink(this._resultsController.layoutTestsBasePath() + testPrefix, '-expected-mismatch.' + testExtension, 'ref mismatch'); 1252 result += TestResultsController.resultLink(testPrefix, '-actual.png', 'actual'); 1253 } else { 1254 if (testResult.isMatchRefTest()) 1255 result += TestResultsController.resultLink(this._resultsController.layoutTestsBasePath() + testPrefix, '-expected.' + testExtension, 'reference'); 1256 1257 if (this._resultsController.shouldToggleImages) 1258 result += TestResultsController.resultLink(testPrefix, '-diffs.html', 'images'); 1259 else { 1260 result += TestResultsController.resultLink(testPrefix, '-expected.png', 'expected'); 1261 result += TestResultsController.resultLink(testPrefix, '-actual.png', 'actual'); 1262 } 1263 1264 let diff = testResult.info.image_diff_percent; 1265 result += TestResultsController.resultLink(testPrefix, '-diff.png', 'diff (' + diff + '%)'); 1266 } 1267 } 1268 1269 if (testResult.isMissing() && testResult.isMissingImage()) 1270 result += TestResultsController.resultLink(testPrefix, '-actual.png', 'png result'); 1271 1272 return result; 1273 } 1274 }; 1275 1276 class FailingTestsSectionBuilder extends FailuresSectionBuilder { 1277 tableID() { return 'results-table'; } 1278 sectionTitle() { return 'Tests that failed text/pixel/audio diff'; } 1279 }; 1280 1281 class TestsWithMissingResultsSectionBuilder extends FailuresSectionBuilder { 1282 tableID() { return 'missing-table'; } 1283 sectionTitle() { return 'Tests that had no expected results (probably new)'; } 1284 }; 1285 1286 class FlakyPassTestsSectionBuilder extends FailuresSectionBuilder { 1287 tableID() { return 'flaky-tests-table'; } 1288 sectionTitle() { return 'Flaky tests (failed the first run and passed on retry)'; } 1289 }; 1290 1291 class UnexpectedPassTestsSectionBuilder extends SectionBuilder { 1292 tableID() { return 'passes-table'; } 1293 sectionTitle() { return 'Tests expected to fail but passed'; } 1294 1295 addTableHeader() 1296 { 1297 let header = document.createElement('thead'); 1298 header.innerHTML = '<th>test</th><th>expected failure</th><th>history</th>'; 1299 this._table.appendChild(header); 1300 } 1301 1302 fillTestCell(testResult, cell) 1303 { 1304 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>'; 1305 } 1306 1307 fillTestResultCell(testResult, cell) 1308 { 1309 cell.innerHTML = testResult.info.expected; 1310 } 1311 }; 1312 1313 1314 class TestsWithStdErrSectionBuilder extends SectionBuilder { 1315 tableID() { return 'stderr-table'; } 1316 sectionTitle() { return 'Tests that had stderr output'; } 1317 hideWhenShowingUnexpectedResultsOnly() { return false; } 1318 1319 fillTestResultCell(testResult, cell) 1320 { 1321 cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-stderr.txt', 'stderr'); 1322 } 1323 }; 1324 1325 class TimedOutTestsSectionBuilder extends SectionBuilder { 1326 tableID() { return 'timeout-tests-table'; } 1327 sectionTitle() { return 'Tests that timed out'; } 1328 1329 fillTestResultCell(testResult, cell) 1330 { 1331 // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests. 1332 cell.innerHTML = this._resultsController.textResultLinks(Utils.stripExtension(testResult.name)); 1333 } 1334 }; 1335 1336 class CrashingTestsSectionBuilder extends SectionBuilder { 1337 tableID() { return 'crash-tests-table'; } 1338 sectionTitle() { return 'Tests that crashed'; } 1339 1340 fillTestResultCell(testResult, cell) 1341 { 1342 cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-crash-log.txt', 'crash log') 1343 + TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-sample.txt', 'sample'); 1344 } 1345 }; 1346 1347 class OtherCrashesSectionBuilder extends SectionBuilder { 1348 tableID() { return 'other-crash-tests-table'; } 1349 sectionTitle() { return 'Other crashes'; } 1350 fillTestCell(testResult, cell) 1351 { 1352 cell.innerHTML = '<span class=expand-button onclick="controller.toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testResult.name; 1353 } 1354 1355 fillTestResultCell(testResult, cell) 1356 { 1357 cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-crash-log.txt', 'crash log'); 1358 } 1359 1360 createHistoryCell(testResult) 1361 { 1362 return null; 1363 } 1364 }; 1365 1366 class PixelZoomer { 1367 constructor() 1368 { 1369 this.showOnDelay = true; 1370 this._zoomFactor = 6; 1371 1372 this._resultWidth = 800; 1373 this._resultHeight = 600; 1374 1375 this._percentX = 0; 1376 this._percentY = 0; 1377 1378 document.addEventListener('mousemove', this, false); 1379 document.addEventListener('mouseout', this, false); 1380 } 1381 1382 _zoomedResultWidth() 1383 { 1384 return this._resultWidth * this._zoomFactor; 1385 } 1386 1387 _zoomedResultHeight() 1388 { 1389 return this._resultHeight * this._zoomFactor; 1390 } 1391 1392 _zoomImageContainer(url) 1393 { 1394 let container = document.createElement('div'); 1395 container.className = 'zoom-image-container'; 1396 1397 let title = url.match(/\-([^\-]*)\.png/)[1]; 1398 1399 let label = document.createElement('div'); 1400 label.className = 'label'; 1401 label.appendChild(document.createTextNode(title)); 1402 container.appendChild(label); 1403 1404 let imageContainer = document.createElement('div'); 1405 imageContainer.className = 'scaled-image-container'; 1406 1407 let image = new Image(); 1408 image.src = url; 1409 image.style.width = this._zoomedResultWidth() + 'px'; 1410 image.style.height = this._zoomedResultHeight() + 'px'; 1411 image.style.border = '1px solid black'; 1412 imageContainer.appendChild(image); 1413 container.appendChild(imageContainer); 1414 1415 return container; 1416 } 1417 1418 _createContainer(e) 1419 { 1420 let tbody = Utils.parentOfType(e.target, 'tbody'); 1421 let row = tbody.querySelector('tr'); 1422 let imageDiffLinks = row.querySelectorAll('a[href$=".png"]'); 1423 1424 let container = document.createElement('div'); 1425 container.className = 'pixel-zoom-container'; 1426 1427 let html = ''; 1428 1429 let togglingImageLink = row.querySelector('a[href$="-diffs.html"]'); 1430 if (togglingImageLink) { 1431 let prefix = togglingImageLink.getAttribute('data-prefix'); 1432 container.appendChild(this._zoomImageContainer(prefix + '-expected.png')); 1433 container.appendChild(this._zoomImageContainer(prefix + '-actual.png')); 1434 } 1435 1436 for (let link of imageDiffLinks) 1437 container.appendChild(this._zoomImageContainer(link.href)); 1438 1439 document.body.appendChild(container); 1440 this._drawAll(); 1441 } 1442 1443 _draw(imageContainer) 1444 { 1445 let image = imageContainer.querySelector('img'); 1446 let containerBounds = imageContainer.getBoundingClientRect(); 1447 image.style.left = (containerBounds.width / 2 - this._percentX * this._zoomedResultWidth()) + 'px'; 1448 image.style.top = (containerBounds.height / 2 - this._percentY * this._zoomedResultHeight()) + 'px'; 1449 } 1450 1451 _drawAll() 1452 { 1453 Utils.forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), element => { this._draw(element) }); 1454 } 1455 1456 handleEvent(event) 1457 { 1458 if (event.type == 'mousemove') { 1459 this._handleMouseMove(event); 1460 return; 1461 } 1462 1463 if (event.type == 'mouseout') { 1464 this._handleMouseOut(event); 1465 return; 1466 } 1467 } 1468 1469 _handleMouseOut(event) 1470 { 1471 if (event.relatedTarget && event.relatedTarget.tagName != 'IFRAME') 1472 return; 1473 1474 // If e.relatedTarget is null, we've moused out of the document. 1475 let container = document.querySelector('.pixel-zoom-container'); 1476 if (container) 1477 container.remove(); 1478 } 1479 1480 _handleMouseMove(event) 1481 { 1482 if (this._mouseMoveTimeout) { 1483 clearTimeout(this._mouseMoveTimeout); 1484 this._mouseMoveTimeout = 0; 1485 } 1486 1487 if (Utils.parentOfType(event.target, '.pixel-zoom-container')) 1488 return; 1489 1490 let container = document.querySelector('.pixel-zoom-container'); 1491 1492 let resultContainer = (event.target.className == 'result-container') ? event.target : Utils.parentOfType(event.target, '.result-container'); 1493 if (!resultContainer || !resultContainer.querySelector('img')) { 1494 if (container) 1495 container.remove(); 1496 return; 1497 } 1498 1499 let targetLocation = event.target.getBoundingClientRect(); 1500 this._percentX = (event.clientX - targetLocation.left) / targetLocation.width; 1501 this._percentY = (event.clientY - targetLocation.top) / targetLocation.height; 1502 1503 if (!container) { 1504 if (this.showOnDelay) { 1505 this._mouseMoveTimeout = setTimeout(() => { 1506 this._createContainer(event); 1507 }, 400); 1508 return; 1509 } 1510 1511 this._createContainer(event); 1512 return; 1513 } 1514 1515 this._drawAll(); 1516 } 1517 }; 1518 1519 class TableSorter 1520 { 1521 static _forwardArrow() 1522 { 1523 return '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>'; 1524 } 1525 1526 static _backwardArrow() 1527 { 1528 return '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>'; 1529 } 1530 1531 static _sortedContents(header, arrow) 1532 { 1533 return arrow + ' ' + Utils.trim(header.textContent) + ' ' + arrow; 1534 } 1535 1536 static _updateHeaderClassNames(newHeader) 1537 { 1538 let sortHeader = document.querySelector('.sortHeader'); 1539 if (sortHeader) { 1540 if (sortHeader == newHeader) { 1541 let isAlreadyReversed = sortHeader.classList.contains('reversed'); 1542 if (isAlreadyReversed) 1543 sortHeader.classList.remove('reversed'); 1544 else 1545 sortHeader.classList.add('reversed'); 1546 } else { 1547 sortHeader.textContent = sortHeader.textContent; 1548 sortHeader.classList.remove('sortHeader'); 1549 sortHeader.classList.remove('reversed'); 1550 } 1551 } 1552 1553 newHeader.classList.add('sortHeader'); 1554 } 1555 1556 static _textContent(tbodyRow, column) 1557 { 1558 return tbodyRow.querySelectorAll('td')[column].textContent; 1559 } 1560 1561 static _sortRows(newHeader, reversed) 1562 { 1563 let testsTable = document.getElementById('results-table'); 1564 let headers = Utils.toArray(testsTable.querySelectorAll('th')); 1565 let sortColumn = headers.indexOf(newHeader); 1566 1567 let rows = Utils.toArray(testsTable.querySelectorAll('tbody')); 1568 1569 rows.sort(function(a, b) { 1570 // Only need to support lexicographic sort for now. 1571 let aText = TableSorter._textContent(a, sortColumn); 1572 let bText = TableSorter._textContent(b, sortColumn); 1573 1574 // Forward sort equal values by test name. 1575 if (sortColumn && aText == bText) { 1576 let aTestName = TableSorter._textContent(a, 0); 1577 let bTestName = TableSorter._textContent(b, 0); 1578 if (aTestName == bTestName) 1579 return 0; 1580 return aTestName < bTestName ? -1 : 1; 1581 } 1582 1583 if (reversed) 1584 return aText < bText ? 1 : -1; 1585 else 1586 return aText < bText ? -1 : 1; 1587 }); 1588 1589 for (let row of rows) 1590 testsTable.appendChild(row); 1591 } 1592 1593 static sortColumn(columnNumber) 1594 { 1595 let newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber]; 1596 TableSorter._sort(newHeader); 1597 } 1598 1599 static handleClick(e) 1600 { 1601 let newHeader = e.target; 1602 if (newHeader.localName != 'th') 1603 return; 1604 TableSorter._sort(newHeader); 1605 } 1606 1607 static _sort(newHeader) 1608 { 1609 TableSorter._updateHeaderClassNames(newHeader); 1610 1611 let reversed = newHeader.classList.contains('reversed'); 1612 let sortArrow = reversed ? TableSorter._backwardArrow() : TableSorter._forwardArrow(); 1613 newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow); 1614 1615 TableSorter._sortRows(newHeader, reversed); 1616 } 1617 }; 1618 1619 class OptionWriter { 1620 static save() 1621 { 1622 let options = document.querySelectorAll('label input'); 1623 let data = {}; 1624 for (let option of options) 1625 data[option.id] = option.checked; 1626 1627 try { 1628 localStorage.setItem(OptionWriter._key, JSON.stringify(data)); 1629 } catch (err) { 1630 if (err.name != "SecurityError") 1631 throw err; 1632 } 1633 } 1634 1635 static apply() 1636 { 1637 let json; 1638 try { 1639 json = localStorage.getItem(OptionWriter._key); 1640 } catch (err) { 1641 if (err.name != "SecurityError") 1642 throw err; 1643 } 1644 1645 if (!json) { 1646 controller.updateAllOptions(); 1647 return; 1648 } 1649 1650 let data = JSON.parse(json); 1651 for (let id in data) { 1652 let input = document.getElementById(id); 1653 if (input) 1654 input.checked = data[id]; 1655 } 1656 controller.updateAllOptions(); 1657 } 1658 1659 static get _key() 1660 { 1661 return 'run-webkit-tests-options'; 1662 } 1663 }; 1664 1665 let testResults; 255 1666 function ADD_RESULTS(input) 256 1667 { 257 globalState().results = input;1668 testResults = new TestResults(input); 258 1669 } 259 1670 </script> … … 262 1673 263 1674 <script> 264 function splitExtension(test) 1675 1676 class TestNavigator 265 1677 { 266 var index = test.lastIndexOf('.'); 267 if (index == -1) { 268 return [test, ""]; 269 } 270 return [test.substring(0, index), test.substring(index + 1)]; 271 } 272 273 function stripExtension(test) 274 { 275 // Temporary fix, also in Tools/Scripts/webkitpy/layout_tests/constrollers/test_result_writer.py, line 95. 276 // FIXME: Refactor to avoid confusing reference to both test and process names. 277 if (splitExtension(test)[1].length > 5) 278 return test; 279 return splitExtension(test)[0]; 280 } 281 282 function matchesSelector(node, selector) 283 { 284 if (node.matches) 285 return node.matches(selector); 286 287 if (node.webkitMatchesSelector) 288 return node.webkitMatchesSelector(selector); 289 290 if (node.mozMatchesSelector) 291 return node.mozMatchesSelector(selector); 292 } 293 294 function parentOfType(node, selector) 295 { 296 while (node = node.parentNode) { 297 if (matchesSelector(node, selector)) 298 return node; 299 } 300 return null; 301 } 302 303 function remove(node) 304 { 305 node.parentNode.removeChild(node); 306 } 307 308 function forEach(nodeList, handler) 309 { 310 Array.prototype.forEach.call(nodeList, handler); 311 } 312 313 function resultIframe(src) 314 { 315 // FIXME: use audio tags for AUDIO tests? 316 var layoutTestsIndex = src.indexOf('LayoutTests'); 317 var name; 318 if (layoutTestsIndex != -1) { 319 var hasTrac = src.indexOf('trac.webkit.org') != -1; 320 var prefix = hasTrac ? 'trac.webkit.org/.../' : ''; 321 name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length); 322 } else { 323 var lastDashIndex = src.lastIndexOf('-pretty'); 324 if (lastDashIndex == -1) 325 lastDashIndex = src.lastIndexOf('-'); 326 name = src.substring(lastDashIndex + 1); 327 } 328 329 var tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img'; 330 331 if (tagName != 'img') 332 src += '?format=txt'; 333 return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="' + src + '"></' + tagName + '></div>'; 334 } 335 336 function togglingImage(prefix) 337 { 338 return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' + 339 prefix + '"></img></div>'; 340 } 341 342 function toggleExpectations(element) 343 { 344 var expandLink = element; 345 if (expandLink.className != 'expand-button-text') 346 expandLink = expandLink.querySelector('.expand-button-text'); 347 348 if (expandLink.textContent == '+') 349 expandExpectations(expandLink); 350 else 351 collapseExpectations(expandLink); 352 } 353 354 function collapseExpectations(expandLink) 355 { 356 expandLink.textContent = '+'; 357 var existingResultsRow = parentOfType(expandLink, 'tbody').querySelector('.results-row'); 358 if (existingResultsRow) 359 updateExpandedState(existingResultsRow, false); 360 } 361 362 function updateExpandedState(row, isExpanded) 363 { 364 row.setAttribute('data-expanded', isExpanded); 365 updateImageTogglingTimer(); 366 } 367 368 function appendHTML(node, html) 369 { 370 if (node.insertAdjacentHTML) 371 node.insertAdjacentHTML('beforeEnd', html); 372 else 373 node.innerHTML += html; 374 } 375 376 function expandExpectations(expandLink) 377 { 378 var row = parentOfType(expandLink, 'tr'); 379 var parentTbody = row.parentNode; 380 var existingResultsRow = parentTbody.querySelector('.results-row'); 381 382 var enDash = '\u2013'; 383 expandLink.textContent = enDash; 384 if (existingResultsRow) { 385 updateExpandedState(existingResultsRow, true); 386 return; 387 } 388 389 var newRow = document.createElement('tr'); 390 newRow.className = 'results-row'; 391 var newCell = document.createElement('td'); 392 newCell.colSpan = row.querySelectorAll('td').length; 393 394 var resultLinks = row.querySelectorAll('.result-link'); 395 var hasTogglingImages = false; 396 for (var i = 0; i < resultLinks.length; i++) { 397 var link = resultLinks[i]; 398 var result; 399 if (link.textContent == 'images') { 400 hasTogglingImages = true; 401 result = togglingImage(link.getAttribute('data-prefix')); 402 } else 403 result = resultIframe(link.href); 404 405 appendHTML(newCell, result); 406 } 407 408 newRow.appendChild(newCell); 409 parentTbody.appendChild(newRow); 410 411 updateExpandedState(newRow, true); 412 413 updateImageTogglingTimer(); 414 } 415 416 function updateImageTogglingTimer() 417 { 418 var hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage'); 419 if (!hasVisibleAnimatedImage) { 420 clearInterval(globalState().togglingImageInterval); 421 globalState().togglingImageInterval = null; 422 return; 423 } 424 425 if (!globalState().togglingImageInterval) { 426 toggleImages(); 427 globalState().togglingImageInterval = setInterval(toggleImages, 2000); 428 } 429 } 430 431 function async(func, args) 432 { 433 setTimeout(function() { func.apply(null, args); }, 100); 434 } 435 436 function visibleTests(opt_container) 437 { 438 var container = opt_container || document; 439 if (onlyShowUnexpectedFailures()) 440 return container.querySelectorAll('tbody:not(.expected)'); 441 else 442 return container.querySelectorAll('tbody'); 443 } 444 445 function visibleExpandLinks() 446 { 447 if (onlyShowUnexpectedFailures()) 448 return document.querySelectorAll('tbody:not(.expected) .expand-button-text'); 449 else 450 return document.querySelectorAll('.expand-button-text'); 451 } 452 453 function expandAllExpectations() 454 { 455 var expandLinks = visibleExpandLinks(); 456 for (var i = 0, len = expandLinks.length; i < len; i++) 457 async(expandExpectations, [expandLinks[i]]); 458 } 459 460 function collapseAllExpectations() 461 { 462 var expandLinks = visibleExpandLinks(); 463 for (var i = 0, len = expandLinks.length; i < len; i++) 464 async(collapseExpectations, [expandLinks[i]]); 465 } 466 467 function shouldUseTracLinks() 468 { 469 return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0; 470 } 471 472 function layoutTestsBasePath() 473 { 474 var basePath; 475 if (shouldUseTracLinks()) { 476 var revision = globalState().results.revision; 477 basePath = 'http://trac.webkit.org'; 478 basePath += revision ? ('/export/' + revision) : '/browser'; 479 basePath += '/trunk/LayoutTests/'; 480 } else 481 basePath = globalState().results.layout_tests_dir + '/'; 482 return basePath; 483 } 484 485 var mappings = { 486 "http/tests/ssl/": "https://127.0.0.1:8443/ssl/", 487 "http/tests/": "http://127.0.0.1:8000/", 488 "http/wpt/": "http://localhost:8800/WebKit/", 489 "imported/w3c/web-platform-tests/": "http://localhost:8800/" 490 } 491 492 function testToURL(test, layoutTestsPath) 493 { 494 for (let key in mappings) { 495 if (test.startsWith(key)) 496 return mappings[key] + test.substring(key.length); 497 498 } 499 return "file://" + layoutTestsPath + "/" + test 500 } 501 502 function layoutTestURL(test) 503 { 504 if (shouldUseTracLinks()) 505 return layoutTestsBasePath() + test; 506 return testToURL(test, layoutTestsBasePath()); 507 } 508 509 function checkServerIsRunning(event) 510 { 511 if (shouldUseTracLinks()) 512 return; 513 514 var url = event.target.href; 515 if (url.startsWith("file://")) 516 return; 517 518 event.preventDefault(); 519 fetch(url, {mode: "no-cors"}).then(() => { 520 window.location = url; 521 }, () => { 522 alert("HTTP server does not seem to be running, please use the run-webkit-httpd script"); 523 }); 524 } 525 526 function testLink(test) 527 { 528 return '<a class=test-link onclick="checkServerIsRunning(event)" href="' + layoutTestURL(test) + '">' + test + '</a><span class=flag onclick="unflag(this)"> \u2691</span>'; 529 } 530 531 function unflag(flag) 532 { 533 var shouldFlag = false; 534 TestNavigator.flagTest(parentOfType(flag, 'tbody'), shouldFlag); 535 } 536 537 function testLinkWithExpandButton(test) 538 { 539 return '<span class=expand-button onclick="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + testLink(test); 540 } 541 542 function testWithExpandButton(test) 543 { 544 return '<span class=expand-button onclick="toggleExpectations(this)"><span class=expand-button-text>+</span></span>' + test; 545 } 546 547 function resultLink(testPrefix, suffix, contents) 548 { 549 return '<a class=result-link href="' + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> '; 550 } 551 552 function isFailureExpected(expected, actual) 553 { 554 var isExpected = true; 555 if (actual != 'SKIP') { 556 var expectedArray = expected.split(' '); 557 var actualArray = actual.split(' '); 558 for (var i = 0; i < actualArray.length; i++) { 559 var actualValue = actualArray[i]; 560 if (expectedArray.indexOf(actualValue) == -1 && 561 (expectedArray.indexOf('FAIL') == -1 || 562 (actualValue != 'TEXT' && actualValue != 'IMAGE+TEXT' && actualValue != 'AUDIO'))) 563 isExpected = false; 564 } 565 } 566 return isExpected; 567 } 568 569 function processGlobalStateFor(testObject) 570 { 571 var test = testObject.name; 572 if (testObject.has_stderr) 573 globalState().testsWithStderr.push(testObject); 574 575 globalState().hasHttpTests = globalState().hasHttpTests || test.indexOf('http/') == 0; 576 577 var actual = testObject.actual; 578 var expected = testObject.expected || 'PASS'; 579 if (globalState().results.uses_expectations_file) 580 testObject.isExpected = isFailureExpected(expected, actual); 581 582 if (actual == 'MISSING') { 583 // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for 584 // tests with MISSING results. 585 globalState().missingResults.push(testObject); 586 return; 587 } 588 589 var actualTokens = actual.split(' '); 590 var passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE'; 591 if (actualTokens[1] && actual.indexOf('PASS') != -1 || (!globalState().results.pixel_tests_enabled && passedWithImageOnlyFailureInRetry)) { 592 globalState().flakyPassTests.push(testObject); 593 return; 594 } 595 596 if (actual == 'PASS' && expected != 'PASS') { 597 if (expected != 'IMAGE' || (globalState().results.pixel_tests_enabled || testObject.reftest_type)) { 598 globalState().unexpectedPassTests.push(testObject); 599 } 600 return; 601 } 602 603 if (actual == 'CRASH') { 604 globalState().crashTests.push(testObject); 605 return; 606 } 607 608 if (actual == 'TIMEOUT') { 609 globalState().timeoutTests.push(testObject); 610 return; 611 } 612 613 globalState().failingTests.push(testObject); 614 } 615 616 function toggleImages() 617 { 618 var images = document.querySelectorAll('.animatedImage'); 619 var imageTexts = document.querySelectorAll('.imageText'); 620 for (var i = 0, len = images.length; i < len; i++) { 621 var image = images[i]; 622 var text = imageTexts[i]; 623 if (text.textContent == 'Expected Image') { 624 text.textContent = 'Actual Image'; 625 image.src = image.getAttribute('data-prefix') + '-actual.png'; 1678 constructor() { 1679 this.currentTestIndex = -1; 1680 this.flaggedTests = {}; 1681 document.addEventListener('keypress', this, false); 1682 } 1683 1684 handleEvent(event) 1685 { 1686 if (event.type == 'keypress') { 1687 this.handleKeyEvent(event); 1688 return; 1689 } 1690 } 1691 1692 handleKeyEvent(event) 1693 { 1694 if (event.metaKey || event.shiftKey || event.ctrlKey) 1695 return; 1696 1697 switch (String.fromCharCode(event.charCode)) { 1698 case 'i': 1699 this._scrollToFirstTest(); 1700 break; 1701 case 'j': 1702 this._scrollToNextTest(); 1703 break; 1704 case 'k': 1705 this._scrollToPreviousTest(); 1706 break; 1707 case 'l': 1708 this._scrollToLastTest(); 1709 break; 1710 case 'e': 1711 this._expandCurrentTest(); 1712 break; 1713 case 'c': 1714 this._collapseCurrentTest(); 1715 break; 1716 case 't': 1717 this._toggleCurrentTest(); 1718 break; 1719 case 'f': 1720 this._toggleCurrentTestFlagged(); 1721 break; 1722 } 1723 } 1724 1725 _scrollToFirstTest() 1726 { 1727 if (this._setCurrentTest(0)) 1728 this._scrollToCurrentTest(); 1729 } 1730 1731 _scrollToLastTest() 1732 { 1733 let links = controller.visibleTests(); 1734 if (this._setCurrentTest(links.length - 1)) 1735 this._scrollToCurrentTest(); 1736 } 1737 1738 _scrollToNextTest() 1739 { 1740 if (this.currentTestIndex == -1) 1741 this._scrollToFirstTest(); 1742 else if (this._setCurrentTest(this.currentTestIndex + 1)) 1743 this._scrollToCurrentTest(); 1744 } 1745 1746 _scrollToPreviousTest() 1747 { 1748 if (this.currentTestIndex == -1) 1749 this._scrollToLastTest(); 1750 else if (this._setCurrentTest(this.currentTestIndex - 1)) 1751 this._scrollToCurrentTest(); 1752 } 1753 1754 _currentTestLink() 1755 { 1756 let links = controller.visibleTests(); 1757 return links[this.currentTestIndex]; 1758 } 1759 1760 _currentTestExpandLink() 1761 { 1762 return this._currentTestLink().querySelector('.expand-button-text'); 1763 } 1764 1765 _expandCurrentTest() 1766 { 1767 controller.expandExpectations(this._currentTestExpandLink()); 1768 } 1769 1770 _collapseCurrentTest() 1771 { 1772 controller.collapseExpectations(this._currentTestExpandLink()); 1773 } 1774 1775 _toggleCurrentTest() 1776 { 1777 controller.toggleExpectations(this._currentTestExpandLink()); 1778 } 1779 1780 _toggleCurrentTestFlagged() 1781 { 1782 let testLink = this._currentTestLink(); 1783 this.flagTest(testLink, !testLink.classList.contains('flagged')); 1784 } 1785 1786 // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something. 1787 // FIXME: Batch flagging (avoid updateFlaggedTests on each test). 1788 flagTest(testTbody, shouldFlag) 1789 { 1790 let testName = testTbody.querySelector('.test-link').innerText; 1791 1792 if (shouldFlag) { 1793 testTbody.classList.add('flagged'); 1794 this.flaggedTests[testName] = 1; 626 1795 } else { 627 text.textContent = 'Expected Image'; 628 image.src = image.getAttribute('data-prefix') + '-expected.png'; 629 } 630 } 631 } 632 633 function textResultLinks(prefix) 634 { 635 var html = resultLink(prefix, '-expected.txt', 'expected') + 636 resultLink(prefix, '-actual.txt', 'actual') + 637 resultLink(prefix, '-diff.txt', 'diff'); 638 639 if (globalState().results.has_pretty_patch) 640 html += resultLink(prefix, '-pretty-diff.html', 'pretty diff'); 641 642 if (globalState().results.has_wdiff) 643 html += resultLink(prefix, '-wdiff.html', 'wdiff'); 644 645 return html; 646 } 647 648 function imageResultsCell(testObject, testPrefix, actual) { 649 var row = ''; 650 651 if (actual.indexOf('IMAGE') != -1) { 652 var testExtension = splitExtension(testObject.name)[1]; 653 globalState().hasImageFailures = true; 654 655 if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1) { 656 row += resultLink(layoutTestsBasePath() + testPrefix, '-expected-mismatch.' + testExtension, 'ref mismatch'); 657 row += resultLink(testPrefix, '-actual.png', 'actual'); 658 } else { 659 if (testObject.reftest_type && testObject.reftest_type.indexOf('==') != -1) { 660 row += resultLink(layoutTestsBasePath() + testPrefix, '-expected.' + testExtension, 'reference'); 661 } 662 if (globalState().shouldToggleImages) { 663 row += resultLink(testPrefix, '-diffs.html', 'images'); 664 } else { 665 row += resultLink(testPrefix, '-expected.png', 'expected'); 666 row += resultLink(testPrefix, '-actual.png', 'actual'); 667 } 668 669 var diff = testObject.image_diff_percent; 670 row += resultLink(testPrefix, '-diff.png', 'diff (' + diff + '%)'); 671 } 672 } 673 674 if (actual.indexOf('MISSING') != -1 && testObject.is_missing_image) 675 row += resultLink(testPrefix, '-actual.png', 'png result'); 676 677 return row; 678 } 679 680 function flakinessDashboardURLForTests(testObjects) 681 { 682 var testList = ""; 683 for (var i = 0; i < testObjects.length; ++i) { 684 testList += testObjects[i].name; 685 686 if (i != testObjects.length - 1) 687 testList += ","; 688 } 689 690 return 'http://webkit-test-results.webkit.org/dashboards/flakiness_dashboard.html#showAllRuns=true&tests=' + encodeURIComponent(testList); 691 } 692 693 function tableRow(testObject) 694 { 695 var row = '<tbody' 696 if (globalState().results.uses_expectations_file) 697 row += ' class="' + (testObject.isExpected ? 'expected' : '') + '"'; 698 if (testObject.reftest_type && testObject.reftest_type.indexOf('!=') != -1) 699 row += ' mismatchreftest=true'; 700 row += '><tr>'; 701 702 row += '<td>' + testLinkWithExpandButton(testObject.name) + '</td>'; 703 704 var testPrefix = stripExtension(testObject.name); 705 row += '<td>'; 706 707 var actual = testObject.actual; 708 if (actual.indexOf('TEXT') != -1) { 709 globalState().hasTextFailures = true; 710 row += textResultLinks(testPrefix); 711 } 712 713 if (actual.indexOf('AUDIO') != -1) { 714 row += resultLink(testPrefix, '-expected.wav', 'expected audio'); 715 row += resultLink(testPrefix, '-actual.wav', 'actual audio'); 716 row += resultLink(testPrefix, '-diff.txt', 'textual diff'); 717 } 718 719 if (actual.indexOf('MISSING') != -1) { 720 if (testObject.is_missing_audio) 721 row += resultLink(testPrefix, '-actual.wav', 'audio result'); 722 if (testObject.is_missing_text) 723 row += resultLink(testPrefix, '-actual.txt', 'result'); 724 } 725 726 var actualTokens = actual.split(/\s+/); 727 var cell = imageResultsCell(testObject, testPrefix, actualTokens[0]); 728 if (!cell && actualTokens.length > 1) 729 cell = imageResultsCell(testObject, 'retries/' + testPrefix, actualTokens[1]); 730 731 row += '</td><td>' + cell + '</td>'; 732 733 if (globalState().results.uses_expectations_file || actual.indexOf(' ') != -1) 734 row += '<td>' + actual + '</td>'; 735 736 if (globalState().results.uses_expectations_file) 737 row += '<td>' + (actual.indexOf('MISSING') == -1 ? testObject.expected : '') + '</td>'; 738 739 row += '<td><a href="' + flakinessDashboardURLForTests([testObject]) + '">history</a></td>'; 740 741 row += '</tr></tbody>'; 742 return row; 743 } 744 745 function forEachTest(handler, opt_tree, opt_prefix) 746 { 747 var tree = opt_tree || globalState().results.tests; 748 var prefix = opt_prefix || ''; 749 750 for (var key in tree) { 751 var newPrefix = prefix ? (prefix + '/' + key) : key; 752 if ('actual' in tree[key]) { 753 var testObject = tree[key]; 754 testObject.name = newPrefix; 755 handler(testObject); 756 } else 757 forEachTest(handler, tree[key], newPrefix); 758 } 759 } 760 761 function forOtherCrashes() 762 { 763 var tree = globalState().results.other_crashes; 764 for (var key in tree) { 765 var testObject = tree[key]; 766 testObject.name = key; 767 globalState().crashOther.push(testObject); 768 } 769 } 770 771 function hasUnexpected(tests) 772 { 773 return tests.some(function (test) { return !test.isExpected; }); 774 } 775 776 function updateTestlistCounts() 777 { 778 forEach(document.querySelectorAll('.test-list-count'), function(count) { 779 var container = parentOfType(count, 'div'); 780 var testContainers; 781 if (onlyShowUnexpectedFailures()) 782 testContainers = container.querySelectorAll('tbody:not(.expected)'); 783 else 784 testContainers = container.querySelectorAll('tbody'); 785 786 count.textContent = testContainers.length; 787 }) 788 } 789 790 function flagAll(headerLink) 791 { 792 var tests = visibleTests(parentOfType(headerLink, 'div')); 793 forEach(tests, function(tests) { 794 var shouldFlag = true; 795 TestNavigator.flagTest(tests, shouldFlag); 796 }) 797 } 798 799 function testListHeaderHtml(header) 800 { 801 return '<h1>' + header + ' (<span class=test-list-count></span>): <a href="#" class=flag-all onclick="flagAll(this)">flag all</a></h1>'; 802 } 803 804 function testList(tests, header, tableId) 805 { 806 tests.sort(function (a, b) { return a.name.localeCompare(b.name) }); 807 808 var html = '<div' + ((!hasUnexpected(tests) && tableId != 'stderr-table') ? ' class=expected' : '') + ' id=' + tableId + '>' + 809 testListHeaderHtml(header) + '<table>'; 810 811 // FIXME: add the expected failure column for all the test lists if globalState().results.uses_expectations_file 812 if (tableId == 'passes-table') 813 html += '<thead><th>test</th><th>expected failure</th></thead>'; 814 815 for (var i = 0; i < tests.length; i++) { 816 var testObject = tests[i]; 817 var test = testObject.name; 818 html += '<tbody'; 819 if (globalState().results.uses_expectations_file) 820 html += ' class="' + ((testObject.isExpected && tableId != 'stderr-table') ? 'expected' : '') + '"'; 821 html += '><tr><td>'; 822 if (tableId == 'passes-table') 823 html += testLink(test); 824 else if (tableId == 'other-crash-tests-table') 825 html += testWithExpandButton(test); 826 else 827 html += testLinkWithExpandButton(test); 828 829 html += '</td><td>'; 830 831 if (tableId == 'stderr-table') 832 html += resultLink(stripExtension(test), '-stderr.txt', 'stderr'); 833 else if (tableId == 'passes-table') 834 html += testObject.expected; 835 else if (tableId == 'other-crash-tests-table') 836 html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log'); 837 else if (tableId == 'crash-tests-table') { 838 html += resultLink(stripExtension(test), '-crash-log.txt', 'crash log'); 839 html += resultLink(stripExtension(test), '-sample.txt', 'sample'); 840 } else if (tableId == 'timeout-tests-table') { 841 // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests. 842 html += textResultLinks(stripExtension(test)); 843 } 844 845 if (tableId != 'other-crash-tests-table') 846 html += '</td><td><a href="' + flakinessDashboardURLForTests([testObject]) + '">history</a></td>'; 847 848 html += '</tr></tbody>'; 849 } 850 html += '</table></div>'; 851 return html; 852 } 853 854 function toArray(nodeList) 855 { 856 return Array.prototype.slice.call(nodeList); 857 } 858 859 function trim(string) 860 { 861 return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); 862 } 863 864 // Just a namespace for code management. 865 var TableSorter = {}; 866 867 TableSorter._forwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>'; 868 869 TableSorter._backwardArrow = '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>'; 870 871 TableSorter._sortedContents = function(header, arrow) 872 { 873 return arrow + ' ' + trim(header.textContent) + ' ' + arrow; 874 } 875 876 TableSorter._updateHeaderClassNames = function(newHeader) 877 { 878 var sortHeader = document.querySelector('.sortHeader'); 879 if (sortHeader) { 880 if (sortHeader == newHeader) { 881 var isAlreadyReversed = sortHeader.classList.contains('reversed'); 882 if (isAlreadyReversed) 883 sortHeader.classList.remove('reversed'); 884 else 885 sortHeader.classList.add('reversed'); 886 } else { 887 sortHeader.textContent = sortHeader.textContent; 888 sortHeader.classList.remove('sortHeader'); 889 sortHeader.classList.remove('reversed'); 890 } 891 } 892 893 newHeader.classList.add('sortHeader'); 894 } 895 896 TableSorter._textContent = function(tbodyRow, column) 897 { 898 return tbodyRow.querySelectorAll('td')[column].textContent; 899 } 900 901 TableSorter._sortRows = function(newHeader, reversed) 902 { 903 var testsTable = document.getElementById('results-table'); 904 var headers = toArray(testsTable.querySelectorAll('th')); 905 var sortColumn = headers.indexOf(newHeader); 906 907 var rows = toArray(testsTable.querySelectorAll('tbody')); 908 909 rows.sort(function(a, b) { 910 // Only need to support lexicographic sort for now. 911 var aText = TableSorter._textContent(a, sortColumn); 912 var bText = TableSorter._textContent(b, sortColumn); 913 914 // Forward sort equal values by test name. 915 if (sortColumn && aText == bText) { 916 var aTestName = TableSorter._textContent(a, 0); 917 var bTestName = TableSorter._textContent(b, 0); 918 if (aTestName == bTestName) 919 return 0; 920 return aTestName < bTestName ? -1 : 1; 921 } 922 923 if (reversed) 924 return aText < bText ? 1 : -1; 925 else 926 return aText < bText ? -1 : 1; 927 }); 928 929 for (var i = 0; i < rows.length; i++) 930 testsTable.appendChild(rows[i]); 931 } 932 933 TableSorter.sortColumn = function(columnNumber) 934 { 935 var newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber]; 936 TableSorter._sort(newHeader); 937 } 938 939 TableSorter.handleClick = function(e) 940 { 941 var newHeader = e.target; 942 if (newHeader.localName != 'th') 943 return; 944 TableSorter._sort(newHeader); 945 } 946 947 TableSorter._sort = function(newHeader) 948 { 949 TableSorter._updateHeaderClassNames(newHeader); 950 951 var reversed = newHeader.classList.contains('reversed'); 952 var sortArrow = reversed ? TableSorter._backwardArrow : TableSorter._forwardArrow; 953 newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow); 954 955 TableSorter._sortRows(newHeader, reversed); 956 } 957 958 var PixelZoomer = {}; 959 960 PixelZoomer.showOnDelay = true; 961 PixelZoomer._zoomFactor = 6; 962 963 var kResultWidth = 800; 964 var kResultHeight = 600; 965 966 var kZoomedResultWidth = kResultWidth * PixelZoomer._zoomFactor; 967 var kZoomedResultHeight = kResultHeight * PixelZoomer._zoomFactor; 968 969 PixelZoomer._zoomImageContainer = function(url) 970 { 971 var container = document.createElement('div'); 972 container.className = 'zoom-image-container'; 973 974 var title = url.match(/\-([^\-]*)\.png/)[1]; 975 976 var label = document.createElement('div'); 977 label.className = 'label'; 978 label.appendChild(document.createTextNode(title)); 979 container.appendChild(label); 980 981 var imageContainer = document.createElement('div'); 982 imageContainer.className = 'scaled-image-container'; 983 984 var image = new Image(); 985 image.src = url; 986 image.style.width = kZoomedResultWidth + 'px'; 987 image.style.height = kZoomedResultHeight + 'px'; 988 image.style.border = '1px solid black'; 989 imageContainer.appendChild(image); 990 container.appendChild(imageContainer); 991 992 return container; 993 } 994 995 PixelZoomer._createContainer = function(e) 996 { 997 var tbody = parentOfType(e.target, 'tbody'); 998 var row = tbody.querySelector('tr'); 999 var imageDiffLinks = row.querySelectorAll('a[href$=".png"]'); 1000 1001 var container = document.createElement('div'); 1002 container.className = 'pixel-zoom-container'; 1003 1004 var html = ''; 1005 1006 var togglingImageLink = row.querySelector('a[href$="-diffs.html"]'); 1007 if (togglingImageLink) { 1008 var prefix = togglingImageLink.getAttribute('data-prefix'); 1009 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-expected.png')); 1010 container.appendChild(PixelZoomer._zoomImageContainer(prefix + '-actual.png')); 1011 } 1012 1013 for (var i = 0; i < imageDiffLinks.length; i++) 1014 container.appendChild(PixelZoomer._zoomImageContainer(imageDiffLinks[i].href)); 1015 1016 document.body.appendChild(container); 1017 PixelZoomer._drawAll(); 1018 } 1019 1020 PixelZoomer._draw = function(imageContainer) 1021 { 1022 var image = imageContainer.querySelector('img'); 1023 var containerBounds = imageContainer.getBoundingClientRect(); 1024 image.style.left = (containerBounds.width / 2 - PixelZoomer._percentX * kZoomedResultWidth) + 'px'; 1025 image.style.top = (containerBounds.height / 2 - PixelZoomer._percentY * kZoomedResultHeight) + 'px'; 1026 } 1027 1028 PixelZoomer._drawAll = function() 1029 { 1030 forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), PixelZoomer._draw); 1031 } 1032 1033 PixelZoomer.handleMouseOut = function(e) 1034 { 1035 if (e.relatedTarget && e.relatedTarget.tagName != 'IFRAME') 1036 return; 1037 1038 // If e.relatedTarget is null, we've moused out of the document. 1039 var container = document.querySelector('.pixel-zoom-container'); 1040 if (container) 1041 remove(container); 1042 } 1043 1044 PixelZoomer.handleMouseMove = function(e) { 1045 if (PixelZoomer._mouseMoveTimeout) 1046 clearTimeout(PixelZoomer._mouseMoveTimeout); 1047 1048 if (parentOfType(e.target, '.pixel-zoom-container')) 1049 return; 1050 1051 var container = document.querySelector('.pixel-zoom-container'); 1052 1053 var resultContainer = (e.target.className == 'result-container') ? 1054 e.target : parentOfType(e.target, '.result-container'); 1055 if (!resultContainer || !resultContainer.querySelector('img')) { 1056 if (container) 1057 remove(container); 1058 return; 1059 } 1060 1061 var targetLocation = e.target.getBoundingClientRect(); 1062 PixelZoomer._percentX = (e.clientX - targetLocation.left) / targetLocation.width; 1063 PixelZoomer._percentY = (e.clientY - targetLocation.top) / targetLocation.height; 1064 1065 if (!container) { 1066 if (PixelZoomer.showOnDelay) { 1067 PixelZoomer._mouseMoveTimeout = setTimeout(function() { 1068 PixelZoomer._createContainer(e); 1069 }, 400); 1070 return; 1071 } 1072 1073 PixelZoomer._createContainer(e); 1074 return; 1075 } 1076 1077 PixelZoomer._drawAll(); 1078 } 1079 1080 document.addEventListener('mousemove', PixelZoomer.handleMouseMove, false); 1081 document.addEventListener('mouseout', PixelZoomer.handleMouseOut, false); 1082 1083 var TestNavigator = {}; 1084 1085 TestNavigator.reset = function() { 1086 TestNavigator.currentTestIndex = -1; 1087 TestNavigator.flaggedTests = {}; 1088 } 1089 1090 TestNavigator.handleKeyEvent = function(event) 1091 { 1092 if (event.metaKey || event.shiftKey || event.ctrlKey) 1093 return; 1094 1095 switch (String.fromCharCode(event.charCode)) { 1096 case 'i': 1097 TestNavigator._scrollToFirstTest(); 1098 break; 1099 case 'j': 1100 TestNavigator._scrollToNextTest(); 1101 break; 1102 case 'k': 1103 TestNavigator._scrollToPreviousTest(); 1104 break; 1105 case 'l': 1106 TestNavigator._scrollToLastTest(); 1107 break; 1108 case 'e': 1109 TestNavigator._expandCurrentTest(); 1110 break; 1111 case 'c': 1112 TestNavigator._collapseCurrentTest(); 1113 break; 1114 case 't': 1115 TestNavigator._toggleCurrentTest(); 1116 break; 1117 case 'f': 1118 TestNavigator._toggleCurrentTestFlagged(); 1119 break; 1120 } 1121 } 1122 1123 TestNavigator._scrollToFirstTest = function() 1124 { 1125 if (TestNavigator._setCurrentTest(0)) 1126 TestNavigator._scrollToCurrentTest(); 1127 } 1128 1129 TestNavigator._scrollToLastTest = function() 1130 { 1131 var links = visibleTests(); 1132 if (TestNavigator._setCurrentTest(links.length - 1)) 1133 TestNavigator._scrollToCurrentTest(); 1134 } 1135 1136 TestNavigator._scrollToNextTest = function() 1137 { 1138 if (TestNavigator.currentTestIndex == -1) 1139 TestNavigator._scrollToFirstTest(); 1140 else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex + 1)) 1141 TestNavigator._scrollToCurrentTest(); 1142 } 1143 1144 TestNavigator._scrollToPreviousTest = function() 1145 { 1146 if (TestNavigator.currentTestIndex == -1) 1147 TestNavigator._scrollToLastTest(); 1148 else if (TestNavigator._setCurrentTest(TestNavigator.currentTestIndex - 1)) 1149 TestNavigator._scrollToCurrentTest(); 1150 } 1151 1152 TestNavigator._currentTestLink = function() 1153 { 1154 var links = visibleTests(); 1155 return links[TestNavigator.currentTestIndex]; 1156 } 1157 1158 TestNavigator._currentTestExpandLink = function() 1159 { 1160 return TestNavigator._currentTestLink().querySelector('.expand-button-text'); 1161 } 1162 1163 TestNavigator._expandCurrentTest = function() 1164 { 1165 expandExpectations(TestNavigator._currentTestExpandLink()); 1166 } 1167 1168 TestNavigator._collapseCurrentTest = function() 1169 { 1170 collapseExpectations(TestNavigator._currentTestExpandLink()); 1171 } 1172 1173 TestNavigator._toggleCurrentTest = function() 1174 { 1175 toggleExpectations(TestNavigator._currentTestExpandLink()); 1176 } 1177 1178 TestNavigator._toggleCurrentTestFlagged = function() 1179 { 1180 var testLink = TestNavigator._currentTestLink(); 1181 TestNavigator.flagTest(testLink, !testLink.classList.contains('flagged')); 1182 } 1183 1184 // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something. 1185 TestNavigator.flagTest = function(testTbody, shouldFlag) 1186 { 1187 var testName = testTbody.querySelector('.test-link').innerText; 1188 1189 if (shouldFlag) { 1190 testTbody.classList.add('flagged'); 1191 TestNavigator.flaggedTests[testName] = 1; 1192 } else { 1193 testTbody.classList.remove('flagged'); 1194 delete TestNavigator.flaggedTests[testName]; 1195 } 1196 1197 TestNavigator.updateFlaggedTests(); 1198 } 1199 1200 TestNavigator.updateFlaggedTests = function() 1201 { 1202 var flaggedTestTextbox = document.getElementById('flagged-tests'); 1203 if (!flaggedTestTextbox) { 1204 var flaggedTestContainer = document.createElement('div'); 1205 flaggedTestContainer.id = 'flagged-test-container'; 1206 flaggedTestContainer.className = 'floating-panel'; 1207 flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2><pre id="flagged-tests" contentEditable></pre>'; 1208 document.body.appendChild(flaggedTestContainer); 1209 1210 flaggedTestTextbox = document.getElementById('flagged-tests'); 1211 } 1212 1213 var flaggedTests = Object.keys(this.flaggedTests); 1214 flaggedTests.sort(); 1215 var separator = document.getElementById('use-newlines').checked ? '\n' : ' '; 1216 flaggedTestTextbox.innerHTML = flaggedTests.join(separator); 1217 document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none'; 1218 } 1219 1220 TestNavigator._setCurrentTest = function(testIndex) 1221 { 1222 var links = visibleTests(); 1223 if (testIndex < 0 || testIndex >= links.length) 1224 return false; 1225 1226 var currentTest = links[TestNavigator.currentTestIndex]; 1227 if (currentTest) 1228 currentTest.classList.remove('current'); 1229 1230 TestNavigator.currentTestIndex = testIndex; 1231 1232 currentTest = links[TestNavigator.currentTestIndex]; 1233 currentTest.classList.add('current'); 1234 1235 return true; 1236 } 1237 1238 TestNavigator._scrollToCurrentTest = function() 1239 { 1240 var targetLink = TestNavigator._currentTestLink(); 1241 if (!targetLink) 1242 return; 1243 1244 var rowRect = targetLink.getBoundingClientRect(); 1245 // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position. 1246 document.body.scrollTop += rowRect.top; 1247 } 1248 1249 TestNavigator.onlyShowUnexpectedFailuresChanged = function() 1250 { 1251 var currentTest = document.querySelector('.current'); 1252 if (!currentTest) 1253 return; 1254 1255 // If our currentTest became hidden, reset the currentTestIndex. 1256 if (onlyShowUnexpectedFailures() && currentTest.classList.contains('expected')) 1257 TestNavigator._scrollToFirstTest(); 1258 else { 1259 // Recompute TestNavigator.currentTestIndex 1260 var links = visibleTests(); 1261 TestNavigator.currentTestIndex = links.indexOf(currentTest); 1262 } 1263 } 1264 1265 document.addEventListener('keypress', TestNavigator.handleKeyEvent, false); 1266 1267 1268 function onlyShowUnexpectedFailures() 1269 { 1270 return document.getElementById('unexpected-results').checked; 1271 } 1272 1273 function handleUnexpectedResultsChange() 1274 { 1275 OptionWriter.save(); 1276 updateExpectedFailures(); 1277 } 1278 1279 function updateExpectedFailures() 1280 { 1281 document.getElementById('unexpected-style').textContent = onlyShowUnexpectedFailures() ? 1282 '.expected { display: none; }' : ''; 1283 1284 updateTestlistCounts(); 1285 TestNavigator.onlyShowUnexpectedFailuresChanged(); 1286 } 1287 1288 var OptionWriter = {}; 1289 1290 OptionWriter._key = 'run-webkit-tests-options'; 1291 1292 OptionWriter.save = function() 1293 { 1294 var options = document.querySelectorAll('label input'); 1295 var data = {}; 1296 for (var i = 0, len = options.length; i < len; i++) { 1297 var option = options[i]; 1298 data[option.id] = option.checked; 1299 } 1300 try { 1301 localStorage.setItem(OptionWriter._key, JSON.stringify(data)); 1302 } catch (err) { 1303 if (err.name != "SecurityError") 1304 throw err; 1305 } 1306 } 1307 1308 OptionWriter.apply = function() 1309 { 1310 var json; 1311 try { 1312 json = localStorage.getItem(OptionWriter._key); 1313 } catch (err) { 1314 if (err.name != "SecurityError") 1315 throw err; 1316 } 1317 1318 if (!json) { 1319 updateAllOptions(); 1320 return; 1321 } 1322 1323 var data = JSON.parse(json); 1324 for (var id in data) { 1325 var input = document.getElementById(id); 1326 if (input) 1327 input.checked = data[id]; 1328 } 1329 updateAllOptions(); 1330 } 1331 1332 function updateAllOptions() 1333 { 1334 forEach(document.querySelectorAll('#options-menu input'), function(input) { input.onchange(); }); 1335 } 1336 1337 function handleToggleUseNewlines() 1338 { 1339 OptionWriter.save(); 1340 TestNavigator.updateFlaggedTests(); 1341 } 1342 1343 function handleToggleImagesChange() 1344 { 1345 OptionWriter.save(); 1346 updateTogglingImages(); 1347 } 1348 1349 function updateTogglingImages() 1350 { 1351 var shouldToggle = document.getElementById('toggle-images').checked; 1352 globalState().shouldToggleImages = shouldToggle; 1353 1354 if (shouldToggle) { 1355 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), convertToTogglingHandler(function(prefix) { 1356 return resultLink(prefix, '-diffs.html', 'images'); 1357 })); 1358 forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), convertToTogglingHandler(togglingImage)); 1359 } else { 1360 forEach(document.querySelectorAll('a[href$="-diffs.html"]'), convertToNonTogglingHandler(resultLink)); 1361 forEach(document.querySelectorAll('.animatedImage'), convertToNonTogglingHandler(function (absolutePrefix, suffix) { 1362 return resultIframe(absolutePrefix + suffix); 1363 })); 1364 } 1365 1366 updateImageTogglingTimer(); 1367 } 1368 1369 function getResultContainer(node) 1370 { 1371 return (node.tagName == 'IMG') ? parentOfType(node, '.result-container') : node; 1372 } 1373 1374 function convertToTogglingHandler(togglingImageFunction) 1375 { 1376 return function(node) { 1377 var url = (node.tagName == 'IMG') ? node.src : node.href; 1378 if (url.match('-expected.png$')) 1379 remove(getResultContainer(node)); 1380 else if (url.match('-actual.png$')) { 1381 var name = parentOfType(node, 'tbody').querySelector('.test-link').textContent; 1382 getResultContainer(node).outerHTML = togglingImageFunction(stripExtension(name)); 1383 } 1384 } 1385 } 1386 1387 function convertToNonTogglingHandler(resultFunction) 1388 { 1389 return function(node) { 1390 var prefix = node.getAttribute('data-prefix'); 1391 getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual'); 1392 } 1393 } 1394 1395 function toggleOptionsMenu() 1396 { 1397 var menu = document.getElementById('options-menu'); 1398 menu.className = (menu.className == 'hidden-menu') ? '' : 'hidden-menu'; 1399 } 1796 testTbody.classList.remove('flagged'); 1797 delete this.flaggedTests[testName]; 1798 } 1799 1800 this.updateFlaggedTests(); 1801 } 1802 1803 updateFlaggedTests() 1804 { 1805 let flaggedTestTextbox = document.getElementById('flagged-tests'); 1806 if (!flaggedTestTextbox) { 1807 let flaggedTestContainer = document.createElement('div'); 1808 flaggedTestContainer.id = 'flagged-test-container'; 1809 flaggedTestContainer.className = 'floating-panel'; 1810 flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2><pre id="flagged-tests" contentEditable></pre>'; 1811 document.body.appendChild(flaggedTestContainer); 1812 1813 flaggedTestTextbox = document.getElementById('flagged-tests'); 1814 } 1815 1816 let flaggedTests = Object.keys(this.flaggedTests); 1817 flaggedTests.sort(); 1818 let separator = document.getElementById('use-newlines').checked ? '\n' : ' '; 1819 flaggedTestTextbox.innerHTML = flaggedTests.join(separator); 1820 document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none'; 1821 } 1822 1823 _setCurrentTest(testIndex) 1824 { 1825 let links = controller.visibleTests(); 1826 if (testIndex < 0 || testIndex >= links.length) 1827 return false; 1828 1829 let currentTest = links[this.currentTestIndex]; 1830 if (currentTest) 1831 currentTest.classList.remove('current'); 1832 1833 this.currentTestIndex = testIndex; 1834 1835 currentTest = links[this.currentTestIndex]; 1836 currentTest.classList.add('current'); 1837 1838 return true; 1839 } 1840 1841 _scrollToCurrentTest() 1842 { 1843 let targetLink = this._currentTestLink(); 1844 if (!targetLink) 1845 return; 1846 1847 let rowRect = targetLink.getBoundingClientRect(); 1848 // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position. 1849 document.body.scrollTop += rowRect.top; 1850 } 1851 1852 onlyShowUnexpectedFailuresChanged() 1853 { 1854 let currentTest = document.querySelector('.current'); 1855 if (!currentTest) 1856 return; 1857 1858 // If our currentTest became hidden, reset the currentTestIndex. 1859 if (controller.onlyShowUnexpectedFailures() && currentTest.classList.contains('expected')) 1860 this._scrollToFirstTest(); 1861 else { 1862 // Recompute this.currentTestIndex 1863 let links = controller.visibleTests(); 1864 this.currentTestIndex = links.indexOf(currentTest); 1865 } 1866 } 1867 }; 1400 1868 1401 1869 function handleMouseDown(e) 1402 1870 { 1403 if (! parentOfType(e.target, '#options-menu') && e.target.id != 'options-link')1871 if (!Utils.parentOfType(e.target, '#options-menu') && e.target.id != 'options-link') 1404 1872 document.getElementById('options-menu').className = 'hidden-menu'; 1405 1873 } … … 1407 1875 document.addEventListener('mousedown', handleMouseDown, false); 1408 1876 1409 function failingTestsTable(tests, title, id) 1410 { 1411 if (!tests.length) 1412 return ''; 1413 1414 var numberofUnexpectedFailures = 0; 1415 var tableRowHtml = ''; 1416 for (var i = 0; i < tests.length; i++){ 1417 tableRowHtml += tableRow(tests[i]); 1418 if (!tests[i].isExpected) 1419 numberofUnexpectedFailures++; 1420 } 1421 1422 var header = '<div'; 1423 if (!hasUnexpected(tests)) 1424 header += ' class=expected'; 1425 1426 header += '>' + testListHeaderHtml(title) + 1427 '<table id="' + id + '"><thead><tr>' + 1428 '<th>test</th>' + 1429 '<th id="text-results-header">results</th>' + 1430 '<th id="image-results-header">image results</th>'; 1431 1432 if (globalState().results.uses_expectations_file) 1433 header += '<th>actual failure</th><th>expected failure</th>'; 1434 1435 header += '<th><a href="' + flakinessDashboardURLForTests(tests) + '">history</a></th>'; 1436 1437 if (id == 'flaky-tests-table') 1438 header += '<th>failures</th>'; 1439 1440 header += '</tr></thead>'; 1441 1442 return header + tableRowHtml + '</table></div>'; 1443 } 1444 1445 function updateTitle() 1446 { 1447 var dateString = globalState().results.date; 1448 1449 var title = document.createElement('title'); 1450 title.textContent = 'Layout Test Results from ' + dateString; 1451 document.head.appendChild(title); 1452 } 1877 let controller; 1878 let pixelZoomer; 1879 let testNavigator; 1453 1880 1454 1881 function generatePage() 1455 1882 { 1456 updateTitle(); 1457 forEachTest(processGlobalStateFor); 1458 forOtherCrashes(); 1459 1460 var html = ""; 1461 1462 if (globalState().results.interrupted) 1463 html += "<p class='stopped-running-early-message'>Testing exited early.</p>" 1464 1465 if (globalState().crashTests.length) 1466 html += testList(globalState().crashTests, 'Tests that crashed', 'crash-tests-table'); 1467 1468 if (globalState().crashOther.length) 1469 html += testList(globalState().crashOther, 'Other Crashes', 'other-crash-tests-table'); 1470 1471 html += failingTestsTable(globalState().failingTests, 1472 'Tests that failed text/pixel/audio diff', 'results-table'); 1473 1474 html += failingTestsTable(globalState().missingResults, 1475 'Tests that had no expected results (probably new)', 'missing-table'); 1476 1477 if (globalState().timeoutTests.length) 1478 html += testList(globalState().timeoutTests, 'Tests that timed out', 'timeout-tests-table'); 1479 1480 if (globalState().testsWithStderr.length) 1481 html += testList(globalState().testsWithStderr, 'Tests that had stderr output', 'stderr-table'); 1482 1483 html += failingTestsTable(globalState().flakyPassTests, 1484 'Flaky tests (failed the first run and passed on retry)', 'flaky-tests-table'); 1485 1486 if (globalState().results.uses_expectations_file && globalState().unexpectedPassTests.length) 1487 html += testList(globalState().unexpectedPassTests, 'Tests expected to fail but passed', 'passes-table'); 1488 1489 if (globalState().hasHttpTests) { 1490 html += '<p>httpd access log: <a href="access_log.txt">access_log.txt</a></p>' + 1491 '<p>httpd error log: <a href="error_log.txt">error_log.txt</a></p>'; 1492 } 1493 1494 document.getElementById('main-content').innerHTML = html + '</div>'; 1495 1496 if (document.getElementById('results-table')) { 1497 document.getElementById('results-table').addEventListener('click', TableSorter.handleClick, false); 1498 TableSorter.sortColumn(0); 1499 if (!globalState().results.uses_expectations_file) 1500 parentOfType(document.getElementById('unexpected-results'), 'label').style.display = 'none'; 1501 if (!globalState().hasTextFailures) 1502 document.getElementById('text-results-header').textContent = ''; 1503 if (!globalState().hasImageFailures) { 1504 document.getElementById('image-results-header').textContent = ''; 1505 parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none'; 1506 } 1507 } 1508 1509 updateTestlistCounts(); 1510 1511 TestNavigator.reset(); 1883 let container = document.getElementById('main-content'); 1884 1885 controller = new TestResultsController(container, testResults); 1886 pixelZoomer = new PixelZoomer(); 1887 testNavigator = new TestNavigator(); 1888 1512 1889 OptionWriter.apply(); 1513 1890 } 1891 1892 window.addEventListener('load', generatePage, false); 1893 1514 1894 </script> 1515 <body onload="generatePage()">1895 <body> 1516 1896 1517 1897 <div class="content-container"> 1518 1898 <div id="toolbar" class="floating-panel"> 1519 1899 <div class="note">Use the i, j, k and l keys to navigate, e, c to expand and collapse, and f to flag</div> 1520 <a href="javascript:void()" onclick="expandAllExpectations()">expand all</a>1521 <a href="javascript:void()" onclick="collapseAllExpectations()">collapse all</a>1522 <a href="javascript:void()" id=options-link onclick="toggleOptionsMenu()">options</a>1900 <a class="clickable" onclick="controller.expandAllExpectations()">expand all</a> 1901 <a class="clickable" onclick="controller.collapseAllExpectations()">collapse all</a> 1902 <a class="clickable" id=options-link onclick="controller.toggleOptionsMenu()">options</a> 1523 1903 <div id="options-menu" class="hidden-menu"> 1524 <label><input id="unexpected-results" type="checkbox" checked onchange=" handleUnexpectedResultsChange()">Only unexpected results</label>1525 <label><input id="toggle-images" type="checkbox" checked onchange=" handleToggleImagesChange()">Toggle images</label>1526 <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>1904 <label><input id="unexpected-results" type="checkbox" checked onchange="controller.handleUnexpectedResultsChange()">Only unexpected results</label> 1905 <label><input id="toggle-images" type="checkbox" checked onchange="controller.handleToggleImagesChange()">Toggle images</label> 1906 <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> 1527 1907 </div> 1528 1908 </div>
Note:
See TracChangeset
for help on using the changeset viewer.