Source code
Revision control
Copy as Markdown
Other Tools
<!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>