@testomatio/reporter 1.6.13 → 2.0.1-beta-esm

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.
Files changed (142) hide show
  1. package/lib/adapter/codecept.d.ts +2 -0
  2. package/lib/adapter/codecept.js +295 -335
  3. package/lib/adapter/cucumber/current.d.ts +14 -0
  4. package/lib/adapter/cucumber/current.js +195 -203
  5. package/lib/adapter/cucumber/legacy.d.ts +0 -0
  6. package/lib/adapter/cucumber/legacy.js +130 -155
  7. package/lib/adapter/cucumber.d.ts +2 -0
  8. package/lib/adapter/cucumber.js +5 -16
  9. package/lib/adapter/cypress-plugin/index.d.ts +2 -0
  10. package/lib/adapter/cypress-plugin/index.js +93 -105
  11. package/lib/adapter/jasmine.d.ts +11 -0
  12. package/lib/adapter/jasmine.js +54 -53
  13. package/lib/adapter/jest.d.ts +13 -0
  14. package/lib/adapter/jest.js +97 -99
  15. package/lib/adapter/mocha.d.ts +2 -0
  16. package/lib/adapter/mocha.js +112 -140
  17. package/lib/adapter/playwright.d.ts +14 -0
  18. package/lib/adapter/playwright.js +195 -231
  19. package/lib/adapter/vitest.d.ts +35 -0
  20. package/lib/adapter/vitest.js +150 -149
  21. package/lib/adapter/webdriver.d.ts +24 -0
  22. package/lib/adapter/webdriver.js +134 -119
  23. package/lib/bin/cli.d.ts +2 -0
  24. package/lib/bin/cli.js +164 -211
  25. package/lib/bin/reportXml.d.ts +2 -0
  26. package/lib/bin/reportXml.js +49 -52
  27. package/lib/bin/startTest.d.ts +2 -0
  28. package/lib/bin/startTest.js +82 -95
  29. package/lib/bin/uploadArtifacts.d.ts +2 -0
  30. package/lib/bin/uploadArtifacts.js +55 -61
  31. package/lib/client.d.ts +76 -0
  32. package/lib/client.js +411 -465
  33. package/lib/config.d.ts +1 -0
  34. package/lib/config.js +16 -21
  35. package/lib/constants.d.ts +25 -0
  36. package/lib/constants.js +50 -44
  37. package/lib/data-storage.d.ts +34 -0
  38. package/lib/data-storage.js +206 -188
  39. package/lib/junit-adapter/adapter.d.ts +9 -0
  40. package/lib/junit-adapter/adapter.js +17 -20
  41. package/lib/junit-adapter/csharp.d.ts +4 -0
  42. package/lib/junit-adapter/csharp.js +18 -14
  43. package/lib/junit-adapter/index.d.ts +3 -0
  44. package/lib/junit-adapter/index.js +27 -25
  45. package/lib/junit-adapter/java.d.ts +5 -0
  46. package/lib/junit-adapter/java.js +41 -53
  47. package/lib/junit-adapter/javascript.d.ts +4 -0
  48. package/lib/junit-adapter/javascript.js +30 -27
  49. package/lib/junit-adapter/python.d.ts +5 -0
  50. package/lib/junit-adapter/python.js +38 -37
  51. package/lib/junit-adapter/ruby.d.ts +4 -0
  52. package/lib/junit-adapter/ruby.js +11 -8
  53. package/lib/output.d.ts +11 -0
  54. package/lib/output.js +44 -52
  55. package/lib/package.json +3 -0
  56. package/lib/pipe/bitbucket.d.ts +23 -0
  57. package/lib/pipe/bitbucket.js +210 -229
  58. package/lib/pipe/csv.d.ts +47 -0
  59. package/lib/pipe/csv.js +113 -126
  60. package/lib/pipe/debug.d.ts +29 -0
  61. package/lib/pipe/debug.js +104 -99
  62. package/lib/pipe/github.d.ts +30 -0
  63. package/lib/pipe/github.js +186 -213
  64. package/lib/pipe/gitlab.d.ts +23 -0
  65. package/lib/pipe/gitlab.js +166 -207
  66. package/lib/pipe/html.d.ts +34 -0
  67. package/lib/pipe/html.js +260 -319
  68. package/lib/pipe/index.d.ts +1 -0
  69. package/lib/pipe/index.js +84 -66
  70. package/lib/pipe/testomatio.d.ts +70 -0
  71. package/lib/pipe/testomatio.js +413 -462
  72. package/lib/reporter-functions.d.ts +34 -0
  73. package/lib/reporter-functions.js +28 -26
  74. package/lib/reporter.d.ts +232 -0
  75. package/lib/reporter.js +34 -29
  76. package/lib/services/artifacts.d.ts +33 -0
  77. package/lib/services/artifacts.js +55 -51
  78. package/lib/services/index.d.ts +9 -0
  79. package/lib/services/index.js +14 -12
  80. package/lib/services/key-values.d.ts +27 -0
  81. package/lib/services/key-values.js +56 -53
  82. package/lib/services/logger.d.ts +64 -0
  83. package/lib/services/logger.js +227 -245
  84. package/lib/template/testomatio.hbs +651 -1366
  85. package/lib/uploader.d.ts +60 -0
  86. package/lib/uploader.js +291 -360
  87. package/lib/utils/pipe_utils.d.ts +41 -0
  88. package/lib/utils/pipe_utils.js +89 -85
  89. package/lib/utils/utils.d.ts +45 -0
  90. package/lib/utils/utils.js +347 -307
  91. package/lib/xmlReader.d.ts +92 -0
  92. package/lib/xmlReader.js +490 -529
  93. package/package.json +57 -15
  94. package/src/adapter/codecept.js +375 -0
  95. package/src/adapter/cucumber/current.js +228 -0
  96. package/src/adapter/cucumber/legacy.js +158 -0
  97. package/src/adapter/cucumber.js +4 -0
  98. package/src/adapter/cypress-plugin/index.js +112 -0
  99. package/src/adapter/jasmine.js +60 -0
  100. package/src/adapter/jest.js +107 -0
  101. package/src/adapter/mocha.cjs +2 -0
  102. package/src/adapter/mocha.js +157 -0
  103. package/src/adapter/playwright.js +250 -0
  104. package/src/adapter/vitest.js +183 -0
  105. package/src/adapter/webdriver.js +142 -0
  106. package/src/bin/cli.js +280 -0
  107. package/src/bin/reportXml.js +74 -0
  108. package/src/bin/startTest.js +123 -0
  109. package/src/bin/uploadArtifacts.js +90 -0
  110. package/src/client.js +504 -0
  111. package/src/config.js +30 -0
  112. package/src/constants.js +53 -0
  113. package/src/data-storage.js +204 -0
  114. package/src/junit-adapter/adapter.js +23 -0
  115. package/src/junit-adapter/csharp.js +16 -0
  116. package/src/junit-adapter/index.js +28 -0
  117. package/src/junit-adapter/java.js +58 -0
  118. package/src/junit-adapter/javascript.js +31 -0
  119. package/src/junit-adapter/python.js +42 -0
  120. package/src/junit-adapter/ruby.js +10 -0
  121. package/src/output.js +57 -0
  122. package/src/pipe/bitbucket.js +254 -0
  123. package/src/pipe/csv.js +140 -0
  124. package/src/pipe/debug.js +104 -0
  125. package/src/pipe/github.js +233 -0
  126. package/src/pipe/gitlab.js +229 -0
  127. package/src/pipe/html.js +374 -0
  128. package/src/pipe/index.js +71 -0
  129. package/src/pipe/testomatio.js +503 -0
  130. package/src/reporter-functions.js +55 -0
  131. package/src/reporter.cjs_decprecated +21 -0
  132. package/src/reporter.js +33 -0
  133. package/src/services/artifacts.js +59 -0
  134. package/src/services/index.js +13 -0
  135. package/src/services/key-values.js +59 -0
  136. package/src/services/logger.js +316 -0
  137. package/src/template/emptyData.svg +23 -0
  138. package/src/template/testomatio.hbs +706 -0
  139. package/src/uploader.js +371 -0
  140. package/src/utils/pipe_utils.js +119 -0
  141. package/src/utils/utils.js +383 -0
  142. package/src/xmlReader.js +562 -0
@@ -0,0 +1,562 @@
1
+ import createDebugMessages from 'debug';
2
+ import path from 'path';
3
+ import pc from 'picocolors';
4
+ import fs from 'fs';
5
+ import { XMLParser } from 'fast-xml-parser';
6
+ import { APP_PREFIX, STATUS } from './constants.js';
7
+ import { randomUUID } from 'crypto';
8
+ import { fileURLToPath } from 'url';
9
+ import {
10
+ fetchFilesFromStackTrace,
11
+ fetchIdFromOutput,
12
+ fetchSourceCode,
13
+ fetchSourceCodeFromStackTrace,
14
+ fetchIdFromCode,
15
+ humanize,
16
+ } from './utils/utils.js';
17
+ import { pipesFactory } from './pipe/index.js';
18
+ import adapterFactory from './junit-adapter/index.js';
19
+ import { config } from './config.js';
20
+ import { S3Uploader } from './uploader.js';
21
+
22
+ // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const debug = createDebugMessages('@testomatio/reporter:xml');
25
+
26
+ const ridRunId = randomUUID();
27
+
28
+ const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
29
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } =
30
+ process.env;
31
+
32
+ const options = {
33
+ ignoreDeclaration: true,
34
+ ignoreAttributes: false,
35
+ alwaysCreateTextNode: false,
36
+ attributeNamePrefix: '',
37
+ parseTagValue: true,
38
+ };
39
+
40
+ const reduceOptions = {};
41
+
42
+ class XmlReader {
43
+ constructor(opts = {}) {
44
+ this.requestParams = {
45
+ apiKey: opts.apiKey || config.TESTOMATIO,
46
+ url: opts.url || TESTOMATIO_URL,
47
+ title: TESTOMATIO_TITLE,
48
+ env: TESTOMATIO_ENV,
49
+ group_title: TESTOMATIO_RUNGROUP_TITLE,
50
+ detach: TESTOMATIO_MARK_DETACHED,
51
+ // batch uploading is implemented for xml already
52
+ isBatchEnabled: false,
53
+ };
54
+ this.runId = opts.runId || TESTOMATIO_RUN;
55
+ this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts);
56
+ if (!this.adapter) throw new Error('XML adapter for this format not found');
57
+
58
+ this.opts = opts || {};
59
+ this.store = {};
60
+ this.pipesPromise = pipesFactory(opts, this.store);
61
+
62
+ this.parser = new XMLParser(options);
63
+ this.tests = [];
64
+ this.stats = {};
65
+ this.stats.language = opts.lang?.toLowerCase();
66
+ this.uploader = new S3Uploader();
67
+
68
+ // @ts-ignore
69
+ const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
70
+ this.version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
71
+ console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
72
+ }
73
+
74
+ connectAdapter() {
75
+ if (this.opts.javaTests) {
76
+ this.adapter = adapterFactory('java', this.opts);
77
+ return this.adapter;
78
+ }
79
+ this.adapter = adapterFactory(this.stats.language, this.opts);
80
+ return this.adapter;
81
+ }
82
+
83
+ parse(fileName) {
84
+ let xmlData = fs.readFileSync(path.resolve(fileName)).toString();
85
+
86
+ // we remove too long stack traces
87
+ const cutRegexes = [
88
+ /(<output><!\[CDATA\[)([\s\S]*?)(\]\]><\/output>)/g,
89
+ /(<system-err><!\[CDATA\[)([\s\S]*?)(\]\]><\/system-err>)/g,
90
+ /(<system-out><!\[CDATA\[)([\s\S]*?)(\]\]><\/system-out>)/g,
91
+ ];
92
+
93
+ for (const regex of cutRegexes) {
94
+ xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, 5000)}${p3}`);
95
+ }
96
+
97
+ const jsonResult = this.parser.parse(xmlData);
98
+ let jsonSuite;
99
+
100
+ if (jsonResult.testsuites) {
101
+ jsonSuite = jsonResult.testsuites;
102
+ } else if (jsonResult.testsuite) {
103
+ jsonSuite = jsonResult;
104
+ } else if (jsonResult.TestRun) {
105
+ return this.processTRX(jsonResult);
106
+ } else if (jsonResult['test-run']) {
107
+ return this.processNUnit(jsonResult['test-run']);
108
+ } else if (jsonResult.assemblies) {
109
+ return this.processXUnit(jsonResult.assemblies);
110
+ } else {
111
+ console.log(jsonResult);
112
+ throw new Error("Format can't be parsed");
113
+ }
114
+
115
+ return this.processJUnit(jsonSuite);
116
+ }
117
+
118
+ processJUnit(jsonSuite) {
119
+ const { testsuite, name, tests, failures, errors } = jsonSuite;
120
+
121
+ reduceOptions.preferClassname = this.stats.language === 'python';
122
+ const resultTests = processTestSuite(testsuite);
123
+
124
+ const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
125
+ const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
126
+
127
+ const time = testsuite.time || 0;
128
+ // debug('time', jsonSuite, time)
129
+ if (time) {
130
+ if (!this.stats.duration) this.stats.duration = 0;
131
+ this.stats.duration += parseFloat(time);
132
+ }
133
+
134
+ this.tests = this.tests.concat(resultTests);
135
+
136
+ return {
137
+ create_tests: true,
138
+ duration: parseFloat(time),
139
+ failed_count: parseInt(failures, 10),
140
+ name,
141
+ passed_count: parseInt(tests, 10) - parseInt(failures, 10),
142
+ skipped_count: 0,
143
+ status,
144
+ tests: resultTests,
145
+ tests_count: parseInt(tests, 10),
146
+ };
147
+ }
148
+
149
+ processNUnit(jsonSuite) {
150
+ const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
151
+
152
+ reduceOptions.preferClassname = this.stats.language === 'python';
153
+ const resultTests = processTestSuite(jsonSuite['test-suite']);
154
+
155
+ this.tests = this.tests.concat(resultTests);
156
+
157
+ return {
158
+ status: result?.toLowerCase(),
159
+ create_tests: true,
160
+ tests_count: parseInt(total, 10),
161
+ passed_count: parseInt(passed, 10),
162
+ failed_count: parseInt(failed, 10),
163
+ skipped_count: parseInt(inconclusive + skipped, 10),
164
+ tests: resultTests,
165
+ };
166
+ }
167
+
168
+ processTRX(jsonSuite) {
169
+ let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
170
+ if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
171
+
172
+ const tests =
173
+ defs.map(td => {
174
+ const title = td.name.replace(/\(.*?\)/, '').trim();
175
+ let example = td.name.match(/\((.*?)\)/);
176
+ if (example) example = { ...example[1].split(',') };
177
+ const suite = td.TestMethod.className.split(', ')[0].split('.');
178
+ const suite_title = suite.pop();
179
+ return {
180
+ title,
181
+ example,
182
+ file: suite.join('/'),
183
+ description: td.Description,
184
+ suite_title,
185
+ id: td.Execution.id,
186
+ };
187
+ }) || [];
188
+
189
+ let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
190
+ if (!Array.isArray(result)) result = [result].filter(d => !!d);
191
+
192
+ const results = result.map(td => ({
193
+ id: td.executionId,
194
+ // seconds are used in junit reports, but ms are used by testomatio
195
+ run_time: parseFloat(td.duration) * 1000,
196
+ status: td.outcome,
197
+ stack: td.Output.StdOut,
198
+ files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
199
+ }));
200
+
201
+ results.forEach(r => {
202
+ const test = tests.find(t => t.id === r.id) || {};
203
+ r.suite_title = test.suite_title;
204
+ r.title = test.title?.trim();
205
+ if (test.code) r.code = test.code;
206
+ if (test.description) r.description = test.description;
207
+ if (test.example) r.example = test.example;
208
+ if (test.file) r.file = test.file;
209
+ r.create = true;
210
+ if (r.status === 'Passed') r.status = STATUS.PASSED;
211
+ if (r.status === 'Failed') r.status = STATUS.FAILED;
212
+ if (r.status === 'Skipped') r.status = STATUS.SKIPPED;
213
+ delete r.id;
214
+ });
215
+
216
+ debug(results);
217
+
218
+ const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
219
+
220
+ const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
221
+
222
+ let status = STATUS.PASSED.toString();
223
+ if (failed_count > 0) status = STATUS.FAILED;
224
+
225
+ this.tests = results.filter(t => !!t.title);
226
+
227
+ return {
228
+ status,
229
+ create_tests: true,
230
+ tests_count: parseInt(counters.total, 10),
231
+ passed_count: parseInt(counters.passed, 10),
232
+ skipped_count: parseInt(counters.notExecuted, 10),
233
+ failed_count,
234
+ tests: results,
235
+ };
236
+ }
237
+
238
+ processXUnit(assemblies) {
239
+ const tests = [];
240
+
241
+ assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
242
+
243
+ assemblies.forEach(assembly => {
244
+ const { collection } = assembly;
245
+
246
+ const suites = Array.isArray(collection) ? collection : [collection];
247
+
248
+ suites.forEach(suite => {
249
+ const { test } = suite;
250
+ if (!test) return;
251
+ const cases = Array.isArray(test) ? test : [test];
252
+ cases.forEach(testCase => {
253
+ const { type, time, result } = testCase;
254
+
255
+ let message = '';
256
+ let stack = '';
257
+
258
+ if (testCase.failure) {
259
+ message = testCase.failure.message;
260
+ stack = testCase.failure['stack-trace'];
261
+ }
262
+ if (testCase.reason) {
263
+ message = testCase.reason.message;
264
+ }
265
+
266
+ let status = STATUS.PASSED;
267
+ if (result === 'Pass') status = STATUS.PASSED;
268
+ if (result === 'Fail') status = STATUS.FAILED;
269
+ if (result === 'Skip') status = STATUS.SKIPPED;
270
+
271
+ const pathParts = type.split('.');
272
+ const suite_title = pathParts[pathParts.length - 1];
273
+ const file = pathParts.slice(0, -1).join('/');
274
+ const title = testCase.method || testCase.name.split('.').pop();
275
+ const run_time = parseFloat(time) * 1000;
276
+
277
+ tests.push({
278
+ create: true,
279
+ stack,
280
+ message,
281
+ file,
282
+ status,
283
+ title,
284
+ suite_title,
285
+ run_time,
286
+ });
287
+ });
288
+ });
289
+ });
290
+
291
+ const hasFailures = tests.filter(t => t.status === STATUS.FAILED).length > 0;
292
+ const status = hasFailures ? STATUS.FAILED : STATUS.PASSED;
293
+
294
+ this.tests = tests;
295
+
296
+ debug(tests);
297
+
298
+ return {
299
+ status,
300
+ create_tests: true,
301
+ name: 'xUnit',
302
+ tests_count: tests.length,
303
+ passed_count: tests.filter(t => t.status === STATUS.PASSED).length,
304
+ failed_count: tests.filter(t => t.status === STATUS.FAILED).length,
305
+ skipped_count: tests.filter(t => t.status === STATUS.SKIPPED).length,
306
+ tests,
307
+ };
308
+ }
309
+
310
+ calculateStats() {
311
+ this.stats = {
312
+ ...this.stats,
313
+ detach: this.requestParams.detach,
314
+ status: 'passed',
315
+ create_tests: true,
316
+ tests_count: 0,
317
+ passed_count: 0,
318
+ failed_count: 0,
319
+ skipped_count: 0,
320
+ };
321
+ this.tests.forEach(t => {
322
+ this.stats.tests_count++;
323
+ if (t.status === 'passed') this.stats.passed_count++;
324
+ if (t.status === 'failed') this.stats.failed_count++;
325
+ });
326
+ if (this.stats.failed_count) this.stats.status = 'failed';
327
+
328
+ return this.stats;
329
+ }
330
+
331
+ fetchSourceCode() {
332
+ this.tests.forEach(t => {
333
+ try {
334
+ const file = this.adapter.getFilePath(t);
335
+ if (!file) return;
336
+
337
+ if (!this.stats.language) {
338
+ if (file.endsWith('.php')) this.stats.language = 'php';
339
+ if (file.endsWith('.py')) this.stats.language = 'python';
340
+ if (file.endsWith('.java')) this.stats.language = 'java';
341
+ if (file.endsWith('.rb')) this.stats.language = 'ruby';
342
+ if (file.endsWith('.js')) this.stats.language = 'js';
343
+ if (file.endsWith('.ts')) this.stats.language = 'ts';
344
+ }
345
+
346
+ if (!fs.existsSync(file)) {
347
+ debug('Failed to open file with the source code', file);
348
+ return;
349
+ }
350
+ const contents = fs.readFileSync(file).toString();
351
+ t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language });
352
+ if (t.code) debug('Fetched code for test %s', t.title);
353
+ t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language });
354
+ if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
355
+ } catch (err) {
356
+ debug(err);
357
+ }
358
+ });
359
+ }
360
+
361
+ formatTests() {
362
+ this.tests.forEach(t => {
363
+ if (t.file) {
364
+ t.file = t.file.replace(process.cwd() + path.sep, '');
365
+ }
366
+
367
+ this.adapter.formatTest(t);
368
+
369
+ t.title = humanize(t.title);
370
+ });
371
+ }
372
+
373
+ formatErrors() {
374
+ this.tests
375
+ .filter(t => !!t.stack)
376
+ .forEach(t => {
377
+ t.stack = this.formatStack(t);
378
+ t.message = this.adapter.formatMessage(t);
379
+ });
380
+ }
381
+
382
+ formatStack(t) {
383
+ const stack = this.adapter.formatStack(t);
384
+
385
+ const sourcePart = fetchSourceCodeFromStackTrace(stack);
386
+
387
+ if (!sourcePart) return stack;
388
+
389
+ const separator = pc.bold(pc.red('################[ Failure ]################'));
390
+
391
+ return `${stack}\n\n${separator}\n${fetchSourceCodeFromStackTrace(stack)}`;
392
+ }
393
+
394
+ async uploadArtifacts() {
395
+ for (const test of this.tests.filter(t => !!t.stack)) {
396
+ let files = [];
397
+ if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
398
+ files = [...files, ...fetchFilesFromStackTrace(test.stack)];
399
+
400
+ if (!files.length) continue;
401
+
402
+ const runId = this.runId || this.store.runId || Date.now().toString();
403
+ test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
404
+ console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
405
+ }
406
+ }
407
+
408
+ async createRun() {
409
+ const runParams = {
410
+ api_key: this.requestParams.apiKey,
411
+ title: this.requestParams.title,
412
+ env: this.requestParams.env,
413
+ group_title: this.requestParams.group_title,
414
+ isBatchEnabled: this.requestParams.isBatchEnabled,
415
+ };
416
+
417
+ debug('Run', runParams);
418
+ this.pipes = this.pipes || (await this.pipesPromise);
419
+
420
+ const run = await Promise.all(this.pipes.map(p => p.createRun(runParams)));
421
+ this.uploader.checkEnabled();
422
+ return run;
423
+ }
424
+
425
+ async uploadData() {
426
+ await this.uploadArtifacts();
427
+ this.calculateStats();
428
+ this.connectAdapter();
429
+ this.fetchSourceCode();
430
+ this.formatErrors();
431
+ this.formatTests();
432
+
433
+ const dataString = {
434
+ ...this.stats,
435
+ api_key: this.requestParams.apiKey,
436
+ status: 'finished',
437
+ duration: this.stats.duration,
438
+ tests: this.tests,
439
+ };
440
+
441
+ debug('Uploading data', dataString);
442
+
443
+ this.pipes = this.pipes || (await this.pipesPromise);
444
+ return Promise.all(this.pipes.map(p => p.finishRun(dataString)));
445
+ }
446
+
447
+ async _finishRun() {
448
+ this.pipes = this.pipes || (await this.pipesPromise);
449
+ return Promise.all(this.pipes.map(p => p.finishRun({ status: 'finished' })));
450
+ }
451
+ }
452
+
453
+ export default XmlReader;
454
+
455
+ function reduceTestCases(prev, item) {
456
+ let testCases = item.testcase;
457
+ if (!testCases) testCases = item['test-case'];
458
+ if (!Array.isArray(testCases)) {
459
+ testCases = [testCases];
460
+ }
461
+
462
+ // suite inside test case
463
+ if (item['test-suite'] && item['test-suite']['test-case']) testCases.push(...item['test-suite']['test-case']);
464
+
465
+ const suiteOutput = item['system-out'] || item.output || item.log || '';
466
+ const suiteErr = item['system-err'] || item.output || item.log || '';
467
+ testCases
468
+ .filter(t => !!t)
469
+ .forEach(testCaseItem => {
470
+ const file = testCaseItem.file || item.filepath || '';
471
+
472
+ let stack = '';
473
+ let message = '';
474
+ if (testCaseItem.error) stack = testCaseItem.error;
475
+ if (testCaseItem.failure) stack = testCaseItem.failure;
476
+ if (testCaseItem?.failure?.['stack-trace']) stack = testCaseItem.failure['stack-trace'];
477
+ if (testCaseItem?.failure?.message) message = testCaseItem.failure.message;
478
+ if (testCaseItem?.error?.message) message = testCaseItem.error.message;
479
+
480
+ if (testCaseItem.failure && testCaseItem.failure['#text']) stack = testCaseItem.failure['#text'];
481
+ if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
482
+ if (!message) message = stack.trim().split('\n')[0];
483
+
484
+ const isParametrized = item.type === 'ParameterizedMethod';
485
+ const preferClassname = reduceOptions.preferClassname || isParametrized;
486
+
487
+ // SpecFlow config
488
+ let { title, tags } = fetchProperties(isParametrized ? item : testCaseItem);
489
+ let example = null;
490
+ const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
491
+
492
+ title ||= testCaseItem.name || testCaseItem.methodname || testCaseItem.classname;
493
+ tags ||= [];
494
+
495
+ const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
496
+ if (exampleMatches) {
497
+ example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
498
+ title = title.replace(/\(.*?\)/, '').trim();
499
+ }
500
+
501
+ // eslint-disable-next-line
502
+ stack = `${
503
+ testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
504
+ }\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
505
+ const testId = fetchIdFromOutput(stack);
506
+
507
+ let status = STATUS.PASSED.toString();
508
+ if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
509
+ if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
510
+
511
+ let rid = null;
512
+ if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
513
+
514
+ prev.push({
515
+ rid,
516
+ file,
517
+ stack,
518
+ example,
519
+ tags,
520
+ create: true,
521
+ test_id: testId,
522
+ message,
523
+ line: testCaseItem.lineno,
524
+ // seconds are used in junit reports, but ms are used by testomatio
525
+ run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
526
+ status,
527
+ title,
528
+ suite_title: suiteTitle,
529
+ });
530
+ });
531
+ return prev;
532
+ }
533
+
534
+ function processTestSuite(testsuite) {
535
+ if (!testsuite) return [];
536
+ if (testsuite.testsuite) return processTestSuite(testsuite.testsuite);
537
+ if (testsuite['test-suite'] && !testsuite['test-case']) return processTestSuite(testsuite['test-suite']);
538
+
539
+ let suites = testsuite;
540
+ if (!Array.isArray(testsuite)) {
541
+ suites = [testsuite];
542
+ }
543
+
544
+ const subSuites = suites.filter(s => s['test-suite'] && !testsuite['test-case']);
545
+
546
+ return [...subSuites.map(s => processTestSuite(s['test-suite'])), ...suites.reduce(reduceTestCases, [])].flat();
547
+ }
548
+
549
+ function fetchProperties(item) {
550
+ const tags = [];
551
+ let title = '';
552
+
553
+ if (!item.properties) return {};
554
+
555
+ const prop = [item.properties?.property].flat().find(p => p.name === 'Description');
556
+ if (prop) title = prop.value;
557
+ [item.properties?.property]
558
+ .flat()
559
+ .filter(p => p.name === 'Category')
560
+ .forEach(p => tags.push(p.value));
561
+ return { title, tags };
562
+ }