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