ortoni-report 1.1.2 → 1.1.4

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,59 +1,45 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" data-theme="{{preferredTheme}}">
3
+
3
4
  <head>
4
5
  <meta charset="UTF-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
7
  <meta name="description" content="Plawright HTML report by LetCode Koushik">
7
8
  <title>Playwright Test Report</title>
8
9
  <link rel="icon" href="node_modules/ortoni-report/dist/icon/32.png" type="image/x-icon">
9
- <link rel="stylesheet" href="node_modules/ortoni-report/dist/css/pico.css">
10
- <style>
11
- body {
12
- zoom: 0.9;
13
- }
14
-
15
- main.container {
16
- display: grid;
17
- grid-template-columns: 1fr 2fr;
18
- gap: 20px;
19
- }
20
-
21
- .header {
22
- display: grid;
23
- grid-template-columns: 1fr 2fr;
24
- grid-column-gap: 20px;
25
- grid-row-gap: 20px;
26
- justify-items: stretch;
27
- align-items: center;
28
- justify-content: space-evenly;
10
+ <link rel="stylesheet" href="node_modules/ortoni-report/dist/css/main.css">
11
+ </head>
12
+ <style>
13
+ #summary, #testDetails, button#back-to-summary {
14
+ transition: opacity 0.2s ease-in-out;
29
15
  }
30
-
31
- .highlight {
32
- background-color: var(--pico-primary-background);
16
+ .filter.active {
17
+ background-color: var(--bulma-background-active);
33
18
  }
34
19
 
35
- .filter.active {
36
- background-color: var(--pico-primary-hover-background);
37
- color: var(--pico-primary-hover-color);
20
+ ::-webkit-scrollbar {
21
+ width: 0px;
22
+ background-color: transparent;
38
23
  }
39
24
 
40
-
41
- .text-success {
42
- color: #28a745;
25
+ ::-webkit-scrollbar-thumb {
26
+ background-color: var(--pico-secondary-background);
27
+ ;
28
+ border-radius: 0px;
43
29
  }
44
30
 
45
- .text-failure {
46
- color: #dc3545;
31
+ ::-webkit-scrollbar-thumb:hover {
32
+ background-color: var(--pico-secondary-background);
33
+ ;
47
34
  }
48
35
 
49
- .text-skip {
50
- color: #d5d4a1;
36
+ ::-webkit-scrollbar-track {
37
+ background-color: #f1f1f1;
51
38
  }
52
39
 
53
- .text-flaky {
54
- color: #d5d4a1;
40
+ ::-webkit-scrollbar-corner {
41
+ background-color: #fff;
55
42
  }
56
-
57
43
  div#testDetails {
58
44
  position: sticky;
59
45
  top: 0;
@@ -62,123 +48,150 @@
62
48
 
63
49
  .sidebar {
64
50
  overflow-y: auto;
65
- max-height: calc(100vh - 100px); /* Adjust as needed */
66
- border-right: 1px solid #ddd;
67
- padding-right: 10px;
68
- }
69
-
70
- pre {
71
- background-color: #f8f9fa;
72
- padding: 1rem;
73
- border-radius: 0.5rem;
74
- overflow-x: auto;
51
+ max-height: calc(100vh - 100px);
75
52
  }
76
53
 
77
- .back-button {
78
- display: none;
79
- margin-bottom: 1rem;
80
- }
81
- .clickable {
54
+ aside li {
82
55
  cursor: pointer;
83
- transition: background-color 0.3s;
84
- }
85
-
86
- .clickable:hover {
87
- background-color: #607D8B;
88
56
  }
89
- li[data-test-id] {
90
- overflow: hidden;
91
- text-overflow: ellipsis;
92
- white-space: nowrap;
93
- max-width: 25ch;
94
- }
95
- li img{
96
- max-width: 8%
97
- }
98
- summary img{
99
- max-width: 5%
100
- }
101
- dialog article {
102
- max-width: 768px;
103
- }
104
- ::-webkit-scrollbar {
105
- width: 0px;
106
- background-color: transparent;
57
+ details summary {
58
+ cursor: pointer;
59
+ display: flex;
60
+ justify-content: space-between;
61
+ align-items: center;
107
62
  }
108
-
109
- ::-webkit-scrollbar-thumb {
110
- background-color: var(--pico-secondary-background);;
111
- border-radius: 0px;
63
+ details summary::after {
64
+ content: '';
65
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
66
+ transform:rotate(-90deg);
67
+ background-size: 1em;
68
+ background-repeat: no-repeat;
69
+ background-position:right-center;
70
+ width: 1em;
71
+ height: 1em;
72
+ transition: transform 0.2s ease-in-out;
112
73
  }
113
-
114
- ::-webkit-scrollbar-thumb:hover {
115
- background-color: var(--pico-secondary-background);;
74
+ details[open] > summary::after {
75
+ transform: rotate(0deg);
116
76
  }
117
77
 
118
- ::-webkit-scrollbar-track {
119
- background-color: #f1f1f1;
120
- }
78
+ </style>
121
79
 
122
- ::-webkit-scrollbar-corner {
123
- background-color: #fff;
124
- }
125
- </style>
126
- </head>
127
80
  <body>
128
- <header class="container">
129
- <div class="header">
130
- {{!-- Custom Project Name --}}
131
- <div>
132
- {{#if projectName}}<h1>{{projectName}}</h1>{{/if}}
133
- </div>
134
- <div>
135
- <input name="search" type="search" placeholder="Search by test title" />
81
+ <!-- Header -->
82
+ <section class="section">
83
+ <header class="container">
84
+ <div class="columns is-vcentered">
85
+ <div class="column is-one-third">
86
+ {{!-- Custom Project Name --}}
87
+ {{#if projectName}}<h1 class="title">{{projectName}}</h1>{{/if}}
88
+ </div>
89
+ <div class="column">
90
+ <div class="control">
91
+ <input class="input" name="search" type="search" placeholder="Search by test title" />
92
+ </div>
93
+ </div>
94
+ <div class="column is-1">
95
+ <div class="control">
96
+ <button id="toggle-theme" data-theme-status="{{preferredTheme}}" class="button">Theme</button>
97
+ </div>
98
+ </div>
136
99
  </div>
137
- </div>
138
- </header>
139
- <main class="container">
140
- {{!-- Test Scripts --}}
141
- <aside class="sidebar">
142
- <h2>Tests</h2>
143
- <div>
144
- {{#each groupedResults}}
145
- <details>
146
- <summary>
147
- <img src="node_modules/ortoni-report/dist/icon/file.png" alt="retry">
148
- <span>{{@key}}<span>
149
- </summary>
150
- <ul>
151
- {{#each this}}
152
- <details>
153
- <summary>
154
- <img src="node_modules/ortoni-report/dist/icon/test.png" alt="retry">
155
- <span>{{@key}}<span>
100
+ </header>
101
+ </section>
102
+
103
+ <!-- Main Content -->
104
+ <section class="section">
105
+ <main class="container">
106
+ <div class="columns">
107
+ <aside class="column is-one-third sidebar">
108
+ <div class="columns">
109
+ <div class="column">
110
+ <h2 class="title is-4">Tests</h2>
111
+ </div>
112
+ <div class="column">
113
+ <div class="select is-pulled-right">
114
+ <select id="project-filter">
115
+ <option value="all">All Projects</option>
116
+ {{#each projects}}
117
+ <option value="{{this}}">{{this}}</option>
118
+ {{/each}}
119
+ </select>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ <div class="content">
124
+ {{#each groupedResults}}
125
+ <details class="box">
126
+ <summary class="is-size-5 has-icon-right">
127
+ <div class="icon-text">
128
+ <span class="icon has-text-info">
129
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/file.png" alt="file name">
130
+ </span>
131
+ <span>{{@key}}</span>
132
+ </div>
156
133
  </summary>
157
134
  <ul>
158
135
  {{#each this}}
159
136
  <details>
160
- <summary>{{@key}}</summary>
137
+ <summary class="is-size-5">
138
+ <div class="icon-text">
139
+ <span class="icon has-text-info">
140
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/test.png" alt="test name">
141
+ </span>
142
+ <span>{{@key}}</span>
143
+ </div>
144
+ </summary>
161
145
  <ul>
162
146
  {{#each this}}
163
- <li data-suite-name="{{suite}}" data-project-name="{{projectName}}" data-test-id="{{index}}" data-test-status="{{status}}">
164
- {{#if isRetry}}
165
- <img src="node_modules/ortoni-report/dist/icon/retry.png" alt="Retry">
166
- {{/if}}
167
- {{#if (eq status "passed")}}
168
- <img src="node_modules/ortoni-report/dist/icon/pass.png" alt="Pass">
169
- {{/if}}
170
- {{#if (eq status "failed")}}
171
- <img src="node_modules/ortoni-report/dist/icon/fail.png" alt="Fail">
172
- {{else}}
173
- {{#if (eq status "skipped")}}
174
- <img src="node_modules/ortoni-report/dist/icon/skip.png" alt="Skip">
175
- {{/if}}
176
- {{/if}}
177
- {{#if (eq status "timedOut")}}
178
- <img src="node_modules/ortoni-report/dist/icon/timeout.png" alt="timedOut">
179
- {{/if}}
180
- <span>{{title}}</span>
181
- </li>
147
+ <details>
148
+ <summary>{{@key}}</summary>
149
+ <ul>
150
+ {{#each this}}
151
+ <li class="media" data-suite-name="{{suite}}"
152
+ data-project-name="{{projectName}}" data-test-id="{{index}}"
153
+ data-test-status="{{status}} {{retry}}">
154
+ <div class="icon-text">
155
+ {{#if isRetry}}
156
+ <span class="icon has-text-info">
157
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/retry.png"
158
+ alt="Retry"></span>
159
+ {{/if}}
160
+ {{#if (eq status "passed")}}
161
+ <span class="icon has-text-info">
162
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/pass.png"
163
+ alt="Pass"></span>
164
+ {{/if}}
165
+ {{#if (eq status "failed")}}
166
+ <span class="icon has-text-info">
167
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/fail.png"
168
+ alt="Fail"></span>
169
+ {{else}}
170
+ {{#if (eq status "skipped")}}
171
+ <span class="icon has-text-info">
172
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/skip.png"
173
+ alt="Skip"></span>
174
+ {{/if}}
175
+ {{/if}}
176
+ {{#if (eq status "timedOut")}}
177
+ <span class="icon has-text-info">
178
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/timeout.png"
179
+ alt="timedOut"></span>
180
+ {{/if}}
181
+ {{#if (eq status "flaky")}}
182
+ <span class="icon has-text-info">
183
+ <img class="image is-16x16" src="node_modules/ortoni-report/dist/icon/flaky.png"
184
+ alt="flaky"></span>
185
+ {{/if}}
186
+ <span>{{title}}</span>
187
+ {{#if retryCount}}
188
+ <p>Retry Count: {{retryCount}}</p>
189
+ {{/if}}
190
+ </div>
191
+ </li>
192
+ {{/each}}
193
+ </ul>
194
+ </details>
182
195
  {{/each}}
183
196
  </ul>
184
197
  </details>
@@ -186,75 +199,117 @@
186
199
  </ul>
187
200
  </details>
188
201
  {{/each}}
189
- </ul>
190
- </details>
191
- {{/each}}
192
- </div>
193
- </aside>
194
- <section>
195
- {{!-- Overall summary --}}
196
- <div id="summary">
197
- <section class="grid">
198
- <div>
199
- <article class="clickable filter" data-status="all">
200
- <header>All Tests</header>
201
- <p>{{totalCount}}</p>
202
- </article>
203
- </div>
204
- <div>
205
- <article class="clickable filter" data-status="passed">
206
- <header>Passed</header>
207
- <p class="text-success">{{passCount}}</p>
208
- </article>
209
202
  </div>
210
- <div>
211
- <article class="clickable filter" data-status="failed">
212
- <header>Failed</header>
213
- <p class="text-failure">{{failCount}}</p>
214
- </article>
215
- </div>
216
- </section>
217
- <section class="grid">
218
- <div>
219
- <article class="clickable filter" data-status="skipped">
220
- <header>Skipped</header>
221
- <p class="text-skip">{{skipCount}}</p>
222
- </article>
223
- </div>
224
- <div>
225
- <article class="clickable filter" data-status="flaky">
226
- <header>Flaky</header>
227
- <p class="text-flaky">{{flakyCount}}</p>
228
- </article>
229
- </div>
230
- </section>
231
- {{!-- Suite details with chart --}}
232
- <section>
233
- <article>
234
- <header>Suite</header>
235
- <div class="grid">
236
- <div>
237
- {{#if authorName}}<h4>Author: {{authorName}}</h4>{{/if}}
238
- {{#if testType}}<h4>Test Type: {{testType}}</h4>{{/if}}
239
- {{#if totalDuration}}<h4>Duration: {{totalDuration}}</h4>{{/if}}
240
- <h4>Success Rate: {{successRate}} %</h4>
241
- <h4>Last Run: {{lastRunDate}}</h4>
203
+ </aside>
204
+
205
+ <section class="column">
206
+ {{!-- Overall summary --}}
207
+ <div id="summary">
208
+ <div class="columns is-multiline">
209
+ <div class="column is-one-third">
210
+ <div class="card is-clickable filter" data-status="all">
211
+ <header class="card-header has-text-centered">
212
+ <p class="card-header-title">All Tests</p>
213
+ </header>
214
+ <div class="card-content">
215
+ <div class="content">
216
+ <p class="has-text-centered has-text-primary">{{totalCount}}</p>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ <div class="column is-one-third">
222
+ <div class="card is-clickable filter" data-status="passed">
223
+ <header class="card-header has-text-centered">
224
+ <p class="card-header-title">Passed</p>
225
+ </header>
226
+ <div class="card-content">
227
+ <div class="content">
228
+ <p class="has-text-centered has-text-success">{{passCount}}</p>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ <div class="column is-one-third">
234
+ <div class="card is-clickable filter" data-status="failed">
235
+ <header class="card-header">
236
+ <p class="card-header-title has-text-centered">Failed</p>
237
+ </header>
238
+ <div class="card-content">
239
+ <div class="content">
240
+ <p class="has-text-centered has-text-danger">{{failCount}}</p>
241
+ </div>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ </div>
246
+ <div class="columns is-multiline">
247
+ <div class="column is-one-third">
248
+ <div class="card is-clickable filter" data-status="skipped">
249
+ <header class="card-header has-text-centered">
250
+ <p class="card-header-title">Skipped</p>
251
+ </header>
252
+ <div class="card-content">
253
+ <div class="content">
254
+ <p class="has-text-centered has-text-info">{{skipCount}}</p>
255
+ </div>
256
+ </div>
257
+ </div>
242
258
  </div>
243
- <div class="chart-container">
244
- <canvas id="testChart"></canvas>
259
+ <div class="column is-one-third">
260
+ <div class="card is-clickable filter" data-status="flaky">
261
+ <header class="card-header has-text-centered">
262
+ <p class="card-header-title">Flaky</p>
263
+ </header>
264
+ <div class="card-content">
265
+ <div class="content">
266
+ <p class="has-text-centered has-text-warning">{{flakyCount}}</p>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ <div class="column is-one-third">
272
+ <div class="card is-clickable filter" data-status="retry">
273
+ <header class="card-header has-text-centered">
274
+ <p class="card-header-title">Retry</p>
275
+ </header>
276
+ <div class="card-content">
277
+ <div class="content">
278
+ <p class="has-text-centered has-text-info">{{retryCount}}</p>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ {{!-- Suite details with chart --}}
285
+ <div class="box">
286
+ <header class="has-text-centered title is-4">Suite</header>
287
+ <div class="columns">
288
+ <div class="column is-half">
289
+ {{#if authorName}}<h4>Author: {{authorName}}</h4>{{/if}}
290
+ {{#if testType}}<h4>Test Type: {{testType}}</h4>{{/if}}
291
+ {{#if totalDuration}}<h4>Duration: {{totalDuration}}</h4>{{/if}}
292
+ <h4>Success Rate: {{successRate}} %</h4>
293
+ <h4>Last Run: {{lastRunDate}}</h4>
294
+ </div>
295
+ <div class="column is-half">
296
+ <div class="chart-container">
297
+ <canvas id="testChart"></canvas>
298
+ </div>
299
+ </div>
245
300
  </div>
246
301
  </div>
247
- </article>
302
+ </div>
303
+ {{!-- Test details --}}
304
+ <div id="testDetails" style="display: none;">
305
+ <!-- Back button should be outside the dynamic content -->
306
+ <button class="button content" id="back-to-summary"onclick="showSummary()">Back to Summary</button>
307
+ <!-- Test Details will be displayed here -->
308
+ </div>
248
309
  </section>
249
310
  </div>
250
- {{!-- Test details --}}
251
- <div id="testDetails" style="display: none;">
252
- <!-- Back button should be outside the dynamic content -->
253
- <button class="back-button" onclick="showSummary()">Back to Summary</button>
254
- <!-- Test Details will be displayed here -->
255
- </div>
256
- </section>
257
- </main>
311
+ </main>
312
+ </section>
258
313
  <script src="node_modules/ortoni-report/dist/utils/chart.js"></script>
259
314
  <script>
260
315
  function escapeHtml(unsafe) {
@@ -270,189 +325,272 @@
270
325
  });
271
326
  }
272
327
 
273
- document.addEventListener('DOMContentLoaded', () => {
274
- const testData = {{{ json results }}};
275
- const testDetails = document.getElementById('testDetails');
276
- const summary = document.getElementById('summary');
277
- const backButton = document.querySelector('.back-button');
278
- let highlightedItem = null;
279
-
280
- function showSummary() {
281
- summary.style.display = 'block';
282
- testDetails.style.display = 'none';
283
- backButton.style.display = 'none';
284
- if (highlightedItem) {
285
- highlightedItem.classList.remove('highlight');
286
- }
328
+ document.addEventListener('DOMContentLoaded', () => {
329
+ const testData = {{{ json results }}};
330
+ const testDetails = document.getElementById('testDetails');
331
+ const summary = document.getElementById('summary');
332
+ const backButton = document.querySelector('button#back-to-summary');
333
+
334
+ const themeButton = document.getElementById("toggle-theme");
335
+ const preferredTheme = themeButton.getAttribute("data-theme-status");
336
+ const htmlElement = document.documentElement;
337
+
338
+ if (preferredTheme === 'dark') {
339
+ htmlElement.setAttribute('data-theme', 'dark');
340
+ themeButton.classList.add('is-dark');
341
+ themeButton.textContent = 'Dark';
342
+ } else if (preferredTheme === 'light') {
343
+ htmlElement.setAttribute('data-theme', 'light');
344
+ themeButton.classList.add('is-light');
345
+ themeButton.textContent = 'Light';
346
+ }
347
+
348
+ themeButton.addEventListener('click', () => {
349
+ const currentTheme = htmlElement.getAttribute('data-theme');
350
+ const newTheme = currentTheme === 'light' ? 'dark' : 'light';
351
+ htmlElement.setAttribute('data-theme', newTheme);
352
+ if (newTheme === 'dark') {
353
+ themeButton.classList.remove('is-light');
354
+ themeButton.classList.add('is-dark');
355
+ themeButton.textContent = 'Dark';
356
+ } else {
357
+ themeButton.classList.remove('is-dark');
358
+ themeButton.classList.add('is-light');
359
+ themeButton.textContent = 'Light';
287
360
  }
361
+ });
288
362
 
289
- window.showSummary = showSummary;
363
+ function showSummary() {
364
+ summary.style.display = 'block';
365
+ testDetails.style.display = 'none';
366
+ backButton.style.display = 'none';
367
+ }
368
+
369
+ window.showSummary = showSummary;
290
370
 
291
- function displayTestDetails(test) {
371
+ function displayTestDetails(test) {
372
+ const summary = document.getElementById('summary');
373
+ const testDetails = document.getElementById('testDetails');
374
+ const backButton = document.querySelector('button#back-to-summary');
292
375
  summary.style.display = 'none';
376
+ testDetails.style.opacity = '0';
293
377
  testDetails.style.display = 'block';
294
- backButton.style.display = 'block';
295
- testDetails.innerHTML = `
296
- <button class="back-button" style="display: block" onclick="showSummary()">Back to Summary</button>
297
- <h3>${test.title}</h3>
298
- <div class="grid">
299
- <div>
300
- <h4>Status</h4>
301
- <p class="${test.status === 'passed' ? 'text-success' : 'text-failure'}">${test.status.toUpperCase()}</p>
302
- ${test.duration != '0s' ? `
303
- <h4>Duration</h4>
304
- <p>${test.duration}</p>` : ""}
305
- </div>
306
- <div>
307
- ${test.screenshotPath ? `
308
- <img src="${test.screenshotPath}" data-target="modal-example" onclick="toggleModal(event)" alt="Screenshot">
309
- <dialog id="modal-example">
310
- <article>
311
- <header>
312
- <button aria-label="Close" rel="prev" data-target="modal-example" onclick="toggleModal(event)"></button>
313
- <p>
314
- <strong>Screenshot</strong>
378
+ setTimeout(() => {
379
+ testDetails.style.opacity = '1';
380
+ backButton.style.opacity = '1';
381
+ }, 50);
382
+
383
+
384
+ let statusClass = '';
385
+ let statusText = test.status.toUpperCase();
386
+ if (test.status.startsWith('passed')) {
387
+ statusClass = 'tag is-success';
388
+ } else if (test.status === 'flaky') {
389
+ statusClass = 'tag is-warning';
390
+ } else if (test.status === 'failed') {
391
+ statusClass = 'tag is-danger';
392
+ } else {
393
+ statusClass = 'tag is-info';
394
+ }
395
+
396
+ testDetails.innerHTML = `
397
+ <button class="button content" id="back-to-summary" style="display: block" onclick="showSummary()">Back to Summary</button>
398
+ <h3 class="title is-3 has-text-centered">${test.title}</h3>
399
+ <div class="columns">
400
+ <div class="column content">
401
+ <h4 class="title is-4">Status</h4>
402
+ <p class="${statusClass}">${statusText}</p>
403
+ ${test.duration.length > 0 ? `
404
+ <h4 class="title is-4">Duration</h4>
405
+ <p class="${statusClass}">${test.duration}</p>` : ""}
406
+ </div>
407
+ <div class="column content">
408
+ ${test.screenshotPath ? `
409
+ <div id="modal-js-example" class="modal">
410
+ <div class="modal-background"></div>
411
+ <div class="modal-content">
412
+ <p class="image is-16by9">
413
+ <img src="data:image/png;base64, ${test.screenshotPath}" alt="Screenshot">
315
414
  </p>
316
- </header><p>
317
- <img src="${test.screenshotPath}" alt="Screenshot"></p>
318
- </article>
319
- </dialog>` : ''}
415
+ </div>
416
+ <button onclick="closeModal()" class="modal-close is-large" aria-label="close"></button>
320
417
  </div>
418
+ <figure class="image is-16by9">
419
+ <img onclick="openModal()" src="data:image/png;base64, ${test.screenshotPath}" alt="Screenshot">
420
+ </figure>` : ''}
321
421
  </div>
322
- <div>
422
+ </div>
423
+ <div class="content">
424
+ ${test.steps.length > 0 ? `
425
+ <details id="stepopen">
426
+ <summary><h4 class="title is-4">Steps</h4></summary>
427
+ <span id="stepDetails" class="content"></span>
428
+ </details>
429
+ `: ``}
430
+ </div class="content">
431
+ <div>
323
432
  ${test.errors.length ? `
324
- <h4>Errors</h4>
325
- <div class="grid">
326
- <pre>${escapeHtml(test.errors.join('\n'))}</pre></div>
327
- ` : ''}
328
- </div>
329
- <div>
433
+ <h4 class="title is-4">Errors</h4>
434
+ <div class="content">
435
+ <pre>${escapeHtml(test.errors.join('\n'))}</pre>
436
+ </div>` : ''}
437
+ </div>
438
+ <div>
330
439
  ${test.logs ? `
331
- <h4>Logs</h4>
332
- <div class="grid">
333
- <pre>${escapeHtml(test.logs)}</pre></div>
334
- ` : ''}
335
- </div>
336
- `;
440
+ <h4 class="title is-4">Logs</h4>
441
+ <div class="box">
442
+ <pre>${escapeHtml(test.logs)}</pre>
443
+ </div>` : ''}
444
+ </div>
445
+ `;
446
+
447
+ const stepDetailsDiv = document.getElementById('stepDetails');
448
+ if(stepDetailsDiv){
449
+ const stepsList = attachSteps(test);
450
+ const detail = document.getElementById("stepopen");
451
+ detail.setAttribute("open", "");
452
+ stepDetailsDiv.appendChild(stepsList);
337
453
  }
454
+ }
338
455
 
339
- function attachEventListeners() {
340
- const testItems = document.querySelectorAll('[data-test-id]');
341
- testItems.forEach(item => {
342
- item.addEventListener('click', () => {
343
- const testId = item.getAttribute('data-test-id');
344
- const test = testData[testId];
345
- displayTestDetails(test);
346
- if (highlightedItem) {
347
- highlightedItem.classList.remove('highlight');
348
- }
349
- item.classList.add('highlight');
350
- highlightedItem = item;
351
- });
352
- });
456
+ function attachSteps(test) {
457
+ const stepsList = document.createElement("ul");
458
+ stepsList.setAttribute("id", "steps");
459
+ stepsList.innerHTML = '';
460
+ test.steps.forEach(step => {
461
+ const li = document.createElement('li');
462
+ li.innerHTML = `<strong class="${step.error ? 'has-text-danger' : ''}">${step.title}</strong>`;
463
+ stepsList.appendChild(li);
464
+ });
465
+ return stepsList;
466
+ }
353
467
 
354
- // Event listeners for the filter articles
355
- const filters = document.querySelectorAll('.filter');
356
- filters.forEach(filter => {
357
- filter.addEventListener('click', () => {
358
- const status = filter.getAttribute('data-status');
359
- filters.forEach(f => {
360
- if (f.getAttribute('data-status') !== 'all') {
361
- f.classList.remove('active');
362
- }
363
- });
364
- if (status !== 'all') {
365
- filter.classList.add('active');
366
- }
367
- applyFilter(status);
368
- });
468
+ function attachEventListeners() {
469
+ const testItems = document.querySelectorAll('[data-test-id]');
470
+ testItems.forEach(item => {
471
+ item.addEventListener('click', () => {
472
+ const testId = item.getAttribute('data-test-id');
473
+ const test = testData[testId];
474
+ displayTestDetails(test);
369
475
  });
370
- }
476
+ });
371
477
 
372
- function applyFilter(status) {
373
- const testItems = document.querySelectorAll('[data-test-id]');
374
- const detailsElements = document.querySelectorAll('details');
375
-
376
- detailsElements.forEach(details => {
377
- let shouldShowDetails = false;
378
- const items = details.querySelectorAll('[data-test-id]');
379
- items.forEach(item => {
380
- const testStatus = item.getAttribute('data-test-status');
381
- if (status === 'all' || testStatus === status || (status === 'failed' &&
382
- (testStatus === 'failed' || testStatus === 'timedOut'))) {
383
- item.style.display = 'block'; // Show the item
384
- shouldShowDetails = true; // Set shouldShowDetails to true
385
- } else {
386
- item.style.display = 'none'; // Hide the item
478
+ // Event listeners for the filter articles
479
+ const filters = document.querySelectorAll('.filter');
480
+ filters.forEach(filter => {
481
+ filter.addEventListener('click', () => {
482
+ const status = filter.getAttribute('data-status');
483
+ filters.forEach(f => {
484
+ if (f.getAttribute('data-status')) {
485
+ f.classList.remove('active');
387
486
  }
388
487
  });
389
- details.open = shouldShowDetails; // Open the details element if it has any matching items
390
- details.style.display = shouldShowDetails ? 'block' : 'none'; // Show/hide the details element
488
+ filter.classList.add('active');
489
+ applyFilters();
391
490
  });
392
- }
491
+ });
393
492
 
493
+ const projectFilter = document.getElementById('project-filter');
494
+ projectFilter.addEventListener('change', () => {
495
+ applyFilters();
496
+ });
497
+ }
394
498
 
395
- const searchInput = document.querySelector('input[name="search"]');
499
+ function applyFilters() {
500
+ const selectedProject = document.getElementById('project-filter').value;
501
+ const activeFilter = document.querySelector('.filter.active');
502
+ const selectedStatus = activeFilter ? activeFilter.getAttribute('data-status') : 'all';
503
+
504
+ const testItems = document.querySelectorAll('li[data-test-id]');
396
505
  const detailsElements = document.querySelectorAll('details');
397
506
 
398
- searchInput.addEventListener('input', () => {
399
- const searchTerm = searchInput.value.toLowerCase();
400
- const testItems = document.querySelectorAll('[data-test-id]');
507
+ detailsElements.forEach(details => {
508
+ let shouldShowDetails = false;
509
+ const items = details.querySelectorAll('li[data-test-id]');
510
+ items.forEach(item => {
511
+ const projectName = item.getAttribute('data-project-name').trim();
512
+ const testStatus = item.getAttribute('data-test-status').trim();
513
+ const matchesProject = (selectedProject === 'all' || projectName === selectedProject);
514
+ const matchesStatus = (selectedStatus === 'all' || testStatus.includes(selectedStatus) ||
515
+ (selectedStatus === 'failed' && (testStatus === 'failed' || testStatus === 'timedOut')) ||
516
+ (selectedStatus === 'retry' && testStatus.includes('retry')) ||
517
+ (selectedStatus === 'flaky' && testStatus.includes('flaky')));
518
+
519
+ if (matchesProject && matchesStatus) {
520
+ item.classList.remove('is-hidden');
521
+ shouldShowDetails = true;
522
+ } else {
523
+ item.classList.add('is-hidden');
524
+ }
525
+ });
526
+ details.open = shouldShowDetails;
527
+ details.classList.toggle('is-hidden', !shouldShowDetails);
528
+ });
529
+ }
530
+
531
+ const searchInput = document.querySelector('input[name="search"]');
532
+ const detailsElements = document.querySelectorAll('details');
533
+ searchInput.addEventListener('input', () => {
534
+ const searchTerm = searchInput.value.toLowerCase();
535
+ const testItems = document.querySelectorAll('[data-test-id]');
401
536
 
402
- if (searchTerm) {
403
- detailsElements.forEach(detail => {
404
- detail.open = false; // Collapse all details initially
405
- });
537
+ if (searchTerm) {
538
+ detailsElements.forEach(detail => {
539
+ detail.open = false; // Collapse all details initially
540
+ });
406
541
 
407
- testItems.forEach(item => {
408
- const testTitle = item.textContent.toLowerCase();
409
- if (testTitle.includes(searchTerm)) {
410
- item.style.display = 'block'; // Show matching test item
411
-
412
- let parent = item.parentElement;
413
- while (parent && parent.tagName !== 'ASIDE') {
414
- if (parent.tagName === 'DETAILS') {
415
- parent.open = true;
416
- }
417
- parent = parent.parentElement;
542
+ testItems.forEach(item => {
543
+ const testTitle = item.textContent.toLowerCase();
544
+ if (testTitle.includes(searchTerm)) {
545
+ item.style.display = 'block'; // Show matching test item
546
+
547
+ let parent = item.parentElement;
548
+ while (parent && parent.tagName !== 'ASIDE') {
549
+ if (parent.tagName === 'DETAILS') {
550
+ parent.open = true;
418
551
  }
419
- } else {
420
- item.style.display = 'none';
552
+ parent = parent.parentElement;
421
553
  }
422
- });
423
- } else {
424
- testItems.forEach(item => {
425
- item.style.display = 'block';
426
- });
427
- detailsElements.forEach(detail => {
428
- detail.open = false;
429
- });
430
- }
431
- });
554
+ } else {
555
+ item.style.display = 'none';
556
+ }
557
+ });
558
+ } else {
559
+ testItems.forEach(item => {
560
+ item.style.display = 'block';
561
+ });
562
+ detailsElements.forEach(detail => {
563
+ detail.open = false;
564
+ });
565
+ }
566
+ });
432
567
 
433
- const ctx = document.getElementById('testChart').getContext('2d');
434
- new Chart(ctx, {
568
+ const ctx = document.getElementById('testChart').getContext('2d');
569
+ new Chart(ctx, {
435
570
  type: 'doughnut',
436
571
  data: {
437
- labels: ['Passed', 'Failed', 'Skipped'],
572
+ labels: ['Passed', 'Failed', 'Skipped','Flaky'],
438
573
  datasets: [{
439
- data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}],
440
- backgroundColor: ['#28a745', '#dc3545', '#d5d4a1']
574
+ data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}, {{flakyCount}}],
575
+ backgroundColor: ['#28a745', '#dc3545', '#d5d4a1', '#FFB704']
441
576
  }]
442
577
  },
443
578
  options: {
444
- responsive: true,
445
- maintainAspectRatio: false,
579
+ responsive: true,
580
+ maintainAspectRatio: false,
446
581
  plugins: {
447
582
  legend: {
448
583
  position: 'bottom'
449
584
  }
450
585
  }
451
586
  }
452
- });
453
- attachEventListeners();
454
587
  });
455
- </script>
456
- <script src="node_modules/ortoni-report/dist/utils/modal.js"></script>
588
+
589
+ attachEventListeners();
590
+ });
591
+
592
+ </script>
593
+ <script src="node_modules/ortoni-report/dist/utils/modal.js"></script>
457
594
  </body>
458
- </html>
595
+
596
+ </html>