@testomatio/reporter 1.2.0-beta-4 → 1.2.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.
package/lib/pipe/html.js CHANGED
@@ -10,246 +10,248 @@ const { fileSystem, isSameTest, ansiRegExp } = require('../utils/utils');
10
10
  const { HTML_REPORT } = require('../constants');
11
11
 
12
12
  class HtmlPipe {
13
- constructor(params, store = {}) {
14
- this.store = store || {};
15
- this.title = params.title || process.env.TESTOMATIO_TITLE;
16
- this.apiKey = params.apiKey || process.env.TESTOMATIO;
17
- this.isHtml = process.env.TESTOMATIO_HTML_REPORT_SAVE;
18
-
19
- debug('HTML Pipe: ', this.apiKey ? 'API KEY' : '*no api key provided*');
20
-
21
- this.isEnabled = false;
22
- this.htmlOutputPath = "";
23
- this.fullHtmlOutputPath = "";
24
- this.filenameMsg = "";
25
- this.tests = [];
26
-
27
- if (this.isHtml) {
28
- this.isEnabled = true;
29
- this.htmlReportDir = process.env.TESTOMATIO_HTML_REPORT_FOLDER || HTML_REPORT.FOLDER;
30
-
31
- if (process.env.TESTOMATIO_HTML_FILENAME && process.env.TESTOMATIO_HTML_FILENAME.endsWith(".html")) {
32
- this.htmlReportName = process.env.TESTOMATIO_HTML_FILENAME
33
- }
34
-
35
- if (process.env.TESTOMATIO_HTML_FILENAME && !process.env.TESTOMATIO_HTML_FILENAME.endsWith(".html")) {
36
- this.htmlReportName = HTML_REPORT.REPORT_DEFAULT_NAME;
37
- this.filenameMsg = "HTML filename must include the extension \".html\"." +
38
- ` The default report name "${this.htmlReportDir}/${HTML_REPORT.REPORT_DEFAULT_NAME}" is used!`;
39
- }
40
-
41
- if (!process.env.TESTOMATIO_HTML_FILENAME) {
42
- this.htmlReportName = HTML_REPORT.REPORT_DEFAULT_NAME;
43
- }
44
-
45
- this.templateFolderPath = path.resolve(__dirname, '..', 'template');
46
- this.templateHtmlPath = path.resolve(this.templateFolderPath, HTML_REPORT.TEMPLATE_NAME);
47
- this.htmlOutputPath = path.join(this.htmlReportDir, this.htmlReportName);
48
- // create a new folder for the HTML reports
49
- fileSystem.createDir(this.htmlReportDir);
50
-
51
- debug(
52
- chalk.yellow('HTML Pipe:'),
53
- `Save HTML report: ${this.isEnabled}`,
54
- `HTML report folder: ${this.htmlReportDir}, report name: ${this.htmlReportName}`
55
- );
56
- }
13
+ constructor(params, store = {}) {
14
+ this.store = store || {};
15
+ this.title = params.title || process.env.TESTOMATIO_TITLE;
16
+ this.apiKey = params.apiKey || process.env.TESTOMATIO;
17
+ this.isHtml = process.env.TESTOMATIO_HTML_REPORT_SAVE;
18
+
19
+ debug('HTML Pipe: ', this.apiKey ? 'API KEY' : '*no api key provided*');
20
+
21
+ this.isEnabled = false;
22
+ this.htmlOutputPath = '';
23
+ this.fullHtmlOutputPath = '';
24
+ this.filenameMsg = '';
25
+ this.tests = [];
26
+
27
+ if (this.isHtml) {
28
+ this.isEnabled = true;
29
+ this.htmlReportDir = process.env.TESTOMATIO_HTML_REPORT_FOLDER || HTML_REPORT.FOLDER;
30
+
31
+ if (process.env.TESTOMATIO_HTML_FILENAME && process.env.TESTOMATIO_HTML_FILENAME.endsWith('.html')) {
32
+ this.htmlReportName = process.env.TESTOMATIO_HTML_FILENAME;
33
+ }
34
+
35
+ if (process.env.TESTOMATIO_HTML_FILENAME && !process.env.TESTOMATIO_HTML_FILENAME.endsWith('.html')) {
36
+ this.htmlReportName = HTML_REPORT.REPORT_DEFAULT_NAME;
37
+ this.filenameMsg =
38
+ 'HTML filename must include the extension ".html".' +
39
+ ` The default report name "${this.htmlReportDir}/${HTML_REPORT.REPORT_DEFAULT_NAME}" is used!`;
40
+ }
41
+
42
+ if (!process.env.TESTOMATIO_HTML_FILENAME) {
43
+ this.htmlReportName = HTML_REPORT.REPORT_DEFAULT_NAME;
44
+ }
45
+
46
+ this.templateFolderPath = path.resolve(__dirname, '..', 'template');
47
+ this.templateHtmlPath = path.resolve(this.templateFolderPath, HTML_REPORT.TEMPLATE_NAME);
48
+ this.htmlOutputPath = path.join(this.htmlReportDir, this.htmlReportName);
49
+ // create a new folder for the HTML reports
50
+ fileSystem.createDir(this.htmlReportDir);
51
+
52
+ debug(
53
+ chalk.yellow('HTML Pipe:'),
54
+ `Save HTML report: ${this.isEnabled}`,
55
+ `HTML report folder: ${this.htmlReportDir}, report name: ${this.htmlReportName}`,
56
+ );
57
57
  }
58
-
59
- async createRun() {
60
- // empty
58
+ }
59
+
60
+ async createRun() {
61
+ // empty
62
+ }
63
+
64
+ updateRun() {
65
+ // empty
66
+ }
67
+
68
+ /**
69
+ * Add test data to the result array for saving. As a result of this function, we get a result object to save.
70
+ * @param {import('../../types').RunData} test - object which includes each test entry.
71
+ */
72
+ addTest(test) {
73
+ if (!this.isEnabled) return;
74
+
75
+ if (!test.status) return;
76
+
77
+ const index = this.tests.findIndex(t => isSameTest(t, test));
78
+ // update if they were already added
79
+ if (index >= 0) {
80
+ this.tests[index] = merge(this.tests[index], test);
81
+ return;
61
82
  }
62
83
 
63
- updateRun() {
64
- // empty
84
+ this.tests.push(test);
85
+ }
86
+
87
+ async finishRun(runParams) {
88
+ if (!this.isEnabled) return;
89
+
90
+ if (this.isHtml) {
91
+ // GENERATE HTML reports based on the results data
92
+ this.buildReport({
93
+ runParams,
94
+ // TODO: this.tests=[] in case of Mocha
95
+ tests: this.tests,
96
+ outputPath: this.htmlOutputPath,
97
+ templatePath: this.templateHtmlPath,
98
+ warningMsg: this.filenameMsg,
99
+ });
65
100
  }
66
-
67
- /**
68
- * Add test data to the result array for saving. As a result of this function, we get a result object to save.
69
- * @param {Object} test - object which includes each test entry.
70
- */
71
- addTest(test) {
72
- if (!this.isEnabled) return;
73
-
74
- if (!test.steps || !test.status) return;
75
-
76
- const index = this.tests.findIndex(t => isSameTest(t, test));
77
- // update if they were already added
78
- if (index >= 0) {
79
- this.tests[index] = merge(this.tests[index], test);
80
- return;
81
- }
82
-
83
- this.tests.push(test);
84
- }
85
-
86
- async finishRun(runParams) {
87
- if (!this.isEnabled) return;
88
-
89
- if (this.isHtml) {
90
- // GENERATE HTML reports based on the results data
91
- this.buildReport({
92
- runParams,
93
- tests: this.tests,
94
- outputPath: this.htmlOutputPath,
95
- templatePath: this.templateHtmlPath,
96
- warningMsg: this.filenameMsg
97
- });
98
- }
101
+ }
102
+ /**
103
+ * Generates an HTML report based on provided test data and a template.
104
+ * @param {object} opts - Test options used to generate the HTML report:
105
+ * runParams, tests, outputPath, templatePath
106
+ * @returns {void} - This function does not return anything.
107
+ */
108
+
109
+ buildReport(opts) {
110
+ const { runParams, tests, outputPath, templatePath, warningMsg: msg } = opts;
111
+
112
+ debug('HTML tests data:', tests);
113
+
114
+ if (!outputPath) {
115
+ console.log(chalk.yellow(`🚨 HTML export path is not set, ignoring...`));
116
+ return;
99
117
  }
100
- /**
101
- * Generates an HTML report based on provided test data and a template.
102
- * @param {object} opts - Test options used to generate the HTML report:
103
- * runParams, tests, outputPath, templatePath
104
- * @returns {void} - This function does not return anything.
105
- */
106
-
107
- buildReport(opts) {
108
- const { runParams, tests, outputPath, templatePath, warningMsg: msg } = opts;
109
-
110
- debug('HTML tests data:', tests);
111
-
112
- if (!outputPath) {
113
- console.log(chalk.yellow(`🚨 HTML export path is not set, ignoring...`));
114
- return;
115
- }
116
-
117
- console.log(chalk.yellow(`⏳ The test results will be added to the HTML report. It will take some time...`));
118
118
 
119
- if (msg) {
120
- console.log(chalk.blue(msg));
121
- }
119
+ console.log(chalk.yellow(`⏳ The test results will be added to the HTML report. It will take some time...`));
122
120
 
123
- tests.forEach(test => {
124
-
125
- if (!test.message || test.message.trim() === "") {
126
- test.message = "This test has no 'message' code";
127
- }
121
+ if (msg) {
122
+ console.log(chalk.blue(msg));
123
+ }
128
124
 
129
- if (!test.suite_title || test.suite_title.trim() === "") {
130
- test.suite_title = "Unknown suite";
131
- }
125
+ tests.forEach(test => {
126
+ if (!test.message || test.message.trim() === '') {
127
+ test.message = "This test has no 'message' code";
128
+ }
132
129
 
133
- if (!test.title || test.title.trim() === "") {
134
- test.title = "Unknown test title";
135
- }
130
+ if (!test.suite_title || test.suite_title.trim() === '') {
131
+ test.suite_title = 'Unknown suite';
132
+ }
136
133
 
137
- if (!test.files || test.files.length === 0) {
138
- test.files = "This test has no files";
139
- }
134
+ if (!test.title || test.title.trim() === '') {
135
+ test.title = 'Unknown test title';
136
+ }
140
137
 
141
- if (test.steps) {
142
- if (!test.steps || test.steps.trim() === "") {
143
- test.steps = "This test has no 'steps' code";
144
- }
145
- else {
146
- test.steps = removeAnsiColorCodes(test.steps);
147
- }
148
- }
149
-
150
- // TODO: u can added an additional test values to this checks in the future
151
- });
138
+ if (!test.files || test.files.length === 0) {
139
+ test.files = 'This test has no files';
140
+ }
152
141
 
153
- const data = {
154
- runId: this.store.runId || "",
155
- status: runParams.status || "No status info",
156
- parallel: runParams.isParallel || "No parallel info",
157
- runUrl: this.store.runUrl || "",
158
- executionTime: testExecutionSumTime(tests),
159
- executionDate: getCurrentDateTimeFormatted(),
160
- tests
161
- };
162
- // generate output HTML based on the template
163
- const html = this.#generateHTMLReport(data, templatePath);
164
-
165
- if (!html) return;
166
-
167
- fs.writeFileSync(outputPath, html, 'utf-8');
168
- // Check if the file exists
169
- if (fs.existsSync(outputPath)) {
170
- // Get the absolute path of the file
171
- const absolutePath = path.resolve(outputPath);
172
- // Convert the file path to a file URL
173
- const fileUrlPath = fileUrl(absolutePath, {resolve: true});
174
-
175
- debug('HTML tests data:', fileUrlPath);
176
-
177
- console.log(chalk.green(`📊 The HTML report was successfully generated. Full filepath: ${fileUrlPath}`));
142
+ if (test.steps) {
143
+ if (!test.steps || test.steps.trim() === '') {
144
+ test.steps = "This test has no 'steps' code";
178
145
  } else {
179
- console.log(chalk.red(`🚨 Failed to generate the HTML report.`));
146
+ test.steps = removeAnsiColorCodes(test.steps);
180
147
  }
148
+ }
149
+
150
+ // TODO: u can added an additional test values to this checks in the future
151
+ });
152
+
153
+ const data = {
154
+ runId: this.store.runId || '',
155
+ status: runParams.status || 'No status info',
156
+ parallel: runParams.isParallel || 'No parallel info',
157
+ runUrl: this.store.runUrl || '',
158
+ executionTime: testExecutionSumTime(tests),
159
+ executionDate: getCurrentDateTimeFormatted(),
160
+ tests,
161
+ };
162
+ // generate output HTML based on the template
163
+ const html = this.#generateHTMLReport(data, templatePath);
164
+
165
+ if (!html) return;
166
+
167
+ fs.writeFileSync(outputPath, html, 'utf-8');
168
+ // Check if the file exists
169
+ if (fs.existsSync(outputPath)) {
170
+ // Get the absolute path of the file
171
+ const absolutePath = path.resolve(outputPath);
172
+ // Convert the file path to a file URL
173
+ const fileUrlPath = fileUrl(absolutePath, { resolve: true });
174
+
175
+ debug('HTML tests data:', fileUrlPath);
176
+
177
+ console.log(chalk.green(`📊 The HTML report was successfully generated. Full filepath: ${fileUrlPath}`));
178
+ } else {
179
+ console.log(chalk.red(`🚨 Failed to generate the HTML report.`));
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Generates an HTML report based on provided test data and a template path.
185
+ * @param {any} data - Test data used to generate the HTML report.
186
+ * @param {string} [templatePath=""] - The path to the HTML template used for generating the report.
187
+ * @returns {string | void} - The generated HTML report as a string or void if templatePath is not provided.
188
+ */
189
+ #generateHTMLReport(data, templatePath = '') {
190
+ if (!templatePath) {
191
+ console.log(chalk.red(`🚨 HTML template not found. Report generation is impossible!`));
192
+ return;
181
193
  }
182
194
 
183
- /**
184
- * Generates an HTML report based on provided test data and a template path.
185
- * @param {any} data - Test data used to generate the HTML report.
186
- * @param {string} [templatePath=""] - The path to the HTML template used for generating the report.
187
- * @returns {string | void} - The generated HTML report as a string or void if templatePath is not provided.
188
- */
189
- #generateHTMLReport(data, templatePath = "") {
190
- if (!templatePath) {
191
- console.log(chalk.red(`🚨 HTML template not found. Report generation is impossible!`))
192
- return;
193
- }
194
-
195
- const templateSource = fs.readFileSync(templatePath, 'utf8');
196
- this.#loadReportHelpers();
197
- try {
198
- const template = handlebars.compile(templateSource);
195
+ const templateSource = fs.readFileSync(templatePath, 'utf8');
196
+ this.#loadReportHelpers();
197
+ try {
198
+ const template = handlebars.compile(templateSource);
199
199
 
200
- return template(data);
201
- }
202
- catch (e) {
203
- console.log('Unknown HTML report generation error: ', e);
204
- }
200
+ return template(data);
201
+ } catch (e) {
202
+ console.log('Unknown HTML report generation error: ', e);
205
203
  }
206
-
207
- #loadReportHelpers() {
208
- handlebars.registerHelper('getTestsByStatus', (tests, status) =>
209
- tests.filter(test => test.status.toLowerCase() === status.toLowerCase()).length
210
- );
211
-
212
- handlebars.registerHelper('json', (tests) => {
213
- function replaceScriptTagsInArray(array) {
214
- return array.map(obj => {
215
- const keysToCheck = ["steps", "stack", "title", "suite_title", "message", "code"];
216
- const newObj = {};
217
-
218
- for (const key in obj) {
219
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
220
- if (key === "example") {
221
- newObj[key] = {};
222
- for (const subKey in obj[key]) {
223
- if (Object.prototype.hasOwnProperty.call(obj[key], subKey)) {
224
- newObj[key][subKey] = typeof obj[key][subKey] === "string"
225
- ? obj[key][subKey]
226
- .replace(/<script>/g, "<$cript>")
227
- .replace(/<\/script>/g, "</$cript>")
228
- : obj[key][subKey];
229
- }
230
- }
231
- } else if (keysToCheck.includes(key)) {
232
- newObj[key] = typeof obj[key] === "string"
233
- ? obj[key].replace(/<script>/g, "<$cript>").replace(/<\/script>/g, "</$cript>")
234
- : obj[key];
235
- } else {
236
- newObj[key] = obj[key];
237
- }
238
- }
239
- }
240
-
241
- return newObj;
242
- });
204
+ }
205
+
206
+ #loadReportHelpers() {
207
+ handlebars.registerHelper(
208
+ 'getTestsByStatus',
209
+ (tests, status) => tests.filter(test => test.status.toLowerCase() === status.toLowerCase()).length,
210
+ );
211
+
212
+ handlebars.registerHelper('json', tests => {
213
+
214
+ // TODO: please refactor the code below, add meaningful variable names, separate into functions
215
+ function replaceScriptTagsInArray(array) {
216
+ return array.map(obj => {
217
+ const keysToCheck = ['steps', 'stack', 'title', 'suite_title', 'message', 'code'];
218
+ const newObj = {};
219
+
220
+ for (const key in obj) {
221
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
222
+ if (key === 'example') {
223
+ newObj[key] = {};
224
+ for (const subKey in obj[key]) {
225
+ if (Object.prototype.hasOwnProperty.call(obj[key], subKey)) {
226
+ newObj[key][subKey] =
227
+ typeof obj[key][subKey] === 'string'
228
+ ? obj[key][subKey].replace(/<script>/g, '<$cript>').replace(/<\/script>/g, '</$cript>')
229
+ : obj[key][subKey];
230
+ }
231
+ }
232
+ } else if (keysToCheck.includes(key)) {
233
+ newObj[key] =
234
+ typeof obj[key] === 'string'
235
+ ? obj[key].replace(/<script>/g, '<$cript>').replace(/<\/script>/g, '</$cript>')
236
+ : obj[key];
237
+ } else {
238
+ newObj[key] = obj[key];
239
+ }
243
240
  }
241
+ }
244
242
 
245
- // Remove ANSI escape codes
246
- return JSON.stringify(replaceScriptTagsInArray(tests));
243
+ return newObj;
247
244
  });
248
- }
245
+ }
249
246
 
250
- toString() {
251
- return 'HTML Reporter';
252
- }
247
+ // Remove ANSI escape codes
248
+ return JSON.stringify(replaceScriptTagsInArray(tests));
249
+ });
250
+ }
251
+
252
+ toString() {
253
+ return 'HTML Reporter';
254
+ }
253
255
  }
254
256
 
255
257
  /**
@@ -259,14 +261,14 @@ class HtmlPipe {
259
261
  * @returns {string} - The total execution time in a formatted duration string.
260
262
  */
261
263
  function testExecutionSumTime(tests) {
262
- const totalMilliseconds = tests.reduce((sum, test) => {
263
- if (typeof test.run_time === 'number') {
264
- return sum + test.run_time;
265
- }
266
- return sum;
267
- }, 0);
264
+ const totalMilliseconds = tests.reduce((sum, test) => {
265
+ if (typeof test.run_time === 'number') {
266
+ return sum + test.run_time;
267
+ }
268
+ return sum;
269
+ }, 0);
268
270
 
269
- return formatDuration(totalMilliseconds);
271
+ return formatDuration(totalMilliseconds);
270
272
  }
271
273
 
272
274
  /**
@@ -275,10 +277,10 @@ function testExecutionSumTime(tests) {
275
277
  * @returns {string} - The updated string with removed ANSI color codes and replaced newline characters.
276
278
  */
277
279
  function removeAnsiColorCodes(str) {
278
- let updatedStr = str.replace(ansiRegExp(), "");
279
- updatedStr = updatedStr.replace(/\n/g, '<br>');
280
+ let updatedStr = str.replace(ansiRegExp(), '');
281
+ updatedStr = updatedStr.replace(/\n/g, '<br>');
280
282
 
281
- return updatedStr;
283
+ return updatedStr;
282
284
  }
283
285
 
284
286
  /**
@@ -287,14 +289,14 @@ function removeAnsiColorCodes(str) {
287
289
  * @returns {string} - The formatted duration string (e.g., "2h 30m 15s 500ms").
288
290
  */
289
291
  function formatDuration(duration) {
290
- const milliseconds = duration % 1000;
291
- duration = (duration - milliseconds) / 1000;
292
- const seconds = duration % 60;
293
- duration = (duration - seconds) / 60;
294
- const minutes = duration % 60;
295
- const hours = (duration - minutes) / 60;
296
-
297
- return `${hours}h ${minutes}m ${seconds}s ${milliseconds}ms`;
292
+ const milliseconds = duration % 1000;
293
+ duration = (duration - milliseconds) / 1000;
294
+ const seconds = duration % 60;
295
+ duration = (duration - seconds) / 60;
296
+ const minutes = duration % 60;
297
+ const hours = (duration - minutes) / 60;
298
+
299
+ return `${hours}h ${minutes}m ${seconds}s ${milliseconds}ms`;
298
300
  }
299
301
 
300
302
  /**
@@ -302,15 +304,15 @@ function formatDuration(duration) {
302
304
  * @returns {string} - The formatted date and time string (e.g., "(01/01/2023 12:00:00)").
303
305
  */
304
306
  function getCurrentDateTimeFormatted() {
305
- const currentDate = new Date();
306
- const day = currentDate.getDate().toString().padStart(2, '0');
307
- const month = (currentDate.getMonth() + 1).toString().padStart(2, '0');
308
- const year = currentDate.getFullYear();
309
- const hours = currentDate.getHours().toString().padStart(2, '0');
310
- const minutes = currentDate.getMinutes().toString().padStart(2, '0');
311
- const seconds = currentDate.getSeconds().toString().padStart(2, '0');
312
-
313
- return `(${day}/${month}/${year} ${hours}:${minutes}:${seconds})`;
307
+ const currentDate = new Date();
308
+ const day = currentDate.getDate().toString().padStart(2, '0');
309
+ const month = (currentDate.getMonth() + 1).toString().padStart(2, '0');
310
+ const year = currentDate.getFullYear();
311
+ const hours = currentDate.getHours().toString().padStart(2, '0');
312
+ const minutes = currentDate.getMinutes().toString().padStart(2, '0');
313
+ const seconds = currentDate.getSeconds().toString().padStart(2, '0');
314
+
315
+ return `(${day}/${month}/${year} ${hours}:${minutes}:${seconds})`;
314
316
  }
315
317
 
316
- module.exports = HtmlPipe;
318
+ module.exports = HtmlPipe;