ortoni-report 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +25 -0
- package/dist/ortoni-report.d.ts +27 -47
- package/dist/ortoni-report.js +23152 -268
- package/dist/ortoni-report.mjs +23147 -269
- package/dist/style/main.css +41 -36
- package/dist/utils/expressServer.js +33 -0
- package/dist/utils/utils.js +17 -1
- package/dist/utils/webSocketHelper.js +93 -0
- package/dist/views/main.hbs +186 -117
- package/dist/views/navbar.hbs +1 -11
- package/dist/views/project.hbs +203 -95
- package/dist/views/testIcons.hbs +163 -0
- package/dist/views/testPanel.hbs +16 -8
- package/dist/views/testStatus.hbs +8 -32
- package/dist/views/userInfo.hbs +10 -9
- package/package.json +3 -1
- package/readme.md +127 -68
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startReportServer = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
/**
|
|
9
|
+
* Starts an Express server to serve the HTML report and keeps it running.
|
|
10
|
+
* @param {string} reportPath - Path to the folder where the report is stored.
|
|
11
|
+
* @param {string} reportFilename - Name of the HTML report file to serve.
|
|
12
|
+
* @param {number} port - Port number to serve the report on (default is 8080).
|
|
13
|
+
*/
|
|
14
|
+
function startReportServer(reportPath, reportFilename, port = 8080) {
|
|
15
|
+
const app = (0, express_1.default)();
|
|
16
|
+
// Serve static files from the report directory
|
|
17
|
+
app.use(express_1.default.static(reportPath));
|
|
18
|
+
// Start the server and keep it running
|
|
19
|
+
const server = app.listen(port, () => {
|
|
20
|
+
const reportUrl = `http://localhost:${port}/${reportFilename}`;
|
|
21
|
+
console.log(`Report is available at ${reportUrl}`);
|
|
22
|
+
});
|
|
23
|
+
// Ensure that the process doesn't exit prematurely
|
|
24
|
+
process.on('SIGINT', () => {
|
|
25
|
+
console.log('Shutting down the server...');
|
|
26
|
+
server.close(() => {
|
|
27
|
+
console.log('Server closed');
|
|
28
|
+
process.exit(0);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
return server;
|
|
32
|
+
}
|
|
33
|
+
exports.startReportServer = startReportServer;
|
package/dist/utils/utils.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ensureHtmlExtension = exports.safeStringify = exports.formatDate = exports.normalizeFilePath = exports.msToTime = void 0;
|
|
6
|
+
exports.escapeHtml = exports.ensureHtmlExtension = exports.safeStringify = exports.formatDate = exports.normalizeFilePath = exports.msToTime = void 0;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
function msToTime(duration) {
|
|
9
9
|
const milliseconds = Math.floor(duration % 1000);
|
|
@@ -67,3 +67,19 @@ function ensureHtmlExtension(filename) {
|
|
|
67
67
|
return `${filename}.html`;
|
|
68
68
|
}
|
|
69
69
|
exports.ensureHtmlExtension = ensureHtmlExtension;
|
|
70
|
+
function escapeHtml(unsafe) {
|
|
71
|
+
if (typeof unsafe !== 'string') {
|
|
72
|
+
return String(unsafe);
|
|
73
|
+
}
|
|
74
|
+
return unsafe.replace(/[&<"']/g, function (match) {
|
|
75
|
+
const escapeMap = {
|
|
76
|
+
'&': '&',
|
|
77
|
+
'<': '<',
|
|
78
|
+
'>': '>',
|
|
79
|
+
'"': '"',
|
|
80
|
+
"'": '''
|
|
81
|
+
};
|
|
82
|
+
return escapeMap[match] || match;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
exports.escapeHtml = escapeHtml;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const http_1 = __importDefault(require("http"));
|
|
30
|
+
const ws_1 = __importStar(require("ws"));
|
|
31
|
+
const utils_1 = require("./utils");
|
|
32
|
+
class WebSocketHelper {
|
|
33
|
+
constructor(port) {
|
|
34
|
+
this.port = port;
|
|
35
|
+
this.wss = null;
|
|
36
|
+
}
|
|
37
|
+
setupWebSocket() {
|
|
38
|
+
const server = http_1.default.createServer();
|
|
39
|
+
this.wss = new ws_1.WebSocketServer({ server });
|
|
40
|
+
this.wss.on('connection', (ws) => {
|
|
41
|
+
ws.send((0, utils_1.safeStringify)({ type: 'initial', data: 'Connected to WS service' }));
|
|
42
|
+
});
|
|
43
|
+
server.listen(this.port, () => {
|
|
44
|
+
console.log(`WebSocket server is running on http://localhost:${this.port}`);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
setupCleanup() {
|
|
48
|
+
const gracefulShutdown = () => {
|
|
49
|
+
console.log('Shutting down WebSocket server...');
|
|
50
|
+
this.closeWebSocket();
|
|
51
|
+
process.exit();
|
|
52
|
+
};
|
|
53
|
+
process.on('exit', gracefulShutdown);
|
|
54
|
+
process.on('SIGINT', () => {
|
|
55
|
+
console.log('Received SIGINT. Closing WebSocket server...');
|
|
56
|
+
gracefulShutdown();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
broadcastUpdate(tests) {
|
|
60
|
+
if (this.wss) {
|
|
61
|
+
this.wss.clients.forEach((client) => {
|
|
62
|
+
if (client.readyState === ws_1.default.OPEN) {
|
|
63
|
+
client.send((0, utils_1.safeStringify)({ type: 'update', data: tests }));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
closeWebSocket() {
|
|
69
|
+
if (this.wss) {
|
|
70
|
+
this.wss.clients.forEach((client) => {
|
|
71
|
+
if (client.readyState === ws_1.default.OPEN) {
|
|
72
|
+
client.close(1000, 'Test run completed');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
this.wss.close(() => {
|
|
76
|
+
console.log('WebSocket server closed');
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
testComplete() {
|
|
81
|
+
if (this.wss) {
|
|
82
|
+
this.wss.clients.forEach((client) => {
|
|
83
|
+
if (client.readyState === ws_1.default.OPEN) {
|
|
84
|
+
client.send((0, utils_1.safeStringify)({ type: 'complete', data: 'Test run completed' }));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
this.closeWebSocket();
|
|
90
|
+
}, 1000);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.default = WebSocketHelper;
|
package/dist/views/main.hbs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en" data-theme="{{preferredTheme}}">
|
|
3
|
-
|
|
4
3
|
<head>
|
|
5
4
|
<meta charset="UTF-8">
|
|
6
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<meta name="description" content="Playwright HTML report by LetCode Koushik - V2.0.
|
|
6
|
+
<meta name="description" content="Playwright HTML report by LetCode Koushik - V2.0.6">
|
|
8
7
|
<title>{{title}}</title>
|
|
9
8
|
<link rel="icon" href="https://raw.githubusercontent.com/ortoniKC/ortoni-report/refs/heads/main/favicon.png"
|
|
10
9
|
type="image/x-icon">
|
|
@@ -15,10 +14,9 @@
|
|
|
15
14
|
<style>
|
|
16
15
|
{{{inlineCss}}}
|
|
17
16
|
</style>
|
|
18
|
-
|
|
19
17
|
<body>
|
|
20
18
|
{{> navbar }}
|
|
21
|
-
<section class="section">
|
|
19
|
+
<section class="section mt-6">
|
|
22
20
|
<main class="container">
|
|
23
21
|
<div class="columns">
|
|
24
22
|
<aside class="column is-two-fifths">
|
|
@@ -26,7 +24,6 @@
|
|
|
26
24
|
{{> project}}
|
|
27
25
|
</aside>
|
|
28
26
|
<section class="column is-three-fifths">
|
|
29
|
-
{{!-- Overall summary --}}
|
|
30
27
|
<div id="summary">
|
|
31
28
|
<div class="columns is-multiline has-text-centered">
|
|
32
29
|
{{> summaryCard bg="primary" status="all" statusHeader="All Tests" statusCount=totalCount}}
|
|
@@ -40,13 +37,7 @@
|
|
|
40
37
|
{{> userInfo}}
|
|
41
38
|
</div>
|
|
42
39
|
</div>
|
|
43
|
-
|
|
44
|
-
<div id="testDetails" style="display: none;">
|
|
45
|
-
<!-- Back button should be outside the dynamic content -->
|
|
46
|
-
<button class="button content" id="back-to-summary" onclick="showSummary()">Back to
|
|
47
|
-
Summary</button>
|
|
48
|
-
<!-- Test Details will be displayed here -->
|
|
49
|
-
</div>
|
|
40
|
+
<div id="testDetails" style="display: none;"></div>
|
|
50
41
|
</section>
|
|
51
42
|
</div>
|
|
52
43
|
</main>
|
|
@@ -95,11 +86,18 @@
|
|
|
95
86
|
function showSummary() {
|
|
96
87
|
summary.style.display = 'block';
|
|
97
88
|
testDetails.style.display = 'none';
|
|
98
|
-
backButton.style.display = 'none';
|
|
99
89
|
}
|
|
100
90
|
window.showSummary = showSummary;
|
|
101
91
|
|
|
102
92
|
function displayTestDetails(test) {
|
|
93
|
+
const summary = document.getElementById('summary');
|
|
94
|
+
const testDetails = document.getElementById('testDetails');
|
|
95
|
+
summary.style.display = 'none';
|
|
96
|
+
testDetails.style.opacity = '0';
|
|
97
|
+
testDetails.style.display = 'block';
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
testDetails.style.opacity = '1';
|
|
100
|
+
}, 50);
|
|
103
101
|
let currentScreenshotIndex = 0;
|
|
104
102
|
function changeScreenshot(direction) {
|
|
105
103
|
const screenshots = test.screenshots;
|
|
@@ -125,85 +123,83 @@
|
|
|
125
123
|
}
|
|
126
124
|
});
|
|
127
125
|
}
|
|
128
|
-
const summary = document.getElementById('summary');
|
|
129
|
-
const testDetails = document.getElementById('testDetails');
|
|
130
|
-
const backButton = document.querySelector('button#back-to-summary');
|
|
131
|
-
summary.style.display = 'none';
|
|
132
|
-
testDetails.style.opacity = '0';
|
|
133
|
-
testDetails.style.display = 'block';
|
|
134
|
-
setTimeout(() => {
|
|
135
|
-
testDetails.style.opacity = '1';
|
|
136
|
-
backButton.style.opacity = '1';
|
|
137
|
-
}, 50);
|
|
138
|
-
|
|
139
126
|
let statusClass = '';
|
|
127
|
+
let statusIcon = '';
|
|
140
128
|
let statusText = test.status.toUpperCase();
|
|
129
|
+
|
|
141
130
|
if (test.status.startsWith('passed')) {
|
|
142
|
-
statusClass = '
|
|
131
|
+
statusClass = 'success';
|
|
132
|
+
statusIcon = 'check-circle';
|
|
143
133
|
} else if (test.status === 'flaky') {
|
|
144
|
-
statusClass = '
|
|
134
|
+
statusClass = 'warning';
|
|
135
|
+
statusIcon = 'exclamation-triangle';
|
|
145
136
|
} else if (test.status === 'failed') {
|
|
146
|
-
statusClass = '
|
|
137
|
+
statusClass = 'danger';
|
|
138
|
+
statusIcon = 'times-circle';
|
|
147
139
|
} else {
|
|
148
|
-
statusClass = '
|
|
140
|
+
statusClass = 'info';
|
|
141
|
+
statusIcon = 'question-circle';
|
|
149
142
|
}
|
|
150
|
-
|
|
151
143
|
testDetails.innerHTML = `
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
144
|
+
<div class="sticky-header">
|
|
145
|
+
<div class="card mb-3">
|
|
146
|
+
<button class="button is-primary mb-3" id="back-to-summary" onclick="showSummary()">
|
|
147
|
+
<span class="icon"><i class="fa fa-chevron-left" style="color: #63E6BE;"></i></span>
|
|
148
|
+
<span>Back to Summary</span>
|
|
149
|
+
</button>
|
|
150
|
+
<div class="card-content">
|
|
151
|
+
<div class="content has-text-centered">
|
|
152
|
+
<h1 class="title is-2">${test.title}</h1>
|
|
153
|
+
<p class="subtitle is-5" id="filepath">${test.location}</p>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
<footer class="card-footer">
|
|
157
|
+
<div class="card-footer-item">
|
|
158
|
+
<div class="columns is-mobile">
|
|
159
|
+
<div class="column is-half">
|
|
160
|
+
<div class="is-flex is-align-items-center">
|
|
161
|
+
<span class="icon status-icon has-text-${statusClass}">
|
|
162
|
+
<i class="fa fa-${statusIcon}"></i>
|
|
163
|
+
</span>
|
|
164
|
+
<span class="has-text-weight-bold is-uppercase has-text-${statusClass}">${test.status}</span>
|
|
172
165
|
</div>
|
|
173
|
-
</div>`: ""}
|
|
174
|
-
${test.testTags.length > 0 ? `
|
|
175
|
-
<div class="control mt-4">
|
|
176
|
-
<div class="tags has-addons">
|
|
177
|
-
<span class="tag is-dark">Test Tags</span>
|
|
178
|
-
<span class="tag is-info">${test.testTags.join(" ")}</span>
|
|
179
166
|
</div>
|
|
180
|
-
</div>`: ""}
|
|
181
|
-
${test.videoPath ? `
|
|
182
|
-
<div id="testVideo" class="modal">
|
|
183
|
-
<div class="modal-background"></div>
|
|
184
|
-
<div class="modal-content">
|
|
185
|
-
<figure>
|
|
186
|
-
<video controls>
|
|
187
|
-
<source src="file://${test.videoPath}" type="video/webm">
|
|
188
|
-
Your browser does not support the video tag.
|
|
189
|
-
</video>
|
|
190
|
-
</figure>
|
|
191
|
-
</div>
|
|
192
|
-
<button onclick="closeVideo()" class="modal-close is-large" aria-label="close"></button>
|
|
193
167
|
</div>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
<div class="
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
168
|
+
</div>
|
|
169
|
+
${test.duration ? `
|
|
170
|
+
<div class="card-footer-item">
|
|
171
|
+
<div class="column is-half">
|
|
172
|
+
<div class="is-flex is-align-items-center">
|
|
173
|
+
<span class="icon status-icon has-text-info">
|
|
174
|
+
<i class="fa fa-clock"></i>
|
|
175
|
+
</span>
|
|
176
|
+
<span class="has-text-info has-text-weight-semibold">${test.duration}</span>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
201
179
|
</div>
|
|
202
180
|
` : ''}
|
|
181
|
+
${test.projectName ? `
|
|
182
|
+
<div class="card-footer-item">
|
|
183
|
+
<div class="is-flex is-align-items-center">
|
|
184
|
+
<span class="icon status-icon has-text-info">
|
|
185
|
+
<i class="fa fa-window-maximize" style="color: #B197FC;"></i>
|
|
186
|
+
</span>
|
|
187
|
+
<span class="" style="color: #B197FC;"> ${test.projectName}</span>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
` : ''}
|
|
191
|
+
</footer>
|
|
203
192
|
</div>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div class="content-wrapper">
|
|
196
|
+
${test.status != "skipped" ?
|
|
197
|
+
`<div class="card mb-5">
|
|
198
|
+
<div class="card-content">
|
|
199
|
+
<div class="columns is-multiline">
|
|
200
|
+
${test.screenshots && test.screenshots.length > 0 ? `
|
|
201
|
+
<div class="column is-half">
|
|
202
|
+
<div id="testImage" class="modal">
|
|
207
203
|
<div class="modal-background"></div>
|
|
208
204
|
<div class="modal-content">
|
|
209
205
|
<p class="image">
|
|
@@ -213,10 +209,10 @@
|
|
|
213
209
|
<button onclick="closeModal()" class="modal-close is-large" aria-label="close"></button>
|
|
214
210
|
</div>
|
|
215
211
|
|
|
216
|
-
<figure class="image
|
|
212
|
+
<figure class="image">
|
|
217
213
|
<img id="screenshot-main-img" onclick="openModal()" src="${test.screenshots[0]}" alt="Screenshot">
|
|
218
214
|
</figure>
|
|
219
|
-
<nav class="pagination is-small is-centered ${test.screenshots.length > 1 ? '' : 'is-hidden'}" role="navigation" aria-label="pagination">
|
|
215
|
+
<nav class="mt-4 pagination is-small is-centered ${test.screenshots.length > 1 ? '' : 'is-hidden'}" role="navigation" aria-label="pagination">
|
|
220
216
|
<a class="pagination-previous" >Previous</a>
|
|
221
217
|
<a class="pagination-next" >Next</a>
|
|
222
218
|
<ul class="pagination-list">
|
|
@@ -226,55 +222,118 @@
|
|
|
226
222
|
</li>`).join('')}
|
|
227
223
|
</ul>
|
|
228
224
|
</nav>
|
|
229
|
-
|
|
225
|
+
</div>
|
|
226
|
+
` : ''}
|
|
227
|
+
${test.videoPath ? `
|
|
228
|
+
<div class="column is-half">
|
|
229
|
+
<div class="video-preview" onclick="openVideo()">
|
|
230
|
+
<video controls width="100%" height="auto" preload="metadata">
|
|
231
|
+
<source src="${test.videoPath}" type="video/webm">
|
|
232
|
+
Your browser does not support the video tag.
|
|
233
|
+
</video>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
` : ''}
|
|
237
|
+
</div>
|
|
238
|
+
${test.tracePath ? `
|
|
239
|
+
<div class="columns is-centered">
|
|
240
|
+
<div class="column is-3">
|
|
241
|
+
<button
|
|
242
|
+
data-trace="${test.tracePath}"
|
|
243
|
+
onclick="openTraceViewer(this)"
|
|
244
|
+
class="button is-link is-fullwidth mt-3">
|
|
245
|
+
<span class="icon"><i class="fa-solid fa-tv" style="color: #FFD43B;"></i></span>
|
|
246
|
+
<span class="has-text-white pl-2">View Trace</span>
|
|
247
|
+
</button>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
` : ''}
|
|
230
251
|
</div>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
252
|
+
</div>` : ''}
|
|
253
|
+
${test.annotations.length || test.testTags.length > 0 ? `
|
|
254
|
+
<div class="card mb-5">
|
|
255
|
+
<header class="card-header">
|
|
256
|
+
<p class="card-header-title">Additional Information</p>
|
|
257
|
+
</header>
|
|
258
|
+
<div class="card-content">
|
|
259
|
+
<div class="content">
|
|
260
|
+
${test.testTags.length > 0 ? `
|
|
261
|
+
<div class="control mb-4">
|
|
262
|
+
<div class="tags is-rounded">
|
|
263
|
+
${test.testTags.map(tag => `<span class="tag is-primary is-medium">${tag}</span>`).join('')}
|
|
264
|
+
</div>
|
|
265
|
+
</div>` : ""}
|
|
266
|
+
${test.annotations
|
|
236
267
|
.filter(annotation => annotation !== null && annotation !== undefined)
|
|
237
268
|
.map(annotation => `
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<br>
|
|
241
|
-
${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation?.description}</span>` : ''}
|
|
242
|
-
</div>
|
|
269
|
+
<div class="mb-4">
|
|
270
|
+
${annotation?.type ? `<strong class="has-text-link">Type: </strong><span>${annotation.type}</span>` : ''}
|
|
243
271
|
<br>
|
|
272
|
+
${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation.description}</span>` : ''}
|
|
273
|
+
</div>
|
|
244
274
|
`).join('')}
|
|
245
275
|
</div>
|
|
246
|
-
</
|
|
247
|
-
|
|
248
|
-
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
` : ''}
|
|
249
279
|
${test.steps.length > 0 ? `
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
<
|
|
253
|
-
</
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
280
|
+
<div class="card">
|
|
281
|
+
<header class="card-header">
|
|
282
|
+
<p class="card-header-title">Steps</p>
|
|
283
|
+
</header>
|
|
284
|
+
<div class="card-content">
|
|
285
|
+
<div class="content">
|
|
286
|
+
<span id="stepDetails" class="content"></span>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
` : ''}
|
|
257
291
|
${test.errors.length ? `
|
|
258
|
-
<
|
|
259
|
-
<
|
|
292
|
+
<div class="card mt-5">
|
|
293
|
+
<header class="card-header">
|
|
294
|
+
<p class="card-header-title">Errors</p>
|
|
295
|
+
</header>
|
|
296
|
+
<div class="card-content">
|
|
297
|
+
<div class="content">
|
|
260
298
|
<pre><code class="data-lang=js">${test.errors.join('\n')}</code></pre>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
` : ''}
|
|
264
303
|
${test.logs ? `
|
|
265
|
-
<
|
|
266
|
-
<
|
|
304
|
+
<div class="card mt-5">
|
|
305
|
+
<header class="card-header">
|
|
306
|
+
<p class="card-header-title">Logs</p>
|
|
307
|
+
</header>
|
|
308
|
+
<div class="card-content">
|
|
309
|
+
<div class="content">
|
|
267
310
|
<pre>${test.logs}</pre>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
` : ''}
|
|
315
|
+
${test.videoPath ? `
|
|
316
|
+
<div id="testVideo" class="modal">
|
|
317
|
+
<div class="modal-background"></div>
|
|
318
|
+
<div class="modal-content">
|
|
319
|
+
<div class="box">
|
|
320
|
+
<video controls style="width: 100%;">
|
|
321
|
+
<source src="${test.videoPath}" type="video/webm">
|
|
322
|
+
Your browser does not support the video tag.
|
|
323
|
+
</video>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
<button class="modal-close is-large" aria-label="close" onclick="closeVideo()"></button>
|
|
327
|
+
</div>
|
|
328
|
+
` : ''}
|
|
329
|
+
</div>`
|
|
330
|
+
if (test.screenshots.length > 0) {
|
|
272
331
|
document.querySelector('.pagination-previous').addEventListener('click', () => changeScreenshot(-1));
|
|
273
332
|
document.querySelector('.pagination-next').addEventListener('click', () => changeScreenshot(1));
|
|
274
333
|
document.querySelectorAll('.pagination-link').forEach((link, index) => {
|
|
275
334
|
link.addEventListener('click', () => gotoScreenshot(index));
|
|
276
335
|
});
|
|
277
|
-
}
|
|
336
|
+
}
|
|
278
337
|
const stepDetailsDiv = document.getElementById('stepDetails');
|
|
279
338
|
if (stepDetailsDiv) {
|
|
280
339
|
const stepsList = attachSteps(test);
|
|
@@ -282,7 +341,6 @@
|
|
|
282
341
|
stepDetailsDiv.appendChild(stepsList);
|
|
283
342
|
}
|
|
284
343
|
}
|
|
285
|
-
|
|
286
344
|
function attachSteps(test) {
|
|
287
345
|
const stepsList = document.createElement("ul");
|
|
288
346
|
stepsList.setAttribute("id", "steps");
|
|
@@ -324,12 +382,23 @@
|
|
|
324
382
|
window.openVideo = openVideo;
|
|
325
383
|
window.closeVideo = closeVideo;
|
|
326
384
|
window.closeModal = closeModal;
|
|
385
|
+
window.openTraceViewer = openTraceViewer;
|
|
327
386
|
|
|
328
387
|
document.addEventListener('keydown', (event) => {
|
|
329
388
|
if (event.key === "Escape") {
|
|
330
389
|
closeModal();
|
|
331
390
|
}
|
|
332
391
|
});
|
|
392
|
+
function openTraceViewer(button) {
|
|
393
|
+
const tracePath = button.getAttribute("data-trace");
|
|
394
|
+
try {
|
|
395
|
+
if (tracePath) {
|
|
396
|
+
const baseUrl = window.location.origin;
|
|
397
|
+
const traceViewerUrl = `${baseUrl}/trace/index.html?trace=${baseUrl}/${tracePath}`;
|
|
398
|
+
window.open(traceViewerUrl, "_blank");
|
|
399
|
+
}
|
|
400
|
+
} catch (error) { }
|
|
401
|
+
}
|
|
333
402
|
|
|
334
403
|
function attachEventListeners() {
|
|
335
404
|
const checkboxes = document.querySelectorAll('#select-filter input[type="checkbox"]');
|
|
@@ -377,7 +446,7 @@
|
|
|
377
446
|
const detailsElements = document.querySelectorAll('.sidebar details');
|
|
378
447
|
detailsElements.forEach(details => {
|
|
379
448
|
let shouldShowDetails = false;
|
|
380
|
-
const items = details.querySelectorAll('
|
|
449
|
+
const items = details.querySelectorAll('div[data-test-id]');
|
|
381
450
|
items.forEach(item => {
|
|
382
451
|
const testTags = item.getAttribute('data-test-tags').trim().split(' ').filter(Boolean);
|
|
383
452
|
const projectName = item.getAttribute('data-project-name').trim();
|
|
@@ -436,7 +505,7 @@
|
|
|
436
505
|
testItems.forEach(item => {
|
|
437
506
|
const testTitle = item.textContent.toLowerCase();
|
|
438
507
|
if (testTitle.includes(searchTerm)) {
|
|
439
|
-
item.style.display = '
|
|
508
|
+
item.style.display = 'flex'; // Show matching test item
|
|
440
509
|
|
|
441
510
|
let parent = item.parentElement;
|
|
442
511
|
while (parent && parent.tagName !== 'ASIDE') {
|
|
@@ -451,7 +520,7 @@
|
|
|
451
520
|
});
|
|
452
521
|
} else {
|
|
453
522
|
testItems.forEach(item => {
|
|
454
|
-
item.style.display = '
|
|
523
|
+
item.style.display = 'flex';
|
|
455
524
|
});
|
|
456
525
|
detailsElements.forEach(detail => {
|
|
457
526
|
detail.open = false;
|
package/dist/views/navbar.hbs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
|
|
1
|
+
<nav class="navbar is-primary is-fixed-top" role="navigation" aria-label="main navigation">
|
|
2
2
|
<div class="navbar-brand">
|
|
3
3
|
<a class="navbar-item">
|
|
4
4
|
{{#if logo}}
|
|
@@ -16,16 +16,6 @@
|
|
|
16
16
|
</div>
|
|
17
17
|
<div class="navbar-menu">
|
|
18
18
|
<div class="navbar-end">
|
|
19
|
-
<div class="navbar-item">
|
|
20
|
-
<span id="selected-filters"></span>
|
|
21
|
-
</div>
|
|
22
|
-
<div class="navbar-item">
|
|
23
|
-
<div class="field">
|
|
24
|
-
<p class="control">
|
|
25
|
-
<input class="input" name="search" type="search" placeholder="Search by test">
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
19
|
<div class="navbar-item">
|
|
30
20
|
<button id="toggle-theme" class="" data-theme-status="{{preferredTheme}}">
|
|
31
21
|
<span class="icon">
|