| <!DOCTYPE html> |
| <html lang="en" style="overflow: auto"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <title>Session Results - 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" /> |
| <!-- <link rel="stylesheet" href="css/result.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> |
| let token = null; |
| window.onload = () => { |
| const query = utils.parseQuery(location.search); |
| token = query.token; |
| if (token) { |
| resultUi.render(); |
| resultUi.refreshData(); |
| } else { |
| location.href = WEB_ROOT + "overview.html" + location.search; |
| } |
| WaveService.addRecentSession(token); |
| }; |
| const resultUi = { |
| state: { |
| details: null, |
| results: null, |
| referenceSessions: [], |
| lastCompletedTests: [], |
| malfunctioningTests: [], |
| addLabelVisible: false |
| }, |
| refreshData: toUpdate => { |
| WaveService.readResultsConfig(function(config) { |
| resultUi.state.reportsEnabled = config.reportsEnabled; |
| resultUi.renderApiResults(); |
| }); |
| switch (toUpdate) { |
| case "test_completed": |
| resultUi.refreshSessionStatus(() => { |
| resultUi.refreshSessionResults(() => { |
| resultUi.renderApiResults(); |
| }); |
| }); |
| resultUi.refreshLastCompletedTests(() => { |
| resultUi.renderLastCompletedTests(); |
| }); |
| break; |
| case "status": |
| resultUi.refreshSessionStatus(() => { |
| resultUi.renderControls(); |
| resultUi.renderSessionDetails(); |
| }); |
| break; |
| case "": |
| case null: |
| case undefined: |
| resultUi.refreshSessionConfiguration(() => { |
| resultUi.refreshSessionStatus(() => { |
| resultUi.refreshSessionResults(() => { |
| resultUi.refreshReferenceSessions(() => |
| resultUi.renderReferenceSessions() |
| ); |
| resultUi.renderControls(); |
| resultUi.renderSessionDetails(); |
| resultUi.renderApiResults(); |
| resultUi.renderExportView(); |
| resultUi.refreshLastCompletedTests(() => { |
| resultUi.renderLastCompletedTests(); |
| }); |
| resultUi.refreshMalfunctioningTests(() => { |
| resultUi.renderMalfunctioningTests(); |
| }); |
| }); |
| }); |
| }); |
| break; |
| } |
| }, |
| refreshSessionConfiguration(callback = () => {}) { |
| WaveService.readSession(token, configuration => { |
| resultUi.state.configuration = configuration; |
| callback(configuration); |
| }); |
| }, |
| refreshSessionStatus(callback = () => {}) { |
| WaveService.readSessionStatus(token, status => { |
| resultUi.state.status = status; |
| if (status.status !== "completed" && status.status !== "aborted") |
| resultUi.connectWebSocket(); |
| callback(status); |
| }); |
| }, |
| refreshReferenceSessions(callback = () => {}) { |
| const { configuration } = resultUi.state; |
| if (!configuration) return; |
| const { referenceTokens } = configuration; |
| if (!referenceTokens) return; |
| WaveService.readMultipleSessions(referenceTokens, configuration => { |
| resultUi.state.referenceSessions = configuration; |
| resultUi.renderReferenceSessions(); |
| callback(configuration); |
| }); |
| }, |
| refreshSessionResults(callback = () => {}) { |
| WaveService.readResultsCompact(token, results => { |
| resultUi.state.results = results; |
| callback(results); |
| }); |
| }, |
| refreshLastCompletedTests(callback = () => {}) { |
| if (resultUi.state.configuration.isPublic) return; |
| WaveService.readLastCompletedTests(token, ["timeout"], tests => { |
| resultUi.state.lastCompletedTests = tests; |
| callback(); |
| }); |
| }, |
| refreshMalfunctioningTests(callback = () => {}) { |
| WaveService.readMalfunctioningTests(token, tests => { |
| resultUi.state.malfunctioningTests = tests; |
| callback(); |
| }); |
| }, |
| connectWebSocket() { |
| if (!WaveService.isConnected()) { |
| WaveService.connectHttpPolling(token); |
| WaveService.onMessage(message => { |
| resultUi.refreshData(message.type); |
| }); |
| } |
| }, |
| openResultsOverview() { |
| location.href = WEB_ROOT + "overview.html"; |
| }, |
| stopSession() { |
| WaveService.stopSession(token, resultUi.refreshData); |
| }, |
| deleteSession() { |
| WaveService.deleteSession(token, () => |
| resultUi.openResultsOverview() |
| ); |
| }, |
| showDeleteModal() { |
| const modal = UI.getElement("delete-modal"); |
| const className = modal.getAttribute("class"); |
| modal.setAttribute("class", className + " is-active"); |
| }, |
| hideDeleteModal() { |
| const modal = UI.getElement("delete-modal"); |
| let className = modal.getAttribute("class"); |
| className = className.replace(" is-active", ""); |
| modal.setAttribute("class", className); |
| }, |
| downloadApiResultJson(api) { |
| const { results } = resultUi.state; |
| WaveService.downloadApiResult(token, api); |
| }, |
| openHtmlReport(api) { |
| const { results } = resultUi.state; |
| if (results[api].complete != results[api].total) return; |
| WaveService.readReportUri(token, api, function(uri) { |
| window.open(uri, "_blank"); |
| }); |
| }, |
| downloadFinishedApiJsons() { |
| WaveService.downloadAllApiResults(token); |
| }, |
| downloadHtmlZip() { |
| WaveService.downloadResultsOverview(token); |
| }, |
| downloadResults() { |
| if (resultUi.state.status.status !== "completed") return; |
| WaveService.downloadResults(token); |
| }, |
| addMalfunctioningTest(testPath) { |
| const { malfunctioningTests } = resultUi.state; |
| if (malfunctioningTests.indexOf(testPath) !== -1) return; |
| malfunctioningTests.push(testPath); |
| WaveService.updateMalfunctioningTests( |
| token, |
| malfunctioningTests, |
| () => { |
| resultUi.renderMalfunctioningTests(); |
| } |
| ); |
| resultUi.renderLastCompletedTests(); |
| }, |
| removeMalfunctioningTest(testPath) { |
| const { malfunctioningTests } = resultUi.state; |
| malfunctioningTests.splice(malfunctioningTests.indexOf(testPath), 1); |
| WaveService.updateMalfunctioningTests( |
| token, |
| malfunctioningTests, |
| () => { |
| resultUi.renderMalfunctioningTests(); |
| } |
| ); |
| resultUi.renderLastCompletedTests(); |
| }, |
| isTestOnMalfunctioningList(test) { |
| const { malfunctioningTests } = resultUi.state; |
| return malfunctioningTests.indexOf(test) !== -1; |
| }, |
| showExcluded() { |
| resultUi.state.showExcluded = true; |
| resultUi.renderSessionDetails(); |
| }, |
| hideExcluded() { |
| resultUi.state.showExcluded = false; |
| resultUi.renderSessionDetails(); |
| }, |
| addLabel() { |
| const label = UI.getElement("session-label-input").value; |
| if (!label) return; |
| const { configuration } = resultUi.state; |
| configuration.labels.push(label); |
| WaveService.updateLabels(token, configuration.labels); |
| resultUi.renderSessionDetails(); |
| UI.getElement("session-label-input").focus(); |
| }, |
| removeLabel(index) { |
| const { configuration } = resultUi.state; |
| configuration.labels.splice(index, 1); |
| WaveService.updateLabels(token, configuration.labels); |
| resultUi.renderSessionDetails(); |
| }, |
| showAddLabel() { |
| resultUi.state.addLabelVisible = true; |
| resultUi.renderSessionDetails(); |
| UI.getElement("session-label-input").focus(); |
| }, |
| hideAddLabel() { |
| resultUi.state.addLabelVisible = false; |
| resultUi.renderSessionDetails(); |
| }, |
| render() { |
| const resultView = UI.createElement({ |
| className: "section", |
| children: [ |
| { |
| className: "container", |
| style: "margin-bottom: 2em", |
| children: [ |
| { |
| className: "columns", |
| children: [ |
| { |
| className: "column", |
| children: [ |
| { |
| element: "img", |
| src: "res/wavelogo_2016.jpg", |
| className: "site-logo" |
| } |
| ] |
| }, |
| { |
| className: "column is-narrow", |
| children: { |
| className: "button is-dark is-outlined", |
| onclick: resultUi.openResultsOverview, |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: [ |
| { |
| element: "i", |
| className: "fas fa-arrow-left" |
| } |
| ] |
| }, |
| { |
| text: "Results Overview", |
| element: "span" |
| } |
| ] |
| } |
| } |
| ] |
| }, |
| { |
| className: "container", |
| children: { |
| className: "columns", |
| children: [ |
| { |
| className: "column", |
| children: { className: "title", text: "Result" } |
| }, |
| { |
| className: "column is-narrow", |
| children: { id: "controls" } |
| } |
| ] |
| } |
| } |
| ] |
| }, |
| { |
| id: "session-details", |
| className: "container", |
| style: "margin-bottom: 2em" |
| }, |
| { |
| id: "last-completed-tests", |
| className: "container", |
| style: "margin-bottom: 2em" |
| }, |
| { |
| id: "api-results", |
| className: "container", |
| style: "margin-bottom: 2em" |
| }, |
| { |
| id: "timeout-files", |
| className: "container", |
| style: "margin-bottom: 2em" |
| }, |
| { |
| id: "export", |
| className: "container", |
| style: "margin-bottom: 2em" |
| }, |
| { |
| id: "malfunctioning-tests", |
| className: "container", |
| style: "margin-bottom: 2em" |
| } |
| ] |
| }); |
| const root = UI.getRoot(); |
| root.innerHTML = ""; |
| root.appendChild(resultView); |
| resultUi.renderControls(); |
| resultUi.renderSessionDetails(); |
| resultUi.renderApiResults(); |
| resultUi.renderExportView(); |
| }, |
| renderControls() { |
| const { state } = resultUi; |
| if (!state.status) return; |
| const { status } = state.status; |
| const { isPublic } = state.configuration; |
| const controlsView = UI.createElement({ |
| className: "field is-grouped is-grouped-multiline" |
| }); |
| if ( |
| status && |
| status !== "aborted" && |
| status !== "completed" && |
| status !== "pending" |
| ) { |
| const pauseResumeButton = UI.createElement({ |
| id: "pause-resume-button", |
| className: "control button is-dark is-outlined", |
| onclick: function() { |
| if (status === "running") { |
| WaveService.pauseSession(token, resultUi.refreshData); |
| } else { |
| WaveService.startSession(token, resultUi.refreshData); |
| } |
| }, |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: [ |
| { |
| element: "i", |
| className: |
| status === "running" ? "fas fa-pause" : "fas fa-play" |
| } |
| ] |
| }, |
| { |
| text: status === "running" ? "Pause" : "Resume", |
| element: "span" |
| } |
| ] |
| }); |
| controlsView.appendChild(pauseResumeButton); |
| } |
| |
| if (status && status !== "aborted" && status !== "completed") { |
| const stopButton = UI.createElement({ |
| id: "stop-button", |
| className: "control button is-dark is-outlined", |
| onclick: resultUi.stopSession, |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: [ |
| { |
| element: "i", |
| className: "fas fa-square" |
| } |
| ] |
| }, |
| { |
| text: "Stop", |
| element: "span" |
| } |
| ] |
| }); |
| controlsView.appendChild(stopButton); |
| } |
| if (!isPublic) { |
| const deleteButton = UI.createElement({ |
| id: "delete-button", |
| className: "control button is-dark is-outlined", |
| onclick: resultUi.showDeleteModal, |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: [ |
| { |
| element: "i", |
| className: "fas fa-trash-alt" |
| } |
| ] |
| }, |
| { |
| text: "Delete", |
| element: "span" |
| } |
| ] |
| }); |
| controlsView.appendChild(deleteButton); |
| } |
| |
| const deleteModal = UI.createElement({ |
| id: "delete-modal", |
| className: "modal", |
| children: [ |
| { |
| className: "modal-background", |
| onclick: resultUi.hideDeleteModal |
| }, |
| { |
| className: "modal-card", |
| children: [ |
| { |
| className: "modal-card-head", |
| children: [ |
| { |
| element: "p", |
| className: "modal-card-title", |
| text: "Delete Session" |
| } |
| ] |
| }, |
| { |
| className: "modal-card-body", |
| children: [ |
| { |
| element: "p", |
| text: "Are you sure you want to delete this session?" |
| }, |
| { element: "p", text: "This action cannot be undone." } |
| ] |
| }, |
| { |
| className: "modal-card-foot", |
| children: [ |
| { |
| className: "button is-danger", |
| text: "Delete Session", |
| onclick: resultUi.deleteSession |
| }, |
| { |
| className: "button", |
| text: "Cancel", |
| onclick: resultUi.hideDeleteModal |
| } |
| ] |
| } |
| ] |
| } |
| ] |
| }); |
| controlsView.appendChild(deleteModal); |
| |
| const controls = UI.getElement("controls"); |
| controls.innerHTML = ""; |
| controls.appendChild(controlsView); |
| }, |
| renderSessionDetails() { |
| const { state } = resultUi; |
| const { configuration, status, results } = state; |
| if (!configuration || !status) return; |
| const sessionDetailsView = UI.createElement({ |
| style: "margin-bottom: 20px" |
| }); |
| |
| const heading = UI.createElement({ |
| text: "Session details", |
| className: "title is-4" |
| }); |
| sessionDetailsView.appendChild(heading); |
| |
| const getTagStyle = status => { |
| switch (status) { |
| case "completed": |
| return "is-success"; |
| case "running": |
| return "is-info"; |
| case "aborted": |
| return "is-danger"; |
| case "paused": |
| return "is-warning"; |
| case "pending": |
| return "is-primary"; |
| } |
| }; |
| if (status.dateFinished) { |
| if (state.durationInterval) clearInterval(state.durationInterval); |
| } else if (status.dateStarted) { |
| if (!state.durationInterval) |
| state.durationInterval = setInterval(() => { |
| UI.getElement("duration").innerHTML = utils.millisToTimeString( |
| Date.now() - parseInt(status.dateStarted) |
| ); |
| }, 1000); |
| } |
| |
| const { addLabelVisible } = state; |
| const { showExcluded } = state; |
| |
| const tokenField = UI.createElement({ |
| 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 |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(tokenField); |
| |
| const userAgentField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "User Agent" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: "control", |
| text: configuration.userAgent || "" |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(userAgentField); |
| |
| const testPathsField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Test Paths" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: "control", |
| text: configuration.tests.include |
| .reduce((text, test) => text + test + ", ", "") |
| .slice(0, -2) |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(testPathsField); |
| |
| const excludedTestsField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Excluded Test Paths" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: "control", |
| children: [ |
| { |
| element: "span", |
| text: configuration.tests.exclude.length |
| }, |
| { |
| element: "span", |
| className: "button is-small is-rounded", |
| style: "margin-left: 10px", |
| text: showExcluded ? "hide" : "show", |
| onClick: showExcluded |
| ? resultUi.hideExcluded |
| : resultUi.showExcluded |
| }, |
| showExcluded |
| ? { |
| style: |
| "max-height: 250px; overflow: auto; margin-bottom: 10px", |
| children: configuration.tests.exclude.map(test => ({ |
| text: test |
| })) |
| } |
| : null |
| ] |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(excludedTestsField); |
| |
| const referenceSessionField = UI.createElement({ |
| style: "display: none", |
| id: "reference-session-field", |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Reference Sessions" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: "control", |
| children: { id: "reference-sessions" } |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(referenceSessionField); |
| |
| const totalTestFilesField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Total Test Files" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: "control", |
| text: Object.keys(results).reduce( |
| (sum, api) => (sum += results[api].total), |
| 0 |
| ) |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(totalTestFilesField); |
| |
| const statusField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Status" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: `control tag ${getTagStyle(status.status)}`, |
| text: status.status |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(statusField); |
| |
| const timeoutsField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Test Timeouts" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: `control`, |
| text: Object.keys(configuration.timeouts).reduce( |
| (text, timeout) => |
| `${text}${timeout}: ${configuration.timeouts[timeout] / |
| 1000}s\n`, |
| "" |
| ) |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(timeoutsField); |
| |
| if (status.dateStarted) { |
| const startedField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Date Started" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: `control`, |
| text: new Date(status.dateStarted).toLocaleString() |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(startedField); |
| } |
| |
| if (status.dateFinished) { |
| const finishedField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Date Finished" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: `control`, |
| text: new Date(status.dateFinished).toLocaleString() |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(finishedField); |
| } |
| |
| if (status.dateStarted) { |
| const durationField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Duration" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "field", |
| children: { |
| className: `control`, |
| id: "duration", |
| text: utils.millisToTimeString( |
| status.dateFinished |
| ? parseInt(status.dateFinished) - |
| parseInt(status.dateStarted) |
| : Date.now() - parseInt(status.dateStarted) |
| ) |
| } |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(durationField); |
| } |
| |
| const labelsField = UI.createElement({ |
| 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: () => resultUi.removeLabel(index) |
| } |
| ] |
| } |
| })) |
| .concat( |
| resultUi.state.configuration.isPublic |
| ? [] |
| : 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 |
| ? resultUi.addLabel() |
| : null |
| }, |
| { |
| className: |
| "button is-dark is-outlined is-small is-rounded control", |
| text: "save", |
| onClick: resultUi.addLabel |
| }, |
| { |
| className: |
| "button is-dark is-outlined is-small is-rounded control", |
| text: "cancel", |
| onClick: resultUi.hideAddLabel |
| } |
| ] |
| } |
| ] |
| : [ |
| { |
| className: "button is-rounded is-small", |
| text: "Add", |
| onClick: resultUi.showAddLabel |
| } |
| ] |
| ) |
| } |
| } |
| ] |
| }); |
| sessionDetailsView.appendChild(labelsField); |
| |
| const sessionDetails = UI.getElement("session-details"); |
| sessionDetails.innerHTML = ""; |
| sessionDetails.appendChild(sessionDetailsView); |
| resultUi.renderReferenceSessions(); |
| }, |
| renderReferenceSessions() { |
| const { referenceSessions } = resultUi.state; |
| if (!referenceSessions || referenceSessions.length === 0) return; |
| const referenceSessionsList = UI.createElement({ |
| className: "field is-grouped is-grouped-multiline" |
| }); |
| const getBrowserIcon = browser => { |
| switch (browser.toLowerCase()) { |
| case "firefox": |
| return "fab fa-firefox"; |
| case "edge": |
| return "fab fa-edge"; |
| case "chrome": |
| case "chromium": |
| return "fab fa-chrome"; |
| case "safari": |
| case "webkit": |
| return "fab fa-safari"; |
| } |
| }; |
| referenceSessions.forEach(session => { |
| const { token, browser } = session; |
| const referenceSessionItem = UI.createElement({ |
| className: |
| "control button is-dark is-small is-rounded is-outlined", |
| onClick: () => WaveService.openSession(token), |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: { |
| element: "i", |
| className: getBrowserIcon(browser.name) |
| } |
| }, |
| { |
| element: "span", |
| text: token.split("-").shift() |
| } |
| ] |
| }); |
| referenceSessionsList.appendChild(referenceSessionItem); |
| }); |
| const referenceSessionsTarget = UI.getElement("reference-sessions"); |
| referenceSessionsTarget.innerHTML = ""; |
| referenceSessionsTarget.appendChild(referenceSessionsList); |
| const field = UI.getElement("reference-session-field"); |
| field.style["display"] = "flex"; |
| }, |
| renderLastCompletedTests() { |
| if (resultUi.state.configuration.isPublic) return; |
| const lastCompletedTestsView = UI.createElement({}); |
| |
| const heading = UI.createElement({ |
| className: "title is-4", |
| children: [ |
| { element: "span", text: "Last Timed-Out Test Files" }, |
| { |
| element: "span", |
| className: "title is-7", |
| text: " (most recent first)" |
| } |
| ] |
| }); |
| lastCompletedTestsView.appendChild(heading); |
| |
| const { lastCompletedTests } = resultUi.state; |
| const testsTable = UI.createElement({ |
| element: "table", |
| className: "table", |
| style: "min-width: 100%", |
| children: [ |
| { |
| element: "thead", |
| children: [ |
| { |
| element: "tr", |
| children: [ |
| { element: "td", text: "Test File" }, |
| { element: "td", text: "Malfunctioning List" } |
| ] |
| } |
| ] |
| }, |
| { |
| element: "tbody", |
| children: lastCompletedTests.map(({ path, status }) => ({ |
| element: "tr", |
| children: [ |
| { element: "td", text: path }, |
| { |
| element: "td", |
| children: [ |
| { |
| element: "button", |
| className: "button is-dark is-outlined is-small", |
| onClick: () => resultUi.addMalfunctioningTest(path), |
| title: "Add to malfunctioning tests list.", |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: [ |
| { |
| element: "i", |
| className: resultUi.isTestOnMalfunctioningList( |
| path |
| ) |
| ? "fas fa-check" |
| : "fas fa-plus" |
| } |
| ] |
| } |
| ] |
| } |
| ] |
| } |
| ] |
| })) |
| } |
| ] |
| }); |
| if (lastCompletedTests.length > 0) { |
| lastCompletedTestsView.appendChild( |
| UI.createElement({ |
| className: "container", |
| style: "overflow-x: auto;", |
| id: "last-completed-overflow", |
| children: testsTable |
| }) |
| ); |
| } else { |
| const noTestsLabel = UI.createElement({ |
| text: "- No Timed-Out Tests -", |
| style: "text-align: center" |
| }); |
| lastCompletedTestsView.appendChild(noTestsLabel); |
| } |
| |
| UI.saveScrollPosition("last-completed-overflow"); |
| |
| const lastCompletedTestsElement = UI.getElement( |
| "last-completed-tests" |
| ); |
| lastCompletedTestsElement.innerHTML = ""; |
| lastCompletedTestsElement.appendChild(lastCompletedTestsView); |
| |
| UI.loadScrollPosition("last-completed-overflow"); |
| }, |
| renderApiResults() { |
| const { results, status } = resultUi.state; |
| |
| const apiResultsView = UI.createElement({ |
| style: "margin-bottom: 20px" |
| }); |
| |
| const heading = UI.createElement({ |
| text: "API Results", |
| className: "title is-4" |
| }); |
| apiResultsView.appendChild(heading); |
| |
| if (!results) { |
| const loadingIndicator = 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: "Loading results ..." |
| } |
| ] |
| } |
| }); |
| apiResultsView.appendChild(loadingIndicator); |
| |
| const apiResults = UI.getElement("api-results"); |
| apiResults.innerHTML = ""; |
| apiResults.appendChild(apiResultsView); |
| return; |
| } |
| |
| const width = status.status === "running" ? "7.5em" : "auto"; |
| const header = UI.createElement({ |
| element: "thead", |
| children: [ |
| { |
| element: "tr", |
| children: [ |
| { element: "th", text: "API" }, |
| { element: "th", text: "Pass", style: `min-width: ${width}` }, |
| { element: "th", text: "Fail", style: `min-width: ${width}` }, |
| { |
| element: "th", |
| text: "Timeout", |
| style: `min-width: ${width}` |
| }, |
| { |
| element: "th", |
| text: "Not Run", |
| style: `min-width: ${width}` |
| }, |
| { |
| element: "th", |
| text: "Test Files Run", |
| style: `min-width: ${width}` |
| }, |
| { element: "th", text: "Export" } |
| ] |
| } |
| ] |
| }); |
| |
| const apis = Object.keys(results).sort((apiA, apiB) => |
| apiA.toLowerCase() > apiB.toLowerCase() ? 1 : -1 |
| ); |
| |
| const rows = apis.map(api => { |
| const { |
| complete = 0, |
| pass = 0, |
| fail = 0, |
| timeout = 0, |
| timeoutfiles = [], |
| not_run: notRun = 0, |
| total |
| } = results[api]; |
| isDone = results[api].complete == results[api].total; |
| const totalTestResults = pass + fail + timeout + notRun; |
| return UI.createElement({ |
| element: "tr", |
| style: "white-space: nowrap", |
| children: [ |
| { element: "td", text: api }, |
| { |
| element: "td", |
| children: { |
| style: `color: hsl(141, 71%, 38%); overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${pass} (${utils.percent(pass, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "td", |
| children: { |
| className: "has-text-danger", |
| style: `overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${fail} (${utils.percent(fail, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "td", |
| children: { |
| style: `color: hsl(48, 100%, 40%); overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${timeout} (${utils.percent(timeout, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "td", |
| children: { |
| className: "has-text-info", |
| style: `overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${notRun} (${utils.percent(notRun, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "td", |
| children: { |
| style: `overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${complete}/${total} (${utils.percent( |
| complete, |
| total |
| )}%)` |
| } |
| }, |
| { |
| element: "td", |
| children: { |
| className: "field has-addons", |
| children: [ |
| { |
| className: "control", |
| children: { |
| className: "button is-dark is-outlined is-small", |
| onclick: () => resultUi.downloadApiResultJson(api), |
| text: "json", |
| title: `Download results of ${api} API as JSON file.` |
| } |
| }, |
| resultUi.state.reportsEnabled ? |
| { |
| className: "control", |
| children: { |
| className: "button is-dark is-outlined is-small", |
| disabled: !isDone, |
| onclick: () => resultUi.openHtmlReport(api), |
| text: "report", |
| title: `Show results of ${api} API in WPT Report format.` |
| } |
| } : null |
| ] |
| } |
| } |
| ] |
| }); |
| }); |
| |
| const { pass, fail, timeout, not_run, complete, total } = apis.reduce( |
| (sum, api) => { |
| Object.keys(sum).forEach( |
| key => (sum[key] += results[api][key] ? results[api][key] : 0) |
| ); |
| return sum; |
| }, |
| { complete: 0, total: 0, pass: 0, fail: 0, timeout: 0, not_run: 0 } |
| ); |
| const totalTestResults = pass + fail + timeout + not_run; |
| |
| const footer = UI.createElement({ |
| element: "tfoot", |
| children: [ |
| { |
| element: "tr", |
| children: [ |
| { element: "th", text: "Total" }, |
| { |
| element: "th", |
| children: { |
| style: `color: hsl(141, 71%, 38%); overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${pass} (${utils.percent(pass, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "th", |
| children: { |
| style: `overflow: visible; white-space: nowrap; width: ${width}`, |
| className: "has-text-danger", |
| text: `${fail} (${utils.percent(fail, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "th", |
| children: { |
| style: `color: hsl(48, 100%, 40%); overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${timeout} (${utils.percent(timeout, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "th", |
| children: { |
| style: `overflow: visible; white-space: nowrap; width: ${width}`, |
| className: "has-text-info", |
| text: `${not_run} (${utils.percent(not_run, totalTestResults)}%)` |
| } |
| }, |
| { |
| element: "th", |
| children: { |
| style: `overflow: visible; white-space: nowrap; width: ${width}`, |
| text: `${complete}/${total} (${utils.percent( |
| complete, |
| total |
| )}%)` |
| } |
| }, |
| { element: "th" } |
| ] |
| } |
| ] |
| }); |
| |
| const resultsTable = UI.createElement({ |
| className: "container", |
| style: "overflow-x: auto", |
| id: "results-overflow", |
| children: { |
| element: "table", |
| className: "table", |
| id: "results-table", |
| style: |
| "width: 100%; min-width: 30em; border-radius: 3px; border: 2px solid hsl(0, 0%, 86%);", |
| children: [header, { element: "tbody", children: rows }, footer] |
| } |
| }); |
| apiResultsView.appendChild(resultsTable); |
| |
| UI.saveScrollPosition("results-overflow"); |
| |
| const apiResults = UI.getElement("api-results"); |
| apiResults.innerHTML = ""; |
| apiResults.appendChild(apiResultsView); |
| |
| UI.loadScrollPosition("results-overflow"); |
| }, |
| renderExportView() { |
| const { status } = resultUi.state; |
| if (!status) return; |
| |
| const exportElement = UI.getElement("export"); |
| exportElement.innerHTML = ""; |
| |
| const heading = UI.createElement({ |
| className: "title is-4", |
| text: "Export" |
| }); |
| exportElement.appendChild(heading); |
| |
| const resultsField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { className: "label", text: "Results" } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "control columns", |
| style: "width: 100%", |
| children: [ |
| { |
| className: "column is-9", |
| text: |
| "Download results for import into other WMAS Test Suite instances." |
| }, |
| { |
| className: "column is-3", |
| children: { |
| className: |
| "button is-dark is-outlined is-small is-fullwidth", |
| onClick: resultUi.downloadResults, |
| disabled: status.status !== "completed", |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: { |
| element: "i", |
| className: "fas fa-file-archive" |
| } |
| }, |
| { element: "span", text: "Download Zip" } |
| ] |
| } |
| } |
| ] |
| } |
| } |
| ] |
| }); |
| exportElement.appendChild(resultsField); |
| |
| const jsonField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { |
| className: "label", |
| text: "All JSON Files" |
| } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "control columns", |
| style: "width: 100%", |
| children: [ |
| { |
| className: "column is-9", |
| text: |
| "Download JSON files containing results of completed test files." |
| }, |
| { |
| className: "column is-3", |
| children: { |
| className: |
| "button is-dark is-outlined is-small is-fullwidth", |
| onclick: resultUi.downloadFinishedApiJsons, |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: { |
| element: "i", |
| className: "fas fa-file-archive" |
| } |
| }, |
| { element: "span", text: "Download Zip" } |
| ] |
| } |
| } |
| ] |
| } |
| } |
| ] |
| }); |
| exportElement.appendChild(jsonField); |
| |
| const htmlField = UI.createElement({ |
| className: "field is-horizontal", |
| children: [ |
| { |
| className: "field-label", |
| children: { |
| className: "label", |
| text: "Session result HTML" |
| } |
| }, |
| { |
| className: "field-body", |
| children: { |
| className: "control columns", |
| style: "width: 100%", |
| children: [ |
| { |
| className: "column is-9", |
| text: |
| "Download this sessions result as standalone HTML page, similar to this page." |
| }, |
| { |
| className: "column is-3", |
| children: { |
| className: |
| "button is-dark is-outlined is-small is-fullwidth", |
| onClick: resultUi.downloadHtmlZip, |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: { |
| element: "i", |
| className: "fas fa-code" |
| } |
| }, |
| { element: "span", text: "Download HTML" } |
| ] |
| } |
| } |
| ] |
| } |
| } |
| ] |
| }); |
| exportElement.appendChild(htmlField); |
| }, |
| renderMalfunctioningTests() { |
| const malfunctioningTestsView = UI.createElement({}); |
| const heading = UI.createElement({ |
| className: "title is-4", |
| text: "Malfunctioning Tests" |
| }); |
| malfunctioningTestsView.appendChild(heading); |
| |
| const { malfunctioningTests } = resultUi.state; |
| const testsTable = UI.createElement({ |
| element: "table", |
| className: "table", |
| style: "min-width: 100%", |
| children: [ |
| { |
| element: "thead", |
| children: [ |
| { |
| element: "tr", |
| children: [ |
| { element: "td", text: "Test File" }, |
| { element: "td", text: "" } |
| ] |
| } |
| ] |
| }, |
| { |
| element: "tbody", |
| children: malfunctioningTests.map(path => ({ |
| element: "tr", |
| children: [ |
| { element: "td", text: path }, |
| { |
| element: "td", |
| children: resultUi.state.configuration.isPublic |
| ? null |
| : { |
| element: "button", |
| className: "button is-dark is-outlined is-small", |
| onClick: () => |
| resultUi.removeMalfunctioningTest(path), |
| title: "Remove from malfunctioning tests list.", |
| children: [ |
| { |
| element: "span", |
| className: "icon", |
| children: [ |
| { |
| element: "i", |
| className: "fas fa-trash-alt" |
| } |
| ] |
| } |
| ] |
| } |
| } |
| ] |
| })) |
| } |
| ] |
| }); |
| if (malfunctioningTests.length > 0) { |
| malfunctioningTestsView.appendChild( |
| UI.createElement({ |
| className: "container", |
| style: "overflow-x: auto", |
| id: "malfunctioning-overflow", |
| children: testsTable |
| }) |
| ); |
| } else { |
| const noTestsLabel = UI.createElement({ |
| text: "- No Tests Available -", |
| style: "text-align: center" |
| }); |
| malfunctioningTestsView.appendChild(noTestsLabel); |
| } |
| |
| UI.saveScrollPosition("malfunctioning-overflow"); |
| |
| const malfunctioningTestsElement = UI.getElement( |
| "malfunctioning-tests" |
| ); |
| malfunctioningTestsElement.innerHTML = ""; |
| malfunctioningTestsElement.appendChild(malfunctioningTestsView); |
| |
| UI.loadScrollPosition("malfunctioning-overflow"); |
| } |
| }; |
| </script> |
| </body> |
| </html> |