@testomatio/reporter 1.3.6-beta → 1.4.0-beta-wdio-bdd
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 +60 -57
- package/lib/adapter/codecept.js +96 -35
- package/lib/adapter/cucumber/current.js +18 -11
- package/lib/adapter/cucumber/legacy.js +6 -5
- package/lib/adapter/cucumber.js +2 -2
- package/lib/adapter/cypress-plugin/index.js +52 -25
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +49 -16
- package/lib/adapter/mocha.js +103 -51
- package/lib/adapter/playwright.js +95 -33
- package/lib/adapter/webdriver.js +46 -1
- package/lib/bin/reportXml.js +22 -16
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +179 -53
- package/lib/config.js +34 -0
- package/lib/constants.js +32 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +135 -54
- package/lib/junit-adapter/adapter.js +0 -2
- package/lib/junit-adapter/csharp.js +3 -4
- package/lib/junit-adapter/index.js +3 -3
- package/lib/junit-adapter/java.js +35 -17
- package/lib/junit-adapter/javascript.js +1 -2
- package/lib/junit-adapter/python.js +12 -14
- package/lib/junit-adapter/ruby.js +1 -2
- package/lib/pipe/csv.js +5 -3
- package/lib/pipe/github.js +9 -19
- package/lib/pipe/gitlab.js +22 -26
- package/lib/pipe/html.js +354 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +257 -53
- package/lib/reporter-functions.js +46 -0
- package/lib/reporter.js +11 -9
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/services/key-values.js +58 -0
- package/lib/services/logger.js +311 -0
- package/lib/template/testomatio.hbs +1236 -0
- package/lib/utils/pipe_utils.js +129 -0
- package/lib/{util.js → utils/utils.js} +145 -15
- package/lib/xmlReader.js +211 -122
- package/package.json +17 -9
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -244
- package/lib/logger.js +0 -301
package/lib/xmlReader.js
CHANGED
|
@@ -1,78 +1,89 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:xml');
|
|
2
|
-
const path = require(
|
|
2
|
+
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const fs = require(
|
|
5
|
-
const { XMLParser } = require(
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { XMLParser } = require('fast-xml-parser');
|
|
6
6
|
const { APP_PREFIX, STATUS } = require('./constants');
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
fetchFilesFromStackTrace,
|
|
9
|
+
fetchIdFromOutput,
|
|
10
|
+
fetchSourceCode,
|
|
11
|
+
fetchSourceCodeFromStackTrace,
|
|
12
|
+
fetchIdFromCode,
|
|
13
|
+
humanize,
|
|
14
|
+
} = require('./utils/utils');
|
|
8
15
|
const upload = require('./fileUploader');
|
|
9
16
|
const pipesFactory = require('./pipe');
|
|
10
17
|
const adapterFactory = require('./junit-adapter');
|
|
18
|
+
const config = require('./config');
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
|
|
14
|
-
const TESTOMATIO = process.env.TESTOMATIO; // key?
|
|
20
|
+
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
15
21
|
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
|
|
16
22
|
|
|
17
23
|
const options = {
|
|
18
24
|
ignoreDeclaration: true,
|
|
19
25
|
ignoreAttributes: false,
|
|
20
26
|
alwaysCreateTextNode: false,
|
|
21
|
-
attributeNamePrefix:
|
|
27
|
+
attributeNamePrefix: '',
|
|
22
28
|
parseTagValue: true,
|
|
23
29
|
};
|
|
24
30
|
|
|
25
31
|
const reduceOptions = {};
|
|
26
32
|
|
|
27
33
|
class XmlReader {
|
|
28
|
-
|
|
29
34
|
constructor(opts = {}) {
|
|
30
35
|
this.requestParams = {
|
|
31
|
-
apiKey: opts.apiKey || TESTOMATIO,
|
|
36
|
+
apiKey: opts.apiKey || config.TESTOMATIO,
|
|
32
37
|
url: opts.url || TESTOMATIO_URL,
|
|
33
38
|
title: TESTOMATIO_TITLE,
|
|
34
39
|
env: TESTOMATIO_ENV,
|
|
35
40
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
36
41
|
};
|
|
37
42
|
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
38
|
-
this.adapter = adapterFactory(
|
|
43
|
+
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts);
|
|
39
44
|
if (!this.adapter) throw new Error('XML adapter for this format not found');
|
|
40
45
|
|
|
41
46
|
this.opts = opts || {};
|
|
42
|
-
|
|
43
|
-
this.pipes = pipesFactory(opts, store);
|
|
47
|
+
this.store = {};
|
|
48
|
+
this.pipes = pipesFactory(opts, this.store);
|
|
44
49
|
|
|
45
50
|
this.parser = new XMLParser(options);
|
|
46
|
-
this.tests = []
|
|
47
|
-
this.stats = {}
|
|
51
|
+
this.tests = [];
|
|
52
|
+
this.stats = {};
|
|
48
53
|
this.stats.language = opts.lang?.toLowerCase();
|
|
49
|
-
this.filesToUpload = {}
|
|
54
|
+
this.filesToUpload = {};
|
|
50
55
|
|
|
51
56
|
this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
|
|
52
|
-
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
57
|
+
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
connectAdapter() {
|
|
56
|
-
if (this.opts.javaTests)
|
|
57
|
-
|
|
61
|
+
if (this.opts.javaTests) {
|
|
62
|
+
this.adapter = adapterFactory('java', this.opts);
|
|
63
|
+
return this.adapter;
|
|
64
|
+
}
|
|
65
|
+
this.adapter = adapterFactory(this.stats.language, this.opts);
|
|
66
|
+
return this.adapter;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
|
-
parse(fileName) {
|
|
69
|
+
parse(fileName) {
|
|
61
70
|
const xmlData = fs.readFileSync(path.resolve(fileName));
|
|
62
71
|
const jsonResult = this.parser.parse(xmlData);
|
|
63
72
|
let jsonSuite;
|
|
64
|
-
|
|
73
|
+
|
|
65
74
|
if (jsonResult.testsuites) {
|
|
66
75
|
jsonSuite = jsonResult.testsuites;
|
|
67
76
|
} else if (jsonResult.testsuite) {
|
|
68
77
|
jsonSuite = jsonResult;
|
|
69
78
|
} else if (jsonResult.TestRun) {
|
|
70
|
-
return this.processTRX(jsonResult);
|
|
79
|
+
return this.processTRX(jsonResult);
|
|
71
80
|
} else if (jsonResult['test-run']) {
|
|
72
81
|
return this.processNUnit(jsonResult['test-run']);
|
|
82
|
+
} else if (jsonResult.assemblies) {
|
|
83
|
+
return this.processXUnit(jsonResult.assemblies);
|
|
73
84
|
} else {
|
|
74
|
-
console.log(jsonResult)
|
|
75
|
-
throw new Error("Format can't be parsed")
|
|
85
|
+
console.log(jsonResult);
|
|
86
|
+
throw new Error("Format can't be parsed");
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
return this.processJUnit(jsonSuite);
|
|
@@ -83,9 +94,9 @@ class XmlReader {
|
|
|
83
94
|
|
|
84
95
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
85
96
|
const resultTests = processTestSuite(testsuite);
|
|
86
|
-
|
|
97
|
+
|
|
87
98
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
88
|
-
const status =
|
|
99
|
+
const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
|
|
89
100
|
|
|
90
101
|
this.tests = this.tests.concat(resultTests);
|
|
91
102
|
|
|
@@ -118,43 +129,43 @@ class XmlReader {
|
|
|
118
129
|
skipped_count: parseInt(inconclusive + skipped, 10),
|
|
119
130
|
tests: resultTests,
|
|
120
131
|
};
|
|
121
|
-
}
|
|
132
|
+
}
|
|
122
133
|
|
|
123
134
|
processTRX(jsonSuite) {
|
|
124
135
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
125
136
|
if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
|
|
126
137
|
|
|
127
|
-
const tests =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
const tests =
|
|
139
|
+
defs.map(td => {
|
|
140
|
+
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
141
|
+
let example = td.name.match(/\((.*?)\)/);
|
|
142
|
+
if (example) example = { ...example[1].split(',') };
|
|
143
|
+
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
144
|
+
const suite_title = suite.pop();
|
|
145
|
+
return {
|
|
146
|
+
title,
|
|
147
|
+
example,
|
|
148
|
+
file: suite.join('/'),
|
|
149
|
+
description: td.Description,
|
|
150
|
+
suite_title,
|
|
151
|
+
id: td.Execution.id,
|
|
152
|
+
};
|
|
153
|
+
}) || [];
|
|
142
154
|
|
|
143
155
|
let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
|
|
144
156
|
if (!Array.isArray(result)) result = [result].filter(d => !!d);
|
|
145
157
|
|
|
146
|
-
const results = result.map(td => ({
|
|
158
|
+
const results = result.map(td => ({
|
|
147
159
|
id: td.executionId,
|
|
148
160
|
// seconds are used in junit reports, but ms are used by testomatio
|
|
149
161
|
run_time: parseFloat(td.duration) * 1000,
|
|
150
|
-
status: td.outcome,
|
|
162
|
+
status: td.outcome,
|
|
151
163
|
stack: td.Output.StdOut,
|
|
152
|
-
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
|
|
164
|
+
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
|
|
153
165
|
}));
|
|
154
166
|
|
|
155
|
-
|
|
156
167
|
results.forEach(r => {
|
|
157
|
-
const test = tests.find(t => t.id === r.id) || {
|
|
168
|
+
const test = tests.find(t => t.id === r.id) || {};
|
|
158
169
|
r.suite_title = test.suite_title;
|
|
159
170
|
r.title = test.title?.trim();
|
|
160
171
|
if (test.code) r.code = test.code;
|
|
@@ -169,7 +180,7 @@ class XmlReader {
|
|
|
169
180
|
});
|
|
170
181
|
|
|
171
182
|
debug(results);
|
|
172
|
-
|
|
183
|
+
|
|
173
184
|
const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
|
|
174
185
|
|
|
175
186
|
const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
|
|
@@ -187,7 +198,79 @@ class XmlReader {
|
|
|
187
198
|
skipped_count: parseInt(counters.notExecuted, 10),
|
|
188
199
|
failed_count,
|
|
189
200
|
tests: results,
|
|
190
|
-
};
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
processXUnit(assemblies) {
|
|
205
|
+
const tests = [];
|
|
206
|
+
|
|
207
|
+
assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
|
|
208
|
+
|
|
209
|
+
assemblies.forEach(assembly => {
|
|
210
|
+
const { collection } = assembly;
|
|
211
|
+
|
|
212
|
+
const suites = Array.isArray(collection) ? collection : [collection];
|
|
213
|
+
|
|
214
|
+
suites.forEach(suite => {
|
|
215
|
+
const { test } = suite;
|
|
216
|
+
if (!test) return;
|
|
217
|
+
const cases = Array.isArray(test) ? test : [test];
|
|
218
|
+
cases.forEach(testCase => {
|
|
219
|
+
const { type, time, result } = testCase;
|
|
220
|
+
|
|
221
|
+
let message = '';
|
|
222
|
+
let stack = '';
|
|
223
|
+
|
|
224
|
+
if (testCase.failure) {
|
|
225
|
+
message = testCase.failure.message;
|
|
226
|
+
stack = testCase.failure['stack-trace'];
|
|
227
|
+
}
|
|
228
|
+
if (testCase.reason) {
|
|
229
|
+
message = testCase.reason.message;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let status = STATUS.PASSED;
|
|
233
|
+
if (result === 'Pass') status = STATUS.PASSED;
|
|
234
|
+
if (result === 'Fail') status = STATUS.FAILED;
|
|
235
|
+
if (result === 'Skip') status = STATUS.SKIPPED;
|
|
236
|
+
|
|
237
|
+
const pathParts = type.split('.');
|
|
238
|
+
const suite_title = pathParts[pathParts.length - 1];
|
|
239
|
+
const file = pathParts.slice(0, -1).join('/');
|
|
240
|
+
const title = testCase.method || testCase.name.split('.').pop();
|
|
241
|
+
const run_time = parseFloat(time) * 1000;
|
|
242
|
+
|
|
243
|
+
tests.push({
|
|
244
|
+
create: true,
|
|
245
|
+
stack,
|
|
246
|
+
message,
|
|
247
|
+
file,
|
|
248
|
+
status,
|
|
249
|
+
title,
|
|
250
|
+
suite_title,
|
|
251
|
+
run_time,
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const hasFailures = tests.filter(t => t.status === STATUS.FAILED).length > 0;
|
|
258
|
+
const status = hasFailures ? STATUS.FAILED : STATUS.PASSED;
|
|
259
|
+
|
|
260
|
+
this.tests = tests;
|
|
261
|
+
|
|
262
|
+
debug(tests);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
status,
|
|
266
|
+
create_tests: true,
|
|
267
|
+
name: 'xUnit',
|
|
268
|
+
tests_count: tests.length,
|
|
269
|
+
passed_count: tests.filter(t => t.status === STATUS.PASSED).length,
|
|
270
|
+
failed_count: tests.filter(t => t.status === STATUS.FAILED).length,
|
|
271
|
+
skipped_count: tests.filter(t => t.status === STATUS.SKIPPED).length,
|
|
272
|
+
tests,
|
|
273
|
+
};
|
|
191
274
|
}
|
|
192
275
|
|
|
193
276
|
calculateStats() {
|
|
@@ -199,12 +282,12 @@ class XmlReader {
|
|
|
199
282
|
passed_count: 0,
|
|
200
283
|
failed_count: 0,
|
|
201
284
|
skipped_count: 0,
|
|
202
|
-
}
|
|
285
|
+
};
|
|
203
286
|
this.tests.forEach(t => {
|
|
204
287
|
this.stats.tests_count++;
|
|
205
288
|
if (t.status === 'passed') this.stats.passed_count++;
|
|
206
289
|
if (t.status === 'failed') this.stats.failed_count++;
|
|
207
|
-
})
|
|
290
|
+
});
|
|
208
291
|
if (this.stats.failed_count) this.stats.status = 'failed';
|
|
209
292
|
|
|
210
293
|
return this.stats;
|
|
@@ -213,7 +296,7 @@ class XmlReader {
|
|
|
213
296
|
fetchSourceCode() {
|
|
214
297
|
this.tests.forEach(t => {
|
|
215
298
|
try {
|
|
216
|
-
const file = this.adapter.getFilePath(t)
|
|
299
|
+
const file = this.adapter.getFilePath(t);
|
|
217
300
|
if (!file) return;
|
|
218
301
|
|
|
219
302
|
if (!this.stats.language) {
|
|
@@ -221,14 +304,21 @@ class XmlReader {
|
|
|
221
304
|
if (file.endsWith('.py')) this.stats.language = 'python';
|
|
222
305
|
if (file.endsWith('.java')) this.stats.language = 'java';
|
|
223
306
|
if (file.endsWith('.rb')) this.stats.language = 'ruby';
|
|
224
|
-
if (file.endsWith('.js')) this.stats.language
|
|
307
|
+
if (file.endsWith('.js')) this.stats.language = 'js';
|
|
225
308
|
if (file.endsWith('.ts')) this.stats.language = 'ts';
|
|
226
309
|
}
|
|
227
310
|
|
|
311
|
+
if (!fs.existsSync(file)) {
|
|
312
|
+
debug('Failed to open file with the source code', file);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
228
315
|
const contents = fs.readFileSync(file).toString();
|
|
229
|
-
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
|
|
316
|
+
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language });
|
|
317
|
+
if (t.code) debug('Fetched code for test %s', t.title);
|
|
318
|
+
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language });
|
|
319
|
+
if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
|
|
230
320
|
} catch (err) {
|
|
231
|
-
debug(err)
|
|
321
|
+
debug(err);
|
|
232
322
|
}
|
|
233
323
|
});
|
|
234
324
|
}
|
|
@@ -236,32 +326,22 @@ class XmlReader {
|
|
|
236
326
|
formatTests() {
|
|
237
327
|
this.tests.forEach(t => {
|
|
238
328
|
if (t.file) {
|
|
239
|
-
t.file = t.file.replace(process.cwd() + path.sep, '')
|
|
329
|
+
t.file = t.file.replace(process.cwd() + path.sep, '');
|
|
240
330
|
}
|
|
241
331
|
|
|
242
|
-
this.adapter.formatTest(t)
|
|
332
|
+
this.adapter.formatTest(t);
|
|
243
333
|
|
|
244
|
-
t.title = t.title
|
|
245
|
-
// insert a space before all caps
|
|
246
|
-
.replace(/([A-Z])/g, ' $1')
|
|
247
|
-
// _ chars to spaces
|
|
248
|
-
.replace(/_/g, ' ')
|
|
249
|
-
// uppercase the first character
|
|
250
|
-
.replace(/^(.)|\s(.)/g, $1 => $1.toLowerCase())
|
|
251
|
-
|
|
252
|
-
// remove standard prefixes
|
|
253
|
-
.replace(/^test\s/, '')
|
|
254
|
-
.replace(/^should\s/, '')
|
|
255
|
-
.trim();
|
|
334
|
+
t.title = humanize(t.title);
|
|
256
335
|
});
|
|
257
336
|
}
|
|
258
337
|
|
|
259
338
|
formatErrors() {
|
|
260
|
-
this.tests
|
|
261
|
-
t
|
|
262
|
-
t
|
|
263
|
-
|
|
264
|
-
|
|
339
|
+
this.tests
|
|
340
|
+
.filter(t => !!t.stack)
|
|
341
|
+
.forEach(t => {
|
|
342
|
+
t.stack = this.formatStack(t);
|
|
343
|
+
t.message = this.adapter.formatMessage(t);
|
|
344
|
+
});
|
|
265
345
|
}
|
|
266
346
|
|
|
267
347
|
formatStack(t) {
|
|
@@ -279,10 +359,14 @@ class XmlReader {
|
|
|
279
359
|
async uploadArtifacts() {
|
|
280
360
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
281
361
|
let files = [];
|
|
282
|
-
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
|
|
362
|
+
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
|
|
283
363
|
files = [...files, ...fetchFilesFromStackTrace(test.stack)];
|
|
284
|
-
|
|
285
|
-
|
|
364
|
+
|
|
365
|
+
if (!files.length) continue;
|
|
366
|
+
|
|
367
|
+
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
368
|
+
test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, runId)));
|
|
369
|
+
console.log(APP_PREFIX, `🗄️ Uploaded ${chalk.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
286
370
|
}
|
|
287
371
|
}
|
|
288
372
|
|
|
@@ -292,9 +376,9 @@ class XmlReader {
|
|
|
292
376
|
title: this.requestParams.title,
|
|
293
377
|
env: this.requestParams.env,
|
|
294
378
|
group_title: this.requestParams.group_title,
|
|
295
|
-
};
|
|
379
|
+
};
|
|
296
380
|
|
|
297
|
-
debug(
|
|
381
|
+
debug('Run', runParams);
|
|
298
382
|
|
|
299
383
|
return Promise.all(this.pipes.map(p => p.createRun(runParams)));
|
|
300
384
|
}
|
|
@@ -307,12 +391,10 @@ class XmlReader {
|
|
|
307
391
|
this.formatErrors();
|
|
308
392
|
this.formatTests();
|
|
309
393
|
|
|
310
|
-
debug(
|
|
311
|
-
'Uploading data',
|
|
312
|
-
{
|
|
394
|
+
debug('Uploading data', {
|
|
313
395
|
...this.stats,
|
|
314
396
|
tests: this.tests,
|
|
315
|
-
})
|
|
397
|
+
});
|
|
316
398
|
|
|
317
399
|
const dataString = {
|
|
318
400
|
...this.stats,
|
|
@@ -327,55 +409,62 @@ class XmlReader {
|
|
|
327
409
|
|
|
328
410
|
module.exports = XmlReader;
|
|
329
411
|
|
|
330
|
-
|
|
331
412
|
function reduceTestCases(prev, item) {
|
|
332
413
|
let testCases = item.testcase;
|
|
333
414
|
if (!testCases) testCases = item['test-case'];
|
|
334
415
|
if (!Array.isArray(testCases)) {
|
|
335
|
-
testCases = [testCases]
|
|
416
|
+
testCases = [testCases];
|
|
336
417
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
418
|
+
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
419
|
+
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
420
|
+
testCases
|
|
421
|
+
.filter(t => !!t)
|
|
422
|
+
.forEach(testCaseItem => {
|
|
423
|
+
const file = testCaseItem.file || item.filepath || '';
|
|
424
|
+
|
|
425
|
+
let stack = '';
|
|
426
|
+
let message = '';
|
|
427
|
+
if (testCaseItem.error) stack = testCaseItem.error;
|
|
428
|
+
if (testCaseItem.failure) stack = testCaseItem.failure;
|
|
429
|
+
if (testCaseItem?.failure?.['stack-trace']) stack = testCaseItem.failure['stack-trace'];
|
|
430
|
+
if (testCaseItem?.failure?.message) message = testCaseItem.failure.message;
|
|
431
|
+
if (testCaseItem?.error?.message) message = testCaseItem.error.message;
|
|
432
|
+
|
|
433
|
+
if (testCaseItem.failure && testCaseItem.failure['#text']) stack = testCaseItem.failure['#text'];
|
|
434
|
+
if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
|
|
435
|
+
if (!message) message = stack.trim().split('\n')[0];
|
|
436
|
+
|
|
437
|
+
// eslint-disable-next-line
|
|
438
|
+
stack = `${
|
|
439
|
+
testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
|
|
440
|
+
}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
441
|
+
const testId = fetchIdFromOutput(stack);
|
|
442
|
+
|
|
443
|
+
let status = STATUS.PASSED.toString();
|
|
444
|
+
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
445
|
+
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
446
|
+
|
|
447
|
+
prev.push({
|
|
448
|
+
create: true,
|
|
449
|
+
file,
|
|
450
|
+
stack,
|
|
451
|
+
test_id: testId,
|
|
452
|
+
message,
|
|
453
|
+
line: testCaseItem.lineno,
|
|
454
|
+
// seconds are used in junit reports, but ms are used by testomatio
|
|
455
|
+
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
456
|
+
status,
|
|
457
|
+
title: testCaseItem.name,
|
|
458
|
+
suite_title: reduceOptions.preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname,
|
|
459
|
+
});
|
|
460
|
+
});
|
|
372
461
|
return prev;
|
|
373
462
|
}
|
|
374
463
|
|
|
375
464
|
function processTestSuite(testsuite) {
|
|
376
465
|
if (!testsuite) return [];
|
|
377
|
-
if (testsuite.testsuite) return processTestSuite(testsuite.testsuite)
|
|
378
|
-
if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite'])
|
|
466
|
+
if (testsuite.testsuite) return processTestSuite(testsuite.testsuite);
|
|
467
|
+
if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite']);
|
|
379
468
|
|
|
380
469
|
let suites = testsuite;
|
|
381
470
|
if (!Array.isArray(testsuite)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-beta-wdio-bdd",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"@aws-sdk/lib-storage": "^3.279.0",
|
|
13
13
|
"@octokit/rest": "^19.0.5",
|
|
14
14
|
"aws-sdk": "^2.1072.0",
|
|
15
|
-
"axios": "^
|
|
15
|
+
"axios": "^1.6.2",
|
|
16
|
+
"axios-retry": "^3.9.1",
|
|
16
17
|
"callsite-record": "^4.1.4",
|
|
17
18
|
"chalk": "^4.1.0",
|
|
18
19
|
"commander": "^4.1.1",
|
|
@@ -20,14 +21,17 @@
|
|
|
20
21
|
"debug": "^4.3.4",
|
|
21
22
|
"dotenv": "^16.0.1",
|
|
22
23
|
"fast-xml-parser": "^4.0.8",
|
|
23
|
-
"
|
|
24
|
+
"file-url": "3.0.0",
|
|
25
|
+
"glob": "^10.3",
|
|
26
|
+
"handlebars": "^4.7.8",
|
|
24
27
|
"has-flag": "^5.0.1",
|
|
25
28
|
"humanize-duration": "^3.27.3",
|
|
26
29
|
"is-valid-path": "^0.1.1",
|
|
27
30
|
"json-cycle": "^1.3.0",
|
|
28
|
-
"lodash": "^4.17.21",
|
|
29
31
|
"lodash.memoize": "^4.1.2",
|
|
30
32
|
"lodash.merge": "^4.6.2",
|
|
33
|
+
"minimatch": "^9.0.3",
|
|
34
|
+
"promise-retry": "^2.0.1",
|
|
31
35
|
"uuid": "^9.0.0"
|
|
32
36
|
},
|
|
33
37
|
"files": [
|
|
@@ -37,26 +41,28 @@
|
|
|
37
41
|
],
|
|
38
42
|
"scripts": {
|
|
39
43
|
"clear-exportdir": "rm -rf export/",
|
|
40
|
-
"pretty": "prettier --
|
|
44
|
+
"pretty": "npx prettier --debug-check --check .",
|
|
45
|
+
"pretty:fix": "prettier --write .",
|
|
41
46
|
"lint": "eslint lib",
|
|
42
47
|
"lint:fix": "eslint lib --fix",
|
|
48
|
+
"format": "npm run lint:fix && npm run pretty:fix",
|
|
43
49
|
"test": "mocha tests/**",
|
|
44
50
|
"init": "cd ./tests/adapter/examples/cucumber && npm i",
|
|
45
|
-
"test:unit": "mocha tests",
|
|
46
51
|
"test:adapter": "mocha './tests/adapter/index.test.js'",
|
|
47
52
|
"test:pipes": "mocha './tests/pipes/*_test.js'",
|
|
48
53
|
"test:adapter:jest:example": "jest './tests/adapter/examples/jest/index.test.js' --config='./tests/adapter/examples/jest/jest.config.js'",
|
|
49
54
|
"test:adapter:mocha:example": "mocha './tests/adapter/examples/mocha/index.test.js' --config='./tests/adapter/examples/mocha/mocha.config.js'",
|
|
50
55
|
"test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
|
|
51
56
|
"test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
|
|
52
|
-
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
|
|
57
|
+
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
|
|
58
|
+
"test:storage": "npx mocha tests-storage/artifact-storage.test.js && npx mocha tests-storage/data-storage.test.js && TESTOMATIO_INTERCEPT_CONSOLE_LOGS=true npx mocha tests-storage/logger.test.js && npx mocha tests-storage/logger-2.test.js && npx mocha tests-storage/reporter-functions.test.js"
|
|
53
59
|
},
|
|
54
60
|
"devDependencies": {
|
|
55
61
|
"@cucumber/cucumber": "^9.3.0",
|
|
56
62
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
57
63
|
"@wdio/reporter": "^7.16.13",
|
|
58
64
|
"chai": "^4.3.6",
|
|
59
|
-
"codeceptjs": "
|
|
65
|
+
"codeceptjs": "latest",
|
|
60
66
|
"cucumber": "^6.0.7",
|
|
61
67
|
"eslint": "^8.7.0",
|
|
62
68
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
@@ -64,9 +70,11 @@
|
|
|
64
70
|
"eslint-plugin-import": "^2.25.4",
|
|
65
71
|
"jasmine": "^3.10.0",
|
|
66
72
|
"jest": "^27.4.7",
|
|
73
|
+
"jsdom": "^22.1.0",
|
|
67
74
|
"mocha": "^9.2.0",
|
|
68
75
|
"mock-http-server": "^1.4.5",
|
|
69
|
-
"
|
|
76
|
+
"pino": "^8.15.0",
|
|
77
|
+
"prettier": "^3.2.5",
|
|
70
78
|
"puppeteer": "^13.1.2"
|
|
71
79
|
},
|
|
72
80
|
"bin": {
|