@testomatio/reporter 2.3.7-beta.3-xml-import → 2.3.7-beta.5-stack-artifacts
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 +1 -1
- package/lib/bin/cli.js +1 -1
- package/lib/bin/reportXml.js +2 -5
- package/lib/client.js +3 -7
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +7 -40
- package/lib/pipe/debug.js +1 -1
- package/lib/pipe/testomatio.js +14 -18
- package/lib/uploader.js +0 -4
- package/lib/utils/utils.js +11 -90
- package/lib/xmlReader.d.ts +26 -32
- package/lib/xmlReader.js +50 -106
- package/package.json +1 -1
- package/src/bin/cli.js +1 -1
- package/src/bin/reportXml.js +2 -5
- package/src/client.js +4 -7
- package/src/junit-adapter/csharp.js +6 -45
- package/src/pipe/debug.js +3 -2
- package/src/pipe/testomatio.js +80 -74
- package/src/uploader.js +0 -5
- package/src/utils/utils.js +9 -96
- package/src/xmlReader.js +45 -128
- package/lib/junit-adapter/nunit-parser.d.ts +0 -82
- package/lib/junit-adapter/nunit-parser.js +0 -369
- package/src/junit-adapter/nunit-parser.js +0 -404
package/lib/xmlReader.js
CHANGED
|
@@ -11,7 +11,6 @@ const fast_xml_parser_1 = require("fast-xml-parser");
|
|
|
11
11
|
const constants_js_1 = require("./constants.js");
|
|
12
12
|
const crypto_1 = require("crypto");
|
|
13
13
|
const url_1 = require("url");
|
|
14
|
-
const nunit_parser_js_1 = require("./junit-adapter/nunit-parser.js");
|
|
15
14
|
const utils_js_1 = require("./utils/utils.js");
|
|
16
15
|
const index_js_1 = require("./pipe/index.js");
|
|
17
16
|
const index_js_2 = __importDefault(require("./junit-adapter/index.js"));
|
|
@@ -55,9 +54,6 @@ class XmlReader {
|
|
|
55
54
|
this.stats = {};
|
|
56
55
|
this.stats.language = opts.lang?.toLowerCase();
|
|
57
56
|
this.uploader = new uploader_js_1.S3Uploader();
|
|
58
|
-
// Enhanced NUnit parsing - enabled by default for NUnit XML
|
|
59
|
-
this.enhancedNunit = opts.enhancedNunit !== false; // Default true, can be disabled
|
|
60
|
-
this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
|
|
61
57
|
// @ts-ignore
|
|
62
58
|
const packageJsonPath = path_1.default.resolve(__dirname, '..', 'package.json');
|
|
63
59
|
this.version = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()).version;
|
|
@@ -106,8 +102,7 @@ class XmlReader {
|
|
|
106
102
|
return this.processJUnit(jsonSuite);
|
|
107
103
|
}
|
|
108
104
|
processJUnit(jsonSuite) {
|
|
109
|
-
const { testsuite, name, failures, errors } = jsonSuite;
|
|
110
|
-
const tests = testsuite?.tests || jsonSuite.tests;
|
|
105
|
+
const { testsuite, name, tests, failures, errors } = jsonSuite;
|
|
111
106
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
112
107
|
const resultTests = processTestSuite(testsuite);
|
|
113
108
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
@@ -133,13 +128,6 @@ class XmlReader {
|
|
|
133
128
|
};
|
|
134
129
|
}
|
|
135
130
|
processNUnit(jsonSuite) {
|
|
136
|
-
// Use enhanced NUnit parser if enabled and this is actually NUnit XML
|
|
137
|
-
if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
|
|
138
|
-
debug('Using enhanced NUnit parser');
|
|
139
|
-
return this.processNUnitEnhanced(jsonSuite);
|
|
140
|
-
}
|
|
141
|
-
// Fallback to legacy parser for backward compatibility
|
|
142
|
-
debug('Using legacy NUnit parser');
|
|
143
131
|
const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
|
|
144
132
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
145
133
|
const resultTests = processTestSuite(jsonSuite['test-suite']);
|
|
@@ -154,58 +142,65 @@ class XmlReader {
|
|
|
154
142
|
tests: resultTests,
|
|
155
143
|
};
|
|
156
144
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Check if the XML is actually NUnit format (has test-suite hierarchy)
|
|
159
|
-
* @param {Object} jsonSuite - Parsed XML suite object
|
|
160
|
-
* @returns {boolean} - True if this is NUnit XML format
|
|
161
|
-
*/
|
|
162
|
-
isNUnitXml(jsonSuite) {
|
|
163
|
-
// NUnit XML has test-suite elements with type attributes
|
|
164
|
-
if (jsonSuite['test-suite']) {
|
|
165
|
-
const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
|
|
166
|
-
// Check for NUnit-specific test-suite types
|
|
167
|
-
return (testSuite &&
|
|
168
|
-
testSuite.type &&
|
|
169
|
-
['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type));
|
|
170
|
-
}
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
processNUnitEnhanced(jsonSuite) {
|
|
174
|
-
debug('Processing NUnit XML with enhanced parser');
|
|
175
|
-
try {
|
|
176
|
-
const nunitParser = new nunit_parser_js_1.NUnitXmlParser({
|
|
177
|
-
groupParameterized: this.groupParameterized,
|
|
178
|
-
...this.opts,
|
|
179
|
-
});
|
|
180
|
-
const result = nunitParser.parseTestRun(jsonSuite);
|
|
181
|
-
// Add parsed tests to our collection
|
|
182
|
-
this.tests = this.tests.concat(result.tests);
|
|
183
|
-
debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
|
|
184
|
-
return result;
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
|
|
188
|
-
console.warn(`${constants_js_1.APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
|
|
189
|
-
// Fallback to legacy parser
|
|
190
|
-
this.enhancedNunit = false;
|
|
191
|
-
return this.processNUnit(jsonSuite);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
145
|
processTRX(jsonSuite) {
|
|
195
146
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
196
147
|
if (!Array.isArray(defs))
|
|
197
148
|
defs = [defs].filter(d => !!d);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
149
|
+
const tests = defs.map(td => {
|
|
150
|
+
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
151
|
+
let example = td.name.match(/\((.*?)\)/);
|
|
152
|
+
if (example)
|
|
153
|
+
example = { ...example[1].split(',') };
|
|
154
|
+
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
155
|
+
const suite_title = suite.pop();
|
|
156
|
+
return {
|
|
157
|
+
title,
|
|
158
|
+
example,
|
|
159
|
+
file: suite.join('/'),
|
|
160
|
+
description: td.Description,
|
|
161
|
+
suite_title,
|
|
162
|
+
id: td.Execution.id,
|
|
163
|
+
};
|
|
164
|
+
}) || [];
|
|
201
165
|
let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
|
|
202
166
|
if (!Array.isArray(result))
|
|
203
167
|
result = [result].filter(d => !!d);
|
|
204
|
-
const results = result.map(td =>
|
|
168
|
+
const results = result.map(td => ({
|
|
169
|
+
id: td.executionId,
|
|
170
|
+
// seconds are used in junit reports, but ms are used by testomatio
|
|
171
|
+
run_time: parseFloat(td.duration) * 1000,
|
|
172
|
+
status: td.outcome,
|
|
173
|
+
stack: td.Output.StdOut,
|
|
174
|
+
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
|
|
175
|
+
}));
|
|
176
|
+
results.forEach(r => {
|
|
177
|
+
const test = tests.find(t => t.id === r.id) || {};
|
|
178
|
+
r.suite_title = test.suite_title;
|
|
179
|
+
r.title = test.title?.trim();
|
|
180
|
+
if (test.code)
|
|
181
|
+
r.code = test.code;
|
|
182
|
+
if (test.description)
|
|
183
|
+
r.description = test.description;
|
|
184
|
+
if (test.example)
|
|
185
|
+
r.example = test.example;
|
|
186
|
+
if (test.file)
|
|
187
|
+
r.file = test.file;
|
|
188
|
+
r.create = true;
|
|
189
|
+
r.overwrite = true;
|
|
190
|
+
if (r.status === 'Passed')
|
|
191
|
+
r.status = constants_js_1.STATUS.PASSED;
|
|
192
|
+
if (r.status === 'Failed')
|
|
193
|
+
r.status = constants_js_1.STATUS.FAILED;
|
|
194
|
+
if (r.status === 'Skipped')
|
|
195
|
+
r.status = constants_js_1.STATUS.SKIPPED;
|
|
196
|
+
delete r.id;
|
|
197
|
+
});
|
|
205
198
|
debug(results);
|
|
206
199
|
const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
|
|
207
200
|
const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
|
|
208
|
-
|
|
201
|
+
let status = constants_js_1.STATUS.PASSED.toString();
|
|
202
|
+
if (failed_count > 0)
|
|
203
|
+
status = constants_js_1.STATUS.FAILED;
|
|
209
204
|
this.tests = results.filter(t => !!t.title);
|
|
210
205
|
return {
|
|
211
206
|
status,
|
|
@@ -217,57 +212,6 @@ class XmlReader {
|
|
|
217
212
|
tests: results,
|
|
218
213
|
};
|
|
219
214
|
}
|
|
220
|
-
_parseTRXTestDefinition(td) {
|
|
221
|
-
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
222
|
-
const exampleMatch = td.name.match(/\((.*?)\)/);
|
|
223
|
-
const example = exampleMatch ? { ...exampleMatch[1].split(',') } : null;
|
|
224
|
-
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
225
|
-
const suite_title = suite.pop();
|
|
226
|
-
// Convert namespace to file path for C#
|
|
227
|
-
const file = `${suite.join('/')}.cs`;
|
|
228
|
-
return {
|
|
229
|
-
title, // Base name without parameters for test import
|
|
230
|
-
example, // Parameters object for parameterized tests
|
|
231
|
-
file, // File path with .cs extension
|
|
232
|
-
description: td.Description,
|
|
233
|
-
suite_title,
|
|
234
|
-
id: td.Execution.id,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
_parseTRXTestResult(td, tests) {
|
|
238
|
-
const test = tests.find(t => t.id === td.executionId) || {};
|
|
239
|
-
const result = {
|
|
240
|
-
suite_title: test.suite_title,
|
|
241
|
-
title: test.title?.trim(),
|
|
242
|
-
file: test.file,
|
|
243
|
-
description: test.description,
|
|
244
|
-
code: test.code,
|
|
245
|
-
run_time: parseFloat(td.duration) * 1000,
|
|
246
|
-
stack: td.Output?.StdOut || '',
|
|
247
|
-
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
|
|
248
|
-
create: true,
|
|
249
|
-
overwrite: true,
|
|
250
|
-
};
|
|
251
|
-
// Add example for parameterized tests
|
|
252
|
-
if (test.example) {
|
|
253
|
-
result.example = test.example;
|
|
254
|
-
}
|
|
255
|
-
// Map TRX status to Testomat.io status
|
|
256
|
-
result.status = this._mapTRXStatus(td.outcome);
|
|
257
|
-
return result;
|
|
258
|
-
}
|
|
259
|
-
_mapTRXStatus(outcome) {
|
|
260
|
-
switch (outcome) {
|
|
261
|
-
case 'Passed':
|
|
262
|
-
return constants_js_1.STATUS.PASSED;
|
|
263
|
-
case 'Failed':
|
|
264
|
-
return constants_js_1.STATUS.FAILED;
|
|
265
|
-
case 'Skipped':
|
|
266
|
-
return constants_js_1.STATUS.SKIPPED;
|
|
267
|
-
default:
|
|
268
|
-
return constants_js_1.STATUS.PASSED;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
215
|
processXUnit(assemblies) {
|
|
272
216
|
const tests = [];
|
|
273
217
|
assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
|
package/package.json
CHANGED
package/src/bin/cli.js
CHANGED
|
@@ -158,7 +158,7 @@ program
|
|
|
158
158
|
.option('--lang <lang>', 'Language used (python, ruby, java)')
|
|
159
159
|
.option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
|
|
160
160
|
.action(async (pattern, opts) => {
|
|
161
|
-
if (!pattern.endsWith('.xml')
|
|
161
|
+
if (!pattern.endsWith('.xml')) {
|
|
162
162
|
pattern += '.xml';
|
|
163
163
|
}
|
|
164
164
|
let { javaTests, lang } = opts;
|
package/src/bin/reportXml.js
CHANGED
|
@@ -23,7 +23,7 @@ program
|
|
|
23
23
|
.option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
|
|
24
24
|
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
25
25
|
.action(async (pattern, opts) => {
|
|
26
|
-
if (!pattern.endsWith('.xml')
|
|
26
|
+
if (!pattern.endsWith('.xml')) {
|
|
27
27
|
pattern += '.xml';
|
|
28
28
|
}
|
|
29
29
|
let { javaTests, lang } = opts;
|
|
@@ -34,10 +34,7 @@ program
|
|
|
34
34
|
}
|
|
35
35
|
lang = lang?.toLowerCase();
|
|
36
36
|
if (javaTests === true || (lang === 'java' && !javaTests)) javaTests = 'src/test/java';
|
|
37
|
-
const runReader = new XmlReader({
|
|
38
|
-
javaTests,
|
|
39
|
-
lang,
|
|
40
|
-
});
|
|
37
|
+
const runReader = new XmlReader({ javaTests, lang });
|
|
41
38
|
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
42
39
|
if (!files.length) {
|
|
43
40
|
console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
|
package/src/client.js
CHANGED
|
@@ -37,8 +37,9 @@ class Client {
|
|
|
37
37
|
this.runId = '';
|
|
38
38
|
this.queue = Promise.resolve();
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
40
|
+
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
41
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
const pathToPackageJSON = path.join(__dirname, '../package.json');
|
|
42
43
|
try {
|
|
43
44
|
this.version = JSON.parse(fs.readFileSync(pathToPackageJSON).toString()).version;
|
|
44
45
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
@@ -386,11 +387,7 @@ class Client {
|
|
|
386
387
|
*/
|
|
387
388
|
formatLogs({ error, steps, logs }) {
|
|
388
389
|
error = error?.trim();
|
|
389
|
-
logs = logs
|
|
390
|
-
?.trim()
|
|
391
|
-
.split('\n')
|
|
392
|
-
.map(l => truncate(l))
|
|
393
|
-
.join('\n');
|
|
390
|
+
logs = logs?.trim().split('\n').map(l => truncate(l)).join('\n');
|
|
394
391
|
|
|
395
392
|
if (Array.isArray(steps)) {
|
|
396
393
|
steps = steps
|
|
@@ -3,50 +3,18 @@ import Adapter from './adapter.js';
|
|
|
3
3
|
|
|
4
4
|
class CSharpAdapter extends Adapter {
|
|
5
5
|
formatTest(t) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (exampleMatch) {
|
|
10
|
-
// Extract parameters as object with numeric keys for API
|
|
11
|
-
const params = exampleMatch[1].split(',').map(param => param.trim());
|
|
12
|
-
t.example = {};
|
|
13
|
-
params.forEach((param, index) => {
|
|
14
|
-
t.example[index] = param;
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Remove parameters from title to avoid duplicates in Test Suite
|
|
20
|
-
// The example field will be used for grouping on import
|
|
21
|
-
t.title = t.title.replace(/\(.*?\)/, '').trim();
|
|
22
|
-
|
|
6
|
+
const title = t.title.replace(/\(.*?\)/, '').trim();
|
|
7
|
+
const example = t.title.match(/\((.*?)\)/);
|
|
8
|
+
if (example) t.example = { ...example[1].split(',') };
|
|
23
9
|
const suite = t.suite_title.split('.');
|
|
24
10
|
t.suite_title = suite.pop();
|
|
25
11
|
t.file = namespaceToFileName(t.file);
|
|
12
|
+
t.title = title.trim();
|
|
26
13
|
return t;
|
|
27
14
|
}
|
|
28
15
|
|
|
29
16
|
getFilePath(t) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Normalize path separators for cross-platform compatibility
|
|
33
|
-
let filePath = t.file.replace(/\\/g, '/');
|
|
34
|
-
|
|
35
|
-
// If file already has .cs extension, use it directly
|
|
36
|
-
if (filePath.endsWith('.cs')) {
|
|
37
|
-
// Make relative path if it's absolute
|
|
38
|
-
if (path.isAbsolute(filePath)) {
|
|
39
|
-
// Try to find project-relative path
|
|
40
|
-
const cwd = process.cwd().replace(/\\/g, '/');
|
|
41
|
-
if (filePath.startsWith(cwd)) {
|
|
42
|
-
filePath = path.relative(cwd, filePath).replace(/\\/g, '/');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return filePath;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Convert namespace path to file path
|
|
49
|
-
const fileName = namespaceToFileName(filePath);
|
|
17
|
+
const fileName = namespaceToFileName(t.file);
|
|
50
18
|
return fileName;
|
|
51
19
|
}
|
|
52
20
|
}
|
|
@@ -54,14 +22,7 @@ class CSharpAdapter extends Adapter {
|
|
|
54
22
|
export default CSharpAdapter;
|
|
55
23
|
|
|
56
24
|
function namespaceToFileName(fileName) {
|
|
57
|
-
if (!fileName) return '';
|
|
58
|
-
|
|
59
|
-
// If already a .cs file path, clean it up
|
|
60
|
-
if (fileName.endsWith('.cs')) {
|
|
61
|
-
return fileName.replace(/\\/g, '/');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
25
|
const fileParts = fileName.split('.');
|
|
65
26
|
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
66
|
-
return `${fileParts.join(
|
|
27
|
+
return `${fileParts.join(path.sep)}.cs`;
|
|
67
28
|
}
|
package/src/pipe/debug.js
CHANGED
|
@@ -15,7 +15,7 @@ export class DebugPipe {
|
|
|
15
15
|
this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
|
|
16
16
|
if (this.isEnabled) {
|
|
17
17
|
this.batch = {
|
|
18
|
-
isEnabled: this.params.isBatchEnabled ??
|
|
18
|
+
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
19
19
|
intervalFunction: null,
|
|
20
20
|
intervalTime: 5000,
|
|
21
21
|
tests: [],
|
|
@@ -93,7 +93,8 @@ export class DebugPipe {
|
|
|
93
93
|
const logData = { action: 'addTest', testId: data };
|
|
94
94
|
if (this.store.runId) logData.runId = this.store.runId;
|
|
95
95
|
this.logToFile(logData);
|
|
96
|
-
}
|
|
96
|
+
}
|
|
97
|
+
else this.batch.tests.push(data);
|
|
97
98
|
|
|
98
99
|
if (!this.batch.intervalFunction) await this.batchUpload();
|
|
99
100
|
}
|
package/src/pipe/testomatio.js
CHANGED
|
@@ -20,7 +20,7 @@ if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
|
|
|
20
20
|
class TestomatioPipe {
|
|
21
21
|
constructor(params, store) {
|
|
22
22
|
this.batch = {
|
|
23
|
-
isEnabled: params?.isBatchEnabled ??
|
|
23
|
+
isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
24
24
|
intervalFunction: null, // will be created in createRun by setInterval function
|
|
25
25
|
intervalTime: 5000, // how often tests are sent
|
|
26
26
|
tests: [], // array of tests in batch
|
|
@@ -60,8 +60,8 @@ class TestomatioPipe {
|
|
|
60
60
|
retryConfig: {
|
|
61
61
|
retry: REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
62
62
|
retryDelay: REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
63
|
-
httpMethodsToRetry: ['GET',
|
|
64
|
-
shouldRetry: error => {
|
|
63
|
+
httpMethodsToRetry: ['GET','PUT','HEAD','OPTIONS','DELETE','POST'],
|
|
64
|
+
shouldRetry: (error) => {
|
|
65
65
|
if (!error.response) return false;
|
|
66
66
|
switch (error.response?.status) {
|
|
67
67
|
case 400: // Bad request (probably wrong API key)
|
|
@@ -73,8 +73,8 @@ class TestomatioPipe {
|
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
76
|
-
}
|
|
77
|
-
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
this.isEnabled = true;
|
|
@@ -104,6 +104,7 @@ class TestomatioPipe {
|
|
|
104
104
|
// add test ID + run ID
|
|
105
105
|
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
106
106
|
|
|
107
|
+
|
|
107
108
|
if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
|
|
108
109
|
data.stack = null;
|
|
109
110
|
}
|
|
@@ -119,6 +120,7 @@ class TestomatioPipe {
|
|
|
119
120
|
return data;
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
|
|
122
124
|
/**
|
|
123
125
|
* Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
|
|
124
126
|
* @param {Object} opts - The options for preparing the test grepList.
|
|
@@ -213,7 +215,7 @@ class TestomatioPipe {
|
|
|
213
215
|
method: 'PUT',
|
|
214
216
|
url: `/api/reporter/${this.runId}`,
|
|
215
217
|
data: runParams,
|
|
216
|
-
responseType: 'json'
|
|
218
|
+
responseType: 'json'
|
|
217
219
|
});
|
|
218
220
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
219
221
|
return;
|
|
@@ -226,7 +228,7 @@ class TestomatioPipe {
|
|
|
226
228
|
url: '/api/reporter',
|
|
227
229
|
data: runParams,
|
|
228
230
|
maxContentLength: Infinity,
|
|
229
|
-
responseType: 'json'
|
|
231
|
+
responseType: 'json'
|
|
230
232
|
});
|
|
231
233
|
|
|
232
234
|
this.runId = resp.data.uid;
|
|
@@ -285,44 +287,44 @@ class TestomatioPipe {
|
|
|
285
287
|
|
|
286
288
|
debug('Adding test', json);
|
|
287
289
|
|
|
288
|
-
return this.client
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (err.response.status >= 400) {
|
|
303
|
-
const responseData = err.response.data || { message: '' };
|
|
304
|
-
console.log(
|
|
305
|
-
APP_PREFIX,
|
|
306
|
-
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
307
|
-
pc.gray(data?.title || ''),
|
|
308
|
-
);
|
|
309
|
-
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
310
|
-
this.hasUnmatchedTests = true;
|
|
311
|
-
}
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
290
|
+
return this.client.request({
|
|
291
|
+
method: 'POST',
|
|
292
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
293
|
+
data: json,
|
|
294
|
+
headers: {
|
|
295
|
+
'Content-Type': 'application/json',
|
|
296
|
+
},
|
|
297
|
+
maxContentLength: Infinity
|
|
298
|
+
}).catch(err => {
|
|
299
|
+
this.requestFailures++;
|
|
300
|
+
this.notReportedTestsCount++;
|
|
301
|
+
if (err.response) {
|
|
302
|
+
if (err.response.status >= 400) {
|
|
303
|
+
const responseData = err.response.data || { message: '' };
|
|
314
304
|
console.log(
|
|
315
305
|
APP_PREFIX,
|
|
316
|
-
pc.yellow(`Warning: ${
|
|
317
|
-
|
|
306
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
307
|
+
pc.gray(data?.title || ''),
|
|
318
308
|
);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
309
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
310
|
+
this.hasUnmatchedTests = true;
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
322
313
|
}
|
|
323
|
-
|
|
314
|
+
console.log(
|
|
315
|
+
APP_PREFIX,
|
|
316
|
+
pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
317
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
318
|
+
);
|
|
319
|
+
printCreateIssue(err);
|
|
320
|
+
} else {
|
|
321
|
+
console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
324
|
};
|
|
325
325
|
|
|
326
|
+
|
|
327
|
+
|
|
326
328
|
/**
|
|
327
329
|
* Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
|
|
328
330
|
*/
|
|
@@ -347,42 +349,43 @@ class TestomatioPipe {
|
|
|
347
349
|
const testsToSend = this.batch.tests.splice(0);
|
|
348
350
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
349
351
|
|
|
350
|
-
return this.client
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (err.response.status >= 400) {
|
|
369
|
-
const responseData = err.response.data || { message: '' };
|
|
370
|
-
console.log(APP_PREFIX, pc.yellow(`Warning: ${responseData.message} (${err.response.status})`));
|
|
371
|
-
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
372
|
-
this.hasUnmatchedTests = true;
|
|
373
|
-
}
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
352
|
+
return this.client.request({
|
|
353
|
+
method: 'POST',
|
|
354
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
355
|
+
data: {
|
|
356
|
+
api_key: this.apiKey,
|
|
357
|
+
tests: testsToSend,
|
|
358
|
+
batch_index: this.batch.batchIndex
|
|
359
|
+
},
|
|
360
|
+
headers: {
|
|
361
|
+
'Content-Type': 'application/json',
|
|
362
|
+
},
|
|
363
|
+
maxContentLength: Infinity
|
|
364
|
+
}).catch(err => {
|
|
365
|
+
this.requestFailures++;
|
|
366
|
+
this.notReportedTestsCount += testsToSend.length;
|
|
367
|
+
if (err.response) {
|
|
368
|
+
if (err.response.status >= 400) {
|
|
369
|
+
const responseData = err.response.data || { message: '' };
|
|
376
370
|
console.log(
|
|
377
371
|
APP_PREFIX,
|
|
378
|
-
pc.yellow(`Warning: (${err.response
|
|
379
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
372
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
380
373
|
);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
374
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
375
|
+
this.hasUnmatchedTests = true;
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
384
378
|
}
|
|
385
|
-
|
|
379
|
+
console.log(
|
|
380
|
+
APP_PREFIX,
|
|
381
|
+
pc.yellow(`Warning: (${err.response?.status})`),
|
|
382
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
383
|
+
);
|
|
384
|
+
printCreateIssue(err);
|
|
385
|
+
} else {
|
|
386
|
+
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
386
389
|
};
|
|
387
390
|
|
|
388
391
|
/**
|
|
@@ -405,9 +408,9 @@ class TestomatioPipe {
|
|
|
405
408
|
else this.batch.tests.push(data);
|
|
406
409
|
|
|
407
410
|
// if test is added after run which is already finished
|
|
408
|
-
|
|
411
|
+
if (!this.batch.intervalFunction) uploading = this.#batchUpload();
|
|
409
412
|
|
|
410
|
-
|
|
413
|
+
// return promise to be able to wait for it
|
|
411
414
|
return uploading;
|
|
412
415
|
}
|
|
413
416
|
|
|
@@ -456,7 +459,7 @@ class TestomatioPipe {
|
|
|
456
459
|
status_event,
|
|
457
460
|
detach: params.detach,
|
|
458
461
|
tests: params.tests,
|
|
459
|
-
}
|
|
462
|
+
}
|
|
460
463
|
});
|
|
461
464
|
|
|
462
465
|
if (this.runUrl) {
|
|
@@ -522,6 +525,9 @@ function printCreateIssue(err) {
|
|
|
522
525
|
console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
|
|
523
526
|
console.log('```');
|
|
524
527
|
});
|
|
528
|
+
|
|
525
529
|
}
|
|
526
530
|
|
|
531
|
+
|
|
532
|
+
|
|
527
533
|
export default TestomatioPipe;
|
package/src/uploader.js
CHANGED
|
@@ -194,11 +194,6 @@ export class S3Uploader {
|
|
|
194
194
|
filePath = path.join(process.cwd(), filePath);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
// Normalize path separators for cross-platform compatibility
|
|
198
|
-
if (typeof filePath === 'string') {
|
|
199
|
-
filePath = filePath.replace(/\\/g, '/');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
197
|
const data = { rid, file: filePath, uploaded };
|
|
203
198
|
const jsonLine = `${JSON.stringify(data)}\n`;
|
|
204
199
|
fs.appendFileSync(tempFilePath, jsonLine);
|