@testomatio/reporter 1.2.0-beta-3 → 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/README.md +27 -18
- package/lib/adapter/codecept.js +83 -11
- package/lib/adapter/cucumber/current.js +17 -10
- package/lib/adapter/cucumber/legacy.js +4 -3
- package/lib/adapter/cypress-plugin/index.js +51 -24
- package/lib/adapter/jest.js +47 -23
- package/lib/adapter/mocha.js +99 -45
- package/lib/adapter/playwright.js +94 -32
- package/lib/bin/startTest.js +8 -7
- package/lib/client.js +119 -62
- package/lib/config.js +34 -0
- package/lib/constants.js +9 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +125 -46
- package/lib/junit-adapter/index.js +3 -2
- package/lib/pipe/gitlab.js +1 -1
- package/lib/pipe/html.js +269 -232
- package/lib/pipe/testomatio.js +73 -50
- package/lib/reporter-functions.js +46 -0
- package/lib/reporter.js +11 -10
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/services/key-values.js +58 -0
- package/lib/{logger.js → services/logger.js} +58 -66
- package/lib/template/testomatio.hbs +94 -62
- package/lib/utils/utils.js +28 -2
- package/lib/xmlReader.js +106 -105
- package/package.json +6 -3
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -232
- package/lib/template/real-data-example.js +0 -47
- /package/lib/template/{template.hbs → template-draft.hbs} +0 -0
package/lib/pipe/html.js
CHANGED
|
@@ -10,272 +10,309 @@ const { fileSystem, isSameTest, ansiRegExp } = require('../utils/utils');
|
|
|
10
10
|
const { HTML_REPORT } = require('../constants');
|
|
11
11
|
|
|
12
12
|
class HtmlPipe {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
58
|
-
}
|
|
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
|
+
);
|
|
59
57
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
63
82
|
}
|
|
64
83
|
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
});
|
|
67
100
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.tests.push(test);
|
|
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;
|
|
86
117
|
}
|
|
87
118
|
|
|
88
|
-
|
|
89
|
-
if (!this.isEnabled) return;
|
|
90
|
-
|
|
91
|
-
if (this.isHtml) {
|
|
92
|
-
|
|
93
|
-
this.tests.forEach(test => {
|
|
94
|
-
|
|
95
|
-
if (!test.message || test.message.trim() === "") {
|
|
96
|
-
test.message = "This test has no 'message' code";
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!test.suite_title || test.suite_title.trim() === "") {
|
|
100
|
-
test.suite_title = "Unknown suite";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!test.title || test.title.trim() === "") {
|
|
104
|
-
test.title = "Unknown test title";
|
|
105
|
-
}
|
|
119
|
+
console.log(chalk.yellow(`⏳ The test results will be added to the HTML report. It will take some time...`));
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (test.steps) {
|
|
112
|
-
if (!test.steps || test.steps.trim() === "") {
|
|
113
|
-
test.steps = "This test has no 'steps' code";
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
test.steps = this.#removeAnsiColorCodes(test.steps);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// TODO: u can added an additional test values to this checks in the future
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
this.data = {
|
|
124
|
-
runId: this.store.runId,
|
|
125
|
-
status: runParams.status,
|
|
126
|
-
parallel: runParams.isParallel,
|
|
127
|
-
runUrl: this.store.runUrl,
|
|
128
|
-
executionTime: testExecutionSumTime(this.tests),
|
|
129
|
-
executionDate: getCurrentDateTimeFormatted(),
|
|
130
|
-
tests: this.tests
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// GENERATE HTML reports based on the results data
|
|
134
|
-
this.buildReport(this.data);
|
|
135
|
-
}
|
|
121
|
+
if (msg) {
|
|
122
|
+
console.log(chalk.blue(msg));
|
|
136
123
|
}
|
|
137
124
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
console.log(chalk.yellow(`🚨 HTML export path is not set, ignoring...`));
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
console.log(chalk.yellow(`⏳ The test results will be added to the HTML report. It will take some time...`));
|
|
147
|
-
// generate output HTML based on the template
|
|
148
|
-
const html = this.#generateHTMLReport(data);
|
|
149
|
-
|
|
150
|
-
if (process.env.TESTOMATIO_HTML_FILENAME && !process.env.TESTOMATIO_HTML_FILENAME.endsWith(".html")) {
|
|
151
|
-
console.log(
|
|
152
|
-
chalk.blue("HTML filename must include the extension \".html\"." +
|
|
153
|
-
` The default report name "${this.htmlOutputPath}" is used!`)
|
|
154
|
-
);
|
|
155
|
-
}
|
|
125
|
+
tests.forEach(test => {
|
|
126
|
+
if (!test.message || test.message.trim() === '') {
|
|
127
|
+
test.message = "This test has no 'message' code";
|
|
128
|
+
}
|
|
156
129
|
|
|
157
|
-
|
|
130
|
+
if (!test.suite_title || test.suite_title.trim() === '') {
|
|
131
|
+
test.suite_title = 'Unknown suite';
|
|
132
|
+
}
|
|
158
133
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const absolutePath = path.resolve(this.htmlOutputPath);
|
|
163
|
-
// Convert the file path to a file URL
|
|
164
|
-
const fileUrlPath = fileUrl(absolutePath, {resolve: true});
|
|
134
|
+
if (!test.title || test.title.trim() === '') {
|
|
135
|
+
test.title = 'Unknown test title';
|
|
136
|
+
}
|
|
165
137
|
|
|
166
|
-
|
|
138
|
+
if (!test.files || test.files.length === 0) {
|
|
139
|
+
test.files = 'This test has no files';
|
|
140
|
+
}
|
|
167
141
|
|
|
168
|
-
|
|
142
|
+
if (test.steps) {
|
|
143
|
+
if (!test.steps || test.steps.trim() === '') {
|
|
144
|
+
test.steps = "This test has no 'steps' code";
|
|
169
145
|
} else {
|
|
170
|
-
|
|
146
|
+
test.steps = removeAnsiColorCodes(test.steps);
|
|
171
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;
|
|
172
193
|
}
|
|
173
194
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const templateSource = fs.readFileSync(this.templateHtmlPath, 'utf8');
|
|
181
|
-
this.#loadReportHelpers();
|
|
182
|
-
try {
|
|
183
|
-
const template = handlebars.compile(templateSource);
|
|
195
|
+
const templateSource = fs.readFileSync(templatePath, 'utf8');
|
|
196
|
+
this.#loadReportHelpers();
|
|
197
|
+
try {
|
|
198
|
+
const template = handlebars.compile(templateSource);
|
|
184
199
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.log('Unknown HTML report generation error: ', e);
|
|
189
|
-
}
|
|
200
|
+
return template(data);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.log('Unknown HTML report generation error: ', e);
|
|
190
203
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
});
|
|
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
|
+
}
|
|
228
240
|
}
|
|
241
|
+
}
|
|
229
242
|
|
|
230
|
-
|
|
231
|
-
return JSON.stringify(replaceScriptTagsInArray(tests));
|
|
243
|
+
return newObj;
|
|
232
244
|
});
|
|
233
|
-
|
|
245
|
+
}
|
|
234
246
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
// Remove ANSI escape codes
|
|
248
|
+
return JSON.stringify(replaceScriptTagsInArray(tests));
|
|
249
|
+
});
|
|
250
|
+
}
|
|
238
251
|
|
|
239
|
-
|
|
240
|
-
|
|
252
|
+
toString() {
|
|
253
|
+
return 'HTML Reporter';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
241
256
|
|
|
242
|
-
|
|
243
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Calculates the total execution time for an array of tests.
|
|
259
|
+
* @param {Object[]} tests - An array of test objects.
|
|
260
|
+
* @param {number} tests[].run_time - The execution time of each test in milliseconds.
|
|
261
|
+
* @returns {string} - The total execution time in a formatted duration string.
|
|
262
|
+
*/
|
|
263
|
+
function testExecutionSumTime(tests) {
|
|
264
|
+
const totalMilliseconds = tests.reduce((sum, test) => {
|
|
265
|
+
if (typeof test.run_time === 'number') {
|
|
266
|
+
return sum + test.run_time;
|
|
244
267
|
}
|
|
268
|
+
return sum;
|
|
269
|
+
}, 0);
|
|
270
|
+
|
|
271
|
+
return formatDuration(totalMilliseconds);
|
|
245
272
|
}
|
|
246
273
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Removes ANSI color codes and converts newline characters to HTML line breaks in a given string.
|
|
276
|
+
* @param {string} str - The input string containing ANSI color codes.
|
|
277
|
+
* @returns {string} - The updated string with removed ANSI color codes and replaced newline characters.
|
|
278
|
+
*/
|
|
279
|
+
function removeAnsiColorCodes(str) {
|
|
280
|
+
let updatedStr = str.replace(ansiRegExp(), '');
|
|
281
|
+
updatedStr = updatedStr.replace(/\n/g, '<br>');
|
|
254
282
|
|
|
255
|
-
|
|
283
|
+
return updatedStr;
|
|
256
284
|
}
|
|
257
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Formats duration in milliseconds into a human-readable string representation.
|
|
288
|
+
* @param {number} duration - The duration in milliseconds.
|
|
289
|
+
* @returns {string} - The formatted duration string (e.g., "2h 30m 15s 500ms").
|
|
290
|
+
*/
|
|
258
291
|
function formatDuration(duration) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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`;
|
|
267
300
|
}
|
|
268
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Retrieves the current date and time in a formatted string.
|
|
304
|
+
* @returns {string} - The formatted date and time string (e.g., "(01/01/2023 12:00:00)").
|
|
305
|
+
*/
|
|
269
306
|
function getCurrentDateTimeFormatted() {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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})`;
|
|
279
316
|
}
|
|
280
317
|
|
|
281
|
-
module.exports = HtmlPipe;
|
|
318
|
+
module.exports = HtmlPipe;
|