ortoni-report 2.0.6 → 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.
@@ -1,16 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en" data-theme="{{preferredTheme}}">
3
- <head>
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.6">
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>
@@ -44,506 +34,544 @@
44
34
  </section>
45
35
  <script>
46
36
  document.addEventListener('DOMContentLoaded', () => {
47
- let testData = {{{ json results }}};
48
- const testDetails = document.getElementById('testDetails');
49
- const summary = document.getElementById('summary');
50
- const backButton = document.querySelector('button#back-to-summary');
37
+ const testData = {{{ json results }}};
38
+ const testHistory = {{{ json testHistories }}};
39
+ let testHistoriesMap = {};
40
+ let testHistoryTitle = '';
51
41
 
52
- const themeButton = document.getElementById("toggle-theme");
53
- const preferredTheme = themeButton.getAttribute("data-theme-status");
54
- const themeIcon = document.getElementById("theme-icon");
55
- const htmlElement = document.documentElement;
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
- if (preferredTheme === 'dark') {
58
- htmlElement.setAttribute('data-theme', 'dark');
59
- themeIcon.classList = '';
60
- themeIcon.classList.add("fa", "fa-moon");
61
- } else if (preferredTheme === 'light') {
62
- htmlElement.setAttribute('data-theme', 'light');
63
- themeIcon.classList = '';
64
- themeIcon.classList.add("fa", "fa-sun");
65
- } else {
66
- if (window.matchMedia('(prefers-color-scheme: light)').matches) {
67
- themeIcon.classList.add("fa", "fa-sun");
68
- }
69
- else {
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
- });
85
-
86
- function showSummary() {
87
- summary.style.display = 'block';
88
- testDetails.style.display = 'none';
89
- }
90
- window.showSummary = showSummary;
91
-
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);
101
- let currentScreenshotIndex = 0;
102
- function changeScreenshot(direction) {
103
- const screenshots = test.screenshots;
104
- currentScreenshotIndex = (currentScreenshotIndex + direction + screenshots.length) % screenshots.length;
105
- updateScreenshot();
106
- }
107
-
108
- function gotoScreenshot(index) {
109
- currentScreenshotIndex = index;
110
- updateScreenshot();
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');
111
66
  }
67
+ };
112
68
 
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];
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);
117
88
 
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();
129
-
130
- if (test.status.startsWith('passed')) {
131
- statusClass = 'success';
132
- statusIcon = 'check-circle';
133
- } else if (test.status === 'flaky') {
134
- statusClass = 'warning';
135
- statusIcon = 'exclamation-triangle';
136
- } else if (test.status === 'failed') {
137
- statusClass = 'danger';
138
- statusIcon = 'times-circle';
139
- } else {
140
- statusClass = 'info';
141
- statusIcon = 'question-circle';
142
- }
143
- testDetails.innerHTML = `
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>
165
- </div>
166
- </div>
167
- </div>
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>
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>
177
101
  </div>
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>
178
138
  </div>
179
- </div>
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>
192
139
  </div>
193
- </div>
140
+ <div class="content-wrapper">
141
+ ${this.renderTestContent(test)}
142
+ </div>
143
+ `;
194
144
 
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 ? `
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
- <div class="modal-background"></div>
204
- <div class="modal-content">
205
- <p class="image">
206
- <img id="screenshot-modal-img" src="${test.screenshots[0]}" alt="Screenshot">
207
- </p>
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
- <button onclick="closeModal()" class="modal-close is-large" aria-label="close"></button>
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
- <figure class="image">
213
- <img id="screenshot-main-img" onclick="openModal()" src="${test.screenshots[0]}" alt="Screenshot">
214
- </figure>
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" 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>
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="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>
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>
239
+ <span class="has-text-white pl-2">View Trace</span>
240
+ </button>
249
241
  </div>
250
242
  ` : ''}
251
243
  </div>
252
- </div>` : ''}
253
- ${test.annotations.length || test.testTags.length > 0 ? `
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 `
254
252
  <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('')}
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('')}
264
273
  </div>
265
- </div>` : ""}
266
- ${test.annotations
267
- .filter(annotation => annotation !== null && annotation !== undefined)
268
- .map(annotation => `
269
- <div class="mb-4">
270
- ${annotation?.type ? `<strong class="has-text-link">Type: </strong><span>${annotation.type}</span>` : ''}
271
- <br>
272
- ${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation.description}</span>` : ''}
273
- </div>
274
- `).join('')}
275
274
  </div>
276
275
  </div>
277
- </div>
278
- ` : ''}
279
- ${test.steps.length > 0 ? `
276
+ `;
277
+ },
278
+ renderSteps(test) {
279
+ if (test.steps.length === 0) return '';
280
+ return `
280
281
  <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>
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>
287
289
  </div>
288
290
  </div>
289
- </div>
290
- ` : ''}
291
- ${test.errors.length ? `
291
+ `;
292
+ },
293
+ renderErrors(test) {
294
+ if (!test.errors.length) return '';
295
+ return `
292
296
  <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">
298
- <pre><code class="data-lang=js">${test.errors.join('\n')}</code></pre>
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>
299
304
  </div>
300
305
  </div>
301
- </div>
302
- ` : ''}
303
- ${test.logs ? `
306
+ `;
307
+ },
308
+ renderLogs(test) {
309
+ if (!test.logs) return '';
310
+ return `
304
311
  <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">
310
- <pre>${test.logs}</pre>
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>
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>
325
319
  </div>
326
- <button class="modal-close is-large" aria-label="close" onclick="closeVideo()"></button>
327
320
  </div>
328
- ` : ''}
329
- </div>`
330
- if (test.screenshots.length > 0) {
331
- document.querySelector('.pagination-previous').addEventListener('click', () => changeScreenshot(-1));
332
- document.querySelector('.pagination-next').addEventListener('click', () => changeScreenshot(1));
333
- document.querySelectorAll('.pagination-link').forEach((link, index) => {
334
- link.addEventListener('click', () => gotoScreenshot(index));
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);
335
346
  });
336
- }
337
- const stepDetailsDiv = document.getElementById('stepDetails');
338
- if (stepDetailsDiv) {
339
- const stepsList = attachSteps(test);
340
- const detail = document.getElementById("stepopen");
341
- stepDetailsDiv.appendChild(stepsList);
342
- }
343
- }
344
- function attachSteps(test) {
345
- const stepsList = document.createElement("ul");
346
- stepsList.setAttribute("id", "steps");
347
- stepsList.innerHTML = '';
348
- test.steps.forEach(step => {
349
- const li = document.createElement('li');
350
- li.innerHTML = `<strong class="${step.snippet ? 'has-text-danger' : ''}">${step.title}</strong>`;
351
- if (step.snippet) {
352
- const pre = document.createElement('pre');
353
- const code = document.createElement('code');
354
- const locationText = step.location ? `\n\nat: ${step.location}` : '';
355
- code.innerHTML = `${step.snippet}${locationText}`;
356
- code.setAttribute('data-lang', 'js');
357
- pre.appendChild(code);
358
- 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);
359
368
  }
360
- stepsList.appendChild(li);
361
- });
362
- return stepsList;
363
- }
364
- function openModal() {
365
- let modal = document.querySelector("#testImage");
366
- modal.classList.add("is-active");
367
- }
368
- function openVideo() {
369
- let modal = document.querySelector("#testVideo");
370
- modal.classList.add("is-active");
371
- }
372
- function closeVideo() {
373
- let modal = document.querySelector("#testVideo");
374
- modal.classList.remove("is-active");
375
- }
376
- function closeModal() {
377
- let modal = document.querySelector("#testImage");
378
- modal.classList.remove("is-active");
379
- }
380
-
381
- window.openModal = openModal;
382
- window.openVideo = openVideo;
383
- window.closeVideo = closeVideo;
384
- window.closeModal = closeModal;
385
- window.openTraceViewer = openTraceViewer;
386
-
387
- document.addEventListener('keydown', (event) => {
388
- if (event.key === "Escape") {
389
- 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
+ });
390
385
  }
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
- }
386
+ };
402
387
 
403
- function attachEventListeners() {
404
- const checkboxes = document.querySelectorAll('#select-filter input[type="checkbox"]');
405
- checkboxes.forEach(checkbox => {
406
- checkbox.addEventListener('change', applyFilters);
407
- });
408
- const testItems = document.querySelectorAll('[data-test-id]');
409
- testItems.forEach(item => {
410
- item.addEventListener('click', () => {
411
- testItems.forEach(i => i.classList.remove('listselected'));
412
- item.classList.add('listselected');
413
- const testId = item.getAttribute('data-test-id');
414
- const test = testData[testId];
415
- 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());
416
396
  });
417
- });
418
- const filters = document.querySelectorAll('.filter');
419
- filters.forEach(filter => {
420
- filter.addEventListener('click', () => {
421
- const status = filter.getAttribute('data-status');
422
- filters.forEach(f => {
423
- if (f.getAttribute('data-status')) {
424
- f.classList.remove('active');
425
- }
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();
426
404
  });
427
- filter.classList.add('active');
428
- applyFilters();
429
405
  });
430
- });
431
- }
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';
432
411
 
433
- function applyFilters() {
434
- const selectedCheckboxes = document.querySelectorAll('#select-filter input[type="checkbox"]:checked');
435
- const activeFilter = document.querySelector('.filter.active');
436
- const selectedStatus = activeFilter ? activeFilter.getAttribute('data-status') : 'all';
437
- const selectedProjects = [];
438
- const selectedTags = [];
439
- selectedCheckboxes.forEach(checkbox => {
440
- if (checkbox.getAttribute('data-filter-type') === 'project') {
441
- selectedProjects.push(checkbox.value.trim());
442
- } else {
443
- selectedTags.push(checkbox.value.trim());
444
- }
445
- });
446
- const detailsElements = document.querySelectorAll('.sidebar details');
447
- detailsElements.forEach(details => {
448
- let shouldShowDetails = false;
449
- const items = details.querySelectorAll('div[data-test-id]');
450
- items.forEach(item => {
451
- const testTags = item.getAttribute('data-test-tags').trim().split(' ').filter(Boolean);
452
- const projectName = item.getAttribute('data-project-name').trim();
453
- const testStatus = item.getAttribute('data-test-status').trim();
454
- const matchesProject = selectedProjects.length === 0 || selectedProjects.includes(projectName);
455
- const matchesTags = selectedTags.length === 0 || selectedTags.every(tag => testTags.includes(tag));
456
- const matchesStatus = (selectedStatus === 'all' && testStatus !== 'skipped') ||
457
- (selectedStatus !== 'all' && (
458
- testStatus === selectedStatus ||
459
- (selectedStatus === 'failed' && (testStatus === 'failed' || testStatus === 'timedOut')) ||
460
- (selectedStatus === 'retry' && testStatus.includes('retry')) ||
461
- (selectedStatus === 'flaky' && testStatus.includes('flaky'))
462
- ));
463
- if (matchesProject && matchesTags && matchesStatus) {
464
- item.classList.remove('is-hidden');
465
- shouldShowDetails = true;
466
- } else {
467
- item.classList.add('is-hidden');
468
- }
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);
469
424
  });
470
- details.open = shouldShowDetails;
471
- details.classList.toggle('is-hidden', !shouldShowDetails);
472
- });
473
- updateSelectedFiltersDisplay(selectedProjects, selectedTags, selectedStatus);
474
- }
475
425
 
476
- function updateSelectedFiltersDisplay(projects, tags, status) {
477
- const filtersDisplay = document.getElementById('selected-filters');
478
- filtersDisplay.innerHTML = '';
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();
479
436
 
480
- if (projects.length > 0) {
481
- filtersDisplay.innerHTML += `<span> Projects: ${projects.join(', ')}</span>`;
482
- }
483
- if (tags.length > 0) {
484
- filtersDisplay.innerHTML += `<span> Tags: ${tags.join(', ')}</span>`;
485
- }
486
- if (status !== 'all') {
487
- filtersDisplay.innerHTML += `<span> Status: ${status}</span>`;
488
- }
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);
489
440
 
490
- if (filtersDisplay.innerHTML === '') {
491
- filtersDisplay.innerHTML = '<span>All Tests</span>';
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';
492
456
  }
493
- }
494
- const searchInput = document.querySelector('input[name="search"]');
495
- const detailsElements = document.querySelectorAll('details');
496
- function filterTests(search) {
497
- const searchTerm = search.toLowerCase();
498
- const testItems = document.querySelectorAll('[data-test-id]');
457
+ };
499
458
 
500
- if (searchTerm) {
501
- detailsElements.forEach(detail => {
502
- detail.open = false; // Collapse all details initially
503
- });
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]');
504
466
 
505
- testItems.forEach(item => {
506
- const testTitle = item.textContent.toLowerCase();
507
- if (testTitle.includes(searchTerm)) {
508
- item.style.display = 'flex'; // Show matching test item
467
+ elements.detailsElements.forEach(detail => detail.open = !!searchTerm);
509
468
 
510
- let parent = item.parentElement;
511
- while (parent && parent.tagName !== 'ASIDE') {
512
- if (parent.tagName === 'DETAILS') {
513
- parent.open = true;
514
- }
515
- parent = parent.parentElement;
516
- }
517
- } else {
518
- item.style.display = 'none';
519
- }
520
- });
521
- } else {
522
469
  testItems.forEach(item => {
523
- item.style.display = 'flex';
524
- });
525
- detailsElements.forEach(detail => {
526
- 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);
527
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
+ };
528
488
  }
529
- }
530
- function debounce(func, wait) {
531
- let timeout;
532
- return function (...args) {
533
- clearTimeout(timeout);
534
- timeout = setTimeout(() => func.apply(this, args), wait);
535
- };
536
- }
489
+ };
537
490
 
538
- const debouncedSearch = debounce((event) => {
539
- filterTests(event.target.value);
540
- }, 300);
491
+ // Initialize all managers
492
+ themeManager.init();
493
+ testDetailsManager.attachEventListeners();
494
+ filterManager.init();
495
+ searchManager.init();
541
496
 
542
- searchInput.addEventListener('input', debouncedSearch);
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');
522
+
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
+ }
543
542
 
544
- attachEventListeners();
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
+ };
545
574
  });
546
575
  </script>
547
576
  </body>
548
-
549
577
  </html>