results.html (55867B)
1 <!DOCTYPE html> 2 <html lang="en" style="overflow: auto;"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 <title>Session Results - Web Platform Test</title> 7 <link rel="stylesheet" href="css/bulma-0.7.5/bulma.min.css" /> 8 <link rel="stylesheet" href="css/fontawesome-5.7.2.min.css" /> 9 <!-- <link rel="stylesheet" href="css/result.css" /> --> 10 <script src="lib/utils.js"></script> 11 <script src="lib/wave-service.js"></script> 12 <script src="lib/ui.js"></script> 13 <style> 14 .site-logo { 15 max-width: 300px; 16 margin: 0 0 30px -15px; 17 } 18 </style> 19 </head> 20 <body> 21 <script> 22 let token = null; 23 window.onload = () => { 24 const query = utils.parseQuery(location.search); 25 token = query.token; 26 if (token) { 27 resultUi.render(); 28 resultUi.refreshData(); 29 } else { 30 location.href = WEB_ROOT + "overview.html" + location.search; 31 } 32 WaveService.addRecentSession(token); 33 }; 34 const resultUi = { 35 state: { 36 details: null, 37 results: null, 38 referenceSessions: [], 39 lastCompletedTests: [], 40 malfunctioningTests: [], 41 addLabelVisible: false, 42 }, 43 refreshData: (toUpdate) => { 44 WaveService.readStatus(function (config) { 45 resultUi.state.reportsEnabled = config.reportsEnabled; 46 resultUi.renderApiResults(); 47 }); 48 switch (toUpdate) { 49 case "test_completed": 50 resultUi.refreshSessionStatus(() => { 51 resultUi.refreshSessionResults(() => { 52 resultUi.renderApiResults(); 53 }); 54 }); 55 resultUi.refreshLastCompletedTests(() => { 56 resultUi.renderLastCompletedTests(); 57 }); 58 break; 59 case "status": 60 resultUi.refreshSessionStatus(() => { 61 resultUi.renderControls(); 62 resultUi.renderSessionDetails(); 63 }); 64 break; 65 case "": 66 case null: 67 case undefined: 68 resultUi.refreshSessionConfiguration(() => { 69 resultUi.refreshSessionStatus(() => { 70 resultUi.refreshSessionResults(() => { 71 resultUi.refreshReferenceSessions(() => 72 resultUi.renderReferenceSessions() 73 ); 74 resultUi.renderControls(); 75 resultUi.renderSessionDetails(); 76 resultUi.renderApiResults(); 77 resultUi.renderExportView(); 78 resultUi.refreshLastCompletedTests(() => { 79 resultUi.renderLastCompletedTests(); 80 }); 81 resultUi.refreshMalfunctioningTests(() => { 82 resultUi.renderMalfunctioningTests(); 83 }); 84 }); 85 }); 86 }); 87 break; 88 } 89 }, 90 refreshSessionConfiguration(callback = () => {}) { 91 WaveService.readSession(token, (configuration) => { 92 resultUi.state.configuration = configuration; 93 callback(configuration); 94 }); 95 }, 96 refreshSessionStatus(callback = () => {}) { 97 WaveService.readSessionStatus(token, (status) => { 98 resultUi.state.status = status; 99 if (status.status !== "completed" && status.status !== "aborted") 100 WaveService.addSessionEventListener( 101 token, 102 resultUi.handleSessionEvent 103 ); 104 callback(status); 105 }); 106 }, 107 refreshReferenceSessions(callback = () => {}) { 108 const { configuration } = resultUi.state; 109 if (!configuration) return; 110 const { referenceTokens } = configuration; 111 if (!referenceTokens) return; 112 WaveService.readMultipleSessions(referenceTokens, (configuration) => { 113 resultUi.state.referenceSessions = configuration; 114 resultUi.renderReferenceSessions(); 115 callback(configuration); 116 }); 117 }, 118 refreshSessionResults(callback = () => {}) { 119 WaveService.readResultsCompact(token, (results) => { 120 resultUi.state.results = results; 121 callback(results); 122 }); 123 }, 124 refreshLastCompletedTests(callback = () => {}) { 125 if (resultUi.state.configuration.isPublic) return; 126 WaveService.readLastCompletedTests(token, ["timeout"], (tests) => { 127 resultUi.state.lastCompletedTests = tests; 128 callback(); 129 }); 130 }, 131 refreshMalfunctioningTests(callback = () => {}) { 132 WaveService.readMalfunctioningTests(token, (tests) => { 133 resultUi.state.malfunctioningTests = tests; 134 callback(); 135 }); 136 }, 137 handleSessionEvent(message) { 138 resultUi.refreshData(message.type); 139 }, 140 openResultsOverview() { 141 location.href = WEB_ROOT + "overview.html"; 142 }, 143 stopSession() { 144 WaveService.stopSession(token, resultUi.refreshData); 145 }, 146 deleteSession() { 147 WaveService.deleteSession(token, () => 148 resultUi.openResultsOverview() 149 ); 150 }, 151 showDeleteModal() { 152 const modal = UI.getElement("delete-modal"); 153 const className = modal.getAttribute("class"); 154 modal.setAttribute("class", className + " is-active"); 155 }, 156 hideDeleteModal() { 157 const modal = UI.getElement("delete-modal"); 158 let className = modal.getAttribute("class"); 159 className = className.replace(" is-active", ""); 160 modal.setAttribute("class", className); 161 }, 162 downloadApiResultJson(api) { 163 const { results } = resultUi.state; 164 WaveService.downloadApiResult(token, api); 165 }, 166 openHtmlReport(api) { 167 const { results } = resultUi.state; 168 if (results[api].complete != results[api].total) return; 169 WaveService.readReportUri(token, api, function (uri) { 170 window.open(uri, "_blank"); 171 }); 172 }, 173 downloadFinishedApiJsons() { 174 WaveService.downloadAllApiResults(token); 175 }, 176 downloadHtmlZip() { 177 WaveService.downloadResultsOverview(token); 178 }, 179 downloadResults() { 180 if (resultUi.state.status.status !== "completed") return; 181 WaveService.downloadResults(token); 182 }, 183 addMalfunctioningTest(testPath) { 184 const { malfunctioningTests } = resultUi.state; 185 if (malfunctioningTests.indexOf(testPath) !== -1) return; 186 malfunctioningTests.push(testPath); 187 WaveService.updateMalfunctioningTests( 188 token, 189 malfunctioningTests, 190 () => { 191 resultUi.renderMalfunctioningTests(); 192 } 193 ); 194 resultUi.renderLastCompletedTests(); 195 }, 196 removeMalfunctioningTest(testPath) { 197 const { malfunctioningTests } = resultUi.state; 198 malfunctioningTests.splice(malfunctioningTests.indexOf(testPath), 1); 199 WaveService.updateMalfunctioningTests( 200 token, 201 malfunctioningTests, 202 () => { 203 resultUi.renderMalfunctioningTests(); 204 } 205 ); 206 resultUi.renderLastCompletedTests(); 207 }, 208 isTestOnMalfunctioningList(test) { 209 const { malfunctioningTests } = resultUi.state; 210 return malfunctioningTests.indexOf(test) !== -1; 211 }, 212 showExcluded() { 213 resultUi.state.showExcluded = true; 214 resultUi.renderSessionDetails(); 215 }, 216 hideExcluded() { 217 resultUi.state.showExcluded = false; 218 resultUi.renderSessionDetails(); 219 }, 220 addLabel() { 221 const label = UI.getElement("session-label-input").value; 222 if (!label) return; 223 const { configuration } = resultUi.state; 224 configuration.labels.push(label); 225 WaveService.updateLabels(token, configuration.labels); 226 resultUi.renderSessionDetails(); 227 UI.getElement("session-label-input").focus(); 228 }, 229 removeLabel(index) { 230 const { configuration } = resultUi.state; 231 configuration.labels.splice(index, 1); 232 WaveService.updateLabels(token, configuration.labels); 233 resultUi.renderSessionDetails(); 234 }, 235 showAddLabel() { 236 resultUi.state.addLabelVisible = true; 237 resultUi.renderSessionDetails(); 238 UI.getElement("session-label-input").focus(); 239 }, 240 hideAddLabel() { 241 resultUi.state.addLabelVisible = false; 242 resultUi.renderSessionDetails(); 243 }, 244 render() { 245 const resultView = UI.createElement({ 246 className: "section", 247 children: [ 248 { 249 className: "container", 250 style: "margin-bottom: 2em", 251 children: [ 252 { 253 className: "columns", 254 children: [ 255 { 256 className: "column", 257 children: [ 258 { 259 element: "img", 260 src: "res/wavelogo_2016.jpg", 261 className: "site-logo", 262 }, 263 ], 264 }, 265 { 266 className: "column is-narrow", 267 children: { 268 className: "button is-dark is-outlined", 269 onclick: resultUi.openResultsOverview, 270 children: [ 271 { 272 element: "span", 273 className: "icon", 274 children: [ 275 { 276 element: "i", 277 className: "fas fa-arrow-left", 278 }, 279 ], 280 }, 281 { 282 text: "Results Overview", 283 element: "span", 284 }, 285 ], 286 }, 287 }, 288 ], 289 }, 290 { 291 className: "container", 292 children: { 293 className: "columns", 294 children: [ 295 { 296 className: "column", 297 children: { className: "title", text: "Result" }, 298 }, 299 { 300 className: "column is-narrow", 301 children: { id: "controls" }, 302 }, 303 ], 304 }, 305 }, 306 ], 307 }, 308 { 309 id: "session-details", 310 className: "container", 311 style: "margin-bottom: 2em", 312 }, 313 { 314 id: "last-completed-tests", 315 className: "container", 316 style: "margin-bottom: 2em", 317 }, 318 { 319 id: "api-results", 320 className: "container", 321 style: "margin-bottom: 2em", 322 }, 323 { 324 id: "timeout-files", 325 className: "container", 326 style: "margin-bottom: 2em", 327 }, 328 { 329 id: "export", 330 className: "container", 331 style: "margin-bottom: 2em", 332 }, 333 { 334 id: "malfunctioning-tests", 335 className: "container", 336 style: "margin-bottom: 2em", 337 }, 338 ], 339 }); 340 const root = UI.getRoot(); 341 root.innerHTML = ""; 342 root.appendChild(resultView); 343 resultUi.renderControls(); 344 resultUi.renderSessionDetails(); 345 resultUi.renderApiResults(); 346 resultUi.renderExportView(); 347 }, 348 renderControls() { 349 const { state } = resultUi; 350 if (!state.status) return; 351 const { status } = state.status; 352 const { isPublic } = state.configuration; 353 const controlsView = UI.createElement({ 354 className: "field is-grouped is-grouped-multiline", 355 }); 356 if ( 357 status && 358 status !== "aborted" && 359 status !== "completed" && 360 status !== "pending" 361 ) { 362 const pauseResumeButton = UI.createElement({ 363 id: "pause-resume-button", 364 className: "control button is-dark is-outlined", 365 onclick: function () { 366 if (status === "running") { 367 WaveService.pauseSession(token, resultUi.refreshData); 368 } else { 369 WaveService.startSession(token, resultUi.refreshData); 370 } 371 }, 372 children: [ 373 { 374 element: "span", 375 className: "icon", 376 children: [ 377 { 378 element: "i", 379 className: 380 status === "running" ? "fas fa-pause" : "fas fa-play", 381 }, 382 ], 383 }, 384 { 385 text: status === "running" ? "Pause" : "Resume", 386 element: "span", 387 }, 388 ], 389 }); 390 controlsView.appendChild(pauseResumeButton); 391 } 392 393 if (status && status !== "aborted" && status !== "completed") { 394 const stopButton = UI.createElement({ 395 id: "stop-button", 396 className: "control button is-dark is-outlined", 397 onclick: resultUi.stopSession, 398 children: [ 399 { 400 element: "span", 401 className: "icon", 402 children: [ 403 { 404 element: "i", 405 className: "fas fa-square", 406 }, 407 ], 408 }, 409 { 410 text: "Stop", 411 element: "span", 412 }, 413 ], 414 }); 415 controlsView.appendChild(stopButton); 416 } 417 if (!isPublic) { 418 const deleteButton = UI.createElement({ 419 id: "delete-button", 420 className: "control button is-dark is-outlined", 421 onclick: resultUi.showDeleteModal, 422 children: [ 423 { 424 element: "span", 425 className: "icon", 426 children: [ 427 { 428 element: "i", 429 className: "fas fa-trash-alt", 430 }, 431 ], 432 }, 433 { 434 text: "Delete", 435 element: "span", 436 }, 437 ], 438 }); 439 controlsView.appendChild(deleteButton); 440 } 441 442 const deleteModal = UI.createElement({ 443 id: "delete-modal", 444 className: "modal", 445 children: [ 446 { 447 className: "modal-background", 448 onclick: resultUi.hideDeleteModal, 449 }, 450 { 451 className: "modal-card", 452 children: [ 453 { 454 className: "modal-card-head", 455 children: [ 456 { 457 element: "p", 458 className: "modal-card-title", 459 text: "Delete Session", 460 }, 461 ], 462 }, 463 { 464 className: "modal-card-body", 465 children: [ 466 { 467 element: "p", 468 text: "Are you sure you want to delete this session?", 469 }, 470 { element: "p", text: "This action cannot be undone." }, 471 ], 472 }, 473 { 474 className: "modal-card-foot", 475 children: [ 476 { 477 className: "button is-danger", 478 text: "Delete Session", 479 onclick: resultUi.deleteSession, 480 }, 481 { 482 className: "button", 483 text: "Cancel", 484 onclick: resultUi.hideDeleteModal, 485 }, 486 ], 487 }, 488 ], 489 }, 490 ], 491 }); 492 controlsView.appendChild(deleteModal); 493 494 const controls = UI.getElement("controls"); 495 controls.innerHTML = ""; 496 controls.appendChild(controlsView); 497 }, 498 renderSessionDetails() { 499 const { state } = resultUi; 500 const { configuration, status, results } = state; 501 if (!configuration || !status) return; 502 const sessionDetailsView = UI.createElement({ 503 style: "margin-bottom: 20px", 504 }); 505 506 const heading = UI.createElement({ 507 text: "Session details", 508 className: "title is-4", 509 }); 510 sessionDetailsView.appendChild(heading); 511 512 const getTagStyle = (status) => { 513 switch (status) { 514 case "completed": 515 return "is-success"; 516 case "running": 517 return "is-info"; 518 case "aborted": 519 return "is-danger"; 520 case "paused": 521 return "is-warning"; 522 case "pending": 523 return "is-primary"; 524 } 525 }; 526 if (status.dateFinished) { 527 if (state.durationInterval) clearInterval(state.durationInterval); 528 } else if (status.dateStarted) { 529 if (!state.durationInterval) 530 state.durationInterval = setInterval(() => { 531 UI.getElement("duration").innerHTML = utils.millisToTimeString( 532 Date.now() - status.dateStarted.getTime() 533 ); 534 }, 1000); 535 } 536 537 const { addLabelVisible } = state; 538 const { showExcluded } = state; 539 540 const tokenField = UI.createElement({ 541 className: "field is-horizontal", 542 children: [ 543 { 544 className: "field-label", 545 children: { className: "label", text: "Token" }, 546 }, 547 { 548 className: "field-body", 549 children: { 550 className: "field", 551 children: { 552 className: "control", 553 text: configuration.token, 554 }, 555 }, 556 }, 557 ], 558 }); 559 sessionDetailsView.appendChild(tokenField); 560 561 const userAgentField = UI.createElement({ 562 className: "field is-horizontal", 563 children: [ 564 { 565 className: "field-label", 566 children: { className: "label", text: "User Agent" }, 567 }, 568 { 569 className: "field-body", 570 children: { 571 className: "field", 572 children: { 573 className: "control", 574 text: configuration.userAgent || "", 575 }, 576 }, 577 }, 578 ], 579 }); 580 sessionDetailsView.appendChild(userAgentField); 581 582 const testPathsField = UI.createElement({ 583 className: "field is-horizontal", 584 children: [ 585 { 586 className: "field-label", 587 children: { className: "label", text: "Test Paths" }, 588 }, 589 { 590 className: "field-body", 591 children: { 592 className: "field", 593 children: { 594 className: "control", 595 text: configuration.tests.include 596 .reduce((text, test) => text + test + ", ", "") 597 .slice(0, -2), 598 }, 599 }, 600 }, 601 ], 602 }); 603 sessionDetailsView.appendChild(testPathsField); 604 605 const excludedTestsField = UI.createElement({ 606 className: "field is-horizontal", 607 children: [ 608 { 609 className: "field-label", 610 children: { className: "label", text: "Excluded Test Paths" }, 611 }, 612 { 613 className: "field-body", 614 children: { 615 className: "field", 616 children: { 617 className: "control", 618 children: [ 619 { 620 element: "span", 621 text: configuration.tests.exclude.length, 622 }, 623 { 624 element: "span", 625 className: "button is-small is-rounded", 626 style: "margin-left: 10px", 627 text: showExcluded ? "hide" : "show", 628 onClick: showExcluded 629 ? resultUi.hideExcluded 630 : resultUi.showExcluded, 631 }, 632 showExcluded 633 ? { 634 style: 635 "max-height: 250px; overflow: auto; margin-bottom: 10px", 636 children: configuration.tests.exclude.map( 637 (test) => ({ 638 text: test, 639 }) 640 ), 641 } 642 : null, 643 ], 644 }, 645 }, 646 }, 647 ], 648 }); 649 sessionDetailsView.appendChild(excludedTestsField); 650 651 const referenceSessionField = UI.createElement({ 652 style: "display: none", 653 id: "reference-session-field", 654 className: "field is-horizontal", 655 children: [ 656 { 657 className: "field-label", 658 children: { className: "label", text: "Reference Sessions" }, 659 }, 660 { 661 className: "field-body", 662 children: { 663 className: "field", 664 children: { 665 className: "control", 666 children: { id: "reference-sessions" }, 667 }, 668 }, 669 }, 670 ], 671 }); 672 sessionDetailsView.appendChild(referenceSessionField); 673 674 const totalTestFilesField = UI.createElement({ 675 className: "field is-horizontal", 676 children: [ 677 { 678 className: "field-label", 679 children: { className: "label", text: "Total Test Files" }, 680 }, 681 { 682 className: "field-body", 683 children: { 684 className: "field", 685 children: { 686 className: "control", 687 text: Object.keys(results).reduce( 688 (sum, api) => (sum += results[api].total), 689 0 690 ), 691 }, 692 }, 693 }, 694 ], 695 }); 696 sessionDetailsView.appendChild(totalTestFilesField); 697 698 const statusField = UI.createElement({ 699 className: "field is-horizontal", 700 children: [ 701 { 702 className: "field-label", 703 children: { className: "label", text: "Status" }, 704 }, 705 { 706 className: "field-body", 707 children: { 708 className: "field", 709 children: { 710 className: `control tag ${getTagStyle(status.status)}`, 711 text: status.status, 712 }, 713 }, 714 }, 715 ], 716 }); 717 sessionDetailsView.appendChild(statusField); 718 719 const timeoutsField = UI.createElement({ 720 className: "field is-horizontal", 721 children: [ 722 { 723 className: "field-label", 724 children: { className: "label", text: "Test Timeouts" }, 725 }, 726 { 727 className: "field-body", 728 children: { 729 className: "field", 730 children: { 731 className: `control`, 732 text: Object.keys(configuration.timeouts).reduce( 733 (text, timeout) => 734 `${text}${timeout}: ${ 735 configuration.timeouts[timeout] / 1000 736 }s\n`, 737 "" 738 ), 739 }, 740 }, 741 }, 742 ], 743 }); 744 sessionDetailsView.appendChild(timeoutsField); 745 746 if (status.dateStarted) { 747 const startedField = UI.createElement({ 748 className: "field is-horizontal", 749 children: [ 750 { 751 className: "field-label", 752 children: { className: "label", text: "Date Started" }, 753 }, 754 { 755 className: "field-body", 756 children: { 757 className: "field", 758 children: { 759 className: `control`, 760 text: new Date(status.dateStarted).toLocaleString(), 761 }, 762 }, 763 }, 764 ], 765 }); 766 sessionDetailsView.appendChild(startedField); 767 } 768 769 if (status.dateFinished) { 770 const finishedField = UI.createElement({ 771 className: "field is-horizontal", 772 children: [ 773 { 774 className: "field-label", 775 children: { className: "label", text: "Date Finished" }, 776 }, 777 { 778 className: "field-body", 779 children: { 780 className: "field", 781 children: { 782 className: `control`, 783 text: new Date(status.dateFinished).toLocaleString(), 784 }, 785 }, 786 }, 787 ], 788 }); 789 sessionDetailsView.appendChild(finishedField); 790 } 791 792 if (status.dateStarted) { 793 const durationField = UI.createElement({ 794 className: "field is-horizontal", 795 children: [ 796 { 797 className: "field-label", 798 children: { className: "label", text: "Duration" }, 799 }, 800 { 801 className: "field-body", 802 children: { 803 className: "field", 804 children: { 805 className: `control`, 806 id: "duration", 807 text: utils.millisToTimeString( 808 status.dateFinished 809 ? status.dateFinished.getTime() - 810 status.dateStarted.getTime() 811 : Date.now() - status.dateStarted.getTime() 812 ), 813 }, 814 }, 815 }, 816 ], 817 }); 818 sessionDetailsView.appendChild(durationField); 819 } 820 821 const labelsField = UI.createElement({ 822 className: "field is-horizontal", 823 children: [ 824 { 825 className: "field-label", 826 children: { className: "label", text: "Labels" }, 827 }, 828 { 829 className: "field-body", 830 children: { 831 className: "field is-grouped is-grouped-multiline", 832 children: configuration.labels 833 .map((label, index) => ({ 834 className: "control", 835 children: { 836 className: "tags has-addons", 837 children: [ 838 { 839 element: "span", 840 className: "tag is-info", 841 text: label, 842 }, 843 { 844 element: "a", 845 className: "tag is-delete", 846 onClick: () => resultUi.removeLabel(index), 847 }, 848 ], 849 }, 850 })) 851 .concat( 852 resultUi.state.configuration.isPublic 853 ? [] 854 : addLabelVisible 855 ? [ 856 { 857 className: "control field is-grouped", 858 children: [ 859 { 860 element: "input", 861 className: "input is-small control", 862 style: "width: 10rem", 863 id: "session-label-input", 864 type: "text", 865 onKeyUp: (event) => 866 event.keyCode === 13 867 ? resultUi.addLabel() 868 : null, 869 }, 870 { 871 className: 872 "button is-dark is-outlined is-small is-rounded control", 873 text: "save", 874 onClick: resultUi.addLabel, 875 }, 876 { 877 className: 878 "button is-dark is-outlined is-small is-rounded control", 879 text: "cancel", 880 onClick: resultUi.hideAddLabel, 881 }, 882 ], 883 }, 884 ] 885 : [ 886 { 887 className: "button is-rounded is-small", 888 text: "Add", 889 onClick: resultUi.showAddLabel, 890 }, 891 ] 892 ), 893 }, 894 }, 895 ], 896 }); 897 sessionDetailsView.appendChild(labelsField); 898 899 const sessionDetails = UI.getElement("session-details"); 900 sessionDetails.innerHTML = ""; 901 sessionDetails.appendChild(sessionDetailsView); 902 resultUi.renderReferenceSessions(); 903 }, 904 renderReferenceSessions() { 905 const { referenceSessions } = resultUi.state; 906 if (!referenceSessions || referenceSessions.length === 0) return; 907 const referenceSessionsList = UI.createElement({ 908 className: "field is-grouped is-grouped-multiline", 909 }); 910 const getBrowserIcon = (browser) => { 911 switch (browser.toLowerCase()) { 912 case "firefox": 913 return "fab fa-firefox"; 914 case "edge": 915 return "fab fa-edge"; 916 case "chrome": 917 case "chromium": 918 return "fab fa-chrome"; 919 case "safari": 920 case "webkit": 921 return "fab fa-safari"; 922 } 923 }; 924 referenceSessions.forEach((session) => { 925 const { token, browser } = session; 926 const referenceSessionItem = UI.createElement({ 927 className: 928 "control button is-dark is-small is-rounded is-outlined", 929 onClick: () => WaveService.openSession(token), 930 children: [ 931 { 932 element: "span", 933 className: "icon", 934 children: { 935 element: "i", 936 className: getBrowserIcon(browser.name), 937 }, 938 }, 939 { 940 element: "span", 941 text: token.split("-").shift(), 942 }, 943 ], 944 }); 945 referenceSessionsList.appendChild(referenceSessionItem); 946 }); 947 const referenceSessionsTarget = UI.getElement("reference-sessions"); 948 referenceSessionsTarget.innerHTML = ""; 949 referenceSessionsTarget.appendChild(referenceSessionsList); 950 const field = UI.getElement("reference-session-field"); 951 field.style["display"] = "flex"; 952 }, 953 renderLastCompletedTests() { 954 if (resultUi.state.configuration.isPublic) return; 955 const lastCompletedTestsView = UI.createElement({}); 956 957 const heading = UI.createElement({ 958 className: "title is-4", 959 children: [ 960 { element: "span", text: "Last Timed-Out Test Files" }, 961 { 962 element: "span", 963 className: "title is-7", 964 text: " (most recent first)", 965 }, 966 ], 967 }); 968 lastCompletedTestsView.appendChild(heading); 969 970 const { lastCompletedTests } = resultUi.state; 971 const testsTable = UI.createElement({ 972 element: "table", 973 className: "table", 974 style: "min-width: 100%", 975 children: [ 976 { 977 element: "thead", 978 children: [ 979 { 980 element: "tr", 981 children: [ 982 { element: "td", text: "Test File" }, 983 { element: "td", text: "Malfunctioning List" }, 984 ], 985 }, 986 ], 987 }, 988 { 989 element: "tbody", 990 children: lastCompletedTests.map(({ path, status }) => ({ 991 element: "tr", 992 children: [ 993 { element: "td", text: path }, 994 { 995 element: "td", 996 children: [ 997 { 998 element: "button", 999 className: "button is-dark is-outlined is-small", 1000 onClick: () => resultUi.addMalfunctioningTest(path), 1001 title: "Add to malfunctioning tests list.", 1002 children: [ 1003 { 1004 element: "span", 1005 className: "icon", 1006 children: [ 1007 { 1008 element: "i", 1009 className: resultUi.isTestOnMalfunctioningList( 1010 path 1011 ) 1012 ? "fas fa-check" 1013 : "fas fa-plus", 1014 }, 1015 ], 1016 }, 1017 ], 1018 }, 1019 ], 1020 }, 1021 ], 1022 })), 1023 }, 1024 ], 1025 }); 1026 if (lastCompletedTests.length > 0) { 1027 lastCompletedTestsView.appendChild( 1028 UI.createElement({ 1029 className: "container", 1030 style: "overflow-x: auto;", 1031 id: "last-completed-overflow", 1032 children: testsTable, 1033 }) 1034 ); 1035 } else { 1036 const noTestsLabel = UI.createElement({ 1037 text: "- No Timed-Out Tests -", 1038 style: "text-align: center", 1039 }); 1040 lastCompletedTestsView.appendChild(noTestsLabel); 1041 } 1042 1043 UI.saveScrollPosition("last-completed-overflow"); 1044 1045 const lastCompletedTestsElement = UI.getElement( 1046 "last-completed-tests" 1047 ); 1048 lastCompletedTestsElement.innerHTML = ""; 1049 lastCompletedTestsElement.appendChild(lastCompletedTestsView); 1050 1051 UI.loadScrollPosition("last-completed-overflow"); 1052 }, 1053 renderApiResults() { 1054 const { results, status } = resultUi.state; 1055 1056 const apiResultsView = UI.createElement({ 1057 style: "margin-bottom: 20px", 1058 }); 1059 1060 const heading = UI.createElement({ 1061 text: "API Results", 1062 className: "title is-4", 1063 }); 1064 apiResultsView.appendChild(heading); 1065 1066 if (!results) { 1067 const loadingIndicator = UI.createElement({ 1068 className: "level", 1069 children: { 1070 element: "span", 1071 className: "level-item", 1072 children: [ 1073 { 1074 element: "i", 1075 className: "fas fa-spinner fa-pulse", 1076 }, 1077 { 1078 style: "margin-left: 0.4em;", 1079 text: "Loading results ...", 1080 }, 1081 ], 1082 }, 1083 }); 1084 apiResultsView.appendChild(loadingIndicator); 1085 1086 const apiResults = UI.getElement("api-results"); 1087 apiResults.innerHTML = ""; 1088 apiResults.appendChild(apiResultsView); 1089 return; 1090 } 1091 1092 const width = status.status === "running" ? "7.5em" : "auto"; 1093 const header = UI.createElement({ 1094 element: "thead", 1095 children: [ 1096 { 1097 element: "tr", 1098 children: [ 1099 { element: "th", text: "API" }, 1100 { element: "th", text: "Pass", style: `min-width: ${width}` }, 1101 { element: "th", text: "Fail", style: `min-width: ${width}` }, 1102 { 1103 element: "th", 1104 text: "Timeout", 1105 style: `min-width: ${width}`, 1106 }, 1107 { 1108 element: "th", 1109 text: "Not Run", 1110 style: `min-width: ${width}`, 1111 }, 1112 { 1113 element: "th", 1114 text: "Test Files Run", 1115 style: `min-width: ${width}`, 1116 }, 1117 { element: "th", text: "Export" }, 1118 ], 1119 }, 1120 ], 1121 }); 1122 1123 const apis = Object.keys(results).sort((apiA, apiB) => 1124 apiA.toLowerCase() > apiB.toLowerCase() ? 1 : -1 1125 ); 1126 1127 const rows = apis.map((api) => { 1128 const { 1129 complete = 0, 1130 pass = 0, 1131 fail = 0, 1132 timeout = 0, 1133 timeoutfiles = [], 1134 not_run: notRun = 0, 1135 total, 1136 } = results[api]; 1137 isDone = results[api].complete == results[api].total; 1138 const totalTestResults = pass + fail + timeout + notRun; 1139 return UI.createElement({ 1140 element: "tr", 1141 style: "white-space: nowrap", 1142 children: [ 1143 { element: "td", text: api }, 1144 { 1145 element: "td", 1146 children: { 1147 style: `color: hsl(141, 71%, 38%); overflow: visible; white-space: nowrap; width: ${width}`, 1148 text: `${pass} (${utils.percent(pass, totalTestResults)}%)`, 1149 }, 1150 }, 1151 { 1152 element: "td", 1153 children: { 1154 className: "has-text-danger", 1155 style: `overflow: visible; white-space: nowrap; width: ${width}`, 1156 text: `${fail} (${utils.percent(fail, totalTestResults)}%)`, 1157 }, 1158 }, 1159 { 1160 element: "td", 1161 children: { 1162 style: `color: hsl(48, 100%, 40%); overflow: visible; white-space: nowrap; width: ${width}`, 1163 text: `${timeout} (${utils.percent( 1164 timeout, 1165 totalTestResults 1166 )}%)`, 1167 }, 1168 }, 1169 { 1170 element: "td", 1171 children: { 1172 className: "has-text-info", 1173 style: `overflow: visible; white-space: nowrap; width: ${width}`, 1174 text: `${notRun} (${utils.percent( 1175 notRun, 1176 totalTestResults 1177 )}%)`, 1178 }, 1179 }, 1180 { 1181 element: "td", 1182 children: { 1183 style: `overflow: visible; white-space: nowrap; width: ${width}`, 1184 text: `${complete}/${total} (${utils.percent( 1185 complete, 1186 total 1187 )}%)`, 1188 }, 1189 }, 1190 { 1191 element: "td", 1192 children: { 1193 className: "field has-addons", 1194 children: [ 1195 { 1196 className: "control", 1197 children: { 1198 className: "button is-dark is-outlined is-small", 1199 onclick: () => resultUi.downloadApiResultJson(api), 1200 text: "json", 1201 title: `Download results of ${api} API as JSON file.`, 1202 }, 1203 }, 1204 resultUi.state.reportsEnabled 1205 ? { 1206 className: "control", 1207 children: { 1208 className: "button is-dark is-outlined is-small", 1209 disabled: !isDone, 1210 onclick: () => resultUi.openHtmlReport(api), 1211 text: "report", 1212 title: `Show results of ${api} API in WPT Report format.`, 1213 }, 1214 } 1215 : null, 1216 ], 1217 }, 1218 }, 1219 ], 1220 }); 1221 }); 1222 1223 const { pass, fail, timeout, not_run, complete, total } = apis.reduce( 1224 (sum, api) => { 1225 Object.keys(sum).forEach( 1226 (key) => (sum[key] += results[api][key] ? results[api][key] : 0) 1227 ); 1228 return sum; 1229 }, 1230 { complete: 0, total: 0, pass: 0, fail: 0, timeout: 0, not_run: 0 } 1231 ); 1232 const totalTestResults = pass + fail + timeout + not_run; 1233 1234 const footer = UI.createElement({ 1235 element: "tfoot", 1236 children: [ 1237 { 1238 element: "tr", 1239 children: [ 1240 { element: "th", text: "Total" }, 1241 { 1242 element: "th", 1243 children: { 1244 style: `color: hsl(141, 71%, 38%); overflow: visible; white-space: nowrap; width: ${width}`, 1245 text: `${pass} (${utils.percent( 1246 pass, 1247 totalTestResults 1248 )}%)`, 1249 }, 1250 }, 1251 { 1252 element: "th", 1253 children: { 1254 style: `overflow: visible; white-space: nowrap; width: ${width}`, 1255 className: "has-text-danger", 1256 text: `${fail} (${utils.percent( 1257 fail, 1258 totalTestResults 1259 )}%)`, 1260 }, 1261 }, 1262 { 1263 element: "th", 1264 children: { 1265 style: `color: hsl(48, 100%, 40%); overflow: visible; white-space: nowrap; width: ${width}`, 1266 text: `${timeout} (${utils.percent( 1267 timeout, 1268 totalTestResults 1269 )}%)`, 1270 }, 1271 }, 1272 { 1273 element: "th", 1274 children: { 1275 style: `overflow: visible; white-space: nowrap; width: ${width}`, 1276 className: "has-text-info", 1277 text: `${not_run} (${utils.percent( 1278 not_run, 1279 totalTestResults 1280 )}%)`, 1281 }, 1282 }, 1283 { 1284 element: "th", 1285 children: { 1286 style: `overflow: visible; white-space: nowrap; width: ${width}`, 1287 text: `${complete}/${total} (${utils.percent( 1288 complete, 1289 total 1290 )}%)`, 1291 }, 1292 }, 1293 { element: "th" }, 1294 ], 1295 }, 1296 ], 1297 }); 1298 1299 const resultsTable = UI.createElement({ 1300 className: "container", 1301 style: "overflow-x: auto", 1302 id: "results-overflow", 1303 children: { 1304 element: "table", 1305 className: "table", 1306 id: "results-table", 1307 style: 1308 "width: 100%; min-width: 30em; border-radius: 3px; border: 2px solid hsl(0, 0%, 86%);", 1309 children: [header, { element: "tbody", children: rows }, footer], 1310 }, 1311 }); 1312 apiResultsView.appendChild(resultsTable); 1313 1314 UI.saveScrollPosition("results-overflow"); 1315 1316 const apiResults = UI.getElement("api-results"); 1317 apiResults.innerHTML = ""; 1318 apiResults.appendChild(apiResultsView); 1319 1320 UI.loadScrollPosition("results-overflow"); 1321 }, 1322 renderExportView() { 1323 const { status } = resultUi.state; 1324 if (!status) return; 1325 1326 const exportElement = UI.getElement("export"); 1327 exportElement.innerHTML = ""; 1328 1329 const heading = UI.createElement({ 1330 className: "title is-4", 1331 text: "Export", 1332 }); 1333 exportElement.appendChild(heading); 1334 1335 const resultsField = UI.createElement({ 1336 className: "field is-horizontal", 1337 children: [ 1338 { 1339 className: "field-label", 1340 children: { className: "label", text: "Results" }, 1341 }, 1342 { 1343 className: "field-body", 1344 children: { 1345 className: "control columns", 1346 style: "width: 100%", 1347 children: [ 1348 { 1349 className: "column is-9", 1350 text: 1351 "Download results for import into other WMAS Test Suite instances.", 1352 }, 1353 { 1354 className: "column is-3", 1355 children: { 1356 className: 1357 "button is-dark is-outlined is-small is-fullwidth", 1358 onClick: resultUi.downloadResults, 1359 disabled: status.status !== "completed", 1360 children: [ 1361 { 1362 element: "span", 1363 className: "icon", 1364 children: { 1365 element: "i", 1366 className: "fas fa-file-archive", 1367 }, 1368 }, 1369 { element: "span", text: "Download Zip" }, 1370 ], 1371 }, 1372 }, 1373 ], 1374 }, 1375 }, 1376 ], 1377 }); 1378 exportElement.appendChild(resultsField); 1379 1380 const jsonField = UI.createElement({ 1381 className: "field is-horizontal", 1382 children: [ 1383 { 1384 className: "field-label", 1385 children: { 1386 className: "label", 1387 text: "All JSON Files", 1388 }, 1389 }, 1390 { 1391 className: "field-body", 1392 children: { 1393 className: "control columns", 1394 style: "width: 100%", 1395 children: [ 1396 { 1397 className: "column is-9", 1398 text: 1399 "Download JSON files containing results of completed test files.", 1400 }, 1401 { 1402 className: "column is-3", 1403 children: { 1404 className: 1405 "button is-dark is-outlined is-small is-fullwidth", 1406 onclick: resultUi.downloadFinishedApiJsons, 1407 children: [ 1408 { 1409 element: "span", 1410 className: "icon", 1411 children: { 1412 element: "i", 1413 className: "fas fa-file-archive", 1414 }, 1415 }, 1416 { element: "span", text: "Download Zip" }, 1417 ], 1418 }, 1419 }, 1420 ], 1421 }, 1422 }, 1423 ], 1424 }); 1425 exportElement.appendChild(jsonField); 1426 1427 const htmlField = UI.createElement({ 1428 className: "field is-horizontal", 1429 children: [ 1430 { 1431 className: "field-label", 1432 children: { 1433 className: "label", 1434 text: "Session result HTML", 1435 }, 1436 }, 1437 { 1438 className: "field-body", 1439 children: { 1440 className: "control columns", 1441 style: "width: 100%", 1442 children: [ 1443 { 1444 className: "column is-9", 1445 text: 1446 "Download this sessions result as standalone HTML page, similar to this page.", 1447 }, 1448 { 1449 className: "column is-3", 1450 children: { 1451 className: 1452 "button is-dark is-outlined is-small is-fullwidth", 1453 onClick: resultUi.downloadHtmlZip, 1454 children: [ 1455 { 1456 element: "span", 1457 className: "icon", 1458 children: { 1459 element: "i", 1460 className: "fas fa-code", 1461 }, 1462 }, 1463 { element: "span", text: "Download HTML" }, 1464 ], 1465 }, 1466 }, 1467 ], 1468 }, 1469 }, 1470 ], 1471 }); 1472 exportElement.appendChild(htmlField); 1473 }, 1474 renderMalfunctioningTests() { 1475 const malfunctioningTestsView = UI.createElement({}); 1476 const heading = UI.createElement({ 1477 className: "title is-4", 1478 text: "Malfunctioning Tests", 1479 }); 1480 malfunctioningTestsView.appendChild(heading); 1481 1482 const { malfunctioningTests } = resultUi.state; 1483 const testsTable = UI.createElement({ 1484 element: "table", 1485 className: "table", 1486 style: "min-width: 100%", 1487 children: [ 1488 { 1489 element: "thead", 1490 children: [ 1491 { 1492 element: "tr", 1493 children: [ 1494 { element: "td", text: "Test File" }, 1495 { element: "td", text: "" }, 1496 ], 1497 }, 1498 ], 1499 }, 1500 { 1501 element: "tbody", 1502 children: malfunctioningTests.map((path) => ({ 1503 element: "tr", 1504 children: [ 1505 { element: "td", text: path }, 1506 { 1507 element: "td", 1508 children: resultUi.state.configuration.isPublic 1509 ? null 1510 : { 1511 element: "button", 1512 className: "button is-dark is-outlined is-small", 1513 onClick: () => 1514 resultUi.removeMalfunctioningTest(path), 1515 title: "Remove from malfunctioning tests list.", 1516 children: [ 1517 { 1518 element: "span", 1519 className: "icon", 1520 children: [ 1521 { 1522 element: "i", 1523 className: "fas fa-trash-alt", 1524 }, 1525 ], 1526 }, 1527 ], 1528 }, 1529 }, 1530 ], 1531 })), 1532 }, 1533 ], 1534 }); 1535 if (malfunctioningTests.length > 0) { 1536 malfunctioningTestsView.appendChild( 1537 UI.createElement({ 1538 className: "container", 1539 style: "overflow-x: auto", 1540 id: "malfunctioning-overflow", 1541 children: testsTable, 1542 }) 1543 ); 1544 } else { 1545 const noTestsLabel = UI.createElement({ 1546 text: "- No Tests Available -", 1547 style: "text-align: center", 1548 }); 1549 malfunctioningTestsView.appendChild(noTestsLabel); 1550 } 1551 1552 UI.saveScrollPosition("malfunctioning-overflow"); 1553 1554 const malfunctioningTestsElement = UI.getElement( 1555 "malfunctioning-tests" 1556 ); 1557 malfunctioningTestsElement.innerHTML = ""; 1558 malfunctioningTestsElement.appendChild(malfunctioningTestsView); 1559 1560 UI.loadScrollPosition("malfunctioning-overflow"); 1561 }, 1562 }; 1563 </script> 1564 </body> 1565 </html>