| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Session Configuration - Web Platform Test</title> | |
| <link rel="stylesheet" href="css/bulma-0.7.5/bulma.min.css" /> | |
| <link rel="stylesheet" href="css/fontawesome-5.7.2.min.css" /> | |
| <script src="lib/utils.js"></script> | |
| <script src="lib/wave-service.js"></script> | |
| <script src="lib/ui.js"></script> | |
| <style> | |
| .site-logo { | |
| max-width: 300px; | |
| margin: 0 0 30px -15px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| // var apis = [ | |
| // { title: "2D Context", path: "/2dcontext" }, | |
| // { title: "Content Security Policy", path: "/content-security-policy" }, | |
| // { title: "CSS", path: "/css" }, | |
| // { title: "DOM", path: "/dom" }, | |
| // { title: "ECMAScript", path: "/ecmascript" }, | |
| // { title: "Encrypted media", path: "/encrypted-media" }, | |
| // { title: "Fetch", path: "/fetch" }, | |
| // { title: "FileAPI", path: "/FileAPI" }, | |
| // { title: "Fullscreen", path: "/fullscreen" }, | |
| // { title: "WebGL", path: "/webgl" }, | |
| // { title: "HTML", path: "/html" }, | |
| // { title: "IndexedDB", path: "/IndexedDB" }, | |
| // { title: "Media Source", path: "/media-source" }, | |
| // { title: "Notifications", path: "/notifications" }, | |
| // { title: "Page Visibility", path: "/page-visibility" }, | |
| // { title: "Service Workers", path: "/service-workers" }, | |
| // { title: "UI Events", path: "/uievents" }, | |
| // { title: "WAVE Extra", path: "/wave-extra" }, | |
| // { title: "Webaudio", path: "/webaudio" }, | |
| // { title: "WebCryptoAPI", path: "/WebCryptoAPI" }, | |
| // { title: "Webmessaging", path: "/webmessaging" }, | |
| // { title: "Websockets", path: "/websockets" }, | |
| // { title: "Webstorage", path: "/webstorage" }, | |
| // { title: "Workers", path: "/workers" }, | |
| // { title: "XHR", path: "/xhr" } | |
| // ]; | |
| // var referenceSessions = [ | |
| // { | |
| // title: "Edge 44.17763", | |
| // engine: "", | |
| // token: "b2924d20-6a93-11e9-98b4-a11fb92a6d1c", | |
| // icon: "fab fa-edge" | |
| // }, | |
| // { | |
| // title: "Firefox 64.0", | |
| // engine: "Gecko 64.0", | |
| // token: "bb7aafa0-6a92-11e9-8ec2-04f58dad2e4f", | |
| // icon: "fab fa-firefox" | |
| // }, | |
| // { | |
| // title: "WebKit 605.1.15", | |
| // engine: "Revision 239158", | |
| // token: "caf823e0-6a92-11e9-b732-3188d0065ebc", | |
| // icon: "fab fa-safari" | |
| // }, | |
| // { | |
| // title: "Chromium 73.0.3640.0", | |
| // engine: "Blink 537.36", | |
| // token: "a50c6db0-6a94-11e9-8d1b-e23fc4555885", | |
| // icon: "fab fa-chrome" | |
| // } | |
| // ]; | |
| var testFileSelectionEnabled = true; | |
| window.onload = function () { | |
| new ConfigurationView(); | |
| }; | |
| function ConfigurationView() { | |
| const query = utils.parseQuery(location.search); | |
| var token = query.token; | |
| if (token) WaveService.addRecentSession(token); | |
| var referenceSessions = []; | |
| var apis = []; | |
| var testTypeSelectionEnabled = true; | |
| var testFileSelectionEnabled = false; | |
| var types = [ | |
| { title: "Automatic", value: "automatic" }, | |
| { title: "Manual", value: "manual" }, | |
| ]; | |
| var state = {}; | |
| loadServerStatus(); | |
| loadSessionData(token, function () { | |
| loadPublicSessionData(function () { | |
| renderReferencesField(); | |
| }); | |
| }); | |
| render(); | |
| function loadServerStatus() { | |
| WaveService.readStatus(function (status) { | |
| testTypeSelectionEnabled = status.testTypeSelectionEnabled; | |
| testFileSelectionEnabled = status.testFileSelectionEnabled; | |
| renderSessionConfiguration(); | |
| }); | |
| } | |
| function loadSessionData(token, callback) { | |
| if (!token) { | |
| state.expired = true; | |
| return; | |
| } | |
| WaveService.readSessionStatus( | |
| token, | |
| function (status) { | |
| if (status.status !== "pending") { | |
| openResultsPage(token); | |
| return; | |
| } | |
| state.status = status; | |
| WaveService.readSession(token, function (configuration) { | |
| if ( | |
| configuration.tests.include.findIndex( | |
| (test) => test === "/" | |
| ) !== -1 | |
| ) { | |
| configuration.tests.include = apis.map((api) => api.path); | |
| } | |
| state.configurationBackup = utils.copyObject(configuration); | |
| state.configuration = configuration; | |
| setTimeout( | |
| handleExpiration, | |
| status.expirationDate.getTime() - Date.now() | |
| ); | |
| renderSessionConfiguration(); | |
| callback(); | |
| WaveService.readAvailableApis(function (available_apis) { | |
| available_apis = available_apis.sort((apiA, apiB) => | |
| apiA.title.toLowerCase() > apiB.title.toLowerCase() ? 1 : -1 | |
| ); | |
| apis = available_apis; | |
| selectAllTests(); | |
| renderSessionConfiguration(); | |
| }); | |
| }); | |
| }, | |
| function () { | |
| state.expired = true; | |
| renderSessionConfiguration(); | |
| renderExcludedTests(); | |
| renderResumeView(); | |
| } | |
| ); | |
| } | |
| function loadPublicSessionData(callback) { | |
| WaveService.readPublicSessions(function (tokens) { | |
| WaveService.readMultipleSessions(tokens, function (configurations) { | |
| console.log(configurations); | |
| referenceSessions = configurations | |
| .sort((confA, confB) => | |
| confA.browser.name.toLowerCase() > | |
| confB.browser.name.toLowerCase() | |
| ? 1 | |
| : -1 | |
| ) | |
| .map((configuration) => { | |
| var icon = ""; | |
| switch (configuration.browser.name.toLowerCase()) { | |
| case "firefox": | |
| icon = "fab fa-firefox"; | |
| break; | |
| case "webkit": | |
| case "safari": | |
| icon = "fab fa-safari"; | |
| break; | |
| case "edge": | |
| icon = "fab fa-edge"; | |
| break; | |
| case "chrome": | |
| case "chromium": | |
| icon = "fab fa-chrome"; | |
| break; | |
| } | |
| return { | |
| title: | |
| configuration.browser.name + | |
| " " + | |
| configuration.browser.version, | |
| token: configuration.token, | |
| icon, | |
| engine: configuration.browser.engine || "", | |
| }; | |
| }); | |
| callback(referenceSessions); | |
| }); | |
| }); | |
| } | |
| function handleConfigureSession() { | |
| const tokenFragmentInput = UI.getElement("token-fragment"); | |
| const fragment = tokenFragmentInput.value; | |
| findSession(fragment, function (session) { | |
| if (!session) { | |
| const errorBox = UI.getElement("find-error"); | |
| errorBox.setAttribute("style", "display: block"); | |
| return; | |
| } | |
| tokenFragmentInput.value = ""; | |
| const errorBox = UI.getElement("find-error"); | |
| errorBox.setAttribute("style", "display: none"); | |
| const path = location.pathname + "?token=" + session.token; | |
| location.href = path; | |
| }); | |
| } | |
| function findSession(fragment, callback) { | |
| if (!fragment || fragment.length < 8) { | |
| callback(null); | |
| return; | |
| } | |
| WaveService.findToken( | |
| fragment, | |
| function (token) { | |
| WaveService.readSession(token, function (session) { | |
| WaveService.readSessionStatus(token, function (status) { | |
| session.status = status.status; | |
| session.dateStarted = status.dateStarted; | |
| session.dateFinished = status.dateFinished; | |
| callback(session); | |
| }); | |
| }); | |
| }, | |
| function () { | |
| callback(null); | |
| } | |
| ); | |
| } | |
| function hasIncludedTest(path) { | |
| var tests = state.configuration.tests; | |
| //var index = tests.include.findIndex(function (test) { | |
| // return test.match(new RegExp("^" + path)); | |
| //}); | |
| var index = tests.include.indexOf(path); | |
| return index !== -1; | |
| } | |
| function handleIncludedTestToggle(path) { | |
| var configuration = state.configuration; | |
| if (hasIncludedTest(path)) { | |
| handleRemoveIncludedTest(path); | |
| } else { | |
| handleAddIncludedTest(path); | |
| } | |
| } | |
| function handleAddIncludedTest(path) { | |
| var tests = state.configuration.tests; | |
| if (state.tests && state.tests[path.substr(1)]) { | |
| tests.include = tests.include.filter(function (test) { | |
| return !test.match(new RegExp("^" + path + "/")); | |
| }); | |
| tests.include = tests.include.concat(state.tests[path.substr(1)]); | |
| } else { | |
| tests.include.push(path); | |
| } | |
| } | |
| function handleRemoveIncludedTest(path) { | |
| var tests = state.configuration.tests; | |
| if (state.tests && state.tests[path.substr(1)]) { | |
| tests.include = tests.include.filter(function (test) { | |
| return !test.match(new RegExp("^" + path + "/")); | |
| }); | |
| } else { | |
| var index = tests.include.findIndex((test) => test === path); | |
| tests.include.splice(index, 1); | |
| } | |
| } | |
| function getIncludedRatio(path) { | |
| var includedTests = state.configuration.tests.include; | |
| if (!state.tests) { | |
| return includedTests.indexOf(path) !== -1 ? 1 : 0; | |
| } | |
| var count = 0; | |
| for (var test of includedTests) { | |
| if (!test.match(new RegExp("^" + path))) continue; | |
| count++; | |
| } | |
| return count / state.tests[path.substr(1)].length; | |
| } | |
| function selectAllTests() { | |
| var tests = state.configuration.tests; | |
| if (state.tests) { | |
| tests.include = []; | |
| for (var api in state.tests) { | |
| tests.include = tests.include.concat(state.tests[api]); | |
| } | |
| } else { | |
| tests.include = apis.map((api) => api.path); | |
| } | |
| } | |
| function deselectAllTests() { | |
| var configuration = state.configuration; | |
| configuration.tests.include = []; | |
| } | |
| function hasTestType(value) { | |
| var configuration = state.configuration; | |
| var index = configuration.types.findIndex((type) => type === value); | |
| return index !== -1; | |
| } | |
| function handleTestTypeToggle(value) { | |
| var configuration = state.configuration; | |
| if (hasTestType(value)) { | |
| var index = configuration.types.findIndex((type) => type === value); | |
| configuration.types.splice(index, 1); | |
| } else { | |
| configuration.types.push(value); | |
| } | |
| } | |
| function selectAllTestTypes() { | |
| var configuration = state.configuration; | |
| configuration.types = types.map((type) => type.value); | |
| } | |
| function deselectAllTestTypes() { | |
| var configuration = state.configuration; | |
| configuration.types = []; | |
| } | |
| function hasRefSession(session) { | |
| var configuration = state.configuration; | |
| var index = configuration.referenceTokens.findIndex( | |
| (token) => token === session.token | |
| ); | |
| return index !== -1; | |
| } | |
| function handleRefSessionToggle(session) { | |
| var configuration = state.configuration; | |
| if (hasRefSession(session)) { | |
| var index = configuration.referenceTokens.findIndex( | |
| (token) => token === session.token | |
| ); | |
| configuration.referenceTokens.splice(index, 1); | |
| } else { | |
| configuration.referenceTokens.push(session.token); | |
| } | |
| } | |
| function selectAllRefSessions() { | |
| var configuration = state.configuration; | |
| configuration.referenceTokens = referenceSessions.map( | |
| (session) => session.token | |
| ); | |
| } | |
| function deselectAllRefSessions() { | |
| var configuration = state.configuration; | |
| configuration.referenceTokens = []; | |
| } | |
| function isTestListValid() { | |
| var configuration = state.configuration; | |
| return configuration.tests.include.length > 0; | |
| } | |
| function isTestTypeListValid() { | |
| var configuration = state.configuration; | |
| return configuration.types.length > 0; | |
| } | |
| function isConfigurationValid() { | |
| if (!isTestListValid()) return false; | |
| if (!isTestTypeListValid()) return false; | |
| return true; | |
| } | |
| function isSessionStarting() { | |
| return state.isStarting; | |
| } | |
| function checkApiList() { | |
| var apiErrorElement = UI.getElement("api-error"); | |
| apiErrorElement.innerHTML = ""; | |
| if (!isTestListValid()) { | |
| apiErrorElement.appendChild( | |
| UI.createElement( | |
| createErrorMessage( | |
| "Select at least one API or at least one test within an API" | |
| ) | |
| ) | |
| ); | |
| } | |
| renderButtonsField(); | |
| } | |
| function handleStart() { | |
| if (isSessionStarting()) return; | |
| var configuration = state.configuration; | |
| var token = configuration.token; | |
| WaveService.updateSession(token, configuration, function () { | |
| WaveService.updateLabels(token, configuration.labels, function () { | |
| WaveService.startSession(token, function () { | |
| openResultsPage(token); | |
| }); | |
| }); | |
| }); | |
| state.isStarting = true; | |
| } | |
| function handleDiscardChanges() { | |
| state.configuration = utils.copyObject(state.configurationBackup); | |
| } | |
| function handleExpiration() { | |
| state.expired = true; | |
| renderSessionConfiguration(); | |
| renderResumeView(); | |
| } | |
| function openResultsPage(token) { | |
| location.href = WEB_ROOT + "results.html?token=" + token; | |
| } | |
| function handleAddExludedTestsRaw() { | |
| var excludedTestsTextArea = UI.getElement("excluded-tests-text"); | |
| var tests = excludedTestsTextArea.value.split("\n"); | |
| var configuration = state.configuration; | |
| var excludedTests = configuration.tests.exclude; | |
| for (var test of tests) { | |
| if (!test) continue; | |
| if (test.startsWith("#")) continue; | |
| if (excludedTests.indexOf(test) !== -1) continue; | |
| excludedTests.push(test); | |
| } | |
| excludedTestsTextArea.value = ""; | |
| renderExcludedTests(); | |
| } | |
| function handleAddExludedTestsMalfunctioning() { | |
| var excludedTestsTextArea = UI.getElement("excluded-tests-text"); | |
| var token = excludedTestsTextArea.value; | |
| var tokenRegExp = new RegExp( | |
| "^[a-f0-9]{8}(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,12}|$)" | |
| ); | |
| var configuration = state.configuration; | |
| var excludedTests = configuration.tests.exclude; | |
| if (tokenRegExp.test(token)) { | |
| WaveService.findToken( | |
| token, | |
| function (token) { | |
| WaveService.readMalfunctioningTests(token, function ( | |
| malfunctioningTests | |
| ) { | |
| for (var test of malfunctioningTests) { | |
| if (!test) continue; | |
| if (excludedTests.indexOf(test) !== -1) continue; | |
| excludedTests.push(test); | |
| } | |
| renderExcludedTests(); | |
| }); | |
| }, | |
| function () { | |
| state.excludedTestError = "Session not found"; | |
| renderExcludedTests(); | |
| } | |
| ); | |
| } else { | |
| state.excludedTestError = "Invalid session token"; | |
| renderExcludedTests(); | |
| } | |
| } | |
| function handleAddExludedTestsExcluded() { | |
| var excludedTestsTextArea = UI.getElement("excluded-tests-text"); | |
| var token = excludedTestsTextArea.value; | |
| var tokenRegExp = new RegExp( | |
| "^[a-f0-9]{8}(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,4}|$)(-[a-f0-9]{0,12}|$)" | |
| ); | |
| var configuration = state.configuration; | |
| var excludedTests = configuration.tests.exclude; | |
| if (tokenRegExp.test(token)) { | |
| WaveService.findToken( | |
| token, | |
| function (token) { | |
| WaveService.readSession(token, function (sessionConfig) { | |
| var prevExcludedTests = sessionConfig.tests.exclude; | |
| for (var test of prevExcludedTests) { | |
| if (!test) continue; | |
| if (excludedTests.indexOf(test) !== -1) continue; | |
| excludedTests.push(test); | |
| } | |
| renderExcludedTests(); | |
| }); | |
| }, | |
| function () { | |
| state.excludedTestError = "Session not found"; | |
| renderExcludedTests(); | |
| } | |
| ); | |
| } else { | |
| state.excludedTestError = "Invalid session token"; | |
| renderExcludedTests(); | |
| } | |
| } | |
| function handleRemoveExcludedTest(test) { | |
| var configuration = state.configuration; | |
| var excludedTests = configuration.tests.exclude; | |
| var index = excludedTests.indexOf(test); | |
| excludedTests.splice(index, 1); | |
| renderExcludedTests(); | |
| } | |
| function handleExcludeInputChange(type) { | |
| if (state.activeExcludeInput === type) { | |
| state.activeExcludeInput = null; | |
| } else { | |
| state.activeExcludeInput = type; | |
| } | |
| renderExcludedTests(); | |
| } | |
| function showAddLabel() { | |
| state.addLabelVisible = true; | |
| renderLabelsField(); | |
| UI.getElement("session-label-input").focus(); | |
| } | |
| function hideAddLabel() { | |
| state.addLabelVisible = false; | |
| renderLabelsField(); | |
| } | |
| function addLabel() { | |
| var label = UI.getElement("session-label-input").value; | |
| if (!label) return; | |
| state.configuration.labels.push(label); | |
| renderLabelsField(); | |
| UI.getElement("session-label-input").focus(); | |
| } | |
| function removeLabel(index) { | |
| const { configuration } = state; | |
| configuration.labels.splice(index, 1); | |
| renderLabelsField(); | |
| } | |
| function resumeSession() { | |
| var resumeToken = UI.getElement("resume-token").value; | |
| if (!resumeToken) return; | |
| WaveService.resumeSession( | |
| state.configuration.token, | |
| resumeToken, | |
| function () { | |
| openResultsPage(resumeToken); | |
| } | |
| ); | |
| } | |
| function render() { | |
| const configurationView = UI.createElement({ | |
| element: "section", | |
| className: "section", | |
| children: [ | |
| { | |
| className: "container", | |
| style: "margin-bottom: 2em", | |
| children: [ | |
| { | |
| element: "img", | |
| src: "res/wavelogo_2016.jpg", | |
| className: "site-logo", | |
| }, | |
| { className: "title", text: "Session Configuration" }, | |
| ], | |
| }, | |
| { | |
| id: "session-configuration", | |
| }, | |
| { | |
| id: "resume-view", | |
| className: "container", | |
| style: "margin-bottom: 2em", | |
| }, | |
| ], | |
| }); | |
| const root = UI.getRoot(); | |
| root.innerHTML = ""; | |
| root.appendChild(configurationView); | |
| renderSessionConfiguration(); | |
| renderResumeView(); | |
| } | |
| function renderSessionConfiguration() { | |
| var configuration = state.configuration; | |
| var status = state.status; | |
| var sessionConfigurationView = UI.createElement({}); | |
| var sessionConfiguration = UI.getElement("session-configuration"); | |
| sessionConfiguration.innerHTML = ""; | |
| if (state.expired) { | |
| var expiredIndicator = UI.createElement({ | |
| className: "level container", | |
| style: "max-width: 500px", | |
| children: { | |
| element: "span", | |
| className: "level-item field column", | |
| children: [ | |
| { | |
| element: "article", | |
| className: "message is-danger", | |
| id: "find-error", | |
| children: [ | |
| { | |
| text: | |
| "Could not find any sessions! Try adding more characters of the token.", | |
| className: "message-body", | |
| }, | |
| ], | |
| style: "display: none", | |
| }, | |
| { | |
| className: "label has-text-weight-normal", | |
| text: "Session token:", | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| children: { | |
| style: "display: flex; margin-bottom: 10px;", | |
| children: [ | |
| { | |
| element: "input", | |
| inputType: "text", | |
| className: "input is-family-monospace", | |
| id: "token-fragment", | |
| placeholder: | |
| "First 8 characters or more of session token", | |
| onKeyDown: function (event) { | |
| if (event.key === "Enter") { | |
| handleConfigureSession(); | |
| } | |
| }, | |
| }, | |
| ], | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| className: "field is-grouped is-grouped-right", | |
| children: { | |
| className: "control", | |
| children: { | |
| className: "button is-dark is-outlined", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-cog", | |
| }, | |
| ], | |
| }, | |
| { text: "Configure Session", element: "span" }, | |
| ], | |
| onclick: function () { | |
| handleConfigureSession(); | |
| }, | |
| }, | |
| }, | |
| }, | |
| ], | |
| }, | |
| }); | |
| sessionConfigurationView.appendChild(expiredIndicator); | |
| sessionConfiguration.appendChild(sessionConfigurationView); | |
| return; | |
| } | |
| if (!configuration) { | |
| var loadingIndicator = createLoadingIndicator( | |
| "Loading configuration ..." | |
| ); | |
| sessionConfigurationView.appendChild(loadingIndicator); | |
| sessionConfiguration.appendChild(sessionConfigurationView); | |
| return; | |
| } | |
| sessionConfiguration.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "session-configuration", | |
| className: "container", | |
| style: "margin-bottom: 2em", | |
| children: [ | |
| { | |
| id: "token-field", | |
| }, | |
| { | |
| id: "expiration-field", | |
| }, | |
| { | |
| id: "labels-field", | |
| }, | |
| { | |
| id: "apis-field", | |
| }, | |
| { | |
| id: "exclude-field", | |
| }, | |
| { | |
| id: "types-field", | |
| }, | |
| { | |
| id: "references-field", | |
| }, | |
| { | |
| id: "buttons-field", | |
| }, | |
| ], | |
| }), | |
| sessionConfiguration | |
| ); | |
| renderTokenField(); | |
| renderExpirationField(); | |
| renderLabelsField(); | |
| renderApisField(); | |
| renderExcludeField(); | |
| renderTypesField(); | |
| renderReferencesField(); | |
| renderButtonsField(); | |
| } | |
| function renderTokenField() { | |
| var configuration = state.configuration; | |
| var tokenField = UI.getElement("token-field"); | |
| tokenField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "token-field", | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: { className: "label", text: "Token" }, | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| text: configuration.token, | |
| }, | |
| }, | |
| }, | |
| ], | |
| }), | |
| tokenField | |
| ); | |
| } | |
| function renderExpirationField() { | |
| var status = state.status; | |
| var expirationField = UI.getElement("expiration-field"); | |
| expirationField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "expiration-field", | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: { className: "label", text: "Expires" }, | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| text: status.expirationDate.toLocaleString(), | |
| }, | |
| }, | |
| }, | |
| ], | |
| }), | |
| expirationField | |
| ); | |
| } | |
| function renderLabelsField() { | |
| var addLabelVisible = state.addLabelVisible; | |
| var configuration = state.configuration; | |
| var labelsField = UI.getElement("labels-field"); | |
| labelsField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "labels-field", | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: { className: "label", text: "Labels" }, | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field is-grouped is-grouped-multiline", | |
| children: configuration.labels | |
| .map((label, index) => ({ | |
| className: "control", | |
| children: { | |
| className: "tags has-addons", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "tag is-info", | |
| text: label, | |
| }, | |
| { | |
| element: "a", | |
| className: "tag is-delete", | |
| onClick: () => removeLabel(index), | |
| }, | |
| ], | |
| }, | |
| })) | |
| .concat( | |
| addLabelVisible | |
| ? [ | |
| { | |
| className: "control field is-grouped", | |
| children: [ | |
| { | |
| element: "input", | |
| className: "input is-small control", | |
| style: "width: 10rem", | |
| id: "session-label-input", | |
| type: "text", | |
| onKeyUp: (event) => | |
| event.keyCode === 13 ? addLabel() : null, | |
| }, | |
| { | |
| className: | |
| "button is-dark is-outlined is-small is-rounded control", | |
| text: "save", | |
| onClick: addLabel, | |
| }, | |
| { | |
| className: | |
| "button is-dark is-outlined is-small is-rounded control", | |
| text: "cancel", | |
| onClick: hideAddLabel, | |
| }, | |
| ], | |
| }, | |
| ] | |
| : [ | |
| { | |
| className: "button is-rounded is-small", | |
| text: "Add", | |
| onClick: showAddLabel, | |
| }, | |
| ] | |
| ), | |
| }, | |
| }, | |
| ], | |
| }), | |
| labelsField | |
| ); | |
| } | |
| function renderApisField() { | |
| var apisField = UI.getElement("apis-field"); | |
| apisField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "apis-field", | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: [ | |
| { className: "label", text: "APIs" }, | |
| createSelectDeselectButtons( | |
| function () { | |
| selectAllTests(); | |
| renderApisField(); | |
| }, | |
| function () { | |
| deselectAllTests(); | |
| renderApisField(); | |
| } | |
| ), | |
| ], | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| children: [ | |
| { | |
| id: "api-error", | |
| }, | |
| { | |
| element: "ul", | |
| className: "menu-list", | |
| children: apis.map(function (api) { | |
| return UI.createElement({ | |
| element: "li", | |
| id: api.title, | |
| }); | |
| }), | |
| }, | |
| ], | |
| }, | |
| }, | |
| }, | |
| ], | |
| }), | |
| apisField | |
| ); | |
| renderApisList(apis); | |
| checkApiList(); | |
| } | |
| function renderApisList(apis) { | |
| for (var api of apis) { | |
| renderApiList(api); | |
| } | |
| } | |
| function renderApiList(api) { | |
| var listItem = UI.getElement(api.title); | |
| var includedRatio = getIncludedRatio(api.path); | |
| var apiListItem = { | |
| element: "a", | |
| onClick: function (event) { | |
| if (!testFileSelectionEnabled) return; | |
| if (!state.expandedApis) state.expandedApis = {}; | |
| state.expandedApis[api.path] = !state.expandedApis[api.path]; | |
| renderApiList(api); | |
| }, | |
| children: [ | |
| { | |
| element: "input", | |
| type: "checkbox", | |
| style: "width: 1.3em; height: 1.3em;vertical-align: middle;", | |
| checked: includedRatio > 0, | |
| indeterminate: includedRatio > 0 && includedRatio < 1, | |
| onclick: function (event) { | |
| event.stopPropagation(); | |
| if (includedRatio > 0) { | |
| handleRemoveIncludedTest(api.path); | |
| } else { | |
| handleAddIncludedTest(api.path); | |
| } | |
| renderApiList(api); | |
| }, | |
| }, | |
| testFileSelectionEnabled | |
| ? { | |
| element: "span", | |
| style: | |
| "display: inline-block;vertical-align: middle;margin-left:0.3em;width: 0.7em", | |
| children: { | |
| element: "i", | |
| className: | |
| state.expandedApis && state.expandedApis[api.path] | |
| ? "fas fa-angle-down" | |
| : "fas fa-angle-right", | |
| }, | |
| } | |
| : null, | |
| { | |
| style: | |
| "display: inline-block;vertical-align: middle;margin-left:0.3em;width: 90%", | |
| text: api.title, | |
| }, | |
| ], | |
| }; | |
| listItem.innerHTML = ""; | |
| listItem.appendChild(UI.createElement(apiListItem)); | |
| if (state.expandedApis && state.expandedApis[api.path]) { | |
| listItem.appendChild(createApiTestsList(api)); | |
| } | |
| checkApiList(); | |
| } | |
| function createApiTestsList(api) { | |
| if (!state.tests) { | |
| WaveService.readTestList( | |
| function (readTests) { | |
| state.tests = readTests; | |
| for (var api in state.tests) { | |
| if (hasIncludedTest("/" + api)) { | |
| handleRemoveIncludedTest("/" + api); | |
| handleAddIncludedTest("/" + api); | |
| } | |
| } | |
| renderApiList(this.api); | |
| }.bind({ api: api }) | |
| ); | |
| return createLoadingIndicator("Loading tests ..."); | |
| } else { | |
| var tests = state.tests[api.path.substr(1)]; | |
| var testListView = { | |
| element: "ul", | |
| children: [], | |
| }; | |
| testListView.children = testListView.children.concat( | |
| tests | |
| .sort() | |
| .map(function (test) { | |
| return { | |
| element: "li", | |
| onclick: function (event) { | |
| handleIncludedTestToggle(test); | |
| renderApiList(api); | |
| }, | |
| children: [ | |
| { | |
| element: "a", | |
| children: [ | |
| { | |
| element: "input", | |
| type: "checkbox", | |
| style: | |
| "width: 1.3em; height: 1.3em;vertical-align: middle;", | |
| checked: hasIncludedTest(test), | |
| }, | |
| { | |
| style: | |
| "display: inline-block;vertical-align: middle;margin-left:0.3em;max-width: 90%", | |
| text: test, | |
| }, | |
| ], | |
| }, | |
| ], | |
| }; | |
| }) | |
| ); | |
| return UI.createElement(testListView); | |
| } | |
| } | |
| function renderExcludeField() { | |
| var excludeField = UI.getElement("exclude-field"); | |
| excludeField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "exclude-field", | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: { className: "label", text: "Excluded Tests" }, | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| children: { id: "excluded-tests-view" }, | |
| }, | |
| }, | |
| }, | |
| ], | |
| }), | |
| excludeField | |
| ); | |
| renderExcludedTests(); | |
| } | |
| function renderTypesField() { | |
| if (!testTypeSelectionEnabled) return; | |
| var typesField = UI.getElement("types-field"); | |
| typesField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "types-field", | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: { className: "label", text: "Test Types" }, | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| children: [ | |
| isTestTypeListValid() | |
| ? null | |
| : createErrorMessage("Select at least one test type"), | |
| ].concat(createTestTypeList(types)), | |
| }, | |
| }, | |
| }, | |
| ], | |
| }), | |
| typesField | |
| ); | |
| } | |
| function renderReferencesField() { | |
| if (referenceSessions.length === 0) { | |
| return; | |
| } | |
| var referencesField = UI.getElement("references-field"); | |
| referencesField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "references-field", | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: [ | |
| { className: "label", text: "Reference Browsers" }, | |
| createSelectDeselectButtons( | |
| function () { | |
| selectAllRefSessions(); | |
| renderReferencesField(); | |
| }, | |
| function () { | |
| deselectAllRefSessions(); | |
| renderReferencesField(); | |
| } | |
| ), | |
| ], | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| children: createRefSessionsList(referenceSessions), | |
| }, | |
| }, | |
| }, | |
| ], | |
| }), | |
| referencesField | |
| ); | |
| } | |
| function renderButtonsField() { | |
| var buttonsField = UI.getElement("buttons-field"); | |
| buttonsField.parentNode.replaceChild( | |
| UI.createElement({ | |
| id: "buttons-field", | |
| className: "level level-right", | |
| children: [ | |
| { | |
| element: "button", | |
| className: "button is-success", | |
| style: "margin-right: 0.3em", | |
| disabled: !isConfigurationValid(), | |
| onClick: function () { | |
| handleStart(); | |
| renderButtonsField(); | |
| }, | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: isSessionStarting() | |
| ? "fas fa-spinner fa-pulse" | |
| : "fas fa-play", | |
| }, | |
| ], | |
| }, | |
| { | |
| element: "span", | |
| text: isSessionStarting() | |
| ? "Starting Session ..." | |
| : "Start Session", | |
| }, | |
| ], | |
| }, | |
| { | |
| element: "button", | |
| className: "button", | |
| onClick: function () { | |
| handleDiscardChanges(); | |
| renderSessionConfiguration(); | |
| }, | |
| disabled: isSessionStarting(), | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-times", | |
| }, | |
| ], | |
| }, | |
| { | |
| element: "span", | |
| text: "Discard Changes", | |
| }, | |
| ], | |
| }, | |
| ], | |
| }), | |
| buttonsField | |
| ); | |
| } | |
| function renderExcludedTests() { | |
| var excludedTestsView = UI.getElement("excluded-tests-view"); | |
| if (!excludedTestsView) return; | |
| excludedTestsView.innerHTML = ""; | |
| var errorMessage = state.excludedTestError; | |
| if (errorMessage) { | |
| var error = createErrorMessage(errorMessage); | |
| excludedTestsView.appendChild(UI.createElement(error)); | |
| } | |
| var excludeInputs = [ | |
| { title: "Add Raw", type: "raw" }, | |
| { title: "Add Malfunctioning", type: "malfunc" }, | |
| { title: "Add Previous Excluded", type: "excluded" }, | |
| ]; | |
| var activeExcludeInput = state.activeExcludeInput; | |
| var excludedTestInputSwitch = UI.createElement({ | |
| className: "tabs is-centered is-toggle is-small", | |
| children: { | |
| element: "ul", | |
| children: excludeInputs.map(function (input) { | |
| return { | |
| element: "li", | |
| onClick: function () { | |
| handleExcludeInputChange(input.type); | |
| }, | |
| className: (function () { | |
| if (activeExcludeInput === input.type) return "is-active"; | |
| return ""; | |
| })(), | |
| children: { element: "a", text: input.title }, | |
| }; | |
| }), | |
| }, | |
| }); | |
| excludedTestsView.appendChild(excludedTestInputSwitch); | |
| if (activeExcludeInput === "raw") { | |
| var rawInput = UI.createElement({ | |
| children: [ | |
| { | |
| className: "is-size-7", | |
| style: "margin-bottom: 20px", | |
| text: | |
| "Provide paths to test files or directories to exclude them from the session. One path per line, lines starting with # are omitted.", | |
| }, | |
| { | |
| element: "textarea", | |
| className: "textarea", | |
| id: "excluded-tests-text", | |
| }, | |
| { | |
| style: "margin-top: 10px", | |
| onClick: function () { | |
| handleAddExludedTestsRaw(); | |
| }, | |
| children: [ | |
| { | |
| element: "button", | |
| className: "button", | |
| style: "margin-bottom: 20px", | |
| text: "Add", | |
| }, | |
| ], | |
| }, | |
| ], | |
| }); | |
| excludedTestsView.appendChild(rawInput); | |
| } else if ( | |
| activeExcludeInput === "malfunc" || | |
| activeExcludeInput === "excluded" | |
| ) { | |
| var malfuncInput = UI.createElement({ | |
| style: "margin-bottom: 1em", | |
| children: [ | |
| { | |
| className: "is-size-7", | |
| style: "margin-bottom: 1em", | |
| text: | |
| activeExcludeInput === "malfunc" | |
| ? "Add malfunctioning tests from past sessions by providing at least the first eight characters of the session's token." | |
| : "Add excluded tests from past sessions by providing at least the first eight characters of the session's token.", | |
| }, | |
| { | |
| className: "field is-horizontal", | |
| children: [ | |
| { | |
| className: "field-label", | |
| children: { className: "label", text: "Session Token" }, | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field is-grouped is-multiline", | |
| children: [ | |
| { | |
| id: "excluded-tests-text", | |
| className: "input", | |
| element: "input", | |
| type: "text", | |
| }, | |
| { | |
| className: "button", | |
| style: "margin-left: 1em", | |
| text: "Add", | |
| onClick: function () { | |
| if (activeExcludeInput === "malfunc") { | |
| handleAddExludedTestsMalfunctioning(); | |
| } else { | |
| handleAddExludedTestsExcluded(); | |
| } | |
| }, | |
| }, | |
| ], | |
| }, | |
| }, | |
| ], | |
| }, | |
| ], | |
| }); | |
| excludedTestsView.appendChild(malfuncInput); | |
| } | |
| var excludedTestsTable = createExcludedTestsTable(); | |
| var tableWrapper = UI.createElement({ | |
| style: "max-height: 250px; overflow: auto; margin-bottom: 10px", | |
| }); | |
| tableWrapper.appendChild(excludedTestsTable); | |
| excludedTestsView.appendChild(tableWrapper); | |
| } | |
| function renderResumeView() { | |
| var query = utils.parseQuery(location.search); | |
| var resumeToken = query.resume; | |
| if (!resumeToken) resumeToken = ""; | |
| var renderResumeElement = UI.getElement("resume-view"); | |
| renderResumeElement.innerHTML = ""; | |
| if (state.expired) return; | |
| var heading = UI.createElement({ | |
| element: "h2", | |
| className: "title is-5", | |
| text: "Resume session", | |
| }); | |
| renderResumeElement.appendChild(heading); | |
| var resumeControls = UI.createElement({ | |
| className: "columns", | |
| children: [ | |
| { | |
| className: "column", | |
| children: { | |
| className: "field", | |
| children: [ | |
| { | |
| element: "label", | |
| className: "label", | |
| text: "Token (first 8 characters or more)", | |
| }, | |
| { | |
| className: "control", | |
| children: { | |
| element: "input", | |
| id: "resume-token", | |
| className: "input is-family-monospace tabbable", | |
| type: "text", | |
| style: "max-width: 30em", | |
| value: resumeToken, | |
| }, | |
| }, | |
| ], | |
| }, | |
| }, | |
| { | |
| className: "column", | |
| style: | |
| "display: flex; align-items: flex-end; justify-content: flex-end", | |
| children: { | |
| className: "button", | |
| onClick: function () { | |
| resumeSession(); | |
| }, | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: { element: "i", className: "fas fa-redo-alt" }, | |
| }, | |
| { | |
| element: "span", | |
| text: "Resume", | |
| }, | |
| ], | |
| }, | |
| }, | |
| ], | |
| }); | |
| renderResumeElement.appendChild(resumeControls); | |
| } | |
| function createExcludedTestsTable() { | |
| var excludedTests = state.configuration.tests.exclude; | |
| if (excludedTests.length === 0) { | |
| return UI.createElement({ | |
| style: "text-align: center", | |
| text: "- No Excluded Tests -", | |
| }); | |
| } | |
| var table = UI.createElement({ | |
| element: "table", | |
| className: "table", | |
| style: "width: 100%", | |
| children: excludedTests.map(function (test) { | |
| return { | |
| element: "tr", | |
| children: [ | |
| { element: "td", style: "width: 100%;", text: test }, | |
| { | |
| element: "td", | |
| children: { | |
| element: "button", | |
| className: "button is-small", | |
| onClick: function () { | |
| handleRemoveExcludedTest(test); | |
| }, | |
| children: { | |
| element: "span", | |
| className: "icon", | |
| children: { | |
| element: "i", | |
| className: "fas fa-trash-alt", | |
| }, | |
| }, | |
| }, | |
| }, | |
| ], | |
| }; | |
| }), | |
| }); | |
| return table; | |
| } | |
| function createTestTypeList(types) { | |
| return types.map((type) => ({ | |
| element: "button", | |
| style: "margin-right: 0.3em; margin-bottom: 0.3em", | |
| className: "button" + (hasTestType(type.value) ? " is-info" : ""), | |
| text: type.title, | |
| onClick: function (event) { | |
| handleTestTypeToggle(type.value); | |
| renderTypesField(); | |
| }, | |
| })); | |
| } | |
| function createRefSessionsList(referenceSessions) { | |
| return referenceSessions.map((session) => ({ | |
| element: "button", | |
| className: "button" + (hasRefSession(session) ? " is-info" : ""), | |
| style: "margin-right: 0.3em; margin-bottom: 0.3em; height: auto", | |
| onClick: function () { | |
| handleRefSessionToggle(session); | |
| renderReferencesField(); | |
| }, | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [{ element: "i", className: session.icon }], | |
| }, | |
| { | |
| element: "span", | |
| children: [ | |
| { text: session.title }, | |
| { | |
| text: session.engine, | |
| style: "font-size: 0.8em", | |
| }, | |
| ], | |
| }, | |
| ], | |
| })); | |
| } | |
| function createSelectDeselectButtons(onSelect, onDeselect) { | |
| return { | |
| style: "margin-top: 0.3em", | |
| children: [ | |
| { | |
| element: "button", | |
| style: "margin-right: 0.3em", | |
| className: "button is-rounded is-small", | |
| text: "All", | |
| onClick: onSelect, | |
| }, | |
| { | |
| element: "button", | |
| className: "button is-rounded is-small", | |
| text: "None", | |
| onClick: onDeselect, | |
| }, | |
| ], | |
| }; | |
| } | |
| function createErrorMessage(message) { | |
| return { | |
| element: "article", | |
| className: "message is-danger", | |
| children: [ | |
| { | |
| className: "message-body", | |
| text: message, | |
| }, | |
| ], | |
| }; | |
| } | |
| function createLoadingIndicator(text) { | |
| return UI.createElement({ | |
| className: "level", | |
| children: { | |
| element: "span", | |
| className: "level-item", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-spinner fa-pulse", | |
| }, | |
| { | |
| style: "margin-left: 0.4em;", | |
| text: text, | |
| }, | |
| ], | |
| }, | |
| }); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |