ortoni-report 2.0.5 → 2.0.7
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 +30 -0
- package/dist/ortoni-report.d.ts +16 -4
- package/dist/ortoni-report.js +428 -240
- package/dist/ortoni-report.mjs +428 -240
- package/dist/style/main.css +92 -128
- package/dist/views/head.hbs +11 -0
- package/dist/views/main.hbs +482 -440
- package/dist/views/navbar.hbs +2 -12
- package/dist/views/project.hbs +201 -95
- package/dist/views/testIcons.hbs +13 -0
- package/dist/views/testPanel.hbs +16 -7
- package/dist/views/testStatus.hbs +8 -32
- package/package.json +4 -1
- package/readme.md +41 -22
- package/dist/types/reporterConfig.js +0 -2
- package/dist/types/testResults.js +0 -2
- package/dist/utils/expressServer.js +0 -33
- package/dist/utils/utils.js +0 -85
- package/dist/utils/webSocketHelper.js +0 -93
package/dist/views/main.hbs
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en" data-theme="{{preferredTheme}}">
|
|
3
|
-
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta name="description" content="Playwright HTML report by LetCode Koushik - V2.0.5">
|
|
7
|
-
<title>{{title}}</title>
|
|
8
|
-
<link rel="icon" href="https://raw.githubusercontent.com/ortoniKC/ortoni-report/refs/heads/main/favicon.png"
|
|
9
|
-
type="image/x-icon">
|
|
10
|
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
|
|
11
|
-
integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg=="
|
|
12
|
-
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
13
|
-
</head>
|
|
3
|
+
{{> head}}
|
|
14
4
|
<style>
|
|
15
5
|
{{{inlineCss}}}
|
|
16
6
|
</style>
|
|
17
7
|
<body>
|
|
18
8
|
{{> navbar }}
|
|
19
|
-
<section class="section">
|
|
9
|
+
<section class="section mt-6">
|
|
20
10
|
<main class="container">
|
|
21
11
|
<div class="columns">
|
|
22
12
|
<aside class="column is-two-fifths">
|
|
@@ -44,492 +34,544 @@
|
|
|
44
34
|
</section>
|
|
45
35
|
<script>
|
|
46
36
|
document.addEventListener('DOMContentLoaded', () => {
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
const testData = {{{ json results }}};
|
|
38
|
+
const testHistory = {{{ json testHistories }}};
|
|
39
|
+
let testHistoriesMap = {};
|
|
40
|
+
let testHistoryTitle = '';
|
|
51
41
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
const elements = {
|
|
43
|
+
testDetails: document.getElementById('testDetails'),
|
|
44
|
+
summary: document.getElementById('summary'),
|
|
45
|
+
themeButton: document.getElementById("toggle-theme"),
|
|
46
|
+
themeIcon: document.getElementById("theme-icon"),
|
|
47
|
+
htmlElement: document.documentElement,
|
|
48
|
+
searchInput: document.querySelector('input[name="search"]'),
|
|
49
|
+
detailsElements: document.querySelectorAll('details'),
|
|
50
|
+
filtersDisplay: document.getElementById('selected-filters')
|
|
51
|
+
};
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
const themeManager = {
|
|
54
|
+
init() {
|
|
55
|
+
const preferredTheme = elements.themeButton.getAttribute("data-theme-status");
|
|
56
|
+
this.setTheme(preferredTheme);
|
|
57
|
+
elements.themeButton.addEventListener('click', () => this.toggleTheme());
|
|
58
|
+
},
|
|
59
|
+
setTheme(theme) {
|
|
60
|
+
elements.htmlElement.setAttribute('data-theme', theme);
|
|
61
|
+
elements.themeIcon.className = `fa fa-${theme === 'dark' ? 'moon' : 'sun'}`;
|
|
62
|
+
},
|
|
63
|
+
toggleTheme() {
|
|
64
|
+
const currentTheme = elements.htmlElement.getAttribute('data-theme');
|
|
65
|
+
this.setTheme(currentTheme === 'light' ? 'dark' : 'light');
|
|
68
66
|
}
|
|
69
|
-
|
|
70
|
-
themeIcon.classList.add("fa", "fa-moon");
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
themeButton.addEventListener('click', () => {
|
|
74
|
-
const currentTheme = htmlElement.getAttribute('data-theme');
|
|
75
|
-
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
76
|
-
htmlElement.setAttribute('data-theme', newTheme);
|
|
77
|
-
if (newTheme === 'dark') {
|
|
78
|
-
themeIcon.classList = '';
|
|
79
|
-
themeIcon.classList.add("fa", "fa-moon");
|
|
80
|
-
} else {
|
|
81
|
-
themeIcon.classList = '';
|
|
82
|
-
themeIcon.classList.add("fa", "fa-sun");
|
|
83
|
-
}
|
|
84
|
-
});
|
|
67
|
+
};
|
|
85
68
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
updateScreenshot();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function gotoScreenshot(index) {
|
|
109
|
-
currentScreenshotIndex = index;
|
|
110
|
-
updateScreenshot();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function updateScreenshot() {
|
|
114
|
-
const screenshots = test.screenshots;
|
|
115
|
-
document.getElementById('screenshot-main-img').src = screenshots[currentScreenshotIndex];
|
|
116
|
-
document.getElementById('screenshot-modal-img').src = screenshots[currentScreenshotIndex];
|
|
117
|
-
|
|
118
|
-
document.querySelectorAll('.pagination-link').forEach((link, index) => {
|
|
119
|
-
if (index === currentScreenshotIndex) {
|
|
120
|
-
link.classList.add('is-current');
|
|
121
|
-
} else {
|
|
122
|
-
link.classList.remove('is-current');
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
let statusClass = '';
|
|
127
|
-
let statusIcon = '';
|
|
128
|
-
let statusText = test.status.toUpperCase();
|
|
69
|
+
const testDetailsManager = {
|
|
70
|
+
show(test) {
|
|
71
|
+
elements.summary.style.display = 'none';
|
|
72
|
+
elements.testDetails.style.opacity = '0';
|
|
73
|
+
elements.testDetails.style.display = 'block';
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
elements.testDetails.style.opacity = '1';
|
|
76
|
+
}, 50);
|
|
77
|
+
this.render(test);
|
|
78
|
+
},
|
|
79
|
+
hide() {
|
|
80
|
+
elements.summary.style.display = 'block';
|
|
81
|
+
elements.testDetails.style.display = 'none';
|
|
82
|
+
},
|
|
83
|
+
render(test) {
|
|
84
|
+
let currentScreenshotIndex = 0;
|
|
85
|
+
const statusClass = this.getStatusClass(test.status);
|
|
86
|
+
const statusIcon = this.getStatusIcon(test.status);
|
|
87
|
+
const projectIcon = this.getProjectIcon(test.projectName);
|
|
129
88
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
testDetails.innerHTML = `
|
|
144
|
-
<div class="sticky-header">
|
|
145
|
-
<div class="card mb-3">
|
|
146
|
-
<button class="button is-info is-light 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>
|
|
89
|
+
elements.testDetails.innerHTML = `
|
|
90
|
+
<div class="sticky-header">
|
|
91
|
+
<div class="card mb-3">
|
|
92
|
+
<button class="button is-primary mb-3" id="back-to-summary" onclick="showSummary()">
|
|
93
|
+
<span class="icon"><i class="fa fa-chevron-left"></i></span>
|
|
94
|
+
<span>Back to Summary</span>
|
|
95
|
+
</button>
|
|
96
|
+
<div class="card-content">
|
|
97
|
+
<div class="content has-text-centered">
|
|
98
|
+
<h1 class="title is-2">${test.title}</h1>
|
|
99
|
+
<p class="subtitle is-5" id="filepath">${test.location}</p>
|
|
100
|
+
</div>
|
|
165
101
|
</div>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
102
|
+
<footer class="card-footer">
|
|
103
|
+
<div class="card-footer-item">
|
|
104
|
+
<div class="columns is-mobile">
|
|
105
|
+
<div class="column is-half">
|
|
106
|
+
<div class="is-flex is-align-items-center">
|
|
107
|
+
<span class="icon status-icon has-text-${statusClass}">
|
|
108
|
+
<i class="fa fa-${statusIcon}"></i>
|
|
109
|
+
</span>
|
|
110
|
+
<span class="has-text-weight-bold is-uppercase has-text-${statusClass}">${test.status}</span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
${test.duration ? `
|
|
116
|
+
<div class="card-footer-item">
|
|
117
|
+
<div class="column is-half">
|
|
118
|
+
<div class="is-flex is-align-items-center">
|
|
119
|
+
<span class="icon status-icon has-text-info">
|
|
120
|
+
<i class="fa fa-clock"></i>
|
|
121
|
+
</span>
|
|
122
|
+
<span class="has-text-info has-text-weight-semibold">${test.duration}</span>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
` : ''}
|
|
127
|
+
${test.projectName ? `
|
|
128
|
+
<div class="card-footer-item">
|
|
129
|
+
<div class="is-flex is-align-items-center">
|
|
130
|
+
<span class="icon status-icon has-text-link">
|
|
131
|
+
${projectIcon}
|
|
132
|
+
</span>
|
|
133
|
+
<span> ${test.projectName}</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
` : ''}
|
|
137
|
+
</footer>
|
|
189
138
|
</div>
|
|
190
|
-
` : ''}
|
|
191
|
-
</footer>
|
|
192
139
|
</div>
|
|
193
|
-
|
|
140
|
+
<div class="content-wrapper">
|
|
141
|
+
${this.renderTestContent(test)}
|
|
142
|
+
</div>
|
|
143
|
+
`;
|
|
194
144
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
145
|
+
this.attachScreenshotListeners(test);
|
|
146
|
+
this.attachSteps(test);
|
|
147
|
+
},
|
|
148
|
+
getStatusClass(status) {
|
|
149
|
+
if (status.startsWith('passed')) return 'success';
|
|
150
|
+
if (status === 'flaky') return 'warning';
|
|
151
|
+
if (status === 'failed') return 'danger';
|
|
152
|
+
return 'info';
|
|
153
|
+
},
|
|
154
|
+
getStatusIcon(status) {
|
|
155
|
+
if (status.startsWith('passed')) return 'check-circle';
|
|
156
|
+
if (status === 'flaky') return 'exclamation-triangle';
|
|
157
|
+
if (status === 'failed') return 'times-circle';
|
|
158
|
+
return 'question-circle';
|
|
159
|
+
},
|
|
160
|
+
getProjectIcon(project) {
|
|
161
|
+
if (project === 'webkit') return `<i class="fa-brands fa-safari"></i>`;
|
|
162
|
+
if (project === 'firefox') return `<i class="fa-brands fa-firefox"></i>`;
|
|
163
|
+
return `<i class="fa-brands fa-chrome"></i>`;
|
|
164
|
+
},
|
|
165
|
+
renderTestContent(test) {
|
|
166
|
+
let content = '';
|
|
167
|
+
if (test.status !== "skipped") {
|
|
168
|
+
content += this.renderScreenshotsAndVideo(test);
|
|
169
|
+
}
|
|
170
|
+
content += this.renderAdditionalInfo(test);
|
|
171
|
+
content += this.renderSteps(test);
|
|
172
|
+
content += this.renderErrors(test);
|
|
173
|
+
content += this.renderLogs(test);
|
|
174
|
+
return content;
|
|
175
|
+
},
|
|
176
|
+
renderScreenshotsAndVideo(test) {
|
|
177
|
+
let content = '<div class="card mb-5"><div class="card-content"><div class="columns is-multiline">';
|
|
178
|
+
if (test.screenshots && test.screenshots.length > 0) {
|
|
179
|
+
content += `
|
|
201
180
|
<div class="column is-half">
|
|
202
181
|
<div id="testImage" class="modal">
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
182
|
+
<div class="modal-background"></div>
|
|
183
|
+
<div class="modal-content">
|
|
184
|
+
<p class="image">
|
|
185
|
+
<img id="screenshot-modal-img" src="${test.screenshots[0]}" alt="Screenshot">
|
|
186
|
+
</p>
|
|
187
|
+
</div>
|
|
188
|
+
<button onclick="closeModal()" class="modal-close is-large" aria-label="close"></button>
|
|
208
189
|
</div>
|
|
209
|
-
<
|
|
190
|
+
<figure class="image">
|
|
191
|
+
<img id="screenshot-main-img" onclick="openModal()" src="${test.screenshots[0]}" alt="Screenshot">
|
|
192
|
+
</figure>
|
|
193
|
+
<nav class="mt-4 pagination is-small is-centered ${test.screenshots.length > 1 ? '' : 'is-hidden'}" role="navigation" aria-label="pagination">
|
|
194
|
+
<a class="pagination-previous">Previous</a>
|
|
195
|
+
<a class="pagination-next">Next</a>
|
|
196
|
+
<ul class="pagination-list">
|
|
197
|
+
${test.screenshots.map((_, index) => `
|
|
198
|
+
<li>
|
|
199
|
+
<a class="pagination-link ${index === 0 ? 'is-current' : ''}" aria-label="Goto screenshot ${index + 1}">${index + 1}</a>
|
|
200
|
+
</li>`).join('')}
|
|
201
|
+
</ul>
|
|
202
|
+
</nav>
|
|
210
203
|
</div>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<nav class="mt-4 pagination is-small is-centered ${test.screenshots.length > 1 ? '' : 'is-hidden'}" role="navigation" aria-label="pagination">
|
|
216
|
-
<a class="pagination-previous" >Previous</a>
|
|
217
|
-
<a class="pagination-next" >Next</a>
|
|
218
|
-
<ul class="pagination-list">
|
|
219
|
-
${test.screenshots.map((_, index) => `
|
|
220
|
-
<li>
|
|
221
|
-
<a class="pagination-link ${index === 0 ? 'is-current' : ''}" aria-label="Goto screenshot ${index + 1}" >${index + 1}</a>
|
|
222
|
-
</li>`).join('')}
|
|
223
|
-
</ul>
|
|
224
|
-
</nav>
|
|
225
|
-
</div>
|
|
226
|
-
` : ''}
|
|
227
|
-
${test.videoPath ? `
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
if (test.videoPath) {
|
|
207
|
+
content += `
|
|
228
208
|
<div class="column is-half">
|
|
229
|
-
<div class="video-preview"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
209
|
+
<div class="video-preview">
|
|
210
|
+
<video controls width="100%" height="auto" preload="metadata">
|
|
211
|
+
<source src="${test.videoPath}" type="video/webm">
|
|
212
|
+
Your browser does not support the video tag.
|
|
213
|
+
</video>
|
|
234
214
|
</div>
|
|
235
215
|
</div>
|
|
236
|
-
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
content += '</div>';
|
|
219
|
+
content += `
|
|
220
|
+
<div class="columns">
|
|
221
|
+
<div class="column">
|
|
222
|
+
<button
|
|
223
|
+
onclick="openHistory()"
|
|
224
|
+
class="button is-primary is-fullwidth mt-3">
|
|
225
|
+
<span class="icon"><i class="fa-solid fa-timeline"></i></span>
|
|
226
|
+
<span class="has-text-white pl-2">Open history</span>
|
|
227
|
+
</button>
|
|
228
|
+
<div id="historyModal" class="modal">
|
|
229
|
+
<div class="modal-background"></div>
|
|
230
|
+
</div>
|
|
237
231
|
</div>
|
|
238
232
|
${test.tracePath ? `
|
|
239
|
-
<div class="
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
233
|
+
<div class="column">
|
|
234
|
+
<button
|
|
235
|
+
data-trace="${test.tracePath}"
|
|
236
|
+
onclick="openTraceViewer(this)"
|
|
237
|
+
class="button is-primary is-fullwidth mt-3">
|
|
238
|
+
<span class="icon"><i class="fa-solid fa-tv"></i></span>
|
|
243
239
|
<span class="has-text-white pl-2">View Trace</span>
|
|
244
|
-
|
|
245
|
-
</div>
|
|
240
|
+
</button>
|
|
246
241
|
</div>
|
|
247
242
|
` : ''}
|
|
248
243
|
</div>
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
`;
|
|
245
|
+
|
|
246
|
+
content += '</div></div>';
|
|
247
|
+
return content;
|
|
248
|
+
},
|
|
249
|
+
renderAdditionalInfo(test) {
|
|
250
|
+
if (!(test.annotations.length || test.testTags.length > 0)) return '';
|
|
251
|
+
return `
|
|
251
252
|
<div class="card mb-5">
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
253
|
+
<header class="card-header">
|
|
254
|
+
<p class="card-header-title">Additional Information</p>
|
|
255
|
+
</header>
|
|
256
|
+
<div class="card-content">
|
|
257
|
+
<div class="content">
|
|
258
|
+
${test.testTags.length > 0 ? `
|
|
259
|
+
<div class="control mb-4">
|
|
260
|
+
<div class="tags is-rounded">
|
|
261
|
+
${test.testTags.map(tag => `<span class="tag is-primary is-medium">${tag}</span>`).join('')}
|
|
262
|
+
</div>
|
|
263
|
+
</div>` : ""}
|
|
264
|
+
${test.annotations
|
|
265
|
+
.filter(annotation => annotation !== null && annotation !== undefined)
|
|
266
|
+
.map(annotation => `
|
|
267
|
+
<div class="mb-4">
|
|
268
|
+
${annotation?.type ? `<strong class="has-text-link">Type: </strong><span>${annotation.type}</span>` : ''}
|
|
269
|
+
<br>
|
|
270
|
+
${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation.description}</span>` : ''}
|
|
271
|
+
</div>
|
|
272
|
+
`).join('')}
|
|
261
273
|
</div>
|
|
262
|
-
</div>` : ""}
|
|
263
|
-
${test.annotations
|
|
264
|
-
.filter(annotation => annotation !== null && annotation !== undefined)
|
|
265
|
-
.map(annotation => `
|
|
266
|
-
<div class="mb-4">
|
|
267
|
-
${annotation?.type ? `<strong class="has-text-link">Type: </strong><span>${annotation.type}</span>` : ''}
|
|
268
|
-
<br>
|
|
269
|
-
${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation.description}</span>` : ''}
|
|
270
|
-
</div>
|
|
271
|
-
`).join('')}
|
|
272
274
|
</div>
|
|
273
275
|
</div>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
276
|
+
`;
|
|
277
|
+
},
|
|
278
|
+
renderSteps(test) {
|
|
279
|
+
if (test.steps.length === 0) return '';
|
|
280
|
+
return `
|
|
277
281
|
<div class="card">
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
<header class="card-header">
|
|
283
|
+
<p class="card-header-title">Steps</p>
|
|
284
|
+
</header>
|
|
285
|
+
<div class="card-content">
|
|
286
|
+
<div class="content">
|
|
287
|
+
<span id="stepDetails" class="content"></span>
|
|
288
|
+
</div>
|
|
284
289
|
</div>
|
|
285
290
|
</div>
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
`;
|
|
292
|
+
},
|
|
293
|
+
renderErrors(test) {
|
|
294
|
+
if (!test.errors.length) return '';
|
|
295
|
+
return `
|
|
289
296
|
<div class="card mt-5">
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
297
|
+
<header class="card-header">
|
|
298
|
+
<p class="card-header-title">Errors</p>
|
|
299
|
+
</header>
|
|
300
|
+
<div class="card-content">
|
|
301
|
+
<div class="content">
|
|
302
|
+
<pre><code class="data-lang=js">${test.errors.join('\n')}</code></pre>
|
|
303
|
+
</div>
|
|
296
304
|
</div>
|
|
297
305
|
</div>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
306
|
+
`;
|
|
307
|
+
},
|
|
308
|
+
renderLogs(test) {
|
|
309
|
+
if (!test.logs) return '';
|
|
310
|
+
return `
|
|
301
311
|
<div class="card mt-5">
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
311
|
-
` : ''}
|
|
312
|
-
${test.videoPath ? `
|
|
313
|
-
<div id="testVideo" class="modal">
|
|
314
|
-
<div class="modal-background"></div>
|
|
315
|
-
<div class="modal-content">
|
|
316
|
-
<div class="box">
|
|
317
|
-
<video controls style="width: 100%;">
|
|
318
|
-
<source src="${test.videoPath}" type="video/webm">
|
|
319
|
-
Your browser does not support the video tag.
|
|
320
|
-
</video>
|
|
321
|
-
</div>
|
|
312
|
+
<header class="card-header">
|
|
313
|
+
<p class="card-header-title">Logs</p>
|
|
314
|
+
</header>
|
|
315
|
+
<div class="card-content">
|
|
316
|
+
<div class="content">
|
|
317
|
+
<pre>${test.logs}</pre>
|
|
318
|
+
</div>
|
|
322
319
|
</div>
|
|
323
|
-
<button class="modal-close is-large" aria-label="close" onclick="closeVideo()"></button>
|
|
324
320
|
</div>
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
321
|
+
`;
|
|
322
|
+
},
|
|
323
|
+
attachScreenshotListeners(test) {
|
|
324
|
+
if (test.screenshots && test.screenshots.length > 0) {
|
|
325
|
+
let currentScreenshotIndex = 0;
|
|
326
|
+
const changeScreenshot = (direction) => {
|
|
327
|
+
currentScreenshotIndex = (currentScreenshotIndex + direction + test.screenshots.length) % test.screenshots.length;
|
|
328
|
+
this.updateScreenshot(test.screenshots, currentScreenshotIndex);
|
|
329
|
+
};
|
|
330
|
+
const gotoScreenshot = (index) => {
|
|
331
|
+
currentScreenshotIndex = index;
|
|
332
|
+
this.updateScreenshot(test.screenshots, currentScreenshotIndex);
|
|
333
|
+
};
|
|
334
|
+
document.querySelector('.pagination-previous').addEventListener('click', () => changeScreenshot(-1));
|
|
335
|
+
document.querySelector('.pagination-next').addEventListener('click', () => changeScreenshot(1));
|
|
336
|
+
document.querySelectorAll('.pagination-link').forEach((link, index) => {
|
|
337
|
+
link.addEventListener('click', () => gotoScreenshot(index));
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
updateScreenshot(screenshots, index) {
|
|
342
|
+
document.getElementById('screenshot-main-img').src = screenshots[index];
|
|
343
|
+
document.getElementById('screenshot-modal-img').src = screenshots[index];
|
|
344
|
+
document.querySelectorAll('.pagination-link').forEach((link, i) => {
|
|
345
|
+
link.classList.toggle('is-current', i === index);
|
|
332
346
|
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
pre.appendChild(code);
|
|
355
|
-
li.appendChild(pre);
|
|
347
|
+
},
|
|
348
|
+
attachSteps(test) {
|
|
349
|
+
const stepDetailsDiv = document.getElementById('stepDetails');
|
|
350
|
+
if (stepDetailsDiv) {
|
|
351
|
+
const stepsList = document.createElement("ul");
|
|
352
|
+
stepsList.setAttribute("id", "steps");
|
|
353
|
+
test.steps.forEach(step => {
|
|
354
|
+
const li = document.createElement('li');
|
|
355
|
+
li.innerHTML = `<strong class="${step.snippet ? 'has-text-danger' : ''}">${step.title}</strong>`;
|
|
356
|
+
if (step.snippet) {
|
|
357
|
+
const pre = document.createElement('pre');
|
|
358
|
+
const code = document.createElement('code');
|
|
359
|
+
const locationText = step.location ? `\n\nat: ${step.location}` : '';
|
|
360
|
+
code.innerHTML = `${step.snippet}${locationText}`;
|
|
361
|
+
code.setAttribute('data-lang', 'js');
|
|
362
|
+
pre.appendChild(code);
|
|
363
|
+
li.appendChild(pre);
|
|
364
|
+
}
|
|
365
|
+
stepsList.appendChild(li);
|
|
366
|
+
});
|
|
367
|
+
stepDetailsDiv.appendChild(stepsList);
|
|
356
368
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
function closeModal() {
|
|
374
|
-
let modal = document.querySelector("#testImage");
|
|
375
|
-
modal.classList.remove("is-active");
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
window.openModal = openModal;
|
|
379
|
-
window.openVideo = openVideo;
|
|
380
|
-
window.closeVideo = closeVideo;
|
|
381
|
-
window.closeModal = closeModal;
|
|
382
|
-
|
|
383
|
-
document.addEventListener('keydown', (event) => {
|
|
384
|
-
if (event.key === "Escape") {
|
|
385
|
-
closeModal();
|
|
369
|
+
},
|
|
370
|
+
attachEventListeners() {
|
|
371
|
+
const testItems = document.querySelectorAll('[data-test-id]');
|
|
372
|
+
testItems.forEach(item => {
|
|
373
|
+
item.addEventListener('click', () => {
|
|
374
|
+
testItems.forEach(i => i.classList.remove('listselected'));
|
|
375
|
+
item.classList.add('listselected');
|
|
376
|
+
const testId = item.getAttribute('data-test-id');
|
|
377
|
+
const testHistoryId = item.getAttribute('data-test-history-id');
|
|
378
|
+
const test = testData[testId];
|
|
379
|
+
const historyEntry = testHistory.find(entry => entry.testId === testHistoryId);
|
|
380
|
+
testHistoriesMap = historyEntry ? historyEntry.history : null;
|
|
381
|
+
testHistoryTitle = historyEntry.testId ? historyEntry.testId.split(":")[2] : '';
|
|
382
|
+
this.show(test);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
386
385
|
}
|
|
387
|
-
}
|
|
386
|
+
};
|
|
388
387
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
testItems.forEach(i => i.classList.remove('listselected'));
|
|
398
|
-
item.classList.add('listselected');
|
|
399
|
-
const testId = item.getAttribute('data-test-id');
|
|
400
|
-
const test = testData[testId];
|
|
401
|
-
displayTestDetails(test);
|
|
388
|
+
const filterManager = {
|
|
389
|
+
init() {
|
|
390
|
+
this.attachEventListeners();
|
|
391
|
+
},
|
|
392
|
+
attachEventListeners() {
|
|
393
|
+
const checkboxes = document.querySelectorAll('#select-filter input[type="checkbox"]');
|
|
394
|
+
checkboxes.forEach(checkbox => {
|
|
395
|
+
checkbox.addEventListener('change', () => this.applyFilters());
|
|
402
396
|
});
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
f.classList.remove('active');
|
|
411
|
-
}
|
|
397
|
+
|
|
398
|
+
const filters = document.querySelectorAll('.filter');
|
|
399
|
+
filters.forEach(filter => {
|
|
400
|
+
filter.addEventListener('click', () => {
|
|
401
|
+
filters.forEach(f => f.classList.remove('active'));
|
|
402
|
+
filter.classList.add('active');
|
|
403
|
+
this.applyFilters();
|
|
412
404
|
});
|
|
413
|
-
filter.classList.add('active');
|
|
414
|
-
applyFilters();
|
|
415
405
|
});
|
|
416
|
-
}
|
|
417
|
-
|
|
406
|
+
},
|
|
407
|
+
applyFilters() {
|
|
408
|
+
const selectedProjects = this.getSelectedValues('project');
|
|
409
|
+
const selectedTags = this.getSelectedValues('test-tags');
|
|
410
|
+
const selectedStatus = document.querySelector('.filter.active')?.getAttribute('data-status') || 'all';
|
|
418
411
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
});
|
|
432
|
-
const detailsElements = document.querySelectorAll('.sidebar details');
|
|
433
|
-
detailsElements.forEach(details => {
|
|
434
|
-
let shouldShowDetails = false;
|
|
435
|
-
const items = details.querySelectorAll('li[data-test-id]');
|
|
436
|
-
items.forEach(item => {
|
|
437
|
-
const testTags = item.getAttribute('data-test-tags').trim().split(' ').filter(Boolean);
|
|
438
|
-
const projectName = item.getAttribute('data-project-name').trim();
|
|
439
|
-
const testStatus = item.getAttribute('data-test-status').trim();
|
|
440
|
-
const matchesProject = selectedProjects.length === 0 || selectedProjects.includes(projectName);
|
|
441
|
-
const matchesTags = selectedTags.length === 0 || selectedTags.every(tag => testTags.includes(tag));
|
|
442
|
-
const matchesStatus = (selectedStatus === 'all' && testStatus !== 'skipped') ||
|
|
443
|
-
(selectedStatus !== 'all' && (
|
|
444
|
-
testStatus === selectedStatus ||
|
|
445
|
-
(selectedStatus === 'failed' && (testStatus === 'failed' || testStatus === 'timedOut')) ||
|
|
446
|
-
(selectedStatus === 'retry' && testStatus.includes('retry')) ||
|
|
447
|
-
(selectedStatus === 'flaky' && testStatus.includes('flaky'))
|
|
448
|
-
));
|
|
449
|
-
if (matchesProject && matchesTags && matchesStatus) {
|
|
450
|
-
item.classList.remove('is-hidden');
|
|
451
|
-
shouldShowDetails = true;
|
|
452
|
-
} else {
|
|
453
|
-
item.classList.add('is-hidden');
|
|
454
|
-
}
|
|
412
|
+
elements.detailsElements.forEach(details => {
|
|
413
|
+
const items = details.querySelectorAll('div[data-test-id]');
|
|
414
|
+
let shouldShowDetails = false;
|
|
415
|
+
|
|
416
|
+
items.forEach(item => {
|
|
417
|
+
const isVisible = this.shouldShowItem(item, selectedProjects, selectedTags, selectedStatus);
|
|
418
|
+
item.classList.toggle('is-hidden', !isVisible);
|
|
419
|
+
shouldShowDetails = shouldShowDetails || isVisible;
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
details.open = shouldShowDetails;
|
|
423
|
+
details.classList.toggle('is-hidden', !shouldShowDetails);
|
|
455
424
|
});
|
|
456
|
-
details.open = shouldShowDetails;
|
|
457
|
-
details.classList.toggle('is-hidden', !shouldShowDetails);
|
|
458
|
-
});
|
|
459
|
-
updateSelectedFiltersDisplay(selectedProjects, selectedTags, selectedStatus);
|
|
460
|
-
}
|
|
461
425
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
426
|
+
this.updateSelectedFiltersDisplay(selectedProjects, selectedTags, selectedStatus);
|
|
427
|
+
},
|
|
428
|
+
getSelectedValues(type) {
|
|
429
|
+
return Array.from(document.querySelectorAll(`#select-filter input[type="checkbox"][data-filter-type="${type}"]:checked`))
|
|
430
|
+
.map(checkbox => checkbox.value.trim());
|
|
431
|
+
},
|
|
432
|
+
shouldShowItem(item, projects, tags, status) {
|
|
433
|
+
const testTags = item.getAttribute('data-test-tags').trim().split(' ').filter(Boolean);
|
|
434
|
+
const projectName = item.getAttribute('data-project-name').trim();
|
|
435
|
+
const testStatus = item.getAttribute('data-test-status').trim();
|
|
465
436
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (tags.length > 0) {
|
|
470
|
-
filtersDisplay.innerHTML += `<span> Tags: ${tags.join(', ')}</span>`;
|
|
471
|
-
}
|
|
472
|
-
if (status !== 'all') {
|
|
473
|
-
filtersDisplay.innerHTML += `<span> Status: ${status}</span>`;
|
|
474
|
-
}
|
|
437
|
+
const matchesProject = projects.length === 0 || projects.includes(projectName);
|
|
438
|
+
const matchesTags = tags.length === 0 || tags.every(tag => testTags.includes(tag));
|
|
439
|
+
const matchesStatus = this.matchesStatus(testStatus, status);
|
|
475
440
|
|
|
476
|
-
|
|
477
|
-
|
|
441
|
+
return matchesProject && matchesTags && matchesStatus;
|
|
442
|
+
},
|
|
443
|
+
matchesStatus(testStatus, selectedStatus) {
|
|
444
|
+
if (selectedStatus === 'all') return testStatus !== 'skipped';
|
|
445
|
+
if (selectedStatus === 'failed') return testStatus === 'failed' || testStatus === 'timedOut';
|
|
446
|
+
if (selectedStatus === 'retry') return testStatus.includes('retry');
|
|
447
|
+
if (selectedStatus === 'flaky') return testStatus.includes('flaky');
|
|
448
|
+
return testStatus === selectedStatus;
|
|
449
|
+
},
|
|
450
|
+
updateSelectedFiltersDisplay(projects, tags, status) {
|
|
451
|
+
let displayText = [];
|
|
452
|
+
if (projects.length > 0) displayText.push(`Projects: ${projects.join(', ')}`);
|
|
453
|
+
if (tags.length > 0) displayText.push(`Tags: ${tags.join(', ')}`);
|
|
454
|
+
if (status !== 'all') displayText.push(`Status: ${status}`);
|
|
455
|
+
elements.filtersDisplay.innerHTML = displayText.length > 0 ? displayText.join(' | ') : 'All Tests';
|
|
478
456
|
}
|
|
479
|
-
}
|
|
480
|
-
const searchInput = document.querySelector('input[name="search"]');
|
|
481
|
-
const detailsElements = document.querySelectorAll('details');
|
|
482
|
-
function filterTests(search) {
|
|
483
|
-
const searchTerm = search.toLowerCase();
|
|
484
|
-
const testItems = document.querySelectorAll('[data-test-id]');
|
|
457
|
+
};
|
|
485
458
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
459
|
+
const searchManager = {
|
|
460
|
+
init() {
|
|
461
|
+
elements.searchInput.addEventListener('input', this.debounce(this.filterTests, 300));
|
|
462
|
+
},
|
|
463
|
+
filterTests(event) {
|
|
464
|
+
const searchTerm = event.target.value.toLowerCase();
|
|
465
|
+
const testItems = document.querySelectorAll('[data-test-id]');
|
|
490
466
|
|
|
491
|
-
|
|
492
|
-
const testTitle = item.textContent.toLowerCase();
|
|
493
|
-
if (testTitle.includes(searchTerm)) {
|
|
494
|
-
item.style.display = 'block'; // Show matching test item
|
|
467
|
+
elements.detailsElements.forEach(detail => detail.open = !!searchTerm);
|
|
495
468
|
|
|
496
|
-
let parent = item.parentElement;
|
|
497
|
-
while (parent && parent.tagName !== 'ASIDE') {
|
|
498
|
-
if (parent.tagName === 'DETAILS') {
|
|
499
|
-
parent.open = true;
|
|
500
|
-
}
|
|
501
|
-
parent = parent.parentElement;
|
|
502
|
-
}
|
|
503
|
-
} else {
|
|
504
|
-
item.style.display = 'none';
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
} else {
|
|
508
469
|
testItems.forEach(item => {
|
|
509
|
-
item.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
detail.open = false;
|
|
470
|
+
const isVisible = item.textContent.toLowerCase().includes(searchTerm);
|
|
471
|
+
item.style.display = isVisible ? 'flex' : 'none';
|
|
472
|
+
if (isVisible && searchTerm) searchManager.openParentDetails(item);
|
|
513
473
|
});
|
|
474
|
+
},
|
|
475
|
+
openParentDetails(item) {
|
|
476
|
+
let parent = item.parentElement;
|
|
477
|
+
while (parent && parent.tagName !== 'ASIDE') {
|
|
478
|
+
if (parent.tagName === 'DETAILS') parent.open = true;
|
|
479
|
+
parent = parent.parentElement;
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
debounce(func, wait) {
|
|
483
|
+
let timeout;
|
|
484
|
+
return function(...args) {
|
|
485
|
+
clearTimeout(timeout);
|
|
486
|
+
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
487
|
+
};
|
|
514
488
|
}
|
|
515
|
-
}
|
|
516
|
-
function debounce(func, wait) {
|
|
517
|
-
let timeout;
|
|
518
|
-
return function (...args) {
|
|
519
|
-
clearTimeout(timeout);
|
|
520
|
-
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
521
|
-
};
|
|
522
|
-
}
|
|
489
|
+
};
|
|
523
490
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
491
|
+
// Initialize all managers
|
|
492
|
+
themeManager.init();
|
|
493
|
+
testDetailsManager.attachEventListeners();
|
|
494
|
+
filterManager.init();
|
|
495
|
+
searchManager.init();
|
|
527
496
|
|
|
528
|
-
|
|
497
|
+
// Expose necessary functions to the global scope
|
|
498
|
+
window.showSummary = testDetailsManager.hide;
|
|
499
|
+
window.openModal = () => document.querySelector("#testImage").classList.add("is-active");
|
|
500
|
+
window.closeModal = () => document.querySelector("#testImage").classList.remove("is-active");
|
|
501
|
+
window.closeErrorModal = (modalId) => document.getElementById(modalId).classList.remove("is-active");
|
|
502
|
+
window.openTraceViewer = (button) => {
|
|
503
|
+
const tracePath = button.getAttribute("data-trace");
|
|
504
|
+
if (tracePath) {
|
|
505
|
+
const normalizedTracePath = tracePath.replace(/\\/g, '/');
|
|
506
|
+
const baseUrl = getAdjustedBaseUrl();
|
|
507
|
+
window.open(`${baseUrl}/trace/index.html?trace=${baseUrl}/${normalizedTracePath}`, "_blank");
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
window.getAdjustedBaseUrl = () => {
|
|
511
|
+
const origin = window.location.origin;
|
|
512
|
+
const pathname = window.location.pathname;
|
|
513
|
+
if (pathname.endsWith('.html')) {
|
|
514
|
+
const directoryPath = pathname.substring(0, pathname.lastIndexOf('/') + 1);
|
|
515
|
+
return `${origin}${directoryPath}`;
|
|
516
|
+
}
|
|
517
|
+
return origin;
|
|
518
|
+
}
|
|
519
|
+
window.openHistory = () => {
|
|
520
|
+
const historyElement = document.getElementById("historyModal");
|
|
521
|
+
historyElement.classList.add('is-active');
|
|
529
522
|
|
|
530
|
-
|
|
523
|
+
let historyContent = '';
|
|
524
|
+
if (testHistoriesMap && testHistoriesMap.length > 0) {
|
|
525
|
+
historyContent = testHistoriesMap.map((h, index) => `
|
|
526
|
+
<tr>
|
|
527
|
+
<td>${h.run_date}</td>
|
|
528
|
+
<td>${h.status}</td>
|
|
529
|
+
<td>${h.duration}</td>
|
|
530
|
+
${h.error_message ? `<td><div class="modal" id="${index}">
|
|
531
|
+
<div class="modal-background"></div>
|
|
532
|
+
<div class="modal-content">
|
|
533
|
+
<pre><code>${h.error_message}</code></pre>
|
|
534
|
+
</div>
|
|
535
|
+
<button class="button is-primary" onclick="closeErrorModal(${index})">Close</button>
|
|
536
|
+
</div><a class="button is-link" onclick="showHistoryErrorMessage(${index})">Show error</a></td>` : '<td>No Error</td>'}
|
|
537
|
+
</tr>
|
|
538
|
+
`).join('');
|
|
539
|
+
} else {
|
|
540
|
+
historyContent = '<p class="title">No history available</p>';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
historyElement.innerHTML = `
|
|
544
|
+
<div class="modal-background"></div>
|
|
545
|
+
<div class="modal-card">
|
|
546
|
+
<header class="modal-card-head">
|
|
547
|
+
<p class="modal-card-title">${testHistoryTitle}</p>
|
|
548
|
+
<button class="button is-primary" onclick="closeHistoryModal()">Close</button>
|
|
549
|
+
</header>
|
|
550
|
+
<section class="modal-card-body">
|
|
551
|
+
<table class="table is-hoverable is-fullwidth">
|
|
552
|
+
<thead>
|
|
553
|
+
<tr>
|
|
554
|
+
<th title="Run Date">Run Date</th>
|
|
555
|
+
<th title="Status">Status</th>
|
|
556
|
+
<th title="Duration">Duration</th>
|
|
557
|
+
<th title="Reason">Reason</th>
|
|
558
|
+
</tr>
|
|
559
|
+
</thead>
|
|
560
|
+
<tbody>
|
|
561
|
+
${historyContent}
|
|
562
|
+
</tbody>
|
|
563
|
+
</table>
|
|
564
|
+
</section>
|
|
565
|
+
</div>
|
|
566
|
+
`;
|
|
567
|
+
};
|
|
568
|
+
window.closeHistoryModal = () => {
|
|
569
|
+
document.getElementById("historyModal").classList.remove('is-active');
|
|
570
|
+
};
|
|
571
|
+
window.showHistoryErrorMessage = (modalId) => {
|
|
572
|
+
document.getElementById(modalId)?.classList.add('is-active');
|
|
573
|
+
};
|
|
531
574
|
});
|
|
532
575
|
</script>
|
|
533
576
|
</body>
|
|
534
|
-
|
|
535
577
|
</html>
|