configuration.html (55443B)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 <title>Session Configuration - 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 <script src="lib/utils.js"></script> 10 <script src="lib/wave-service.js"></script> 11 <script src="lib/ui.js"></script> 12 <style> 13 .site-logo { 14 max-width: 300px; 15 margin: 0 0 30px -15px; 16 } 17 </style> 18 </head> 19 <body> 20 <script> 21 // var apis = [ 22 // { title: "2D Context", path: "/2dcontext" }, 23 // { title: "Content Security Policy", path: "/content-security-policy" }, 24 // { title: "CSS", path: "/css" }, 25 // { title: "DOM", path: "/dom" }, 26 // { title: "ECMAScript", path: "/ecmascript" }, 27 // { title: "Encrypted media", path: "/encrypted-media" }, 28 // { title: "Fetch", path: "/fetch" }, 29 // { title: "FileAPI", path: "/FileAPI" }, 30 // { title: "Fullscreen", path: "/fullscreen" }, 31 // { title: "WebGL", path: "/webgl" }, 32 // { title: "HTML", path: "/html" }, 33 // { title: "IndexedDB", path: "/IndexedDB" }, 34 // { title: "Media Source", path: "/media-source" }, 35 // { title: "Notifications", path: "/notifications" }, 36 // { title: "Page Visibility", path: "/page-visibility" }, 37 // { title: "Service Workers", path: "/service-workers" }, 38 // { title: "UI Events", path: "/uievents" }, 39 // { title: "WAVE Extra", path: "/wave-extra" }, 40 // { title: "Webaudio", path: "/webaudio" }, 41 // { title: "WebCryptoAPI", path: "/WebCryptoAPI" }, 42 // { title: "Webmessaging", path: "/webmessaging" }, 43 // { title: "Websockets", path: "/websockets" }, 44 // { title: "Webstorage", path: "/webstorage" }, 45 // { title: "Workers", path: "/workers" }, 46 // { title: "XHR", path: "/xhr" } 47 // ]; 48 49 // var referenceSessions = [ 50 // { 51 // title: "Edge 44.17763", 52 // engine: "", 53 // token: "b2924d20-6a93-11e9-98b4-a11fb92a6d1c", 54 // icon: "fab fa-edge" 55 // }, 56 // { 57 // title: "Firefox 64.0", 58 // engine: "Gecko 64.0", 59 // token: "bb7aafa0-6a92-11e9-8ec2-04f58dad2e4f", 60 // icon: "fab fa-firefox" 61 // }, 62 // { 63 // title: "WebKit 605.1.15", 64 // engine: "Revision 239158", 65 // token: "caf823e0-6a92-11e9-b732-3188d0065ebc", 66 // icon: "fab fa-safari" 67 // }, 68 // { 69 // title: "Chromium 73.0.3640.0", 70 // engine: "Blink 537.36", 71 // token: "a50c6db0-6a94-11e9-8d1b-e23fc4555885", 72 // icon: "fab fa-chrome" 73 // } 74 // ]; 75 76 var testFileSelectionEnabled = true; 77 78 window.onload = function () { 79 new ConfigurationView(); 80 }; 81 82 function ConfigurationView() { 83 const query = utils.parseQuery(location.search); 84 var token = query.token; 85 if (token) WaveService.addRecentSession(token); 86 var referenceSessions = []; 87 var apis = []; 88 var testTypeSelectionEnabled = true; 89 var testFileSelectionEnabled = false; 90 91 var types = [ 92 { title: "Automatic", value: "automatic" }, 93 { title: "Manual", value: "manual" }, 94 ]; 95 var state = {}; 96 loadServerStatus(); 97 loadSessionData(token, function () { 98 loadPublicSessionData(function () { 99 renderReferencesField(); 100 }); 101 }); 102 render(); 103 104 function loadServerStatus() { 105 WaveService.readStatus(function (status) { 106 testTypeSelectionEnabled = status.testTypeSelectionEnabled; 107 testFileSelectionEnabled = status.testFileSelectionEnabled; 108 renderSessionConfiguration(); 109 }); 110 } 111 112 function loadSessionData(token, callback) { 113 if (!token) { 114 state.expired = true; 115 return; 116 } 117 WaveService.readSessionStatus( 118 token, 119 function (status) { 120 if (status.status !== "pending") { 121 openResultsPage(token); 122 return; 123 } 124 state.status = status; 125 126 WaveService.readSession(token, function (configuration) { 127 if ( 128 configuration.tests.include.findIndex( 129 (test) => test === "/" 130 ) !== -1 131 ) { 132 configuration.tests.include = apis.map((api) => api.path); 133 } 134 state.configurationBackup = utils.copyObject(configuration); 135 state.configuration = configuration; 136 setTimeout( 137 handleExpiration, 138 status.expirationDate.getTime() - Date.now() 139 ); 140 renderSessionConfiguration(); 141 callback(); 142 WaveService.readAvailableApis(function (available_apis) { 143 available_apis = available_apis.sort((apiA, apiB) => 144 apiA.title.toLowerCase() > apiB.title.toLowerCase() ? 1 : -1 145 ); 146 apis = available_apis; 147 selectAllTests(); 148 renderSessionConfiguration(); 149 }); 150 }); 151 }, 152 function () { 153 state.expired = true; 154 renderSessionConfiguration(); 155 renderExcludedTests(); 156 renderResumeView(); 157 } 158 ); 159 } 160 161 function loadPublicSessionData(callback) { 162 WaveService.readPublicSessions(function (tokens) { 163 WaveService.readMultipleSessions(tokens, function (configurations) { 164 console.log(configurations); 165 referenceSessions = configurations 166 .sort((confA, confB) => 167 confA.browser.name.toLowerCase() > 168 confB.browser.name.toLowerCase() 169 ? 1 170 : -1 171 ) 172 .map((configuration) => { 173 var icon = ""; 174 switch (configuration.browser.name.toLowerCase()) { 175 case "firefox": 176 icon = "fab fa-firefox"; 177 break; 178 case "webkit": 179 case "safari": 180 icon = "fab fa-safari"; 181 break; 182 case "edge": 183 icon = "fab fa-edge"; 184 break; 185 case "chrome": 186 case "chromium": 187 icon = "fab fa-chrome"; 188 break; 189 } 190 return { 191 title: 192 configuration.browser.name + 193 " " + 194 configuration.browser.version, 195 token: configuration.token, 196 icon, 197 engine: configuration.browser.engine || "", 198 }; 199 }); 200 callback(referenceSessions); 201 }); 202 }); 203 } 204 205 function handleConfigureSession() { 206 const tokenFragmentInput = UI.getElement("token-fragment"); 207 const fragment = tokenFragmentInput.value; 208 findSession(fragment, function (session) { 209 if (!session) { 210 const errorBox = UI.getElement("find-error"); 211 errorBox.setAttribute("style", "display: block"); 212 return; 213 } 214 tokenFragmentInput.value = ""; 215 const errorBox = UI.getElement("find-error"); 216 errorBox.setAttribute("style", "display: none"); 217 const path = location.pathname + "?token=" + session.token; 218 location.href = path; 219 }); 220 } 221 222 function findSession(fragment, callback) { 223 if (!fragment || fragment.length < 8) { 224 callback(null); 225 return; 226 } 227 WaveService.findToken( 228 fragment, 229 function (token) { 230 WaveService.readSession(token, function (session) { 231 WaveService.readSessionStatus(token, function (status) { 232 session.status = status.status; 233 session.dateStarted = status.dateStarted; 234 session.dateFinished = status.dateFinished; 235 callback(session); 236 }); 237 }); 238 }, 239 function () { 240 callback(null); 241 } 242 ); 243 } 244 245 function hasIncludedTest(path) { 246 var tests = state.configuration.tests; 247 //var index = tests.include.findIndex(function (test) { 248 // return test.match(new RegExp("^" + path)); 249 //}); 250 var index = tests.include.indexOf(path); 251 return index !== -1; 252 } 253 254 function handleIncludedTestToggle(path) { 255 var configuration = state.configuration; 256 if (hasIncludedTest(path)) { 257 handleRemoveIncludedTest(path); 258 } else { 259 handleAddIncludedTest(path); 260 } 261 } 262 263 function handleAddIncludedTest(path) { 264 var tests = state.configuration.tests; 265 if (state.tests && state.tests[path.substr(1)]) { 266 tests.include = tests.include.filter(function (test) { 267 return !test.match(new RegExp("^" + path + "/")); 268 }); 269 tests.include = tests.include.concat(state.tests[path.substr(1)]); 270 } else { 271 tests.include.push(path); 272 } 273 } 274 275 function handleRemoveIncludedTest(path) { 276 var tests = state.configuration.tests; 277 278 if (state.tests && state.tests[path.substr(1)]) { 279 tests.include = tests.include.filter(function (test) { 280 return !test.match(new RegExp("^" + path + "/")); 281 }); 282 } else { 283 var index = tests.include.findIndex((test) => test === path); 284 tests.include.splice(index, 1); 285 } 286 } 287 288 function getIncludedRatio(path) { 289 var includedTests = state.configuration.tests.include; 290 if (!state.tests) { 291 return includedTests.indexOf(path) !== -1 ? 1 : 0; 292 } 293 var count = 0; 294 for (var test of includedTests) { 295 if (!test.match(new RegExp("^" + path))) continue; 296 count++; 297 } 298 return count / state.tests[path.substr(1)].length; 299 } 300 301 function selectAllTests() { 302 var tests = state.configuration.tests; 303 if (state.tests) { 304 tests.include = []; 305 for (var api in state.tests) { 306 tests.include = tests.include.concat(state.tests[api]); 307 } 308 } else { 309 tests.include = apis.map((api) => api.path); 310 } 311 } 312 313 function deselectAllTests() { 314 var configuration = state.configuration; 315 configuration.tests.include = []; 316 } 317 318 function hasTestType(value) { 319 var configuration = state.configuration; 320 var index = configuration.types.findIndex((type) => type === value); 321 return index !== -1; 322 } 323 324 function handleTestTypeToggle(value) { 325 var configuration = state.configuration; 326 if (hasTestType(value)) { 327 var index = configuration.types.findIndex((type) => type === value); 328 configuration.types.splice(index, 1); 329 } else { 330 configuration.types.push(value); 331 } 332 } 333 334 function selectAllTestTypes() { 335 var configuration = state.configuration; 336 configuration.types = types.map((type) => type.value); 337 } 338 339 function deselectAllTestTypes() { 340 var configuration = state.configuration; 341 configuration.types = []; 342 } 343 344 function hasRefSession(session) { 345 var configuration = state.configuration; 346 var index = configuration.referenceTokens.findIndex( 347 (token) => token === session.token 348 ); 349 return index !== -1; 350 } 351 352 function handleRefSessionToggle(session) { 353 var configuration = state.configuration; 354 if (hasRefSession(session)) { 355 var index = configuration.referenceTokens.findIndex( 356 (token) => token === session.token 357 ); 358 configuration.referenceTokens.splice(index, 1); 359 } else { 360 configuration.referenceTokens.push(session.token); 361 } 362 } 363 364 function selectAllRefSessions() { 365 var configuration = state.configuration; 366 configuration.referenceTokens = referenceSessions.map( 367 (session) => session.token 368 ); 369 } 370 371 function deselectAllRefSessions() { 372 var configuration = state.configuration; 373 configuration.referenceTokens = []; 374 } 375 376 function isTestListValid() { 377 var configuration = state.configuration; 378 return configuration.tests.include.length > 0; 379 } 380 381 function isTestTypeListValid() { 382 var configuration = state.configuration; 383 return configuration.types.length > 0; 384 } 385 386 function isConfigurationValid() { 387 if (!isTestListValid()) return false; 388 if (!isTestTypeListValid()) return false; 389 return true; 390 } 391 392 function isSessionStarting() { 393 return state.isStarting; 394 } 395 396 function checkApiList() { 397 var apiErrorElement = UI.getElement("api-error"); 398 apiErrorElement.innerHTML = ""; 399 if (!isTestListValid()) { 400 apiErrorElement.appendChild( 401 UI.createElement( 402 createErrorMessage( 403 "Select at least one API or at least one test within an API" 404 ) 405 ) 406 ); 407 } 408 renderButtonsField(); 409 } 410 411 function handleStart() { 412 if (isSessionStarting()) return; 413 var configuration = state.configuration; 414 var token = configuration.token; 415 WaveService.updateSession(token, configuration, function () { 416 WaveService.updateLabels(token, configuration.labels, function () { 417 WaveService.startSession(token, function () { 418 openResultsPage(token); 419 }); 420 }); 421 }); 422 state.isStarting = true; 423 } 424 425 function handleDiscardChanges() { 426 state.configuration = utils.copyObject(state.configurationBackup); 427 } 428 429 function handleExpiration() { 430 state.expired = true; 431 renderSessionConfiguration(); 432 renderResumeView(); 433 } 434 435 function openResultsPage(token) { 436 location.href = WEB_ROOT + "results.html?token=" + token; 437 } 438 439 function handleAddExludedTestsRaw() { 440 var excludedTestsTextArea = UI.getElement("excluded-tests-text"); 441 var tests = excludedTestsTextArea.value.split("\n"); 442 var configuration = state.configuration; 443 var excludedTests = configuration.tests.exclude; 444 for (var test of tests) { 445 if (!test) continue; 446 if (test.startsWith("#")) continue; 447 if (excludedTests.indexOf(test) !== -1) continue; 448 excludedTests.push(test); 449 } 450 451 excludedTestsTextArea.value = ""; 452 renderExcludedTests(); 453 } 454 455 function handleAddExludedTestsMalfunctioning() { 456 var excludedTestsTextArea = UI.getElement("excluded-tests-text"); 457 var token = excludedTestsTextArea.value; 458 var tokenRegExp = new RegExp( 459 "^[a-f0-9]{8}(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,12}|$)" 460 ); 461 var configuration = state.configuration; 462 var excludedTests = configuration.tests.exclude; 463 if (tokenRegExp.test(token)) { 464 WaveService.findToken( 465 token, 466 function (token) { 467 WaveService.readMalfunctioningTests(token, function ( 468 malfunctioningTests 469 ) { 470 for (var test of malfunctioningTests) { 471 if (!test) continue; 472 if (excludedTests.indexOf(test) !== -1) continue; 473 excludedTests.push(test); 474 } 475 renderExcludedTests(); 476 }); 477 }, 478 function () { 479 state.excludedTestError = "Session not found"; 480 renderExcludedTests(); 481 } 482 ); 483 } else { 484 state.excludedTestError = "Invalid session token"; 485 renderExcludedTests(); 486 } 487 } 488 489 function handleAddExludedTestsExcluded() { 490 var excludedTestsTextArea = UI.getElement("excluded-tests-text"); 491 var token = excludedTestsTextArea.value; 492 var tokenRegExp = new RegExp( 493 "^[a-f0-9]{8}(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,12}|$)" 494 ); 495 var configuration = state.configuration; 496 var excludedTests = configuration.tests.exclude; 497 if (tokenRegExp.test(token)) { 498 WaveService.findToken( 499 token, 500 function (token) { 501 WaveService.readSession(token, function (sessionConfig) { 502 var prevExcludedTests = sessionConfig.tests.exclude; 503 for (var test of prevExcludedTests) { 504 if (!test) continue; 505 if (excludedTests.indexOf(test) !== -1) continue; 506 excludedTests.push(test); 507 } 508 renderExcludedTests(); 509 }); 510 }, 511 function () { 512 state.excludedTestError = "Session not found"; 513 renderExcludedTests(); 514 } 515 ); 516 } else { 517 state.excludedTestError = "Invalid session token"; 518 renderExcludedTests(); 519 } 520 } 521 522 function handleRemoveExcludedTest(test) { 523 var configuration = state.configuration; 524 var excludedTests = configuration.tests.exclude; 525 var index = excludedTests.indexOf(test); 526 excludedTests.splice(index, 1); 527 renderExcludedTests(); 528 } 529 530 function handleExcludeInputChange(type) { 531 if (state.activeExcludeInput === type) { 532 state.activeExcludeInput = null; 533 } else { 534 state.activeExcludeInput = type; 535 } 536 renderExcludedTests(); 537 } 538 539 function showAddLabel() { 540 state.addLabelVisible = true; 541 renderLabelsField(); 542 UI.getElement("session-label-input").focus(); 543 } 544 545 function hideAddLabel() { 546 state.addLabelVisible = false; 547 renderLabelsField(); 548 } 549 550 function addLabel() { 551 var label = UI.getElement("session-label-input").value; 552 if (!label) return; 553 state.configuration.labels.push(label); 554 renderLabelsField(); 555 UI.getElement("session-label-input").focus(); 556 } 557 558 function removeLabel(index) { 559 const { configuration } = state; 560 configuration.labels.splice(index, 1); 561 renderLabelsField(); 562 } 563 564 function resumeSession() { 565 var resumeToken = UI.getElement("resume-token").value; 566 if (!resumeToken) return; 567 568 WaveService.resumeSession( 569 state.configuration.token, 570 resumeToken, 571 function () { 572 openResultsPage(resumeToken); 573 } 574 ); 575 } 576 577 function render() { 578 const configurationView = UI.createElement({ 579 element: "section", 580 className: "section", 581 children: [ 582 { 583 className: "container", 584 style: "margin-bottom: 2em", 585 children: [ 586 { 587 element: "img", 588 src: "res/wavelogo_2016.jpg", 589 className: "site-logo", 590 }, 591 { className: "title", text: "Session Configuration" }, 592 ], 593 }, 594 { 595 id: "session-configuration", 596 }, 597 { 598 id: "resume-view", 599 className: "container", 600 style: "margin-bottom: 2em", 601 }, 602 ], 603 }); 604 const root = UI.getRoot(); 605 root.innerHTML = ""; 606 root.appendChild(configurationView); 607 renderSessionConfiguration(); 608 renderResumeView(); 609 } 610 611 function renderSessionConfiguration() { 612 var configuration = state.configuration; 613 var status = state.status; 614 var sessionConfigurationView = UI.createElement({}); 615 var sessionConfiguration = UI.getElement("session-configuration"); 616 sessionConfiguration.innerHTML = ""; 617 618 if (state.expired) { 619 var expiredIndicator = UI.createElement({ 620 className: "level container", 621 style: "max-width: 500px", 622 children: { 623 element: "span", 624 className: "level-item field column", 625 children: [ 626 { 627 element: "article", 628 className: "message is-danger", 629 id: "find-error", 630 children: [ 631 { 632 text: 633 "Could not find any sessions! Try adding more characters of the token.", 634 className: "message-body", 635 }, 636 ], 637 style: "display: none", 638 }, 639 { 640 className: "label has-text-weight-normal", 641 text: "Session token:", 642 }, 643 { 644 className: "field-body", 645 children: { 646 className: "field", 647 children: { 648 className: "control", 649 children: { 650 style: "display: flex; margin-bottom: 10px;", 651 children: [ 652 { 653 element: "input", 654 inputType: "text", 655 className: "input is-family-monospace", 656 id: "token-fragment", 657 placeholder: 658 "First 8 characters or more of session token", 659 onKeyDown: function (event) { 660 if (event.key === "Enter") { 661 handleConfigureSession(); 662 } 663 }, 664 }, 665 ], 666 }, 667 }, 668 }, 669 }, 670 { 671 className: "field is-grouped is-grouped-right", 672 children: { 673 className: "control", 674 children: { 675 className: "button is-dark is-outlined", 676 children: [ 677 { 678 element: "span", 679 className: "icon", 680 children: [ 681 { 682 element: "i", 683 className: "fas fa-cog", 684 }, 685 ], 686 }, 687 { text: "Configure Session", element: "span" }, 688 ], 689 onclick: function () { 690 handleConfigureSession(); 691 }, 692 }, 693 }, 694 }, 695 ], 696 }, 697 }); 698 sessionConfigurationView.appendChild(expiredIndicator); 699 sessionConfiguration.appendChild(sessionConfigurationView); 700 return; 701 } 702 703 if (!configuration) { 704 var loadingIndicator = createLoadingIndicator( 705 "Loading configuration ..." 706 ); 707 sessionConfigurationView.appendChild(loadingIndicator); 708 sessionConfiguration.appendChild(sessionConfigurationView); 709 return; 710 } 711 712 sessionConfiguration.parentNode.replaceChild( 713 UI.createElement({ 714 id: "session-configuration", 715 className: "container", 716 style: "margin-bottom: 2em", 717 children: [ 718 { 719 id: "token-field", 720 }, 721 { 722 id: "expiration-field", 723 }, 724 { 725 id: "labels-field", 726 }, 727 { 728 id: "apis-field", 729 }, 730 { 731 id: "exclude-field", 732 }, 733 { 734 id: "types-field", 735 }, 736 { 737 id: "references-field", 738 }, 739 { 740 id: "buttons-field", 741 }, 742 ], 743 }), 744 sessionConfiguration 745 ); 746 747 renderTokenField(); 748 renderExpirationField(); 749 renderLabelsField(); 750 renderApisField(); 751 renderExcludeField(); 752 renderTypesField(); 753 renderReferencesField(); 754 renderButtonsField(); 755 } 756 757 function renderTokenField() { 758 var configuration = state.configuration; 759 var tokenField = UI.getElement("token-field"); 760 tokenField.parentNode.replaceChild( 761 UI.createElement({ 762 id: "token-field", 763 className: "field is-horizontal", 764 children: [ 765 { 766 className: "field-label", 767 children: { className: "label", text: "Token" }, 768 }, 769 { 770 className: "field-body", 771 children: { 772 className: "field", 773 children: { 774 className: "control", 775 text: configuration.token, 776 }, 777 }, 778 }, 779 ], 780 }), 781 tokenField 782 ); 783 } 784 785 function renderExpirationField() { 786 var status = state.status; 787 var expirationField = UI.getElement("expiration-field"); 788 expirationField.parentNode.replaceChild( 789 UI.createElement({ 790 id: "expiration-field", 791 className: "field is-horizontal", 792 children: [ 793 { 794 className: "field-label", 795 children: { className: "label", text: "Expires" }, 796 }, 797 { 798 className: "field-body", 799 children: { 800 className: "field", 801 children: { 802 className: "control", 803 text: status.expirationDate.toLocaleString(), 804 }, 805 }, 806 }, 807 ], 808 }), 809 expirationField 810 ); 811 } 812 813 function renderLabelsField() { 814 var addLabelVisible = state.addLabelVisible; 815 var configuration = state.configuration; 816 var labelsField = UI.getElement("labels-field"); 817 818 labelsField.parentNode.replaceChild( 819 UI.createElement({ 820 id: "labels-field", 821 className: "field is-horizontal", 822 children: [ 823 { 824 className: "field-label", 825 children: { className: "label", text: "Labels" }, 826 }, 827 { 828 className: "field-body", 829 children: { 830 className: "field is-grouped is-grouped-multiline", 831 children: configuration.labels 832 .map((label, index) => ({ 833 className: "control", 834 children: { 835 className: "tags has-addons", 836 children: [ 837 { 838 element: "span", 839 className: "tag is-info", 840 text: label, 841 }, 842 { 843 element: "a", 844 className: "tag is-delete", 845 onClick: () => removeLabel(index), 846 }, 847 ], 848 }, 849 })) 850 .concat( 851 addLabelVisible 852 ? [ 853 { 854 className: "control field is-grouped", 855 children: [ 856 { 857 element: "input", 858 className: "input is-small control", 859 style: "width: 10rem", 860 id: "session-label-input", 861 type: "text", 862 onKeyUp: (event) => 863 event.keyCode === 13 ? addLabel() : null, 864 }, 865 { 866 className: 867 "button is-dark is-outlined is-small is-rounded control", 868 text: "save", 869 onClick: addLabel, 870 }, 871 { 872 className: 873 "button is-dark is-outlined is-small is-rounded control", 874 text: "cancel", 875 onClick: hideAddLabel, 876 }, 877 ], 878 }, 879 ] 880 : [ 881 { 882 className: "button is-rounded is-small", 883 text: "Add", 884 onClick: showAddLabel, 885 }, 886 ] 887 ), 888 }, 889 }, 890 ], 891 }), 892 labelsField 893 ); 894 } 895 896 function renderApisField() { 897 var apisField = UI.getElement("apis-field"); 898 apisField.parentNode.replaceChild( 899 UI.createElement({ 900 id: "apis-field", 901 className: "field is-horizontal", 902 children: [ 903 { 904 className: "field-label", 905 children: [ 906 { className: "label", text: "APIs" }, 907 createSelectDeselectButtons( 908 function () { 909 selectAllTests(); 910 renderApisField(); 911 }, 912 function () { 913 deselectAllTests(); 914 renderApisField(); 915 } 916 ), 917 ], 918 }, 919 { 920 className: "field-body", 921 children: { 922 className: "field", 923 children: { 924 className: "control", 925 children: [ 926 { 927 id: "api-error", 928 }, 929 { 930 element: "ul", 931 className: "menu-list", 932 children: apis.map(function (api) { 933 return UI.createElement({ 934 element: "li", 935 id: api.title, 936 }); 937 }), 938 }, 939 ], 940 }, 941 }, 942 }, 943 ], 944 }), 945 apisField 946 ); 947 renderApisList(apis); 948 checkApiList(); 949 } 950 951 function renderApisList(apis) { 952 for (var api of apis) { 953 renderApiList(api); 954 } 955 } 956 957 function renderApiList(api) { 958 var listItem = UI.getElement(api.title); 959 var includedRatio = getIncludedRatio(api.path); 960 var apiListItem = { 961 element: "a", 962 onClick: function (event) { 963 if (!testFileSelectionEnabled) return; 964 if (!state.expandedApis) state.expandedApis = {}; 965 state.expandedApis[api.path] = !state.expandedApis[api.path]; 966 renderApiList(api); 967 }, 968 children: [ 969 { 970 element: "input", 971 type: "checkbox", 972 style: "width: 1.3em; height: 1.3em;vertical-align: middle;", 973 checked: includedRatio > 0, 974 indeterminate: includedRatio > 0 && includedRatio < 1, 975 onclick: function (event) { 976 event.stopPropagation(); 977 if (includedRatio > 0) { 978 handleRemoveIncludedTest(api.path); 979 } else { 980 handleAddIncludedTest(api.path); 981 } 982 renderApiList(api); 983 }, 984 }, 985 testFileSelectionEnabled 986 ? { 987 element: "span", 988 style: 989 "display: inline-block;vertical-align: middle;margin-left:0.3em;width: 0.7em", 990 children: { 991 element: "i", 992 className: 993 state.expandedApis && state.expandedApis[api.path] 994 ? "fas fa-angle-down" 995 : "fas fa-angle-right", 996 }, 997 } 998 : null, 999 { 1000 style: 1001 "display: inline-block;vertical-align: middle;margin-left:0.3em;width: 90%", 1002 text: api.title, 1003 }, 1004 ], 1005 }; 1006 listItem.innerHTML = ""; 1007 listItem.appendChild(UI.createElement(apiListItem)); 1008 if (state.expandedApis && state.expandedApis[api.path]) { 1009 listItem.appendChild(createApiTestsList(api)); 1010 } 1011 checkApiList(); 1012 } 1013 1014 function createApiTestsList(api) { 1015 if (!state.tests) { 1016 WaveService.readTestList( 1017 function (readTests) { 1018 state.tests = readTests; 1019 for (var api in state.tests) { 1020 if (hasIncludedTest("/" + api)) { 1021 handleRemoveIncludedTest("/" + api); 1022 handleAddIncludedTest("/" + api); 1023 } 1024 } 1025 renderApiList(this.api); 1026 }.bind({ api: api }) 1027 ); 1028 return createLoadingIndicator("Loading tests ..."); 1029 } else { 1030 var tests = state.tests[api.path.substr(1)]; 1031 var testListView = { 1032 element: "ul", 1033 children: [], 1034 }; 1035 1036 testListView.children = testListView.children.concat( 1037 tests 1038 .sort() 1039 .map(function (test) { 1040 return { 1041 element: "li", 1042 onclick: function (event) { 1043 handleIncludedTestToggle(test); 1044 renderApiList(api); 1045 }, 1046 children: [ 1047 { 1048 element: "a", 1049 children: [ 1050 { 1051 element: "input", 1052 type: "checkbox", 1053 style: 1054 "width: 1.3em; height: 1.3em;vertical-align: middle;", 1055 checked: hasIncludedTest(test), 1056 }, 1057 { 1058 style: 1059 "display: inline-block;vertical-align: middle;margin-left:0.3em;max-width: 90%", 1060 text: test, 1061 }, 1062 ], 1063 }, 1064 ], 1065 }; 1066 }) 1067 ); 1068 return UI.createElement(testListView); 1069 } 1070 } 1071 1072 function renderExcludeField() { 1073 var excludeField = UI.getElement("exclude-field"); 1074 excludeField.parentNode.replaceChild( 1075 UI.createElement({ 1076 id: "exclude-field", 1077 className: "field is-horizontal", 1078 children: [ 1079 { 1080 className: "field-label", 1081 children: { className: "label", text: "Excluded Tests" }, 1082 }, 1083 { 1084 className: "field-body", 1085 children: { 1086 className: "field", 1087 children: { 1088 className: "control", 1089 children: { id: "excluded-tests-view" }, 1090 }, 1091 }, 1092 }, 1093 ], 1094 }), 1095 excludeField 1096 ); 1097 renderExcludedTests(); 1098 } 1099 1100 function renderTypesField() { 1101 if (!testTypeSelectionEnabled) return; 1102 var typesField = UI.getElement("types-field"); 1103 typesField.parentNode.replaceChild( 1104 UI.createElement({ 1105 id: "types-field", 1106 className: "field is-horizontal", 1107 children: [ 1108 { 1109 className: "field-label", 1110 children: { className: "label", text: "Test Types" }, 1111 }, 1112 { 1113 className: "field-body", 1114 children: { 1115 className: "field", 1116 children: { 1117 className: "control", 1118 children: [ 1119 isTestTypeListValid() 1120 ? null 1121 : createErrorMessage("Select at least one test type"), 1122 ].concat(createTestTypeList(types)), 1123 }, 1124 }, 1125 }, 1126 ], 1127 }), 1128 typesField 1129 ); 1130 } 1131 1132 function renderReferencesField() { 1133 if (referenceSessions.length === 0) { 1134 return; 1135 } 1136 var referencesField = UI.getElement("references-field"); 1137 referencesField.parentNode.replaceChild( 1138 UI.createElement({ 1139 id: "references-field", 1140 className: "field is-horizontal", 1141 children: [ 1142 { 1143 className: "field-label", 1144 children: [ 1145 { className: "label", text: "Reference Browsers" }, 1146 createSelectDeselectButtons( 1147 function () { 1148 selectAllRefSessions(); 1149 renderReferencesField(); 1150 }, 1151 function () { 1152 deselectAllRefSessions(); 1153 renderReferencesField(); 1154 } 1155 ), 1156 ], 1157 }, 1158 { 1159 className: "field-body", 1160 children: { 1161 className: "field", 1162 children: { 1163 className: "control", 1164 children: createRefSessionsList(referenceSessions), 1165 }, 1166 }, 1167 }, 1168 ], 1169 }), 1170 referencesField 1171 ); 1172 } 1173 1174 function renderButtonsField() { 1175 var buttonsField = UI.getElement("buttons-field"); 1176 buttonsField.parentNode.replaceChild( 1177 UI.createElement({ 1178 id: "buttons-field", 1179 className: "level level-right", 1180 children: [ 1181 { 1182 element: "button", 1183 className: "button is-success", 1184 style: "margin-right: 0.3em", 1185 disabled: !isConfigurationValid(), 1186 onClick: function () { 1187 handleStart(); 1188 renderButtonsField(); 1189 }, 1190 children: [ 1191 { 1192 element: "span", 1193 className: "icon", 1194 children: [ 1195 { 1196 element: "i", 1197 className: isSessionStarting() 1198 ? "fas fa-spinner fa-pulse" 1199 : "fas fa-play", 1200 }, 1201 ], 1202 }, 1203 { 1204 element: "span", 1205 text: isSessionStarting() 1206 ? "Starting Session ..." 1207 : "Start Session", 1208 }, 1209 ], 1210 }, 1211 { 1212 element: "button", 1213 className: "button", 1214 onClick: function () { 1215 handleDiscardChanges(); 1216 renderSessionConfiguration(); 1217 }, 1218 disabled: isSessionStarting(), 1219 children: [ 1220 { 1221 element: "span", 1222 className: "icon", 1223 children: [ 1224 { 1225 element: "i", 1226 className: "fas fa-times", 1227 }, 1228 ], 1229 }, 1230 { 1231 element: "span", 1232 text: "Discard Changes", 1233 }, 1234 ], 1235 }, 1236 ], 1237 }), 1238 buttonsField 1239 ); 1240 } 1241 1242 function renderExcludedTests() { 1243 var excludedTestsView = UI.getElement("excluded-tests-view"); 1244 if (!excludedTestsView) return; 1245 excludedTestsView.innerHTML = ""; 1246 1247 var errorMessage = state.excludedTestError; 1248 if (errorMessage) { 1249 var error = createErrorMessage(errorMessage); 1250 excludedTestsView.appendChild(UI.createElement(error)); 1251 } 1252 1253 var excludeInputs = [ 1254 { title: "Add Raw", type: "raw" }, 1255 { title: "Add Malfunctioning", type: "malfunc" }, 1256 { title: "Add Previous Excluded", type: "excluded" }, 1257 ]; 1258 1259 var activeExcludeInput = state.activeExcludeInput; 1260 1261 var excludedTestInputSwitch = UI.createElement({ 1262 className: "tabs is-centered is-toggle is-small", 1263 children: { 1264 element: "ul", 1265 children: excludeInputs.map(function (input) { 1266 return { 1267 element: "li", 1268 onClick: function () { 1269 handleExcludeInputChange(input.type); 1270 }, 1271 className: (function () { 1272 if (activeExcludeInput === input.type) return "is-active"; 1273 return ""; 1274 })(), 1275 children: { element: "a", text: input.title }, 1276 }; 1277 }), 1278 }, 1279 }); 1280 excludedTestsView.appendChild(excludedTestInputSwitch); 1281 1282 if (activeExcludeInput === "raw") { 1283 var rawInput = UI.createElement({ 1284 children: [ 1285 { 1286 className: "is-size-7", 1287 style: "margin-bottom: 20px", 1288 text: 1289 "Provide paths to test files or directories to exclude them from the session. One path per line, lines starting with # are omitted.", 1290 }, 1291 { 1292 element: "textarea", 1293 className: "textarea", 1294 id: "excluded-tests-text", 1295 }, 1296 { 1297 style: "margin-top: 10px", 1298 onClick: function () { 1299 handleAddExludedTestsRaw(); 1300 }, 1301 children: [ 1302 { 1303 element: "button", 1304 className: "button", 1305 style: "margin-bottom: 20px", 1306 text: "Add", 1307 }, 1308 ], 1309 }, 1310 ], 1311 }); 1312 excludedTestsView.appendChild(rawInput); 1313 } else if ( 1314 activeExcludeInput === "malfunc" || 1315 activeExcludeInput === "excluded" 1316 ) { 1317 var malfuncInput = UI.createElement({ 1318 style: "margin-bottom: 1em", 1319 children: [ 1320 { 1321 className: "is-size-7", 1322 style: "margin-bottom: 1em", 1323 text: 1324 activeExcludeInput === "malfunc" 1325 ? "Add malfunctioning tests from past sessions by providing at least the first eight characters of the session's token." 1326 : "Add excluded tests from past sessions by providing at least the first eight characters of the session's token.", 1327 }, 1328 { 1329 className: "field is-horizontal", 1330 children: [ 1331 { 1332 className: "field-label", 1333 children: { className: "label", text: "Session Token" }, 1334 }, 1335 { 1336 className: "field-body", 1337 children: { 1338 className: "field is-grouped is-multiline", 1339 children: [ 1340 { 1341 id: "excluded-tests-text", 1342 className: "input", 1343 element: "input", 1344 type: "text", 1345 }, 1346 { 1347 className: "button", 1348 style: "margin-left: 1em", 1349 text: "Add", 1350 onClick: function () { 1351 if (activeExcludeInput === "malfunc") { 1352 handleAddExludedTestsMalfunctioning(); 1353 } else { 1354 handleAddExludedTestsExcluded(); 1355 } 1356 }, 1357 }, 1358 ], 1359 }, 1360 }, 1361 ], 1362 }, 1363 ], 1364 }); 1365 excludedTestsView.appendChild(malfuncInput); 1366 } 1367 1368 var excludedTestsTable = createExcludedTestsTable(); 1369 var tableWrapper = UI.createElement({ 1370 style: "max-height: 250px; overflow: auto; margin-bottom: 10px", 1371 }); 1372 tableWrapper.appendChild(excludedTestsTable); 1373 excludedTestsView.appendChild(tableWrapper); 1374 } 1375 1376 function renderResumeView() { 1377 var query = utils.parseQuery(location.search); 1378 var resumeToken = query.resume; 1379 if (!resumeToken) resumeToken = ""; 1380 1381 var renderResumeElement = UI.getElement("resume-view"); 1382 renderResumeElement.innerHTML = ""; 1383 if (state.expired) return; 1384 1385 var heading = UI.createElement({ 1386 element: "h2", 1387 className: "title is-5", 1388 text: "Resume session", 1389 }); 1390 renderResumeElement.appendChild(heading); 1391 1392 var resumeControls = UI.createElement({ 1393 className: "columns", 1394 children: [ 1395 { 1396 className: "column", 1397 children: { 1398 className: "field", 1399 children: [ 1400 { 1401 element: "label", 1402 className: "label", 1403 text: "Token (first 8 characters or more)", 1404 }, 1405 { 1406 className: "control", 1407 children: { 1408 element: "input", 1409 id: "resume-token", 1410 className: "input is-family-monospace tabbable", 1411 type: "text", 1412 style: "max-width: 30em", 1413 value: resumeToken, 1414 }, 1415 }, 1416 ], 1417 }, 1418 }, 1419 { 1420 className: "column", 1421 style: 1422 "display: flex; align-items: flex-end; justify-content: flex-end", 1423 children: { 1424 className: "button", 1425 onClick: function () { 1426 resumeSession(); 1427 }, 1428 children: [ 1429 { 1430 element: "span", 1431 className: "icon", 1432 children: { element: "i", className: "fas fa-redo-alt" }, 1433 }, 1434 { 1435 element: "span", 1436 text: "Resume", 1437 }, 1438 ], 1439 }, 1440 }, 1441 ], 1442 }); 1443 renderResumeElement.appendChild(resumeControls); 1444 } 1445 1446 function createExcludedTestsTable() { 1447 var excludedTests = state.configuration.tests.exclude; 1448 if (excludedTests.length === 0) { 1449 return UI.createElement({ 1450 style: "text-align: center", 1451 text: "- No Excluded Tests -", 1452 }); 1453 } 1454 var table = UI.createElement({ 1455 element: "table", 1456 className: "table", 1457 style: "width: 100%", 1458 children: excludedTests.map(function (test) { 1459 return { 1460 element: "tr", 1461 children: [ 1462 { element: "td", style: "width: 100%;", text: test }, 1463 { 1464 element: "td", 1465 children: { 1466 element: "button", 1467 className: "button is-small", 1468 onClick: function () { 1469 handleRemoveExcludedTest(test); 1470 }, 1471 children: { 1472 element: "span", 1473 className: "icon", 1474 children: { 1475 element: "i", 1476 className: "fas fa-trash-alt", 1477 }, 1478 }, 1479 }, 1480 }, 1481 ], 1482 }; 1483 }), 1484 }); 1485 return table; 1486 } 1487 1488 function createTestTypeList(types) { 1489 return types.map((type) => ({ 1490 element: "button", 1491 style: "margin-right: 0.3em; margin-bottom: 0.3em", 1492 className: "button" + (hasTestType(type.value) ? " is-info" : ""), 1493 text: type.title, 1494 onClick: function (event) { 1495 handleTestTypeToggle(type.value); 1496 renderTypesField(); 1497 }, 1498 })); 1499 } 1500 1501 function createRefSessionsList(referenceSessions) { 1502 return referenceSessions.map((session) => ({ 1503 element: "button", 1504 className: "button" + (hasRefSession(session) ? " is-info" : ""), 1505 style: "margin-right: 0.3em; margin-bottom: 0.3em; height: auto", 1506 onClick: function () { 1507 handleRefSessionToggle(session); 1508 renderReferencesField(); 1509 }, 1510 children: [ 1511 { 1512 element: "span", 1513 className: "icon", 1514 children: [{ element: "i", className: session.icon }], 1515 }, 1516 { 1517 element: "span", 1518 children: [ 1519 { text: session.title }, 1520 { 1521 text: session.engine, 1522 style: "font-size: 0.8em", 1523 }, 1524 ], 1525 }, 1526 ], 1527 })); 1528 } 1529 1530 function createSelectDeselectButtons(onSelect, onDeselect) { 1531 return { 1532 style: "margin-top: 0.3em", 1533 children: [ 1534 { 1535 element: "button", 1536 style: "margin-right: 0.3em", 1537 className: "button is-rounded is-small", 1538 text: "All", 1539 onClick: onSelect, 1540 }, 1541 { 1542 element: "button", 1543 className: "button is-rounded is-small", 1544 text: "None", 1545 onClick: onDeselect, 1546 }, 1547 ], 1548 }; 1549 } 1550 1551 function createErrorMessage(message) { 1552 return { 1553 element: "article", 1554 className: "message is-danger", 1555 children: [ 1556 { 1557 className: "message-body", 1558 text: message, 1559 }, 1560 ], 1561 }; 1562 } 1563 1564 function createLoadingIndicator(text) { 1565 return UI.createElement({ 1566 className: "level", 1567 children: { 1568 element: "span", 1569 className: "level-item", 1570 children: [ 1571 { 1572 element: "i", 1573 className: "fas fa-spinner fa-pulse", 1574 }, 1575 { 1576 style: "margin-left: 0.4em;", 1577 text: text, 1578 }, 1579 ], 1580 }, 1581 }); 1582 } 1583 } 1584 </script> 1585 </body> 1586 </html>