Automatic update from web-platform-tests Update WAVE test runner -- wpt-commits: 11f369dfab7cc1fc3f38fc062d87cffd04dc317b wpt-pr: 46286
1567 lines
55 KiB
HTML
1567 lines
55 KiB
HTML
<!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>
|
|
<script src="lib/hbbtv.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.readStatus(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")
|
|
WaveService.addSessionEventListener(
|
|
token,
|
|
resultUi.handleSessionEvent
|
|
);
|
|
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();
|
|
});
|
|
},
|
|
handleSessionEvent(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() - status.dateStarted.getTime()
|
|
);
|
|
}, 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
|
|
? status.dateFinished.getTime() -
|
|
status.dateStarted.getTime()
|
|
: Date.now() - status.dateStarted.getTime()
|
|
),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
});
|
|
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>
|