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.
Binary file
@@ -1,9 +1,27 @@
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
- interface ReporterConfig {
3
+ interface OrtoniReportConfig {
4
4
  projectName?: string;
5
5
  authorName?: string;
6
6
  testType?: string;
7
+ preferredTheme?: 'light' | 'dark';
8
+ }
9
+
10
+ interface TestResultData {
11
+ retry: string;
12
+ isRetry: number;
13
+ projectName: any;
14
+ suite: any;
15
+ title: string;
16
+ status: "passed" | "failed" | "timedOut" | "skipped" | "interrupted" | "expected" | "unexpected" | "flaky";
17
+ flaky: string;
18
+ duration: string;
19
+ errors: any[];
20
+ steps: TestStep[];
21
+ logs: string;
22
+ screenshotPath: string | null;
23
+ filePath: any;
24
+ projects: Set<string>;
7
25
  }
8
26
 
9
27
  declare class OrtoniReport implements Reporter {
@@ -11,13 +29,13 @@ declare class OrtoniReport implements Reporter {
11
29
  private groupedResults;
12
30
  private suiteName;
13
31
  private config;
14
- constructor(config?: ReporterConfig);
32
+ constructor(config?: OrtoniReportConfig);
15
33
  onBegin(config: FullConfig, suite: Suite): void;
16
34
  onTestBegin(test: TestCase, result: TestResult): void;
35
+ private projectSet;
17
36
  onTestEnd(test: TestCase, result: TestResult): void;
18
- private _successRate;
19
37
  onEnd(result: FullResult): void;
20
- generateHTML(): string;
38
+ generateHTML(filteredResults: TestResultData[], totalDuration: string): string;
21
39
  }
22
40
 
23
- export { OrtoniReport as default };
41
+ export { OrtoniReportConfig, OrtoniReport as default };
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/ortoni-report.ts
31
31
  var ortoni_report_exports = {};
32
32
  __export(ortoni_report_exports, {
33
- default: () => ortoni_report_default
33
+ default: () => OrtoniReport
34
34
  });
35
35
  module.exports = __toCommonJS(ortoni_report_exports);
36
36
  var import_fs = __toESM(require("fs"));
@@ -45,11 +45,22 @@ function msToTime(duration) {
45
45
  const seconds = Math.floor(duration / 1e3 % 60);
46
46
  const minutes = Math.floor(duration / (1e3 * 60) % 60);
47
47
  const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
48
- const hoursStr = hours < 10 ? "0" + hours : hours;
49
- const minutesStr = minutes < 10 ? "0" + minutes : minutes;
50
- const secondsStr = seconds < 10 ? "0" + seconds : seconds;
51
- const millisecondsStr = milliseconds < 100 ? "0" + milliseconds : milliseconds;
52
- return `${hoursStr}:${minutesStr}:${secondsStr}.${millisecondsStr}`;
48
+ let result = "";
49
+ if (hours > 0) {
50
+ result += `${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 && !(seconds > 0 || minutes > 0 || hours > 0)) {
59
+ result += `${milliseconds}ms`;
60
+ } else if (milliseconds > 0) {
61
+ result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
62
+ }
63
+ return result;
53
64
  }
54
65
  function normalizeFilePath(filePath) {
55
66
  const normalizedPath = import_path.default.normalize(filePath);
@@ -67,16 +78,11 @@ function formatDate(date) {
67
78
  var OrtoniReport = class {
68
79
  constructor(config = {}) {
69
80
  this.results = [];
70
- this._successRate = "";
81
+ this.projectSet = /* @__PURE__ */ new Set();
71
82
  this.config = config;
72
83
  }
73
84
  onBegin(config, suite) {
74
85
  this.results = [];
75
- const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots");
76
- if (import_fs.default.existsSync(screenshotsDir)) {
77
- import_fs.default.rmSync(screenshotsDir, { recursive: true, force: true });
78
- }
79
- import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
80
86
  }
81
87
  onTestBegin(test, result) {
82
88
  }
@@ -85,46 +91,45 @@ var OrtoniReport = class {
85
91
  if (test.outcome() === "flaky") {
86
92
  status = "flaky";
87
93
  }
94
+ this.projectSet.add(test.titlePath()[1]);
88
95
  const testResult = {
96
+ retry: result.retry > 0 ? "retry" : "",
89
97
  isRetry: result.retry,
90
- totalDuration: "",
91
98
  projectName: test.titlePath()[1],
92
- // Get the project name
93
99
  suite: test.titlePath()[3],
94
- // Adjust the index based on your suite hierarchy
95
100
  title: test.title,
96
101
  status,
97
102
  flaky: test.outcome(),
98
103
  duration: msToTime(result.duration),
99
104
  errors: result.errors.map((e) => import_safe.default.strip(e.message || e.toString())),
100
105
  steps: result.steps.map((step) => ({
101
- title: step.title,
106
+ titlePath: step.titlePath,
102
107
  category: step.category,
103
108
  duration: step.duration,
104
- status: result.status
109
+ error: step.error,
110
+ location: step.location,
111
+ parent: step.parent,
112
+ startTime: step.startTime,
113
+ steps: step.steps,
114
+ title: step.title
105
115
  })),
106
116
  logs: import_safe.default.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
107
117
  screenshotPath: null,
108
- filePath: normalizeFilePath(test.titlePath()[2])
118
+ filePath: normalizeFilePath(test.titlePath()[2]),
119
+ projects: this.projectSet
109
120
  };
110
121
  if (result.attachments) {
111
- const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots", test.id);
112
- if (!import_fs.default.existsSync(screenshotsDir)) {
113
- import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
114
- }
115
122
  const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
116
123
  if (screenshot && screenshot.path) {
117
124
  const screenshotContent = import_fs.default.readFileSync(screenshot.path, "base64");
118
- const screenshotFileName = import_path2.default.join("screenshots", test.id, import_path2.default.basename(screenshot.path));
119
- import_fs.default.writeFileSync(import_path2.default.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
120
- testResult.screenshotPath = screenshotFileName;
125
+ testResult.screenshotPath = screenshotContent;
121
126
  }
122
127
  }
123
128
  this.results.push(testResult);
124
129
  }
125
130
  onEnd(result) {
126
- this._successRate = (this.results.filter((r) => r.status === "passed").length / this.results.length * 100).toFixed(2);
127
- this.results[0].totalDuration = msToTime(result.duration);
131
+ const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
132
+ const totalDuration = msToTime(result.duration);
128
133
  this.groupedResults = this.results.reduce((acc, result2, index) => {
129
134
  const filePath = result2.filePath;
130
135
  const suiteName = result2.suite;
@@ -160,29 +165,37 @@ var OrtoniReport = class {
160
165
  import_handlebars.default.registerHelper("gt", function(a, b) {
161
166
  return a > b;
162
167
  });
163
- const html = this.generateHTML();
168
+ const html = this.generateHTML(filteredResults, totalDuration);
164
169
  const outputPath = import_path2.default.resolve(process.cwd(), "ortoni-report.html");
165
170
  import_fs.default.writeFileSync(outputPath, html);
166
171
  console.log(`Ortoni HTML report generated at ${outputPath}`);
167
172
  }
168
- generateHTML() {
173
+ generateHTML(filteredResults, totalDuration) {
174
+ const totalTests = filteredResults.length;
175
+ const passedTests = this.results.filter((r) => r.status === "passed").length;
176
+ const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
177
+ const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
178
+ const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
169
179
  const templateSource = import_fs.default.readFileSync(import_path2.default.resolve(__dirname, "report-template.hbs"), "utf-8");
170
180
  const template = import_handlebars.default.compile(templateSource);
171
181
  const data = {
172
- totalDuration: this.results[0].totalDuration,
182
+ totalDuration,
173
183
  suiteName: this.suiteName,
174
184
  results: this.results,
175
- passCount: this.results.filter((r) => r.status === "passed").length,
176
- failCount: this.results.filter((r) => r.status === "failed" || r.status === "timedOut").length,
185
+ retryCount: this.results.filter((r) => r.isRetry).length,
186
+ passCount: passedTests,
187
+ failCount: failed,
177
188
  skipCount: this.results.filter((r) => r.status === "skipped").length,
178
- flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
179
- totalCount: this.results.length,
189
+ flakyCount: flakyTests,
190
+ totalCount: filteredResults.length,
180
191
  groupedResults: this.groupedResults,
181
192
  projectName: this.config.projectName,
182
193
  authorName: this.config.authorName,
183
194
  testType: this.config.testType,
184
- successRate: this._successRate,
185
- lastRunDate: formatDate(/* @__PURE__ */ new Date())
195
+ preferredTheme: this.config.preferredTheme,
196
+ successRate,
197
+ lastRunDate: formatDate(/* @__PURE__ */ new Date()),
198
+ projects: this.projectSet
186
199
  };
187
200
  return template(data);
188
201
  }
@@ -201,4 +214,3 @@ function safeStringify(obj, indent = 2) {
201
214
  cache.clear();
202
215
  return json;
203
216
  }
204
- var ortoni_report_default = OrtoniReport;
@@ -11,11 +11,22 @@ function msToTime(duration) {
11
11
  const seconds = Math.floor(duration / 1e3 % 60);
12
12
  const minutes = Math.floor(duration / (1e3 * 60) % 60);
13
13
  const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
14
- const hoursStr = hours < 10 ? "0" + hours : hours;
15
- const minutesStr = minutes < 10 ? "0" + minutes : minutes;
16
- const secondsStr = seconds < 10 ? "0" + seconds : seconds;
17
- const millisecondsStr = milliseconds < 100 ? "0" + milliseconds : milliseconds;
18
- return `${hoursStr}:${minutesStr}:${secondsStr}.${millisecondsStr}`;
14
+ let result = "";
15
+ if (hours > 0) {
16
+ result += `${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 && !(seconds > 0 || minutes > 0 || hours > 0)) {
25
+ result += `${milliseconds}ms`;
26
+ } else if (milliseconds > 0) {
27
+ result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
28
+ }
29
+ return result;
19
30
  }
20
31
  function normalizeFilePath(filePath) {
21
32
  const normalizedPath = path.normalize(filePath);
@@ -33,16 +44,11 @@ function formatDate(date) {
33
44
  var OrtoniReport = class {
34
45
  constructor(config = {}) {
35
46
  this.results = [];
36
- this._successRate = "";
47
+ this.projectSet = /* @__PURE__ */ new Set();
37
48
  this.config = config;
38
49
  }
39
50
  onBegin(config, suite) {
40
51
  this.results = [];
41
- const screenshotsDir = path2.resolve(process.cwd(), "screenshots");
42
- if (fs.existsSync(screenshotsDir)) {
43
- fs.rmSync(screenshotsDir, { recursive: true, force: true });
44
- }
45
- fs.mkdirSync(screenshotsDir, { recursive: true });
46
52
  }
47
53
  onTestBegin(test, result) {
48
54
  }
@@ -51,46 +57,45 @@ var OrtoniReport = class {
51
57
  if (test.outcome() === "flaky") {
52
58
  status = "flaky";
53
59
  }
60
+ this.projectSet.add(test.titlePath()[1]);
54
61
  const testResult = {
62
+ retry: result.retry > 0 ? "retry" : "",
55
63
  isRetry: result.retry,
56
- totalDuration: "",
57
64
  projectName: test.titlePath()[1],
58
- // Get the project name
59
65
  suite: test.titlePath()[3],
60
- // Adjust the index based on your suite hierarchy
61
66
  title: test.title,
62
67
  status,
63
68
  flaky: test.outcome(),
64
69
  duration: msToTime(result.duration),
65
70
  errors: result.errors.map((e) => colors.strip(e.message || e.toString())),
66
71
  steps: result.steps.map((step) => ({
67
- title: step.title,
72
+ titlePath: step.titlePath,
68
73
  category: step.category,
69
74
  duration: step.duration,
70
- status: result.status
75
+ error: step.error,
76
+ location: step.location,
77
+ parent: step.parent,
78
+ startTime: step.startTime,
79
+ steps: step.steps,
80
+ title: step.title
71
81
  })),
72
82
  logs: colors.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
73
83
  screenshotPath: null,
74
- filePath: normalizeFilePath(test.titlePath()[2])
84
+ filePath: normalizeFilePath(test.titlePath()[2]),
85
+ projects: this.projectSet
75
86
  };
76
87
  if (result.attachments) {
77
- const screenshotsDir = path2.resolve(process.cwd(), "screenshots", test.id);
78
- if (!fs.existsSync(screenshotsDir)) {
79
- fs.mkdirSync(screenshotsDir, { recursive: true });
80
- }
81
88
  const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
82
89
  if (screenshot && screenshot.path) {
83
90
  const screenshotContent = fs.readFileSync(screenshot.path, "base64");
84
- const screenshotFileName = path2.join("screenshots", test.id, path2.basename(screenshot.path));
85
- fs.writeFileSync(path2.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
86
- testResult.screenshotPath = screenshotFileName;
91
+ testResult.screenshotPath = screenshotContent;
87
92
  }
88
93
  }
89
94
  this.results.push(testResult);
90
95
  }
91
96
  onEnd(result) {
92
- this._successRate = (this.results.filter((r) => r.status === "passed").length / this.results.length * 100).toFixed(2);
93
- this.results[0].totalDuration = msToTime(result.duration);
97
+ const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
98
+ const totalDuration = msToTime(result.duration);
94
99
  this.groupedResults = this.results.reduce((acc, result2, index) => {
95
100
  const filePath = result2.filePath;
96
101
  const suiteName = result2.suite;
@@ -126,29 +131,37 @@ var OrtoniReport = class {
126
131
  Handlebars.registerHelper("gt", function(a, b) {
127
132
  return a > b;
128
133
  });
129
- const html = this.generateHTML();
134
+ const html = this.generateHTML(filteredResults, totalDuration);
130
135
  const outputPath = path2.resolve(process.cwd(), "ortoni-report.html");
131
136
  fs.writeFileSync(outputPath, html);
132
137
  console.log(`Ortoni HTML report generated at ${outputPath}`);
133
138
  }
134
- generateHTML() {
139
+ generateHTML(filteredResults, totalDuration) {
140
+ const totalTests = filteredResults.length;
141
+ const passedTests = this.results.filter((r) => r.status === "passed").length;
142
+ const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
143
+ const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
144
+ const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
135
145
  const templateSource = fs.readFileSync(path2.resolve(__dirname, "report-template.hbs"), "utf-8");
136
146
  const template = Handlebars.compile(templateSource);
137
147
  const data = {
138
- totalDuration: this.results[0].totalDuration,
148
+ totalDuration,
139
149
  suiteName: this.suiteName,
140
150
  results: this.results,
141
- passCount: this.results.filter((r) => r.status === "passed").length,
142
- failCount: this.results.filter((r) => r.status === "failed" || r.status === "timedOut").length,
151
+ retryCount: this.results.filter((r) => r.isRetry).length,
152
+ passCount: passedTests,
153
+ failCount: failed,
143
154
  skipCount: this.results.filter((r) => r.status === "skipped").length,
144
- flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
145
- totalCount: this.results.length,
155
+ flakyCount: flakyTests,
156
+ totalCount: filteredResults.length,
146
157
  groupedResults: this.groupedResults,
147
158
  projectName: this.config.projectName,
148
159
  authorName: this.config.authorName,
149
160
  testType: this.config.testType,
150
- successRate: this._successRate,
151
- lastRunDate: formatDate(/* @__PURE__ */ new Date())
161
+ preferredTheme: this.config.preferredTheme,
162
+ successRate,
163
+ lastRunDate: formatDate(/* @__PURE__ */ new Date()),
164
+ projects: this.projectSet
152
165
  };
153
166
  return template(data);
154
167
  }
@@ -167,7 +180,6 @@ function safeStringify(obj, indent = 2) {
167
180
  cache.clear();
168
181
  return json;
169
182
  }
170
- var ortoni_report_default = OrtoniReport;
171
183
  export {
172
- ortoni_report_default as default
184
+ OrtoniReport as default
173
185
  };