ortoni-report 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Change Log:
2
2
 
3
+ ## Version 1.1.3
4
+
5
+ **New Features:**
6
+ - Added detailed steps to the testDetails section in the HTML report.
7
+ - Introduced a new flaky icon for better visual representation in the report.
8
+ - Display of test steps in the HTML report.
9
+ - Added a filter for retry tests to better categorize and display them.
10
+
11
+ **Improved:**
12
+ - Updated the package dependencies to remove vulnerabilities.
13
+ - Enhanced time formatting to include milliseconds in the duration display.
14
+ - Enhanced the calculation and display of the success rate in the HTML report.
15
+
16
+ ## Version 1.1.2
17
+
18
+ **New Features:**
19
+ - **Ellipsis Styling for Test Names:** Long test names are now truncated with ellipsis for better readability.
20
+ - **Retry, Pass, Fail, and Skip Indicators:** Added visual indicators (images) to denote the status of each test, including retries.
21
+ - **Enhanced `msToTime` Function:** Duration now includes milliseconds for more precise time tracking.
22
+ - **Summary Filters:** Clicking on summary filters (passed, failed & other) to display the selected filter.
23
+
24
+ **Improvements:**
25
+ - **Unified Status Handling:** The `applyFilter` function now treats `failed` and `timedOut` statuses as the same for consistent filtering.
26
+ - **Date and Time Formatting:** Passed date in the format `DD-MMM-YYYY` and included time for more detailed reporting.
27
+ - **Success Rate and Last Run Data:** Added the ability to pass `successRate` and `lastRun` data to the `onEnd` method for comprehensive report details.
28
+
29
+ **Bug Fixes:**
30
+ - Fixed issues with conditionals in Handlebars for status checking.
31
+ - Improved event listener attachment for filtering and search functionalities.
32
+
33
+ **Notes:**
34
+ - Screenshot handling has been enhanced with modal dialogs, using Pico.css for styling.
35
+
36
+ This release includes several visual and functional enhancements aimed at improving the usability and readability of the Playwright test reports. The added features and improvements will help users better understand test outcomes and statuses at a glance.
37
+
3
38
  ### Version 1.1.0
4
39
 
5
40
  ## New Features
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,4 +1,4 @@
1
- import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
1
+ import { TestStep, Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
2
 
3
3
  interface ReporterConfig {
4
4
  projectName?: string;
@@ -6,6 +6,22 @@ interface ReporterConfig {
6
6
  testType?: string;
7
7
  }
8
8
 
9
+ interface TestResultData {
10
+ retry: string;
11
+ isRetry: number;
12
+ projectName: any;
13
+ suite: any;
14
+ title: string;
15
+ status: "passed" | "failed" | "timedOut" | "skipped" | "interrupted" | "expected" | "unexpected" | "flaky";
16
+ flaky: string;
17
+ duration: string;
18
+ errors: any[];
19
+ steps: TestStep[];
20
+ logs: string;
21
+ screenshotPath: string | null;
22
+ filePath: any;
23
+ }
24
+
9
25
  declare class OrtoniReport implements Reporter {
10
26
  private results;
11
27
  private groupedResults;
@@ -16,7 +32,7 @@ declare class OrtoniReport implements Reporter {
16
32
  onTestBegin(test: TestCase, result: TestResult): void;
17
33
  onTestEnd(test: TestCase, result: TestResult): void;
18
34
  onEnd(result: FullResult): void;
19
- generateHTML(): string;
35
+ generateHTML(filteredResults: TestResultData[], totalDuration: string): string;
20
36
  }
21
37
 
22
38
  export { OrtoniReport as default };
@@ -38,25 +38,39 @@ var import_path2 = __toESM(require("path"));
38
38
  var import_handlebars = __toESM(require("handlebars"));
39
39
  var import_safe = __toESM(require("colors/safe"));
40
40
 
41
- // src/utils/time.ts
41
+ // src/utils/utils.ts
42
42
  var import_path = __toESM(require("path"));
43
43
  function msToTime(duration) {
44
+ const milliseconds = Math.floor(duration % 1e3);
44
45
  const seconds = Math.floor(duration / 1e3 % 60);
45
46
  const minutes = Math.floor(duration / (1e3 * 60) % 60);
46
47
  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(" ");
48
+ let result = "";
49
+ if (hours > 0) {
50
+ result += (hours < 10 ? "0" + hours : hours) + "h:";
51
+ }
52
+ if (minutes > 0 || hours > 0) {
53
+ result += (minutes < 10 ? "0" + minutes : minutes) + "m:";
54
+ }
55
+ if (seconds > 0 || minutes > 0 || hours > 0) {
56
+ result += (seconds < 10 ? "0" + seconds : seconds) + "s";
57
+ }
58
+ if (milliseconds > 0) {
59
+ result += ":" + (milliseconds < 100 ? "0" + milliseconds : milliseconds) + "ms";
60
+ }
61
+ return result;
55
62
  }
56
63
  function normalizeFilePath(filePath) {
57
64
  const normalizedPath = import_path.default.normalize(filePath);
58
65
  return import_path.default.basename(normalizedPath);
59
66
  }
67
+ function formatDate(date) {
68
+ const day = String(date.getDate()).padStart(2, "0");
69
+ const month = date.toLocaleString("default", { month: "short" });
70
+ const year = date.getFullYear();
71
+ const time = date.toLocaleTimeString();
72
+ return `${day}-${month}-${year} ${time}`;
73
+ }
60
74
 
61
75
  // src/ortoni-report.ts
62
76
  var OrtoniReport = class {
@@ -75,22 +89,30 @@ var OrtoniReport = class {
75
89
  onTestBegin(test, result) {
76
90
  }
77
91
  onTestEnd(test, result) {
92
+ let status = result.status;
93
+ if (test.outcome() === "flaky") {
94
+ status = "flaky";
95
+ }
78
96
  const testResult = {
79
- totalDuration: "",
97
+ retry: result.retry > 0 ? "retry" : "",
98
+ isRetry: result.retry,
80
99
  projectName: test.titlePath()[1],
81
- // Get the project name
82
100
  suite: test.titlePath()[3],
83
- // Adjust the index based on your suite hierarchy
84
101
  title: test.title,
85
- status: result.status,
102
+ status,
86
103
  flaky: test.outcome(),
87
104
  duration: msToTime(result.duration),
88
105
  errors: result.errors.map((e) => import_safe.default.strip(e.message || e.toString())),
89
106
  steps: result.steps.map((step) => ({
90
- title: step.title,
107
+ titlePath: step.titlePath,
91
108
  category: step.category,
92
109
  duration: step.duration,
93
- status: result.status
110
+ error: step.error,
111
+ location: step.location,
112
+ parent: step.parent,
113
+ startTime: step.startTime,
114
+ steps: step.steps,
115
+ title: step.title
94
116
  })),
95
117
  logs: import_safe.default.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
96
118
  screenshotPath: null,
@@ -112,7 +134,8 @@ var OrtoniReport = class {
112
134
  this.results.push(testResult);
113
135
  }
114
136
  onEnd(result) {
115
- this.results[0].totalDuration = msToTime(result.duration);
137
+ const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
138
+ const totalDuration = msToTime(result.duration);
116
139
  this.groupedResults = this.results.reduce((acc, result2, index) => {
117
140
  const filePath = result2.filePath;
118
141
  const suiteName = result2.suite;
@@ -132,30 +155,51 @@ var OrtoniReport = class {
132
155
  import_handlebars.default.registerHelper("json", function(context) {
133
156
  return safeStringify(context);
134
157
  });
135
- const html = this.generateHTML();
158
+ import_handlebars.default.registerHelper("eq", function(actualStatus, expectedStatus) {
159
+ return actualStatus === expectedStatus;
160
+ });
161
+ import_handlebars.default.registerHelper("or", () => {
162
+ var args = Array.prototype.slice.call(arguments);
163
+ var options = args.pop();
164
+ for (var i = 0; i < args.length; i++) {
165
+ if (args[i]) {
166
+ return options.fn(this);
167
+ }
168
+ }
169
+ return options.inverse(this);
170
+ });
171
+ import_handlebars.default.registerHelper("gt", function(a, b) {
172
+ return a > b;
173
+ });
174
+ const html = this.generateHTML(filteredResults, totalDuration);
136
175
  const outputPath = import_path2.default.resolve(process.cwd(), "ortoni-report.html");
137
176
  import_fs.default.writeFileSync(outputPath, html);
138
177
  console.log(`Ortoni HTML report generated at ${outputPath}`);
139
178
  }
140
- generateHTML() {
179
+ generateHTML(filteredResults, totalDuration) {
180
+ const totalTests = filteredResults.length;
181
+ const passedTests = this.results.filter((r) => r.status === "passed").length;
182
+ const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
183
+ const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
184
+ const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
141
185
  const templateSource = import_fs.default.readFileSync(import_path2.default.resolve(__dirname, "report-template.hbs"), "utf-8");
142
186
  const template = import_handlebars.default.compile(templateSource);
143
187
  const data = {
144
- totalDuration: this.results[0].totalDuration,
188
+ totalDuration,
145
189
  suiteName: this.suiteName,
146
190
  results: this.results,
147
- passCount: this.results.filter((r) => r.status === "passed").length,
148
- failCount: this.results.filter((r) => r.status === "failed" || r.status === "timedOut").length,
191
+ retryCount: this.results.filter((r) => r.isRetry).length,
192
+ passCount: passedTests,
193
+ failCount: failed,
149
194
  skipCount: this.results.filter((r) => r.status === "skipped").length,
150
- flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
151
- totalCount: this.results.length,
195
+ flakyCount: flakyTests,
196
+ totalCount: filteredResults.length,
152
197
  groupedResults: this.groupedResults,
153
198
  projectName: this.config.projectName,
154
- // Include project name
155
199
  authorName: this.config.authorName,
156
- // Include author name
157
- testType: this.config.testType
158
- // Include test type
200
+ testType: this.config.testType,
201
+ successRate,
202
+ lastRunDate: formatDate(/* @__PURE__ */ new Date())
159
203
  };
160
204
  return template(data);
161
205
  }
@@ -4,25 +4,39 @@ import path2 from "path";
4
4
  import Handlebars from "handlebars";
5
5
  import colors from "colors/safe";
6
6
 
7
- // src/utils/time.ts
7
+ // src/utils/utils.ts
8
8
  import path from "path";
9
9
  function msToTime(duration) {
10
+ const milliseconds = Math.floor(duration % 1e3);
10
11
  const seconds = Math.floor(duration / 1e3 % 60);
11
12
  const minutes = Math.floor(duration / (1e3 * 60) % 60);
12
13
  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(" ");
14
+ let result = "";
15
+ if (hours > 0) {
16
+ result += (hours < 10 ? "0" + hours : hours) + "h:";
17
+ }
18
+ if (minutes > 0 || hours > 0) {
19
+ result += (minutes < 10 ? "0" + minutes : minutes) + "m:";
20
+ }
21
+ if (seconds > 0 || minutes > 0 || hours > 0) {
22
+ result += (seconds < 10 ? "0" + seconds : seconds) + "s";
23
+ }
24
+ if (milliseconds > 0) {
25
+ result += ":" + (milliseconds < 100 ? "0" + milliseconds : milliseconds) + "ms";
26
+ }
27
+ return result;
21
28
  }
22
29
  function normalizeFilePath(filePath) {
23
30
  const normalizedPath = path.normalize(filePath);
24
31
  return path.basename(normalizedPath);
25
32
  }
33
+ function formatDate(date) {
34
+ const day = String(date.getDate()).padStart(2, "0");
35
+ const month = date.toLocaleString("default", { month: "short" });
36
+ const year = date.getFullYear();
37
+ const time = date.toLocaleTimeString();
38
+ return `${day}-${month}-${year} ${time}`;
39
+ }
26
40
 
27
41
  // src/ortoni-report.ts
28
42
  var OrtoniReport = class {
@@ -41,22 +55,30 @@ var OrtoniReport = class {
41
55
  onTestBegin(test, result) {
42
56
  }
43
57
  onTestEnd(test, result) {
58
+ let status = result.status;
59
+ if (test.outcome() === "flaky") {
60
+ status = "flaky";
61
+ }
44
62
  const testResult = {
45
- totalDuration: "",
63
+ retry: result.retry > 0 ? "retry" : "",
64
+ isRetry: result.retry,
46
65
  projectName: test.titlePath()[1],
47
- // Get the project name
48
66
  suite: test.titlePath()[3],
49
- // Adjust the index based on your suite hierarchy
50
67
  title: test.title,
51
- status: result.status,
68
+ status,
52
69
  flaky: test.outcome(),
53
70
  duration: msToTime(result.duration),
54
71
  errors: result.errors.map((e) => colors.strip(e.message || e.toString())),
55
72
  steps: result.steps.map((step) => ({
56
- title: step.title,
73
+ titlePath: step.titlePath,
57
74
  category: step.category,
58
75
  duration: step.duration,
59
- status: result.status
76
+ error: step.error,
77
+ location: step.location,
78
+ parent: step.parent,
79
+ startTime: step.startTime,
80
+ steps: step.steps,
81
+ title: step.title
60
82
  })),
61
83
  logs: colors.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
62
84
  screenshotPath: null,
@@ -78,7 +100,8 @@ var OrtoniReport = class {
78
100
  this.results.push(testResult);
79
101
  }
80
102
  onEnd(result) {
81
- this.results[0].totalDuration = msToTime(result.duration);
103
+ const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
104
+ const totalDuration = msToTime(result.duration);
82
105
  this.groupedResults = this.results.reduce((acc, result2, index) => {
83
106
  const filePath = result2.filePath;
84
107
  const suiteName = result2.suite;
@@ -98,30 +121,51 @@ var OrtoniReport = class {
98
121
  Handlebars.registerHelper("json", function(context) {
99
122
  return safeStringify(context);
100
123
  });
101
- const html = this.generateHTML();
124
+ Handlebars.registerHelper("eq", function(actualStatus, expectedStatus) {
125
+ return actualStatus === expectedStatus;
126
+ });
127
+ Handlebars.registerHelper("or", () => {
128
+ var args = Array.prototype.slice.call(arguments);
129
+ var options = args.pop();
130
+ for (var i = 0; i < args.length; i++) {
131
+ if (args[i]) {
132
+ return options.fn(this);
133
+ }
134
+ }
135
+ return options.inverse(this);
136
+ });
137
+ Handlebars.registerHelper("gt", function(a, b) {
138
+ return a > b;
139
+ });
140
+ const html = this.generateHTML(filteredResults, totalDuration);
102
141
  const outputPath = path2.resolve(process.cwd(), "ortoni-report.html");
103
142
  fs.writeFileSync(outputPath, html);
104
143
  console.log(`Ortoni HTML report generated at ${outputPath}`);
105
144
  }
106
- generateHTML() {
145
+ generateHTML(filteredResults, totalDuration) {
146
+ const totalTests = filteredResults.length;
147
+ const passedTests = this.results.filter((r) => r.status === "passed").length;
148
+ const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
149
+ const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
150
+ const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
107
151
  const templateSource = fs.readFileSync(path2.resolve(__dirname, "report-template.hbs"), "utf-8");
108
152
  const template = Handlebars.compile(templateSource);
109
153
  const data = {
110
- totalDuration: this.results[0].totalDuration,
154
+ totalDuration,
111
155
  suiteName: this.suiteName,
112
156
  results: this.results,
113
- passCount: this.results.filter((r) => r.status === "passed").length,
114
- failCount: this.results.filter((r) => r.status === "failed" || r.status === "timedOut").length,
157
+ retryCount: this.results.filter((r) => r.isRetry).length,
158
+ passCount: passedTests,
159
+ failCount: failed,
115
160
  skipCount: this.results.filter((r) => r.status === "skipped").length,
116
- flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
117
- totalCount: this.results.length,
161
+ flakyCount: flakyTests,
162
+ totalCount: filteredResults.length,
118
163
  groupedResults: this.groupedResults,
119
164
  projectName: this.config.projectName,
120
- // Include project name
121
165
  authorName: this.config.authorName,
122
- // Include author name
123
- testType: this.config.testType
124
- // Include test type
166
+ testType: this.config.testType,
167
+ successRate,
168
+ lastRunDate: formatDate(/* @__PURE__ */ new Date())
125
169
  };
126
170
  return template(data);
127
171
  }