ortoni-report 1.0.8 → 1.1.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.
Binary file
@@ -1,10 +1,17 @@
1
1
  import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
2
 
3
+ interface ReporterConfig {
4
+ projectName?: string;
5
+ authorName?: string;
6
+ testType?: string;
7
+ }
8
+
3
9
  declare class OrtoniReport implements Reporter {
4
10
  private results;
5
11
  private groupedResults;
6
12
  private suiteName;
7
- constructor();
13
+ private config;
14
+ constructor(config?: ReporterConfig);
8
15
  onBegin(config: FullConfig, suite: Suite): void;
9
16
  onTestBegin(test: TestCase, result: TestResult): void;
10
17
  onTestEnd(test: TestCase, result: TestResult): void;
@@ -34,31 +34,57 @@ __export(ortoni_report_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(ortoni_report_exports);
36
36
  var import_fs = __toESM(require("fs"));
37
- var import_path = __toESM(require("path"));
37
+ var import_path2 = __toESM(require("path"));
38
38
  var import_handlebars = __toESM(require("handlebars"));
39
39
  var import_safe = __toESM(require("colors/safe"));
40
+
41
+ // src/utils/time.ts
42
+ var import_path = __toESM(require("path"));
43
+ function msToTime(duration) {
44
+ const seconds = Math.floor(duration / 1e3 % 60);
45
+ const minutes = Math.floor(duration / (1e3 * 60) % 60);
46
+ const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
47
+ const parts = [];
48
+ if (hours > 0)
49
+ parts.push(hours + "h");
50
+ if (minutes > 0)
51
+ parts.push(minutes + "m");
52
+ if (seconds > 0 || parts.length === 0)
53
+ parts.push(seconds + "s");
54
+ return parts.join(" ");
55
+ }
56
+ function normalizeFilePath(filePath) {
57
+ const normalizedPath = import_path.default.normalize(filePath);
58
+ return import_path.default.basename(normalizedPath);
59
+ }
60
+
61
+ // src/ortoni-report.ts
40
62
  var OrtoniReport = class {
41
- constructor() {
63
+ constructor(config = {}) {
42
64
  this.results = [];
65
+ this.config = config;
43
66
  }
44
67
  onBegin(config, suite) {
45
68
  this.results = [];
46
- const screenshotsDir = import_path.default.join(process.cwd(), "screenshots");
47
- if (!import_fs.default.existsSync(screenshotsDir)) {
48
- import_fs.default.mkdirSync(screenshotsDir);
69
+ const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots");
70
+ if (import_fs.default.existsSync(screenshotsDir)) {
71
+ import_fs.default.rmSync(screenshotsDir, { recursive: true, force: true });
49
72
  }
73
+ import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
50
74
  }
51
75
  onTestBegin(test, result) {
52
76
  }
53
77
  onTestEnd(test, result) {
54
78
  const testResult = {
79
+ totalDuration: "",
55
80
  projectName: test.titlePath()[1],
56
81
  // Get the project name
57
82
  suite: test.titlePath()[3],
58
83
  // Adjust the index based on your suite hierarchy
59
84
  title: test.title,
60
85
  status: result.status,
61
- duration: result.duration,
86
+ flaky: test.outcome(),
87
+ duration: msToTime(result.duration),
62
88
  errors: result.errors.map((e) => import_safe.default.strip(e.message || e.toString())),
63
89
  steps: result.steps.map((step) => ({
64
90
  title: step.title,
@@ -68,24 +94,25 @@ var OrtoniReport = class {
68
94
  })),
69
95
  logs: import_safe.default.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
70
96
  screenshotPath: null,
71
- filePath: test.titlePath()[2]
97
+ filePath: normalizeFilePath(test.titlePath()[2])
72
98
  };
73
99
  if (result.attachments) {
74
- const screenshotsDir = import_path.default.join(process.cwd(), "screenshots\\" + test.id);
100
+ const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots", test.id);
75
101
  if (!import_fs.default.existsSync(screenshotsDir)) {
76
- import_fs.default.mkdirSync(screenshotsDir);
102
+ import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
77
103
  }
78
104
  const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
79
105
  if (screenshot && screenshot.path) {
80
106
  const screenshotContent = import_fs.default.readFileSync(screenshot.path, "base64");
81
- const screenshotFileName = `screenshots/${test.id}/${import_path.default.basename(screenshot.path)}`;
82
- import_fs.default.writeFileSync(import_path.default.join(process.cwd(), screenshotFileName), screenshotContent, "base64");
107
+ const screenshotFileName = import_path2.default.join("screenshots", test.id, import_path2.default.basename(screenshot.path));
108
+ import_fs.default.writeFileSync(import_path2.default.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
83
109
  testResult.screenshotPath = screenshotFileName;
84
110
  }
85
111
  }
86
112
  this.results.push(testResult);
87
113
  }
88
114
  onEnd(result) {
115
+ this.results[0].totalDuration = msToTime(result.duration);
89
116
  this.groupedResults = this.results.reduce((acc, result2, index) => {
90
117
  const filePath = result2.filePath;
91
118
  const suiteName = result2.suite;
@@ -105,26 +132,30 @@ var OrtoniReport = class {
105
132
  import_handlebars.default.registerHelper("json", function(context) {
106
133
  return safeStringify(context);
107
134
  });
108
- import_handlebars.default.registerHelper("splitSuiteName", function(suiteName) {
109
- return suiteName.split(" - ");
110
- });
111
135
  const html = this.generateHTML();
112
- const outputPath = import_path.default.join(process.cwd(), "ortoni-report.html");
136
+ const outputPath = import_path2.default.resolve(process.cwd(), "ortoni-report.html");
113
137
  import_fs.default.writeFileSync(outputPath, html);
114
138
  console.log(`Ortoni HTML report generated at ${outputPath}`);
115
139
  }
116
140
  generateHTML() {
117
- const templateSource = import_fs.default.readFileSync(import_path.default.join(__dirname, "report-template.hbs"), "utf-8");
141
+ const templateSource = import_fs.default.readFileSync(import_path2.default.resolve(__dirname, "report-template.hbs"), "utf-8");
118
142
  const template = import_handlebars.default.compile(templateSource);
119
143
  const data = {
144
+ totalDuration: this.results[0].totalDuration,
120
145
  suiteName: this.suiteName,
121
146
  results: this.results,
122
147
  passCount: this.results.filter((r) => r.status === "passed").length,
123
- failCount: this.results.filter((r) => r.status === "failed").length,
148
+ failCount: this.results.filter((r) => r.status === "failed" || r.status === "timedOut").length,
124
149
  skipCount: this.results.filter((r) => r.status === "skipped").length,
125
- retryCount: this.results.filter((r) => r.status === "retry").length,
150
+ flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
126
151
  totalCount: this.results.length,
127
- groupedResults: this.groupedResults
152
+ groupedResults: this.groupedResults,
153
+ projectName: this.config.projectName,
154
+ // Include project name
155
+ authorName: this.config.authorName,
156
+ // Include author name
157
+ testType: this.config.testType
158
+ // Include test type
128
159
  };
129
160
  return template(data);
130
161
  }
@@ -1,30 +1,56 @@
1
1
  // src/ortoni-report.ts
2
2
  import fs from "fs";
3
- import path from "path";
3
+ import path2 from "path";
4
4
  import Handlebars from "handlebars";
5
5
  import colors from "colors/safe";
6
+
7
+ // src/utils/time.ts
8
+ import path from "path";
9
+ function msToTime(duration) {
10
+ const seconds = Math.floor(duration / 1e3 % 60);
11
+ const minutes = Math.floor(duration / (1e3 * 60) % 60);
12
+ const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
13
+ const parts = [];
14
+ if (hours > 0)
15
+ parts.push(hours + "h");
16
+ if (minutes > 0)
17
+ parts.push(minutes + "m");
18
+ if (seconds > 0 || parts.length === 0)
19
+ parts.push(seconds + "s");
20
+ return parts.join(" ");
21
+ }
22
+ function normalizeFilePath(filePath) {
23
+ const normalizedPath = path.normalize(filePath);
24
+ return path.basename(normalizedPath);
25
+ }
26
+
27
+ // src/ortoni-report.ts
6
28
  var OrtoniReport = class {
7
- constructor() {
29
+ constructor(config = {}) {
8
30
  this.results = [];
31
+ this.config = config;
9
32
  }
10
33
  onBegin(config, suite) {
11
34
  this.results = [];
12
- const screenshotsDir = path.join(process.cwd(), "screenshots");
13
- if (!fs.existsSync(screenshotsDir)) {
14
- fs.mkdirSync(screenshotsDir);
35
+ const screenshotsDir = path2.resolve(process.cwd(), "screenshots");
36
+ if (fs.existsSync(screenshotsDir)) {
37
+ fs.rmSync(screenshotsDir, { recursive: true, force: true });
15
38
  }
39
+ fs.mkdirSync(screenshotsDir, { recursive: true });
16
40
  }
17
41
  onTestBegin(test, result) {
18
42
  }
19
43
  onTestEnd(test, result) {
20
44
  const testResult = {
45
+ totalDuration: "",
21
46
  projectName: test.titlePath()[1],
22
47
  // Get the project name
23
48
  suite: test.titlePath()[3],
24
49
  // Adjust the index based on your suite hierarchy
25
50
  title: test.title,
26
51
  status: result.status,
27
- duration: result.duration,
52
+ flaky: test.outcome(),
53
+ duration: msToTime(result.duration),
28
54
  errors: result.errors.map((e) => colors.strip(e.message || e.toString())),
29
55
  steps: result.steps.map((step) => ({
30
56
  title: step.title,
@@ -34,24 +60,25 @@ var OrtoniReport = class {
34
60
  })),
35
61
  logs: colors.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
36
62
  screenshotPath: null,
37
- filePath: test.titlePath()[2]
63
+ filePath: normalizeFilePath(test.titlePath()[2])
38
64
  };
39
65
  if (result.attachments) {
40
- const screenshotsDir = path.join(process.cwd(), "screenshots\\" + test.id);
66
+ const screenshotsDir = path2.resolve(process.cwd(), "screenshots", test.id);
41
67
  if (!fs.existsSync(screenshotsDir)) {
42
- fs.mkdirSync(screenshotsDir);
68
+ fs.mkdirSync(screenshotsDir, { recursive: true });
43
69
  }
44
70
  const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
45
71
  if (screenshot && screenshot.path) {
46
72
  const screenshotContent = fs.readFileSync(screenshot.path, "base64");
47
- const screenshotFileName = `screenshots/${test.id}/${path.basename(screenshot.path)}`;
48
- fs.writeFileSync(path.join(process.cwd(), screenshotFileName), screenshotContent, "base64");
73
+ const screenshotFileName = path2.join("screenshots", test.id, path2.basename(screenshot.path));
74
+ fs.writeFileSync(path2.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
49
75
  testResult.screenshotPath = screenshotFileName;
50
76
  }
51
77
  }
52
78
  this.results.push(testResult);
53
79
  }
54
80
  onEnd(result) {
81
+ this.results[0].totalDuration = msToTime(result.duration);
55
82
  this.groupedResults = this.results.reduce((acc, result2, index) => {
56
83
  const filePath = result2.filePath;
57
84
  const suiteName = result2.suite;
@@ -71,26 +98,30 @@ var OrtoniReport = class {
71
98
  Handlebars.registerHelper("json", function(context) {
72
99
  return safeStringify(context);
73
100
  });
74
- Handlebars.registerHelper("splitSuiteName", function(suiteName) {
75
- return suiteName.split(" - ");
76
- });
77
101
  const html = this.generateHTML();
78
- const outputPath = path.join(process.cwd(), "ortoni-report.html");
102
+ const outputPath = path2.resolve(process.cwd(), "ortoni-report.html");
79
103
  fs.writeFileSync(outputPath, html);
80
104
  console.log(`Ortoni HTML report generated at ${outputPath}`);
81
105
  }
82
106
  generateHTML() {
83
- const templateSource = fs.readFileSync(path.join(__dirname, "report-template.hbs"), "utf-8");
107
+ const templateSource = fs.readFileSync(path2.resolve(__dirname, "report-template.hbs"), "utf-8");
84
108
  const template = Handlebars.compile(templateSource);
85
109
  const data = {
110
+ totalDuration: this.results[0].totalDuration,
86
111
  suiteName: this.suiteName,
87
112
  results: this.results,
88
113
  passCount: this.results.filter((r) => r.status === "passed").length,
89
- failCount: this.results.filter((r) => r.status === "failed").length,
114
+ failCount: this.results.filter((r) => r.status === "failed" || r.status === "timedOut").length,
90
115
  skipCount: this.results.filter((r) => r.status === "skipped").length,
91
- retryCount: this.results.filter((r) => r.status === "retry").length,
116
+ flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
92
117
  totalCount: this.results.length,
93
- groupedResults: this.groupedResults
118
+ groupedResults: this.groupedResults,
119
+ projectName: this.config.projectName,
120
+ // Include project name
121
+ authorName: this.config.authorName,
122
+ // Include author name
123
+ testType: this.config.testType
124
+ // Include test type
94
125
  };
95
126
  return template(data);
96
127
  }
@@ -5,9 +5,13 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>Playwright Test Report</title>
8
- <link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
9
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.css">
8
+ <link rel="icon" href="node_modules\ortoni-report\src\icon\32.png" type="image/x-icon">
9
+ <link rel="stylesheet" href="node_modules\ortoni-report\src\css\pico.css">
10
10
  <style>
11
+ body {
12
+ zoom: 0.9;
13
+ }
14
+
11
15
  main.container {
12
16
  display: grid;
13
17
  grid-template-columns: 1fr 2fr;
@@ -24,27 +28,31 @@
24
28
  justify-content: space-evenly;
25
29
  }
26
30
 
31
+ .highlight {
32
+ background-color: var(--pico-primary-background);
33
+ }
34
+
27
35
  .text-success {
28
36
  color: #28a745;
29
37
  }
30
38
 
31
- .text-danger {
39
+ .text-failure {
32
40
  color: #dc3545;
33
41
  }
34
42
 
43
+ .text-skip {
44
+ color: #d5d4a1;
45
+ }
46
+
47
+ .text-flaky {
48
+ color: #d5d4a1;
49
+ }
50
+
35
51
  .sidebar {
36
52
  border-right: 1px solid #ddd;
37
53
  padding-right: 10px;
38
54
  }
39
55
 
40
- .card {
41
- padding: 1rem;
42
- border: 1px solid #ddd;
43
- border-radius: 0.5rem;
44
- background-color: #fff;
45
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
46
- }
47
-
48
56
  pre {
49
57
  background-color: #f8f9fa;
50
58
  padding: 1rem;
@@ -62,21 +70,21 @@
62
70
  <body>
63
71
  <header class="container">
64
72
  <div class="header">
73
+ {{!-- Custom Project Name --}}
65
74
  <div>
66
- <h1>Playwright Test Report</h1>
75
+ {{#if projectName}}<h1>{{projectName}}</h1>{{/if}}
67
76
  </div>
77
+ {{!-- Dummy for now --}}
68
78
  <div>
69
- <form role="search">
70
- <input name="search" type="search" placeholder="Search" />
71
- <input type="submit" value="Search" />
72
- </form>
79
+ <input name="search" type="search" placeholder="Search by test title" />
73
80
  </div>
74
81
  </div>
75
82
  </header>
76
83
  <main class="container">
84
+ {{!-- Test Scripts --}}
77
85
  <aside class="sidebar">
78
86
  <h2>Tests</h2>
79
- <div class="">
87
+ <div>
80
88
  {{#each groupedResults}}
81
89
  <details>
82
90
  <summary>{{@key}}</summary>
@@ -104,15 +112,13 @@
104
112
  {{/each}}
105
113
  </div>
106
114
  </aside>
107
-
108
-
109
-
110
115
  <section>
116
+ {{!-- Overall summar --}}
111
117
  <div id="summary">
112
118
  <section class="grid">
113
119
  <div>
114
120
  <article>
115
- <header>Total Tests</header>
121
+ <header>All Tests</header>
116
122
  <p>{{totalCount}}</p>
117
123
  </article>
118
124
  </div>
@@ -125,7 +131,7 @@
125
131
  <div>
126
132
  <article>
127
133
  <header>Failed</header>
128
- <p class="text-danger">{{failCount}}</p>
134
+ <p class="text-failure">{{failCount}}</p>
129
135
  </article>
130
136
  </div>
131
137
  </section>
@@ -133,24 +139,34 @@
133
139
  <div>
134
140
  <article>
135
141
  <header>Skipped</header>
136
- <p>{{skipCount}}</p>
142
+ <p class="text-skip">{{skipCount}}</p>
137
143
  </article>
138
144
  </div>
139
145
  <div>
140
146
  <article>
141
- <header>Retry</header>
142
- <p class="text-success">{{retryCount}}</p>
147
+ <header>Flaky</header>
148
+ <p class="text-flaky">{{flakyCount}}</p>
143
149
  </article>
144
150
  </div>
145
151
  </section>
152
+ {{!-- Suite details with chart --}}
146
153
  <section>
147
- <h2>Test Results</h2>
148
- <div class="chart-container">
149
- <canvas id="testChart"></canvas>
150
- </div>
154
+ <article>
155
+ <header>Suite</header>
156
+ <div class="grid">
157
+ <div>
158
+ {{#if authorName}}<h4>Author: {{authorName}}</h4>{{/if}}
159
+ {{#if testType}}<h4>Test Type: {{testType}}</h4>{{/if}}
160
+ {{#if totalDuration}}<h4>Duration: {{totalDuration}}</h4>{{/if}}
161
+ </div>
162
+ <div class="chart-container">
163
+ <canvas id="testChart"></canvas>
164
+ </div>
165
+ </div>
166
+ </article>
151
167
  </section>
152
168
  </div>
153
-
169
+ {{!-- Test details --}}
154
170
  <div id="testDetails" style="display: none;">
155
171
  <!-- Back button should be outside the dynamic content -->
156
172
  <button class="back-button" onclick="showSummary()">Back to Summary</button>
@@ -161,16 +177,33 @@
161
177
 
162
178
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"></script>
163
179
  <script>
180
+ function escapeHtml(unsafe) {
181
+ return unsafe.replace(/[&<"']/g, function (match) {
182
+ const escapeMap = {
183
+ '&': '&amp;',
184
+ '<': '&lt;',
185
+ '>': '&gt;',
186
+ '"': '&quot;',
187
+ "'": '&#039;'
188
+ };
189
+ return escapeMap[match];
190
+ });
191
+ }
192
+
164
193
  document.addEventListener('DOMContentLoaded', () => {
165
- const testData = {{{ json results }}};
194
+ const testData = {{{ json results }}};
166
195
  const testDetails = document.getElementById('testDetails');
167
196
  const summary = document.getElementById('summary');
168
197
  const backButton = document.querySelector('.back-button');
198
+ let highlightedItem = null;
169
199
 
170
200
  function showSummary() {
171
201
  summary.style.display = 'block';
172
202
  testDetails.style.display = 'none';
173
203
  backButton.style.display = 'none';
204
+ if (highlightedItem) {
205
+ highlightedItem.classList.remove('highlight');
206
+ }
174
207
  }
175
208
 
176
209
  window.showSummary = showSummary;
@@ -181,39 +214,94 @@
181
214
  backButton.style.display = 'block';
182
215
  testDetails.innerHTML = `
183
216
  <button class="back-button" style="display: block" onclick="showSummary()">Back to Summary</button>
184
- <h2>${test.title}</h2>
217
+ <h3>${test.title}</h3>
185
218
  <div class="grid">
186
219
  <div>
187
- <h3>Status</h3>
188
- <p class="${test.status === 'passed' ? 'text-success' : 'text-danger'}">${test.status}</p>
189
- <h3>Duration</h3>
190
- <p>${test.duration}ms</p>
191
- ${test.errors.length ? `
192
- <h3>Errors</h3>
193
- <pre>${test.errors.join('\n')}</pre>
194
- ` : ''}
220
+ <h4>Status</h4>
221
+ <p class="${test.status === 'passed' ? 'text-success' : 'text-failure'}">${test.status.toUpperCase()}</p>
222
+ ${test.duration != '0s' ? `
223
+ <h4>Duration</h4>
224
+ <p>${test.duration}</p>` : ""}
195
225
  </div>
196
226
  <div>
197
227
  ${test.screenshotPath ? `
198
- <h3>Screenshot</h3>
228
+ <h4>Screenshot</h4>
199
229
  <img src="${test.screenshotPath}" alt="Screenshot">
200
230
  ` : ''}
201
- ${test.logs ? `
202
- <h3>Logs</h3>
203
- <pre>${test.logs}</pre>
204
- ` : ''}
205
231
  </div>
206
232
  </div>
233
+ <div>
234
+ ${test.errors.length ? `
235
+ <h4>Errors</h4>
236
+ <div class="grid">
237
+ <pre>${escapeHtml(test.errors.join('\n'))}</pre></div>
238
+ ` : ''}
239
+ </div>
240
+ <div>
241
+ ${test.logs ? `
242
+ <h4>Logs</h4>
243
+ <div class="grid">
244
+ <pre>${escapeHtml(test.logs)}</pre></div>
245
+ ` : ''}
246
+ </div>
207
247
  `;
208
248
  }
209
249
 
210
- const testItems = document.querySelectorAll('[data-test-id]');
211
- testItems.forEach(item => {
212
- item.addEventListener('click', () => {
213
- const testId = item.getAttribute('data-test-id');
214
- const test = testData[testId];
215
- displayTestDetails(test);
250
+ function attachEventListeners() {
251
+ const testItems = document.querySelectorAll('[data-test-id]');
252
+ testItems.forEach(item => {
253
+ item.addEventListener('click', () => {
254
+ const testId = item.getAttribute('data-test-id');
255
+ const test = testData[testId];
256
+ displayTestDetails(test);
257
+ if (highlightedItem) {
258
+ highlightedItem.classList.remove('highlight');
259
+ }
260
+ item.classList.add('highlight');
261
+ highlightedItem = item;
262
+ });
216
263
  });
264
+ }
265
+
266
+ attachEventListeners(); // Attach event listeners initially
267
+
268
+ const searchInput = document.querySelector('input[name="search"]');
269
+ const detailsElements = document.querySelectorAll('details');
270
+
271
+ searchInput.addEventListener('input', () => {
272
+ const searchTerm = searchInput.value.toLowerCase();
273
+ const testItems = document.querySelectorAll('[data-test-id]');
274
+
275
+ if (searchTerm) {
276
+ detailsElements.forEach(detail => {
277
+ detail.open = false; // Collapse all details initially
278
+ });
279
+
280
+ testItems.forEach(item => {
281
+ const testTitle = item.textContent.toLowerCase();
282
+ if (testTitle.includes(searchTerm)) {
283
+ item.style.display = 'block'; // Show matching test item
284
+
285
+ let parent = item.parentElement;
286
+ while (parent && parent.tagName !== 'ASIDE') {
287
+ if (parent.tagName === 'DETAILS') {
288
+ parent.open = true; // Expand parent details elements
289
+ }
290
+ parent = parent.parentElement;
291
+ }
292
+ } else {
293
+ item.style.display = 'none'; // Hide non-matching test item
294
+ }
295
+ });
296
+ } else {
297
+ testItems.forEach(item => {
298
+ item.style.display = 'block'; // Show all test items
299
+ });
300
+ detailsElements.forEach(detail => {
301
+ detail.open = false; // Collapse all details elements
302
+ });
303
+ }
304
+ attachEventListeners(); // Reattach event listeners after filtering
217
305
  });
218
306
 
219
307
  const ctx = document.getElementById('testChart').getContext('2d');
@@ -223,23 +311,15 @@
223
311
  labels: ['Passed', 'Failed', 'Skipped'],
224
312
  datasets: [{
225
313
  data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}],
226
- backgroundColor: ['#28a745', '#dc3545', '#f0f662']
314
+ backgroundColor: ['#28a745', '#dc3545', '#d5d4a1']
227
315
  }]
228
316
  },
229
317
  options: {
230
318
  responsive: true,
231
319
  maintainAspectRatio: false,
232
320
  plugins: {
233
- title: {
234
- display: true,
235
- text: 'Overall',
236
- font: {
237
- size: 18,
238
- weight: 'bold'
239
- }
240
- },
241
321
  legend: {
242
- position: 'right'
322
+ position: 'bottom'
243
323
  }
244
324
  }
245
325
  }
package/package.json CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "ortoni-report",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "Playwright Report By LetCode with Koushik",
5
5
  "scripts": {
6
+ "test": "npx playwright test",
6
7
  "build": "tsup",
7
8
  "release": "npm publish",
8
9
  "lint": "tsc"
9
10
  },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "CHANGELOG.md"
15
+ ],
10
16
  "repository": {
11
17
  "type": "git",
12
18
  "url": "git+https://github.com/ortoniKC/ortoni-report"
@@ -19,20 +25,21 @@
19
25
  "ortoni"
20
26
  ],
21
27
  "author": "Koushik Chatterjee (LetCode with Koushik)",
22
- "license": "ISC",
28
+ "license": "GPL-3.0-only",
23
29
  "bugs": {
24
30
  "url": "https://github.com/ortoniKC/ortoni-report/issues"
25
31
  },
26
32
  "homepage": "https://github.com/ortoniKC/ortoni-report#readme",
27
- "dependencies": {
28
- "hiq": "^4.2.3",
33
+ "dependencies": {
29
34
  "colors": "^1.4.0",
30
- "handlebars": "^4.7.8"
35
+ "handlebars": "^4.7.8",
36
+ "hiq": "^4.2.3",
37
+ "rimraf": "^5.0.7"
31
38
  },
32
39
  "devDependencies": {
40
+ "@changesets/cli": "^2.26.0",
33
41
  "@playwright/test": "^1.44.1",
34
42
  "@types/node": "^20.14.2",
35
- "@changesets/cli": "^2.26.0",
36
43
  "tsup": "^6.5.0",
37
44
  "typescript": "^4.9.4"
38
45
  },