@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/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 +248 -246
- 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/utils/utils.js +28 -2
- package/lib/xmlReader.js +106 -105
- package/package.json +5 -3
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -232
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
test.message = "This test has no 'message' code";
|
|
127
|
-
}
|
|
121
|
+
if (msg) {
|
|
122
|
+
console.log(chalk.blue(msg));
|
|
123
|
+
}
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
if (!test.suite_title || test.suite_title.trim() === '') {
|
|
131
|
+
test.suite_title = 'Unknown suite';
|
|
132
|
+
}
|
|
136
133
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
if (!test.title || test.title.trim() === '') {
|
|
135
|
+
test.title = 'Unknown test title';
|
|
136
|
+
}
|
|
140
137
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
return JSON.stringify(replaceScriptTagsInArray(tests));
|
|
243
|
+
return newObj;
|
|
247
244
|
});
|
|
248
|
-
|
|
245
|
+
}
|
|
249
246
|
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
+
let updatedStr = str.replace(ansiRegExp(), '');
|
|
281
|
+
updatedStr = updatedStr.replace(/\n/g, '<br>');
|
|
280
282
|
|
|
281
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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;
|