ortoni-report 3.0.5 → 4.0.2-beta.0
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/chunk-45EJSEX2.mjs +632 -0
- package/dist/chunk-75EAJL2U.mjs +632 -0
- package/dist/chunk-FGIYOFIC.mjs +632 -0
- package/dist/chunk-FHKWBHU6.mjs +633 -0
- package/dist/chunk-GLICR3VS.mjs +637 -0
- package/dist/chunk-HFO6XSKC.mjs +633 -0
- package/dist/chunk-HOZD6YIV.mjs +634 -0
- package/dist/chunk-IJO2YIFE.mjs +637 -0
- package/dist/chunk-INS3E7E6.mjs +638 -0
- package/dist/chunk-JEIWNUQY.mjs +632 -0
- package/dist/chunk-JPLAGYR7.mjs +632 -0
- package/dist/chunk-NM6ULN2O.mjs +632 -0
- package/dist/chunk-OZS6QIJS.mjs +638 -0
- package/dist/chunk-P57227VN.mjs +633 -0
- package/dist/chunk-QMTRYN5N.js +635 -0
- package/dist/chunk-TI33PMMQ.mjs +639 -0
- package/dist/chunk-Z5NBP5TS.mjs +635 -0
- package/dist/cli/cli.cjs +678 -0
- package/dist/cli/cli.d.cts +1 -0
- package/dist/cli/cli.js +580 -11
- package/dist/cli/cli.mjs +62 -5
- package/dist/index.html +21 -0
- package/dist/ortoni-report.cjs +2134 -0
- package/dist/ortoni-report.d.cts +111 -0
- package/dist/ortoni-report.d.mts +3 -12
- package/dist/ortoni-report.d.ts +3 -12
- package/dist/ortoni-report.js +182 -314
- package/dist/ortoni-report.mjs +64 -740
- package/package.json +4 -5
- package/readme.md +26 -33
- package/dist/chunk-AY2PKDHU.mjs +0 -69
- package/dist/chunk-OOALU4XG.mjs +0 -72
- package/dist/chunk-ZSIRUQUA.mjs +0 -68
- package/dist/style/main.css +0 -80
- package/dist/views/analytics.hbs +0 -103
- package/dist/views/head.hbs +0 -11
- package/dist/views/main.hbs +0 -1295
- package/dist/views/project.hbs +0 -238
- package/dist/views/sidebar.hbs +0 -244
- package/dist/views/summaryCard.hbs +0 -15
- package/dist/views/testIcons.hbs +0 -13
- package/dist/views/testPanel.hbs +0 -45
- package/dist/views/testStatus.hbs +0 -9
- package/dist/views/userInfo.hbs +0 -260
package/dist/views/main.hbs
DELETED
|
@@ -1,1295 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" data-theme="{{preferredTheme}}">
|
|
3
|
-
{{> head}}
|
|
4
|
-
<style>
|
|
5
|
-
{{{inlineCss}}}
|
|
6
|
-
|
|
7
|
-
</style>
|
|
8
|
-
|
|
9
|
-
<body>
|
|
10
|
-
<div class="app-container">
|
|
11
|
-
{{> sidebar}}
|
|
12
|
-
<main class="main-content">
|
|
13
|
-
<div id="dashboard-section" class="content-section">
|
|
14
|
-
{{#if logo}}
|
|
15
|
-
<img src="{{logo}}" alt="{{projectName}}" class="logoimage" />
|
|
16
|
-
{{else}}
|
|
17
|
-
<h1 class="title is-3">Dashboard</h1>
|
|
18
|
-
{{/if}}
|
|
19
|
-
<div class="columns is-multiline has-text-centered">
|
|
20
|
-
{{> summaryCard bg="hsl(var(--bulma-primary-h), var(--bulma-primary-s), var(--bulma-primary-l)) !important" status="all" statusHeader="All Tests" statusCount=totalCount}}
|
|
21
|
-
{{> summaryCard bg="hsl(var(--bulma-success-h), var(--bulma-success-s), var(--bulma-success-l)) !important" status="passed" statusHeader="Passed" statusCount=passCount}}
|
|
22
|
-
{{> summaryCard bg="hsl(var(--bulma-danger-h), var(--bulma-danger-s), var(--bulma-danger-l)) !important" status="failed" statusHeader="Failed" statusCount=failCount}}
|
|
23
|
-
{{> summaryCard bg="hsl(var(--bulma-info-h), var(--bulma-info-s), var(--bulma-info-l)) !important" status="skipped" statusHeader="Skipped" statusCount=skipCount}}
|
|
24
|
-
{{> summaryCard bg="hsl(var(--bulma-warning-h), var(--bulma-warning-s), var(--bulma-warning-l)) !important" status="flaky" statusHeader="Flaky" statusCount=flakyCount}}
|
|
25
|
-
{{> summaryCard bg="#69748c" status="retry" statusHeader="Retry" statusCount=retryCount}}
|
|
26
|
-
</div>
|
|
27
|
-
{{> userInfo}}
|
|
28
|
-
</div>
|
|
29
|
-
<div id="tests-section" class="content-section" style="display: none;">
|
|
30
|
-
<div class="test-list">
|
|
31
|
-
<div class="columns">
|
|
32
|
-
<div class="column column is-two-fifths">
|
|
33
|
-
<h1 class="title is-3">Tests</h1>
|
|
34
|
-
<div class="columns is-multiline has-text-centered">
|
|
35
|
-
{{> summaryCard icon="fa-solid fa-vial" filter="filter" bg="hsl(var(--bulma-primary-h), var(--bulma-primary-s), var(--bulma-primary-l)) !important" status="all" statusHeader="All Tests" statusCount=totalCount}}
|
|
36
|
-
{{> summaryCard icon="fa fa-check-circle" filter="filter" bg="hsl(var(--bulma-success-h), var(--bulma-success-s), var(--bulma-success-l)) !important" status="passed" statusHeader="Passed" statusCount=passCount}}
|
|
37
|
-
{{> summaryCard icon="fa fa-times-circle" filter="filter" bg="hsl(var(--bulma-danger-h), var(--bulma-danger-s), var(--bulma-danger-l)) !important" status="failed" statusHeader="Failed" statusCount=failCount}}
|
|
38
|
-
{{> summaryCard icon="fa fa-question-circle" filter="filter" bg="hsl(var(--bulma-info-h), var(--bulma-info-s), var(--bulma-info-l)) !important" status="skipped" statusHeader="Skipped" statusCount=skipCount}}
|
|
39
|
-
{{> summaryCard icon="fa fa-exclamation-triangle" filter="filter" bg="hsl(var(--bulma-warning-h), var(--bulma-warning-s), var(--bulma-warning-l)) !important" status="flaky" statusHeader="Flaky" statusCount=flakyCount}}
|
|
40
|
-
{{> summaryCard icon="fa-solid fa-repeat" filter="filter" bg="#69748c" status="retry" statusHeader="Retry" statusCount=retryCount}}
|
|
41
|
-
</div>
|
|
42
|
-
{{> testPanel}}
|
|
43
|
-
{{> project}}
|
|
44
|
-
</div>
|
|
45
|
-
<div class="column column is-three-fifths">
|
|
46
|
-
<div id="testDetails" style="display: none;"></div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
<div id="analytics-section" class="content-section" style="display: none;">
|
|
52
|
-
{{> analytics}}
|
|
53
|
-
</div>
|
|
54
|
-
</main>
|
|
55
|
-
</div>
|
|
56
|
-
<script>
|
|
57
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
58
|
-
/**
|
|
59
|
-
* ====================================
|
|
60
|
-
* UTILITY FUNCTIONS
|
|
61
|
-
* ====================================
|
|
62
|
-
*/
|
|
63
|
-
const Utils = {
|
|
64
|
-
/**
|
|
65
|
-
* Debounce function to limit how often a function can be called
|
|
66
|
-
* @param {Function} func - Function to debounce
|
|
67
|
-
* @param {number} wait - Wait time in milliseconds
|
|
68
|
-
* @returns {Function} Debounced function
|
|
69
|
-
*/
|
|
70
|
-
debounce(func, wait) {
|
|
71
|
-
let timeout;
|
|
72
|
-
return function (...args) {
|
|
73
|
-
clearTimeout(timeout);
|
|
74
|
-
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
75
|
-
};
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get status class based on test status
|
|
80
|
-
* @param {string} status - Test status
|
|
81
|
-
* @returns {string} CSS class
|
|
82
|
-
*/
|
|
83
|
-
getStatusClass(status) {
|
|
84
|
-
if (status.startsWith('passed')) return 'success';
|
|
85
|
-
if (status === 'flaky') return 'warning';
|
|
86
|
-
if (status === 'failed') return 'danger';
|
|
87
|
-
return 'info';
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Get status icon based on test status
|
|
92
|
-
* @param {string} status - Test status
|
|
93
|
-
* @returns {string} Icon class
|
|
94
|
-
*/
|
|
95
|
-
getStatusIcon(status) {
|
|
96
|
-
if (status.startsWith('passed')) return 'check-circle';
|
|
97
|
-
if (status === 'flaky') return 'exclamation-triangle';
|
|
98
|
-
if (status === 'failed') return 'times-circle';
|
|
99
|
-
return 'question-circle';
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get project icon based on browser
|
|
104
|
-
* @param {string} project - Project name
|
|
105
|
-
* @returns {string} Icon HTML
|
|
106
|
-
*/
|
|
107
|
-
getProjectIcon(project) {
|
|
108
|
-
if (project === 'webkit') return `<i class="fa-brands fa-safari"></i>`;
|
|
109
|
-
if (project === 'firefox') return `<i class="fa-brands fa-firefox"></i>`;
|
|
110
|
-
return `<i class="fa-brands fa-chrome"></i>`;
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get adjusted base URL for trace viewer
|
|
115
|
-
* @returns {string} Adjusted base URL
|
|
116
|
-
*/
|
|
117
|
-
getAdjustedBaseUrl() {
|
|
118
|
-
const origin = window.location.origin;
|
|
119
|
-
const pathname = window.location.pathname;
|
|
120
|
-
if (pathname.endsWith('.html')) {
|
|
121
|
-
const directoryPath = pathname.substring(0, pathname.lastIndexOf('/') + 1);
|
|
122
|
-
return `${origin}${directoryPath}`;
|
|
123
|
-
}
|
|
124
|
-
return origin;
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Copy file path to clipboard
|
|
129
|
-
* @param {HTMLElement} button - Button element
|
|
130
|
-
* @param {string} text - Text to copy
|
|
131
|
-
*/
|
|
132
|
-
copyFilePath(button, text) {
|
|
133
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
134
|
-
const originalIcon = button.innerHTML;
|
|
135
|
-
button.innerHTML = '<i class="fa fa-check"></i>';
|
|
136
|
-
setTimeout(() => {
|
|
137
|
-
button.innerHTML = originalIcon;
|
|
138
|
-
}, 1500);
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* ====================================
|
|
145
|
-
* SIDEBAR MANAGER
|
|
146
|
-
* ====================================
|
|
147
|
-
*/
|
|
148
|
-
class SidebarManager {
|
|
149
|
-
/**
|
|
150
|
-
* Initialize the sidebar manager
|
|
151
|
-
*/
|
|
152
|
-
constructor() {
|
|
153
|
-
this.sidebarLinks = document.querySelectorAll('.sidebar-menu-link');
|
|
154
|
-
this.sections = document.querySelectorAll('.content-section');
|
|
155
|
-
this.testDetailsSection = document.getElementById('testDetails');
|
|
156
|
-
this.analyticsSection = document.getElementById('analytics-section');
|
|
157
|
-
this.sidebar = document.getElementById('sidebar');
|
|
158
|
-
this.mainContent = document.querySelector('.main-content');
|
|
159
|
-
this.init();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Initialize sidebar manager
|
|
164
|
-
*/
|
|
165
|
-
init() {
|
|
166
|
-
this.attachEventListeners();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Attach event listeners
|
|
171
|
-
*/
|
|
172
|
-
attachEventListeners() {
|
|
173
|
-
this.sidebarLinks.forEach(link => {
|
|
174
|
-
link.addEventListener('click', (e) => {
|
|
175
|
-
e.preventDefault();
|
|
176
|
-
this.setActiveLink(link);
|
|
177
|
-
this.showSection(link.getAttribute('data-section'));
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Set active link
|
|
184
|
-
* @param {HTMLElement} activeLink - Active link
|
|
185
|
-
*/
|
|
186
|
-
setActiveLink(activeLink) {
|
|
187
|
-
this.sidebarLinks.forEach(link => {
|
|
188
|
-
link.classList.remove('active');
|
|
189
|
-
});
|
|
190
|
-
activeLink.classList.add('active');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Show section
|
|
195
|
-
* @param {string} sectionId - Section ID
|
|
196
|
-
*/
|
|
197
|
-
showSection(sectionId) {
|
|
198
|
-
// Hide test details if showing a main section
|
|
199
|
-
this.testDetailsSection.style.display = 'none';
|
|
200
|
-
this.analyticsSection.style.display = 'none';
|
|
201
|
-
|
|
202
|
-
// Show the selected section
|
|
203
|
-
this.sections.forEach(section => {
|
|
204
|
-
section.style.display = 'none';
|
|
205
|
-
});
|
|
206
|
-
document.getElementById(`${sectionId}-section`).style.display = 'block';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Show test details
|
|
211
|
-
*/
|
|
212
|
-
showTestDetails() {
|
|
213
|
-
/*
|
|
214
|
-
this.sections.forEach(section => {
|
|
215
|
-
section.style.display = 'none';
|
|
216
|
-
});
|
|
217
|
-
*/
|
|
218
|
-
this.testDetailsSection.style.display = 'block';
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/* Add this new class to the JavaScript section, after the SidebarManager class */
|
|
223
|
-
/**
|
|
224
|
-
* ====================================
|
|
225
|
-
* SIDEBAR COLLAPSE MANAGER
|
|
226
|
-
* ====================================
|
|
227
|
-
*/
|
|
228
|
-
class SidebarCollapseManager {
|
|
229
|
-
/**
|
|
230
|
-
* Initialize the sidebar collapse manager
|
|
231
|
-
*/
|
|
232
|
-
constructor() {
|
|
233
|
-
this.sidebar = document.getElementById('sidebar');
|
|
234
|
-
this.mainContent = document.querySelector('.main-content');
|
|
235
|
-
this.toggleButton = document.getElementById('sidebar-toggle');
|
|
236
|
-
this.toggleIcon = document.getElementById('sidebar-toggle-icon');
|
|
237
|
-
this.isInitialLoad = true;
|
|
238
|
-
this.init();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Initialize sidebar collapse manager
|
|
243
|
-
*/
|
|
244
|
-
init() {
|
|
245
|
-
// Only add the click event listener once
|
|
246
|
-
this.toggleButton.addEventListener('click', (e) => {
|
|
247
|
-
e.preventDefault();
|
|
248
|
-
e.stopPropagation();
|
|
249
|
-
this.toggleSidebar();
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Check for saved state, but only apply it on initial load
|
|
253
|
-
if (this.isInitialLoad) {
|
|
254
|
-
const sidebarCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
|
255
|
-
if (sidebarCollapsed) {
|
|
256
|
-
this.collapseSidebar(false);
|
|
257
|
-
}
|
|
258
|
-
this.isInitialLoad = false;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Toggle sidebar collapsed state
|
|
264
|
-
*/
|
|
265
|
-
toggleSidebar() {
|
|
266
|
-
if (this.sidebar.classList.contains('collapsed')) {
|
|
267
|
-
this.expandSidebar(true);
|
|
268
|
-
} else {
|
|
269
|
-
this.collapseSidebar(true);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Collapse sidebar
|
|
275
|
-
* @param {boolean} saveState - Whether to save state to localStorage
|
|
276
|
-
*/
|
|
277
|
-
collapseSidebar(saveState = true) {
|
|
278
|
-
this.sidebar.classList.add('collapsed');
|
|
279
|
-
this.mainContent.classList.add('expanded');
|
|
280
|
-
this.toggleIcon.classList.remove('fa-chevron-left');
|
|
281
|
-
this.toggleIcon.classList.add('fa-chevron-right');
|
|
282
|
-
if (saveState) {
|
|
283
|
-
localStorage.setItem('sidebarCollapsed', 'true');
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Expand sidebar
|
|
289
|
-
* @param {boolean} saveState - Whether to save state to localStorage
|
|
290
|
-
*/
|
|
291
|
-
expandSidebar(saveState = true) {
|
|
292
|
-
this.sidebar.classList.remove('collapsed');
|
|
293
|
-
this.mainContent.classList.remove('expanded');
|
|
294
|
-
this.toggleIcon.classList.remove('fa-chevron-right');
|
|
295
|
-
this.toggleIcon.classList.add('fa-chevron-left');
|
|
296
|
-
if (saveState) {
|
|
297
|
-
localStorage.setItem('sidebarCollapsed', 'false');
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* ====================================
|
|
304
|
-
* THEME MANAGER
|
|
305
|
-
* ====================================
|
|
306
|
-
*/
|
|
307
|
-
class ThemeManager {
|
|
308
|
-
/**
|
|
309
|
-
* Initialize the theme manager
|
|
310
|
-
* @param {Object} elements - DOM elements
|
|
311
|
-
*/
|
|
312
|
-
constructor(elements) {
|
|
313
|
-
this.elements = elements;
|
|
314
|
-
this.init();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Initialize theme manager
|
|
319
|
-
*/
|
|
320
|
-
init() {
|
|
321
|
-
const preferredTheme = this.elements.themeButton.getAttribute("data-theme-status");
|
|
322
|
-
this.setTheme(preferredTheme);
|
|
323
|
-
this.elements.themeButton.addEventListener('click', () => this.toggleTheme());
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Set theme (light or dark)
|
|
328
|
-
* @param {string} theme - Theme name
|
|
329
|
-
*/
|
|
330
|
-
setTheme(theme) {
|
|
331
|
-
this.elements.htmlElement.setAttribute('data-theme', theme);
|
|
332
|
-
this.elements.themeIcon.className = `fa fa-${theme === 'dark' ? 'moon' : 'sun'}`;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Toggle between light and dark theme
|
|
337
|
-
*/
|
|
338
|
-
toggleTheme() {
|
|
339
|
-
const currentTheme = this.elements.htmlElement.getAttribute('data-theme');
|
|
340
|
-
this.setTheme(currentTheme === 'light' ? 'dark' : 'light');
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* ====================================
|
|
346
|
-
* TEST DETAILS MANAGER
|
|
347
|
-
* ====================================
|
|
348
|
-
*/
|
|
349
|
-
class TestDetailsManager {
|
|
350
|
-
/**
|
|
351
|
-
* Initialize the test details manager
|
|
352
|
-
* @param {Object} elements - DOM elements
|
|
353
|
-
* @param {Object} testData - Test data
|
|
354
|
-
* @param {Array} testHistory - Test history data
|
|
355
|
-
* @param {SidebarManager} sidebarManager - Sidebar manager
|
|
356
|
-
*/
|
|
357
|
-
constructor(elements, testData, testHistory, sidebarManager) {
|
|
358
|
-
this.elements = elements;
|
|
359
|
-
this.testData = testData;
|
|
360
|
-
this.testHistory = testHistory;
|
|
361
|
-
this.sidebarManager = sidebarManager;
|
|
362
|
-
this.testHistoriesMap = null;
|
|
363
|
-
this.testHistoryTitle = '';
|
|
364
|
-
this.currentScreenshotIndex = 0;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Show test details
|
|
369
|
-
* @param {Object} test - Test data
|
|
370
|
-
*/
|
|
371
|
-
show(test) {
|
|
372
|
-
this.sidebarManager.showTestDetails();
|
|
373
|
-
this.elements.testDetails.style.opacity = '0';
|
|
374
|
-
setTimeout(() => {
|
|
375
|
-
this.elements.testDetails.style.opacity = '1';
|
|
376
|
-
}, 50);
|
|
377
|
-
this.render(test);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Hide test details and show dashboard
|
|
382
|
-
*/
|
|
383
|
-
hide() {
|
|
384
|
-
// Find the dashboard link and click it
|
|
385
|
-
document.querySelector('.sidebar-menu-link[data-section="dashboard"]').click();
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Render test details
|
|
390
|
-
* @param {Object} test - Test data
|
|
391
|
-
*/
|
|
392
|
-
render(test) {
|
|
393
|
-
this.currentScreenshotIndex = 0;
|
|
394
|
-
const statusClass = Utils.getStatusClass(test.status);
|
|
395
|
-
const statusIcon = Utils.getStatusIcon(test.status);
|
|
396
|
-
const projectIcon = Utils.getProjectIcon(test.projectName);
|
|
397
|
-
|
|
398
|
-
this.elements.testDetails.innerHTML = `
|
|
399
|
-
<div class="sticky-header">
|
|
400
|
-
<div class="card mb-3">
|
|
401
|
-
<div class="card-content">
|
|
402
|
-
<div class="content has-text-centered">
|
|
403
|
-
<h1 class="title is-4">${test.title}</h1>
|
|
404
|
-
<div class="is-flex is-justify-content-center is-align-items-center">
|
|
405
|
-
<p class="subtitle is-5 mb-0" id="filepath">${test.location}</p>
|
|
406
|
-
<button class="button has-text-primary is-small is-ghost ml-2" onclick="copyFilePath(this, '${test.location}')" title="Copy to clipboard">
|
|
407
|
-
<i class="fa-regular fa-clipboard"></i>
|
|
408
|
-
</button>
|
|
409
|
-
</div>
|
|
410
|
-
</div>
|
|
411
|
-
</div>
|
|
412
|
-
<footer class="card-footer">
|
|
413
|
-
<div class="card-footer-item">
|
|
414
|
-
<div class="columns is-mobile">
|
|
415
|
-
<div class="column is-half">
|
|
416
|
-
<div class="is-flex is-align-items-center">
|
|
417
|
-
<span class="icon status-icon has-text-${statusClass}">
|
|
418
|
-
<i class="fa fa-${statusIcon}"></i>
|
|
419
|
-
</span>
|
|
420
|
-
<span class="has-text-weight-bold is-capitalized has-text-${statusClass}">${test.status}</span>
|
|
421
|
-
</div>
|
|
422
|
-
</div>
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
425
|
-
${test.duration ? `
|
|
426
|
-
<div class="card-footer-item">
|
|
427
|
-
<div class="column is-half">
|
|
428
|
-
<div class="is-flex is-align-items-center">
|
|
429
|
-
<span class="icon status-icon has-text-info">
|
|
430
|
-
<i class="fa fa-clock"></i>
|
|
431
|
-
</span>
|
|
432
|
-
<span class="has-text-info has-text-weight-semibold">${test.duration}</span>
|
|
433
|
-
</div>
|
|
434
|
-
</div>
|
|
435
|
-
</div>
|
|
436
|
-
` : ''}
|
|
437
|
-
${test.projectName ? `
|
|
438
|
-
<div class="card-footer-item">
|
|
439
|
-
<div class="is-flex is-align-items-center">
|
|
440
|
-
<span class="icon status-icon has-text-info">
|
|
441
|
-
${projectIcon}
|
|
442
|
-
</span>
|
|
443
|
-
<span> ${test.projectName}</span>
|
|
444
|
-
</div>
|
|
445
|
-
</div>
|
|
446
|
-
` : ''}
|
|
447
|
-
</footer>
|
|
448
|
-
</div>
|
|
449
|
-
</div>
|
|
450
|
-
<div class="content-wrapper">
|
|
451
|
-
${this.renderTestContent(test)}
|
|
452
|
-
</div>
|
|
453
|
-
`;
|
|
454
|
-
this.attachScreenshotListeners(test);
|
|
455
|
-
this.attachSteps(test);
|
|
456
|
-
this.attachTabListeners();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Render test content
|
|
461
|
-
* @param {Object} test - Test data
|
|
462
|
-
* @returns {string} HTML content
|
|
463
|
-
*/
|
|
464
|
-
renderTestContent(test) {
|
|
465
|
-
let content = '';
|
|
466
|
-
if (test.status !== "skipped") {
|
|
467
|
-
content += this.renderScreenshotsAndVideo(test);
|
|
468
|
-
}
|
|
469
|
-
content += this.renderAdditionalInfo(test);
|
|
470
|
-
content += this.renderTabs(test);
|
|
471
|
-
return content;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Render screenshots and video
|
|
476
|
-
* @param {Object} test - Test data
|
|
477
|
-
* @returns {string} HTML content
|
|
478
|
-
*/
|
|
479
|
-
renderScreenshotsAndVideo(test) {
|
|
480
|
-
let content = '<div class="card mb-5"><div class="card-content"><div class="columns is-multiline">';
|
|
481
|
-
if (test.screenshots && test.screenshots.length > 0) {
|
|
482
|
-
content += `
|
|
483
|
-
<div class="column is-half">
|
|
484
|
-
<div id="testImage" class="modal">
|
|
485
|
-
<div class="modal-background"></div>
|
|
486
|
-
<div class="modal-content">
|
|
487
|
-
<p class="image">
|
|
488
|
-
<img id="screenshot-modal-img" src="${test.screenshots[0]}" alt="Screenshot">
|
|
489
|
-
</p>
|
|
490
|
-
</div>
|
|
491
|
-
<button onclick="closeModal()" class="modal-close is-large" aria-label="close"></button>
|
|
492
|
-
</div>
|
|
493
|
-
<figure class="image">
|
|
494
|
-
<img id="screenshot-main-img" onclick="openModal()" src="${test.screenshots[0]}" alt="Screenshot">
|
|
495
|
-
</figure>
|
|
496
|
-
<nav class="mt-4 pagination is-small is-centered ${test.screenshots.length > 1 ? '' : 'is-hidden'}" role="navigation" aria-label="pagination">
|
|
497
|
-
<a class="pagination-previous">Previous</a>
|
|
498
|
-
<a class="pagination-next">Next</a>
|
|
499
|
-
<ul class="pagination-list">
|
|
500
|
-
${test.screenshots.map((_, index) => `
|
|
501
|
-
<li>
|
|
502
|
-
<a class="pagination-link ${index === 0 ? 'is-current' : ''}" aria-label="Goto screenshot ${index + 1}">${index + 1}</a>
|
|
503
|
-
</li>`).join('')}
|
|
504
|
-
</ul>
|
|
505
|
-
</nav>
|
|
506
|
-
</div>
|
|
507
|
-
`;
|
|
508
|
-
}
|
|
509
|
-
if (test.videoPath) {
|
|
510
|
-
content += `
|
|
511
|
-
<div class="column is-half">
|
|
512
|
-
<div class="video-preview">
|
|
513
|
-
<video controls width="100%" height="auto" preload="metadata">
|
|
514
|
-
<source src="${test.videoPath}" type="video/webm">
|
|
515
|
-
Your browser does not support the video tag.
|
|
516
|
-
</video>
|
|
517
|
-
</div>
|
|
518
|
-
</div>
|
|
519
|
-
`;
|
|
520
|
-
}
|
|
521
|
-
content += '</div>';
|
|
522
|
-
content += `
|
|
523
|
-
<div class="columns">
|
|
524
|
-
<div class="column">
|
|
525
|
-
<button
|
|
526
|
-
onclick="openHistory()"
|
|
527
|
-
class="button is-primary is-fullwidth mt-3">
|
|
528
|
-
<span class="icon"><i class="fa-solid fa-timeline"></i></span>
|
|
529
|
-
<span class="has-text-white pl-2">Open history</span>
|
|
530
|
-
</button>
|
|
531
|
-
<div id="historyModal" class="modal">
|
|
532
|
-
<div class="modal-background"></div>
|
|
533
|
-
</div>
|
|
534
|
-
</div>
|
|
535
|
-
${test.tracePath ? `
|
|
536
|
-
<div class="column">
|
|
537
|
-
<button
|
|
538
|
-
data-trace="${test.tracePath}"
|
|
539
|
-
onclick="openTraceViewer(this)"
|
|
540
|
-
class="button is-primary is-fullwidth mt-3">
|
|
541
|
-
<span class="icon"><i class="fa-solid fa-tv"></i></span>
|
|
542
|
-
<span class="has-text-white pl-2">View Trace</span>
|
|
543
|
-
</button>
|
|
544
|
-
</div>
|
|
545
|
-
` : ''}
|
|
546
|
-
`;
|
|
547
|
-
if (test.markdownPath) {
|
|
548
|
-
content += `
|
|
549
|
-
<div class="column">
|
|
550
|
-
<button
|
|
551
|
-
class="button is-primary is-fullwidth mt-3"
|
|
552
|
-
data-markdown="${test.markdownPath}"
|
|
553
|
-
onclick="openMarkdown(this)">
|
|
554
|
-
<span class="icon"><i class="fa-solid fa-file-lines"></i></span>
|
|
555
|
-
<span class="has-text-white pl-2">Open Markdown</span>
|
|
556
|
-
</button>
|
|
557
|
-
</div>
|
|
558
|
-
`;
|
|
559
|
-
}
|
|
560
|
-
content += '</div></div></div>';
|
|
561
|
-
return content;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Render additional information
|
|
566
|
-
* @param {Object} test - Test data
|
|
567
|
-
* @returns {string} HTML content
|
|
568
|
-
*/
|
|
569
|
-
renderAdditionalInfo(test) {
|
|
570
|
-
if (!(test.annotations.length || test.testTags.length > 0)) return '';
|
|
571
|
-
return `
|
|
572
|
-
<div class="card mb-5">
|
|
573
|
-
<header class="card-header">
|
|
574
|
-
<p class="card-header-title">Additional Information</p>
|
|
575
|
-
</header>
|
|
576
|
-
<div class="card-content">
|
|
577
|
-
<div class="content">
|
|
578
|
-
${test.testTags.length > 0 ? `
|
|
579
|
-
<div class="control mb-4">
|
|
580
|
-
<div class="tags is-rounded">
|
|
581
|
-
${test.testTags.map(tag => `<span class="tag is-primary is-medium">${tag}</span>`).join('')}
|
|
582
|
-
</div>
|
|
583
|
-
</div>` : ""}
|
|
584
|
-
${test.annotations
|
|
585
|
-
.filter(annotation => annotation !== null && annotation !== undefined)
|
|
586
|
-
.map(annotation => `
|
|
587
|
-
<div class="mb-4">
|
|
588
|
-
${annotation?.type ? `<strong class="has-text-link">Type: </strong><span>${annotation.type}</span>` : ''}
|
|
589
|
-
<br>
|
|
590
|
-
${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation.description}</span>` : ''}
|
|
591
|
-
</div>
|
|
592
|
-
`).join('')}
|
|
593
|
-
</div>
|
|
594
|
-
</div>
|
|
595
|
-
</div>
|
|
596
|
-
`;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Render tabs
|
|
601
|
-
* @param {Object} test - Test data
|
|
602
|
-
* @returns {string} HTML content
|
|
603
|
-
*/
|
|
604
|
-
renderTabs(test) {
|
|
605
|
-
return `
|
|
606
|
-
<div class="card mt-5">
|
|
607
|
-
<div class="card-content">
|
|
608
|
-
<div class="tabs is-boxed is-fullwidth">
|
|
609
|
-
<ul>
|
|
610
|
-
<li class="is-active"><a data-tab="steps">Steps</a></li>
|
|
611
|
-
<li><a data-tab="errors">Errors</a></li>
|
|
612
|
-
<li><a data-tab="logs">Logs</a></li>
|
|
613
|
-
</ul>
|
|
614
|
-
</div>
|
|
615
|
-
<div id="tabContent">
|
|
616
|
-
<div id="stepsTab" class="tab-content">
|
|
617
|
-
${this.renderSteps(test)}
|
|
618
|
-
</div>
|
|
619
|
-
<div id="errorsTab" class="tab-content" style="display: none;">
|
|
620
|
-
${this.renderErrors(test)}
|
|
621
|
-
</div>
|
|
622
|
-
<div id="logsTab" class="tab-content" style="display: none;">
|
|
623
|
-
${this.renderLogs(test)}
|
|
624
|
-
</div>
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
627
|
-
</div>
|
|
628
|
-
`;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Render steps
|
|
633
|
-
* @param {Object} test - Test data
|
|
634
|
-
* @returns {string} HTML content
|
|
635
|
-
*/
|
|
636
|
-
renderSteps(test) {
|
|
637
|
-
if (test.steps.length === 0) return '<p>No steps available.</p>';
|
|
638
|
-
return `
|
|
639
|
-
<div class="content">
|
|
640
|
-
<span id="stepDetails" class="content"></span>
|
|
641
|
-
</div>
|
|
642
|
-
`;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* Render errors
|
|
647
|
-
* @param {Object} test - Test data
|
|
648
|
-
* @returns {string} HTML content
|
|
649
|
-
*/
|
|
650
|
-
renderErrors(test) {
|
|
651
|
-
if (!test.errors.length) return '<p>No errors reported.</p>';
|
|
652
|
-
return `
|
|
653
|
-
<div class="content">
|
|
654
|
-
<pre><code class="data-lang=js">${test.errors.join('\n')}</code></pre>
|
|
655
|
-
</div>
|
|
656
|
-
`;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* Render logs
|
|
661
|
-
* @param {Object} test - Test data
|
|
662
|
-
* @returns {string} HTML content
|
|
663
|
-
*/
|
|
664
|
-
renderLogs(test) {
|
|
665
|
-
if (!test.logs) return '<p>No logs available.</p>';
|
|
666
|
-
return `
|
|
667
|
-
<div class="content">
|
|
668
|
-
<pre>${test.logs}</pre>
|
|
669
|
-
</div>
|
|
670
|
-
`;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Attach screenshot listeners
|
|
675
|
-
* @param {Object} test - Test data
|
|
676
|
-
*/
|
|
677
|
-
attachScreenshotListeners(test) {
|
|
678
|
-
if (test.screenshots && test.screenshots.length > 0) {
|
|
679
|
-
const changeScreenshot = (direction) => {
|
|
680
|
-
this.currentScreenshotIndex = (this.currentScreenshotIndex + direction + test.screenshots.length) % test.screenshots.length;
|
|
681
|
-
this.updateScreenshot(test.screenshots, this.currentScreenshotIndex);
|
|
682
|
-
};
|
|
683
|
-
const gotoScreenshot = (index) => {
|
|
684
|
-
this.currentScreenshotIndex = index;
|
|
685
|
-
this.updateScreenshot(test.screenshots, this.currentScreenshotIndex);
|
|
686
|
-
};
|
|
687
|
-
document.querySelector('.pagination-previous').addEventListener('click', () => changeScreenshot(-1));
|
|
688
|
-
document.querySelector('.pagination-next').addEventListener('click', () => changeScreenshot(1));
|
|
689
|
-
document.querySelectorAll('.pagination-link').forEach((link, index) => {
|
|
690
|
-
link.addEventListener('click', () => gotoScreenshot(index));
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Update screenshot
|
|
697
|
-
* @param {Array} screenshots - Screenshot paths
|
|
698
|
-
* @param {number} index - Screenshot index
|
|
699
|
-
*/
|
|
700
|
-
updateScreenshot(screenshots, index) {
|
|
701
|
-
document.getElementById('screenshot-main-img').src = screenshots[index];
|
|
702
|
-
document.getElementById('screenshot-modal-img').src = screenshots[index];
|
|
703
|
-
document.querySelectorAll('.pagination-link').forEach((link, i) => {
|
|
704
|
-
link.classList.toggle('is-current', i === index);
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Attach steps
|
|
710
|
-
* @param {Object} test - Test data
|
|
711
|
-
*/
|
|
712
|
-
attachSteps(test) {
|
|
713
|
-
const stepDetailsDiv = document.getElementById('stepDetails');
|
|
714
|
-
if (stepDetailsDiv) {
|
|
715
|
-
const stepsList = document.createElement("ul");
|
|
716
|
-
stepsList.setAttribute("id", "steps");
|
|
717
|
-
test.steps.forEach(step => {
|
|
718
|
-
const li = document.createElement('li');
|
|
719
|
-
li.innerHTML = `<strong class="${step.snippet ? 'has-text-danger' : ''}">${step.title}</strong>`;
|
|
720
|
-
if (step.snippet) {
|
|
721
|
-
const pre = document.createElement('pre');
|
|
722
|
-
const code = document.createElement('code');
|
|
723
|
-
const locationText = step.location ? `\n\nat: ${step.location}` : '';
|
|
724
|
-
code.innerHTML = `${step.snippet}${locationText}`;
|
|
725
|
-
code.setAttribute('data-lang', 'js');
|
|
726
|
-
pre.appendChild(code);
|
|
727
|
-
li.appendChild(pre);
|
|
728
|
-
}
|
|
729
|
-
stepsList.appendChild(li);
|
|
730
|
-
});
|
|
731
|
-
stepDetailsDiv.appendChild(stepsList);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Attach tab listeners
|
|
737
|
-
*/
|
|
738
|
-
attachTabListeners() {
|
|
739
|
-
const tabLinks = document.querySelectorAll('div.tabs a');
|
|
740
|
-
tabLinks.forEach(link => {
|
|
741
|
-
link.addEventListener('click', (e) => {
|
|
742
|
-
e.preventDefault();
|
|
743
|
-
const tabId = link.getAttribute('data-tab');
|
|
744
|
-
|
|
745
|
-
tabLinks.forEach(l => l.parentElement.classList.remove('is-active'));
|
|
746
|
-
document.querySelectorAll('div.tab-content').forEach(c => c.style.display = 'none');
|
|
747
|
-
|
|
748
|
-
link.parentElement.classList.add('is-active');
|
|
749
|
-
document.getElementById(`${tabId}Tab`).style.display = 'block';
|
|
750
|
-
});
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Attach event listeners
|
|
756
|
-
*/
|
|
757
|
-
attachEventListeners() {
|
|
758
|
-
const testItems = document.querySelectorAll('[data-test-id]');
|
|
759
|
-
testItems.forEach(item => {
|
|
760
|
-
item.addEventListener('click', () => {
|
|
761
|
-
testItems.forEach(i => i.classList.remove('listselected'));
|
|
762
|
-
item.classList.add('listselected');
|
|
763
|
-
const testId = item.getAttribute('data-test-id');
|
|
764
|
-
const testHistoryId = item.getAttribute('data-test-history-id');
|
|
765
|
-
const test = this.testData[testId];
|
|
766
|
-
const historyEntry = this.testHistory.find(entry => entry.testId === testHistoryId);
|
|
767
|
-
this.testHistoriesMap = historyEntry ? historyEntry.history : null;
|
|
768
|
-
this.testHistoryTitle = historyEntry && historyEntry.testId ? historyEntry.testId.split(":")[2] : '';
|
|
769
|
-
this.show(test);
|
|
770
|
-
});
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* ====================================
|
|
777
|
-
* FILTER MANAGER
|
|
778
|
-
* ====================================
|
|
779
|
-
*/
|
|
780
|
-
class FilterManager {
|
|
781
|
-
/**
|
|
782
|
-
* Initialize the filter manager
|
|
783
|
-
* @param {Object} elements - DOM elements
|
|
784
|
-
*/
|
|
785
|
-
constructor(elements) {
|
|
786
|
-
this.elements = elements;
|
|
787
|
-
this.init();
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* Initialize filter manager
|
|
792
|
-
*/
|
|
793
|
-
init() {
|
|
794
|
-
this.attachEventListeners();
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
* Attach event listeners
|
|
799
|
-
*/
|
|
800
|
-
attachEventListeners() {
|
|
801
|
-
const checkboxes = document.querySelectorAll('#select-filter input[type="checkbox"]');
|
|
802
|
-
checkboxes.forEach(checkbox => {
|
|
803
|
-
checkbox.addEventListener('change', () => this.applyFilters());
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
const filters = document.querySelectorAll('.filter');
|
|
807
|
-
filters.forEach(filter => {
|
|
808
|
-
filter.addEventListener('click', (event) => {
|
|
809
|
-
filters.forEach(f => {
|
|
810
|
-
f.classList.remove('active');
|
|
811
|
-
f.style.borderRight = ''; // Reset previous border-right
|
|
812
|
-
});
|
|
813
|
-
const clickedFilter = event.currentTarget;
|
|
814
|
-
clickedFilter.classList.add('active');
|
|
815
|
-
const borderLeftColor = window.getComputedStyle(clickedFilter).borderLeftColor;
|
|
816
|
-
clickedFilter.style.setProperty('border-right', `5px solid ${borderLeftColor}`, 'important');
|
|
817
|
-
this.applyFilters();
|
|
818
|
-
});
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Apply filters
|
|
824
|
-
*/
|
|
825
|
-
applyFilters() {
|
|
826
|
-
const selectedProjects = this.getSelectedValues('project');
|
|
827
|
-
const selectedTags = this.getSelectedValues('test-tags');
|
|
828
|
-
const selectedStatus = document.querySelector('.filter.active')?.getAttribute('data-status') || 'all';
|
|
829
|
-
|
|
830
|
-
this.elements.detailsElements.forEach(details => {
|
|
831
|
-
const items = details.querySelectorAll('div[data-test-id]');
|
|
832
|
-
let shouldShowDetails = false;
|
|
833
|
-
|
|
834
|
-
items.forEach(item => {
|
|
835
|
-
const isVisible = this.shouldShowItem(item, selectedProjects, selectedTags, selectedStatus);
|
|
836
|
-
item.classList.toggle('is-hidden', !isVisible);
|
|
837
|
-
shouldShowDetails = shouldShowDetails || isVisible;
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
details.open = shouldShowDetails;
|
|
841
|
-
details.classList.toggle('is-hidden', !shouldShowDetails);
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
this.updateSelectedFiltersDisplay(selectedProjects, selectedTags, selectedStatus);
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* Get selected values
|
|
849
|
-
* @param {string} type - Filter type
|
|
850
|
-
* @returns {Array} Selected values
|
|
851
|
-
*/
|
|
852
|
-
getSelectedValues(type) {
|
|
853
|
-
return Array.from(document.querySelectorAll(`#select-filter input[type="checkbox"][data-filter-type="${type}"]:checked`))
|
|
854
|
-
.map(checkbox => checkbox.value.trim());
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* Check if item should be shown
|
|
859
|
-
* @param {HTMLElement} item - Test item
|
|
860
|
-
* @param {Array} projects - Selected projects
|
|
861
|
-
* @param {Array} tags - Selected tags
|
|
862
|
-
* @param {string} status - Selected status
|
|
863
|
-
* @returns {boolean} Whether item should be shown
|
|
864
|
-
*/
|
|
865
|
-
shouldShowItem(item, projects, tags, status) {
|
|
866
|
-
const testTags = item.getAttribute('data-test-tags').trim().split(' ').filter(Boolean);
|
|
867
|
-
const projectName = item.getAttribute('data-project-name').trim();
|
|
868
|
-
const testStatus = item.getAttribute('data-test-status').trim();
|
|
869
|
-
|
|
870
|
-
const matchesProject = projects.length === 0 || projects.includes(projectName);
|
|
871
|
-
const matchesTags = tags.length === 0 || tags.every(tag => testTags.includes(tag));
|
|
872
|
-
const matchesStatus = this.matchesStatus(testStatus, status);
|
|
873
|
-
|
|
874
|
-
return matchesProject && matchesTags && matchesStatus;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* Check if test status matches selected status
|
|
879
|
-
* @param {string} testStatus - Test status
|
|
880
|
-
* @param {string} selectedStatus - Selected status
|
|
881
|
-
* @returns {boolean} Whether status matches
|
|
882
|
-
*/
|
|
883
|
-
matchesStatus(testStatus, selectedStatus) {
|
|
884
|
-
if (selectedStatus === 'all') return testStatus !== 'skipped';
|
|
885
|
-
if (selectedStatus === 'failed') return testStatus === 'failed' || testStatus === 'timedOut';
|
|
886
|
-
if (selectedStatus === 'retry') return testStatus.includes('retry');
|
|
887
|
-
if (selectedStatus === 'flaky') return testStatus.includes('flaky');
|
|
888
|
-
return testStatus === selectedStatus;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* Update selected filters display
|
|
893
|
-
* @param {Array} projects - Selected projects
|
|
894
|
-
* @param {Array} tags - Selected tags
|
|
895
|
-
* @param {string} status - Selected status
|
|
896
|
-
*/
|
|
897
|
-
updateSelectedFiltersDisplay(projects, tags, status) {
|
|
898
|
-
let displayText = [];
|
|
899
|
-
if (projects.length > 0) displayText.push(`Projects: ${projects.join(', ')}`);
|
|
900
|
-
if (tags.length > 0) displayText.push(`Tags: ${tags.join(', ')}`);
|
|
901
|
-
if (status !== 'all') displayText.push(`Status: ${status}`);
|
|
902
|
-
this.elements.filtersDisplay.innerHTML = displayText.length > 0 ? displayText.join(' | ') : 'All Tests';
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
/**
|
|
907
|
-
* ====================================
|
|
908
|
-
* SEARCH MANAGER
|
|
909
|
-
* ====================================
|
|
910
|
-
*/
|
|
911
|
-
class SearchManager {
|
|
912
|
-
/**
|
|
913
|
-
* Initialize the search manager
|
|
914
|
-
* @param {Object} elements - DOM elements
|
|
915
|
-
*/
|
|
916
|
-
constructor(elements) {
|
|
917
|
-
this.elements = elements;
|
|
918
|
-
this.init();
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
/**
|
|
922
|
-
* Initialize search manager
|
|
923
|
-
*/
|
|
924
|
-
init() {
|
|
925
|
-
this.elements.searchInput.addEventListener('input', Utils.debounce(this.filterTests.bind(this), 300));
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* Filter tests based on search term
|
|
930
|
-
* @param {Event} event - Input event
|
|
931
|
-
*/
|
|
932
|
-
filterTests(event) {
|
|
933
|
-
const searchTerm = event.target.value.toLowerCase();
|
|
934
|
-
const testItems = document.querySelectorAll('[data-test-id]');
|
|
935
|
-
|
|
936
|
-
this.elements.detailsElements.forEach(detail => detail.open = !!searchTerm);
|
|
937
|
-
|
|
938
|
-
testItems.forEach(item => {
|
|
939
|
-
const isVisible = item.textContent.toLowerCase().includes(searchTerm);
|
|
940
|
-
item.style.display = isVisible ? 'flex' : 'none';
|
|
941
|
-
if (isVisible && searchTerm) this.openParentDetails(item);
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
/**
|
|
946
|
-
* Open parent details elements
|
|
947
|
-
* @param {HTMLElement} item - Test item
|
|
948
|
-
*/
|
|
949
|
-
openParentDetails(item) {
|
|
950
|
-
let parent = item.parentElement;
|
|
951
|
-
while (parent && parent.tagName !== 'ASIDE') {
|
|
952
|
-
if (parent.tagName === 'DETAILS') parent.open = true;
|
|
953
|
-
parent = parent.parentElement;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
/**
|
|
959
|
-
* ====================================
|
|
960
|
-
* HISTORY MANAGER
|
|
961
|
-
* ====================================
|
|
962
|
-
*/
|
|
963
|
-
class HistoryManager {
|
|
964
|
-
/**
|
|
965
|
-
* Initialize the history manager
|
|
966
|
-
* @param {Array} testHistory - Test history data
|
|
967
|
-
*/
|
|
968
|
-
constructor(testHistory) {
|
|
969
|
-
this.testHistory = testHistory
|
|
970
|
-
this.testHistoriesMap = null
|
|
971
|
-
this.testHistoryTitle = ""
|
|
972
|
-
this.activeErrorModal = null
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
/**
|
|
976
|
-
* Set current test history
|
|
977
|
-
* @param {Array} historyMap - History map
|
|
978
|
-
* @param {string} title - Test title
|
|
979
|
-
*/
|
|
980
|
-
setCurrentHistory(historyMap, title) {
|
|
981
|
-
this.testHistoriesMap = historyMap
|
|
982
|
-
this.testHistoryTitle = title
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* Open history modal
|
|
987
|
-
*/
|
|
988
|
-
openHistory() {
|
|
989
|
-
const historyElement = document.getElementById("historyModal")
|
|
990
|
-
historyElement.classList.add("is-active")
|
|
991
|
-
|
|
992
|
-
let historyContent = ""
|
|
993
|
-
if (this.testHistoriesMap && this.testHistoriesMap.length > 0) {
|
|
994
|
-
historyContent = this.testHistoriesMap
|
|
995
|
-
.map(
|
|
996
|
-
(h, index) => `
|
|
997
|
-
<tr>
|
|
998
|
-
<td>${h.run_date}</td>
|
|
999
|
-
<td>
|
|
1000
|
-
<span class="tag is-${this.getStatusClass(h.status)}">
|
|
1001
|
-
${h.status}
|
|
1002
|
-
</span>
|
|
1003
|
-
</td>
|
|
1004
|
-
<td>${h.duration}</td>
|
|
1005
|
-
<td>
|
|
1006
|
-
${h.error_message
|
|
1007
|
-
? `<div class="modal" id="error-${index}">
|
|
1008
|
-
<div class="modal-background"></div>
|
|
1009
|
-
<div class="modal-card">
|
|
1010
|
-
<header class="modal-card-head">
|
|
1011
|
-
<p class="modal-card-title">
|
|
1012
|
-
<span class="icon-text">
|
|
1013
|
-
<span class="icon">
|
|
1014
|
-
<i class="fa-solid fa-exclamation-triangle"></i>
|
|
1015
|
-
</span>
|
|
1016
|
-
<span>Error Details</span>
|
|
1017
|
-
</span>
|
|
1018
|
-
</p>
|
|
1019
|
-
<button class="delete" aria-label="close" onclick="closeErrorModal('error-${index}')"></button>
|
|
1020
|
-
</header>
|
|
1021
|
-
<section class="modal-card-body">
|
|
1022
|
-
<div class="notification is-danger is-light">
|
|
1023
|
-
<pre><code>${h.error_message}</code></pre>
|
|
1024
|
-
</div>
|
|
1025
|
-
</section>
|
|
1026
|
-
<footer class="modal-card-foot">
|
|
1027
|
-
<button class="button is-primary" onclick="closeErrorModal('error-${index}')">Close</button>
|
|
1028
|
-
</footer>
|
|
1029
|
-
</div>
|
|
1030
|
-
</div>
|
|
1031
|
-
<button class="button is-small is-link" onclick="showHistoryErrorMessage('error-${index}')">
|
|
1032
|
-
<span class="icon">
|
|
1033
|
-
<i class="fa-solid fa-exclamation-triangle"></i>
|
|
1034
|
-
</span>
|
|
1035
|
-
<span>View Error</span>
|
|
1036
|
-
</button>`
|
|
1037
|
-
: '<span class="tag is-success is-light">No Error</span>'
|
|
1038
|
-
}
|
|
1039
|
-
</td>
|
|
1040
|
-
</tr>
|
|
1041
|
-
`,
|
|
1042
|
-
)
|
|
1043
|
-
.join("")
|
|
1044
|
-
} else {
|
|
1045
|
-
historyContent = `
|
|
1046
|
-
<tr>
|
|
1047
|
-
<td colspan="4">
|
|
1048
|
-
<div class="notification is-info is-light">
|
|
1049
|
-
<p class="has-text-centered">
|
|
1050
|
-
<span class="icon">
|
|
1051
|
-
<i class="fa-solid fa-info-circle"></i>
|
|
1052
|
-
</span>
|
|
1053
|
-
<span>No history available for this test.</span>
|
|
1054
|
-
</p>
|
|
1055
|
-
</div>
|
|
1056
|
-
</td>
|
|
1057
|
-
</tr>
|
|
1058
|
-
`
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
historyElement.innerHTML = `
|
|
1062
|
-
<div class="modal-background"></div>
|
|
1063
|
-
<div class="modal-card">
|
|
1064
|
-
<header class="modal-card-head">
|
|
1065
|
-
<p class="modal-card-title">
|
|
1066
|
-
<span class="icon-text">
|
|
1067
|
-
<span class="icon">
|
|
1068
|
-
<i class="fa-solid fa-history"></i>
|
|
1069
|
-
</span>
|
|
1070
|
-
<span>Test History: ${this.testHistoryTitle}</span>
|
|
1071
|
-
</span>
|
|
1072
|
-
</p>
|
|
1073
|
-
<button class="delete" aria-label="close" onclick="closeHistoryModal()"></button>
|
|
1074
|
-
</header>
|
|
1075
|
-
<section class="modal-card-body">
|
|
1076
|
-
<div class="table-container">
|
|
1077
|
-
<table class="table is-hoverable is-fullwidth">
|
|
1078
|
-
<thead>
|
|
1079
|
-
<tr>
|
|
1080
|
-
<th title="Run Date">
|
|
1081
|
-
<span class="icon-text">
|
|
1082
|
-
<span class="icon">
|
|
1083
|
-
<i class="fa-solid fa-calendar"></i>
|
|
1084
|
-
</span>
|
|
1085
|
-
<span>Run Date</span>
|
|
1086
|
-
</span>
|
|
1087
|
-
</th>
|
|
1088
|
-
<th title="Status">
|
|
1089
|
-
<span class="icon-text">
|
|
1090
|
-
<span class="icon">
|
|
1091
|
-
<i class="fa-solid fa-check-circle"></i>
|
|
1092
|
-
</span>
|
|
1093
|
-
<span>Status</span>
|
|
1094
|
-
</span>
|
|
1095
|
-
</th>
|
|
1096
|
-
<th title="Duration">
|
|
1097
|
-
<span class="icon-text">
|
|
1098
|
-
<span class="icon">
|
|
1099
|
-
<i class="fa-solid fa-clock"></i>
|
|
1100
|
-
</span>
|
|
1101
|
-
<span>Duration</span>
|
|
1102
|
-
</span>
|
|
1103
|
-
</th>
|
|
1104
|
-
<th title="Details">
|
|
1105
|
-
<span class="icon-text">
|
|
1106
|
-
<span class="icon">
|
|
1107
|
-
<i class="fa-solid fa-info-circle"></i>
|
|
1108
|
-
</span>
|
|
1109
|
-
<span>Details</span>
|
|
1110
|
-
</span>
|
|
1111
|
-
</th>
|
|
1112
|
-
</tr>
|
|
1113
|
-
</thead>
|
|
1114
|
-
<tbody>
|
|
1115
|
-
${historyContent}
|
|
1116
|
-
</tbody>
|
|
1117
|
-
</table>
|
|
1118
|
-
</div>
|
|
1119
|
-
</section>
|
|
1120
|
-
<footer class="modal-card-foot">
|
|
1121
|
-
<button class="button is-primary" onclick="closeHistoryModal()">Close</button>
|
|
1122
|
-
</footer>
|
|
1123
|
-
</div>
|
|
1124
|
-
`
|
|
1125
|
-
|
|
1126
|
-
// Add keyboard event listener for ESC key
|
|
1127
|
-
document.addEventListener("keydown", this.handleEscKeyPress)
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* Get status class based on test status
|
|
1132
|
-
* @param {string} status - Test status
|
|
1133
|
-
* @returns {string} CSS class
|
|
1134
|
-
*/
|
|
1135
|
-
getStatusClass(status) {
|
|
1136
|
-
if (status && status.startsWith("passed")) return "success"
|
|
1137
|
-
if (status === "flaky") return "warning"
|
|
1138
|
-
if (status === "failed") return "danger"
|
|
1139
|
-
if (status === "skipped") return "info"
|
|
1140
|
-
if (status && status.includes("retry")) return "link"
|
|
1141
|
-
return "dark"
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
* Handle ESC key press to close modal
|
|
1146
|
-
* @param {KeyboardEvent} event - Keyboard event
|
|
1147
|
-
*/
|
|
1148
|
-
handleEscKeyPress = (event) => {
|
|
1149
|
-
if (event.key === "Escape") {
|
|
1150
|
-
this.closeHistoryModal()
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
* Close history modal
|
|
1156
|
-
*/
|
|
1157
|
-
closeHistoryModal() {
|
|
1158
|
-
const historyElement = document.getElementById("historyModal")
|
|
1159
|
-
historyElement.classList.remove("is-active")
|
|
1160
|
-
|
|
1161
|
-
// Remove keyboard event listener
|
|
1162
|
-
document.removeEventListener("keydown", this.handleEscKeyPress)
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
/**
|
|
1166
|
-
* Show history error message
|
|
1167
|
-
* @param {string} modalId - Modal ID
|
|
1168
|
-
*/
|
|
1169
|
-
showHistoryErrorMessage(modalId) {
|
|
1170
|
-
document.getElementById(modalId)?.classList.add("is-active")
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
/**
|
|
1174
|
-
* Close error modal
|
|
1175
|
-
* @param {string} modalId - Modal ID
|
|
1176
|
-
*/
|
|
1177
|
-
closeErrorModal(modalId) {
|
|
1178
|
-
document.getElementById(modalId)?.classList.remove("is-active")
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
/**
|
|
1182
|
-
* ====================================
|
|
1183
|
-
* MOBILE RESPONSIVE MANAGER
|
|
1184
|
-
* ====================================
|
|
1185
|
-
*/
|
|
1186
|
-
class MobileManager {
|
|
1187
|
-
/**
|
|
1188
|
-
* Initialize the mobile manager
|
|
1189
|
-
*/
|
|
1190
|
-
constructor() {
|
|
1191
|
-
this.sidebar = document.getElementById('sidebar');
|
|
1192
|
-
this.init();
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
* Initialize mobile manager
|
|
1197
|
-
*/
|
|
1198
|
-
init() {
|
|
1199
|
-
// Add sidebar toggle button to navbar for mobile
|
|
1200
|
-
const navbar = document.querySelector('.navbar-brand');
|
|
1201
|
-
if (navbar) {
|
|
1202
|
-
const toggleButton = document.createElement('a');
|
|
1203
|
-
toggleButton.className = 'navbar-item sidebar-toggle is-hidden-desktop';
|
|
1204
|
-
toggleButton.innerHTML = '<i class="fa fa-bars"></i>';
|
|
1205
|
-
toggleButton.addEventListener('click', () => this.toggleSidebar());
|
|
1206
|
-
navbar.appendChild(toggleButton);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
// Close sidebar when clicking on a link on mobile
|
|
1210
|
-
const sidebarLinks = document.querySelectorAll('.sidebar-menu-link');
|
|
1211
|
-
sidebarLinks.forEach(link => {
|
|
1212
|
-
link.addEventListener('click', () => {
|
|
1213
|
-
if (window.innerWidth < 769) {
|
|
1214
|
-
this.sidebar.classList.remove('is-active');
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
/**
|
|
1221
|
-
* Toggle sidebar on mobile
|
|
1222
|
-
*/
|
|
1223
|
-
toggleSidebar() {
|
|
1224
|
-
this.sidebar.classList.toggle('is-active');
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
/**
|
|
1229
|
-
* ====================================
|
|
1230
|
-
* INITIALIZATION
|
|
1231
|
-
* ====================================
|
|
1232
|
-
*/
|
|
1233
|
-
// Initialize data
|
|
1234
|
-
const testData = {{{ json results }}};
|
|
1235
|
-
const testHistory = {{{ json testHistories }}};
|
|
1236
|
-
|
|
1237
|
-
// Initialize DOM elements
|
|
1238
|
-
const elements = {
|
|
1239
|
-
testDetails: document.getElementById('testDetails'),
|
|
1240
|
-
summary: document.getElementById('dashboard-section'),
|
|
1241
|
-
themeButton: document.getElementById("toggle-theme"),
|
|
1242
|
-
themeIcon: document.getElementById("theme-icon"),
|
|
1243
|
-
htmlElement: document.documentElement,
|
|
1244
|
-
searchInput: document.querySelector('input[name="search"]'),
|
|
1245
|
-
detailsElements: document.querySelectorAll('details'),
|
|
1246
|
-
filtersDisplay: document.getElementById('selected-filters'),
|
|
1247
|
-
};
|
|
1248
|
-
|
|
1249
|
-
// Initialize managers
|
|
1250
|
-
const sidebarManager = new SidebarManager();
|
|
1251
|
-
const themeManager = new ThemeManager(elements);
|
|
1252
|
-
const testDetailsManager = new TestDetailsManager(elements, testData, testHistory, sidebarManager);
|
|
1253
|
-
const filterManager = new FilterManager(elements);
|
|
1254
|
-
const searchManager = new SearchManager(elements);
|
|
1255
|
-
const historyManager = new HistoryManager(testHistory);
|
|
1256
|
-
const mobileManager = new MobileManager();
|
|
1257
|
-
const sidebarCollapseManager = new SidebarCollapseManager();
|
|
1258
|
-
|
|
1259
|
-
// Attach event listeners
|
|
1260
|
-
testDetailsManager.attachEventListeners();
|
|
1261
|
-
|
|
1262
|
-
// Expose necessary functions to the global scope
|
|
1263
|
-
window.showSummary = () => testDetailsManager.hide();
|
|
1264
|
-
window.openModal = () => document.querySelector("#testImage").classList.add("is-active");
|
|
1265
|
-
window.closeModal = () => document.querySelector("#testImage").classList.remove("is-active");
|
|
1266
|
-
window.closeErrorModal = (modalId) => document.getElementById(modalId).classList.remove("is-active");
|
|
1267
|
-
window.openTraceViewer = (button) => {
|
|
1268
|
-
const tracePath = button.getAttribute("data-trace");
|
|
1269
|
-
if (tracePath) {
|
|
1270
|
-
const normalizedTracePath = tracePath.replace(/\\/g, '/');
|
|
1271
|
-
const baseUrl = Utils.getAdjustedBaseUrl();
|
|
1272
|
-
window.open(`${baseUrl}/trace/index.html?trace=${baseUrl}/${normalizedTracePath}`, "_blank");
|
|
1273
|
-
}
|
|
1274
|
-
};
|
|
1275
|
-
window.openMarkdown = (button) => {
|
|
1276
|
-
const path = button.getAttribute('data-markdown');
|
|
1277
|
-
if (path) {
|
|
1278
|
-
const normalizedPath = path.replace(/\\/g, '/');
|
|
1279
|
-
const baseUrl = Utils.getAdjustedBaseUrl();
|
|
1280
|
-
const fullUrl = `${baseUrl.replace(/\/$/, '')}/${normalizedPath.replace(/^\//, '')}`;
|
|
1281
|
-
window.open(fullUrl, '_blank');
|
|
1282
|
-
}
|
|
1283
|
-
};
|
|
1284
|
-
window.getAdjustedBaseUrl = Utils.getAdjustedBaseUrl;
|
|
1285
|
-
window.copyFilePath = Utils.copyFilePath
|
|
1286
|
-
window.openHistory = () => {
|
|
1287
|
-
historyManager.setCurrentHistory(testDetailsManager.testHistoriesMap, testDetailsManager.testHistoryTitle);
|
|
1288
|
-
historyManager.openHistory();
|
|
1289
|
-
};
|
|
1290
|
-
window.closeHistoryModal = () => historyManager.closeHistoryModal();
|
|
1291
|
-
window.showHistoryErrorMessage = (modalId) => historyManager.showHistoryErrorMessage(modalId);
|
|
1292
|
-
});
|
|
1293
|
-
</script>
|
|
1294
|
-
</body>
|
|
1295
|
-
</html>
|