@testomatio/reporter 1.1.0-beta-2 → 1.1.0-beta.artifact-count-retry-msg-1
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/adapter/codecept.js +3 -6
- package/lib/adapter/cypress-plugin/index.js +35 -14
- package/lib/adapter/playwright.js +8 -3
- package/lib/bin/startTest.js +6 -6
- package/lib/client.js +24 -6
- package/lib/config.js +24 -0
- package/lib/constants.js +7 -0
- package/lib/fileUploader.js +118 -61
- package/lib/pipe/gitlab.js +1 -1
- package/lib/pipe/html.js +316 -0
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +123 -29
- package/lib/storages/key-value-storage.js +1 -1
- package/lib/storages/logger.js +2 -2
- package/lib/template/template-draft.hbs +249 -0
- package/lib/template/testomatio.hbs +750 -0
- package/lib/xmlReader.js +1 -2
- package/package.json +6 -2
package/lib/pipe/html.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:pipe:html');
|
|
2
|
+
const merge = require('lodash.merge');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const handlebars = require('handlebars');
|
|
7
|
+
const fileUrl = require('file-url');
|
|
8
|
+
|
|
9
|
+
const { fileSystem, isSameTest, ansiRegExp } = require('../utils/utils');
|
|
10
|
+
const { HTML_REPORT } = require('../constants');
|
|
11
|
+
|
|
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
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async createRun() {
|
|
60
|
+
// empty
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
updateRun() {
|
|
64
|
+
// empty
|
|
65
|
+
}
|
|
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
|
+
}
|
|
99
|
+
}
|
|
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
|
+
|
|
119
|
+
if (msg) {
|
|
120
|
+
console.log(chalk.blue(msg));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
tests.forEach(test => {
|
|
124
|
+
|
|
125
|
+
if (!test.message || test.message.trim() === "") {
|
|
126
|
+
test.message = "This test has no 'message' code";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!test.suite_title || test.suite_title.trim() === "") {
|
|
130
|
+
test.suite_title = "Unknown suite";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!test.title || test.title.trim() === "") {
|
|
134
|
+
test.title = "Unknown test title";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!test.files || test.files.length === 0) {
|
|
138
|
+
test.files = "This test has no files";
|
|
139
|
+
}
|
|
140
|
+
|
|
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
|
+
});
|
|
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;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const templateSource = fs.readFileSync(templatePath, 'utf8');
|
|
196
|
+
this.#loadReportHelpers();
|
|
197
|
+
try {
|
|
198
|
+
const template = handlebars.compile(templateSource);
|
|
199
|
+
|
|
200
|
+
return template(data);
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
console.log('Unknown HTML report generation error: ', e);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
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
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Remove ANSI escape codes
|
|
246
|
+
return JSON.stringify(replaceScriptTagsInArray(tests));
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
toString() {
|
|
251
|
+
return 'HTML Reporter';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Calculates the total execution time for an array of tests.
|
|
257
|
+
* @param {Object[]} tests - An array of test objects.
|
|
258
|
+
* @param {number} tests[].run_time - The execution time of each test in milliseconds.
|
|
259
|
+
* @returns {string} - The total execution time in a formatted duration string.
|
|
260
|
+
*/
|
|
261
|
+
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);
|
|
268
|
+
|
|
269
|
+
return formatDuration(totalMilliseconds);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Removes ANSI color codes and converts newline characters to HTML line breaks in a given string.
|
|
274
|
+
* @param {string} str - The input string containing ANSI color codes.
|
|
275
|
+
* @returns {string} - The updated string with removed ANSI color codes and replaced newline characters.
|
|
276
|
+
*/
|
|
277
|
+
function removeAnsiColorCodes(str) {
|
|
278
|
+
let updatedStr = str.replace(ansiRegExp(), "");
|
|
279
|
+
updatedStr = updatedStr.replace(/\n/g, '<br>');
|
|
280
|
+
|
|
281
|
+
return updatedStr;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Formats duration in milliseconds into a human-readable string representation.
|
|
286
|
+
* @param {number} duration - The duration in milliseconds.
|
|
287
|
+
* @returns {string} - The formatted duration string (e.g., "2h 30m 15s 500ms").
|
|
288
|
+
*/
|
|
289
|
+
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`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Retrieves the current date and time in a formatted string.
|
|
302
|
+
* @returns {string} - The formatted date and time string (e.g., "(01/01/2023 12:00:00)").
|
|
303
|
+
*/
|
|
304
|
+
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})`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = HtmlPipe;
|
package/lib/pipe/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const TestomatioPipe = require('./testomatio');
|
|
|
6
6
|
const GitHubPipe = require('./github');
|
|
7
7
|
const GitLabPipe = require('./gitlab');
|
|
8
8
|
const CsvPipe = require('./csv');
|
|
9
|
+
const HtmlPipe = require('./html');
|
|
9
10
|
|
|
10
11
|
function PipeFactory(params, opts) {
|
|
11
12
|
const extraPipes = [];
|
|
@@ -43,6 +44,7 @@ function PipeFactory(params, opts) {
|
|
|
43
44
|
new GitHubPipe(params, opts),
|
|
44
45
|
new GitLabPipe(params, opts),
|
|
45
46
|
new CsvPipe(params, opts),
|
|
47
|
+
new HtmlPipe(params, opts),
|
|
46
48
|
...extraPipes,
|
|
47
49
|
];
|
|
48
50
|
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -2,13 +2,14 @@ const debug = require('debug')('@testomatio/reporter:pipe:testomatio');
|
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const axios = require('axios');
|
|
4
4
|
const JsonCycle = require('json-cycle');
|
|
5
|
+
const promiseRetry = require('promise-retry');
|
|
6
|
+
|
|
5
7
|
const { APP_PREFIX, STATUS } = require('../constants');
|
|
6
8
|
const { isValidUrl, foundedTestLog } = require('../utils/utils');
|
|
7
|
-
const { parseFilterParams, generateFilterRequestParams, setS3Credentials
|
|
9
|
+
const { parseFilterParams, generateFilterRequestParams, setS3Credentials } = require('../utils/pipe_utils');
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
process.env.runId = TESTOMATIO_RUN;
|
|
11
|
+
if (process.env.TESTOMATIO_RUN) {
|
|
12
|
+
process.env.runId = process.env.TESTOMATIO_RUN;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -33,6 +34,7 @@ class TestomatioPipe {
|
|
|
33
34
|
this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
|
|
34
35
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
35
36
|
this.env = process.env.TESTOMATIO_ENV;
|
|
37
|
+
this.label = process.env.TESTOMATIO_LABEL;
|
|
36
38
|
|
|
37
39
|
this.axios = axios.create({
|
|
38
40
|
baseURL: `${this.url.trim()}`,
|
|
@@ -76,8 +78,14 @@ class TestomatioPipe {
|
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
+
const response = await sendRequest(
|
|
82
|
+
this.axios,
|
|
83
|
+
"get",
|
|
84
|
+
'/api/test_grep',
|
|
85
|
+
q
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const { data } = response;
|
|
81
89
|
|
|
82
90
|
if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
|
|
83
91
|
foundedTestLog(APP_PREFIX, data.tests);
|
|
@@ -131,6 +139,7 @@ class TestomatioPipe {
|
|
|
131
139
|
jira_id: this.jiraId,
|
|
132
140
|
env: this.env,
|
|
133
141
|
title: this.title,
|
|
142
|
+
label: this.label,
|
|
134
143
|
shared_run: this.sharedRun,
|
|
135
144
|
}).filter(([, value]) => !!value),
|
|
136
145
|
);
|
|
@@ -138,25 +147,43 @@ class TestomatioPipe {
|
|
|
138
147
|
|
|
139
148
|
if (this.runId) {
|
|
140
149
|
debug(`Run with id ${this.runId} already created, updating...`);
|
|
141
|
-
|
|
142
|
-
|
|
150
|
+
|
|
151
|
+
const response = await sendRequest(
|
|
152
|
+
this.axios,
|
|
153
|
+
"put",
|
|
154
|
+
`/api/reporter/${this.runId}`,
|
|
155
|
+
runParams
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (response.data.artifacts) setS3Credentials(response.data.artifacts);
|
|
143
159
|
return;
|
|
144
160
|
}
|
|
145
161
|
|
|
146
162
|
try {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
const response = await sendRequest(
|
|
164
|
+
this.axios,
|
|
165
|
+
"post",
|
|
166
|
+
`/api/reporter`,
|
|
167
|
+
runParams,
|
|
168
|
+
{
|
|
169
|
+
maxContentLength: Infinity,
|
|
170
|
+
maxBodyLength: Infinity
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
this.runId = response.data.uid;
|
|
176
|
+
this.runUrl = `${this.url}/${response.data.url.split('/').splice(3).join('/')}`;
|
|
177
|
+
this.runPublicUrl = response.data.public_url;
|
|
178
|
+
|
|
179
|
+
if (response.data.artifacts) setS3Credentials(response.data.artifacts);
|
|
180
|
+
|
|
155
181
|
this.store.runUrl = this.runUrl;
|
|
156
182
|
this.store.runPublicUrl = this.runPublicUrl;
|
|
157
183
|
this.store.runId = this.runId;
|
|
158
184
|
console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId);
|
|
159
185
|
process.env.runId = this.runId;
|
|
186
|
+
|
|
160
187
|
debug('Run created', this.runId);
|
|
161
188
|
} catch (err) {
|
|
162
189
|
console.error(
|
|
@@ -181,14 +208,20 @@ class TestomatioPipe {
|
|
|
181
208
|
data.create = this.createNewTests;
|
|
182
209
|
const json = JsonCycle.stringify(data);
|
|
183
210
|
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
211
|
+
return sendRequest(
|
|
212
|
+
this.axios,
|
|
213
|
+
"post",
|
|
214
|
+
`/api/reporter/${this.runId}/testrun`,
|
|
215
|
+
json,
|
|
216
|
+
{
|
|
217
|
+
maxContentLength: Infinity,
|
|
218
|
+
maxBodyLength: Infinity,
|
|
219
|
+
headers: {
|
|
220
|
+
// Overwrite Axios's automatically set Content-Type
|
|
221
|
+
'Content-Type': 'application/json',
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
)
|
|
192
225
|
.catch(err => {
|
|
193
226
|
if (err.response) {
|
|
194
227
|
if (err.response.status >= 400) {
|
|
@@ -233,11 +266,17 @@ class TestomatioPipe {
|
|
|
233
266
|
|
|
234
267
|
try {
|
|
235
268
|
if (this.runId && !this.proceed) {
|
|
236
|
-
await
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
269
|
+
await sendRequest(
|
|
270
|
+
this.axios,
|
|
271
|
+
"put",
|
|
272
|
+
`/api/reporter/${this.runId}`,
|
|
273
|
+
{
|
|
274
|
+
api_key: this.apiKey,
|
|
275
|
+
status_event,
|
|
276
|
+
tests: params.tests,
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
|
|
241
280
|
if (this.runUrl) {
|
|
242
281
|
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
|
|
243
282
|
}
|
|
@@ -286,4 +325,59 @@ class TestomatioPipe {
|
|
|
286
325
|
}
|
|
287
326
|
}
|
|
288
327
|
|
|
328
|
+
async function sendRequest(sender, method, url, data = {}, opts = {}, attempts = 5, intervalMs = 1000) {
|
|
329
|
+
let send;
|
|
330
|
+
|
|
331
|
+
switch(method) {
|
|
332
|
+
case "post":
|
|
333
|
+
send = async () => {
|
|
334
|
+
const resp = await sender.post(url, data, opts);
|
|
335
|
+
|
|
336
|
+
if (resp.status === 200) {
|
|
337
|
+
return resp;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
throw new Error('POST request failed!');
|
|
341
|
+
};
|
|
342
|
+
break;
|
|
343
|
+
case "put":
|
|
344
|
+
send = async () => {
|
|
345
|
+
const resp = await sender.put(url, data);
|
|
346
|
+
|
|
347
|
+
if (resp.status === 200) {
|
|
348
|
+
return resp;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
throw new Error('PUT request failed!');
|
|
352
|
+
};
|
|
353
|
+
break;
|
|
354
|
+
default:
|
|
355
|
+
send = async () => {
|
|
356
|
+
const resp = await sender.get(url, data);
|
|
357
|
+
|
|
358
|
+
if (resp.status === 200) {
|
|
359
|
+
return resp;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
throw new Error('GET request failed!');
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const response = await promiseRetry(
|
|
368
|
+
{
|
|
369
|
+
retries: attempts,
|
|
370
|
+
minTimeout: intervalMs,
|
|
371
|
+
},
|
|
372
|
+
send
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
return response;
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
console.error('Request failed or timed out:', error.message);
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
289
383
|
module.exports = TestomatioPipe;
|
|
@@ -28,7 +28,7 @@ class KeyValueStorage {
|
|
|
28
28
|
/**
|
|
29
29
|
* Returns key-values pairs for the test as object
|
|
30
30
|
* @param {*} context testId or test context from test runner
|
|
31
|
-
* @returns {Object} key-values pairs as object
|
|
31
|
+
* @returns {Object} key-values pairs as object, e.g. {priority: 'high', browser: 'chrome'}
|
|
32
32
|
*/
|
|
33
33
|
get(context) {
|
|
34
34
|
if (!context) return null;
|
package/lib/storages/logger.js
CHANGED
|
@@ -187,12 +187,12 @@ class Logger {
|
|
|
187
187
|
/* prevent multiple console interceptions (cause of infinite loop)
|
|
188
188
|
actual only for "console", because its used as default output and is intercepted by default */
|
|
189
189
|
const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
|
|
190
|
-
if (isUserLoggerConsole &&
|
|
190
|
+
if (isUserLoggerConsole && global.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
|
|
191
191
|
debug(`Try to intercept console, but it is already intercepted`);
|
|
192
192
|
return;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
global.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = true;
|
|
196
196
|
debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
|
|
197
197
|
|
|
198
198
|
// override user logger (any, e.g. console) methods to intercept log messages
|