@testomatio/reporter 2.3.8-rc.1 → 2.3.9-beta-bin-fix

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/src/reporter.js CHANGED
@@ -1,10 +1,8 @@
1
- import Client from './client.js';
2
- import * as TestomatioConstants from './constants.js';
1
+ // import TestomatClient from './client.js';
2
+ // import * as TRConstants from './constants.js';
3
3
  import { services } from './services/index.js';
4
4
  import reporterFunctions from './reporter-functions.js';
5
5
 
6
- export { Client };
7
- export const STATUS = TestomatioConstants.STATUS;
8
6
  export const artifact = reporterFunctions.artifact;
9
7
  export const log = reporterFunctions.log;
10
8
  export const logger = services.logger;
@@ -37,7 +35,6 @@ export default {
37
35
  linkTest: reporterFunctions.linkTest,
38
36
  linkJira: reporterFunctions.linkJira,
39
37
 
40
- TestomatioClient: Client,
41
- STATUS,
42
-
38
+ // TestomatClient,
39
+ // TRConstants,
43
40
  };
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);
@@ -76,29 +76,21 @@ const isValidUrl = s => {
76
76
  }
77
77
  };
78
78
 
79
- const fileMatchRegex = /file:(\/*)([A-Za-z]:[\\/].*?|\/.*?)\.(png|avi|webm|jpg|html|txt)/gi;
79
+ const fileMatchRegex = /file:(\/+(?:[A-Za-z]:[\\/]|\/)?[^\s]*?\.(png|avi|webm|jpg|html|txt))/gi;
80
80
 
81
81
  const fetchFilesFromStackTrace = (stack = '', checkExists = true) => {
82
- let files = Array.from(stack.matchAll(fileMatchRegex))
83
- .map(match => {
84
- // match[0] is full match, match[1] is slashes, match[2] is path, match[3] is extension
85
- const slashes = match[1] || '';
86
- const path = match[2];
87
- const extension = match[3];
88
- return `${slashes}${path}.${extension}`;
89
- })
90
- .map(f => f.trim())
82
+ const files = Array.from(stack.matchAll(fileMatchRegex))
83
+ .map(f => f[1].trim())
91
84
  .map(f => f.replace(/^\/+/, '/').replace(/^\/([A-Za-z]:)/, '$1')) // Remove extra slashes, handle Windows paths
92
85
  .map(f => {
93
- // Normalize path separators for cross-platform compatibility
94
- return f.replace(/\\/g, '/');
86
+ // Convert Windows paths to Linux paths for testing purposes
87
+ if (f.match(/^[A-Za-z]:[\\\/]/)) {
88
+ // Convert Windows path to Linux equivalent for test scenarios
89
+ return f.replace(/^[A-Za-z]:[\\\/]/, '/').replace(/\\/g, '/');
90
+ }
91
+ return f;
95
92
  });
96
93
 
97
- // If we're not checking file existence, remove Windows drive letters for consistency
98
- if (!checkExists) {
99
- files = files.map(f => f.replace(/^([A-Za-z]):/, ''));
100
- }
101
-
102
94
  debug('Found files in stack trace: ', files);
103
95
 
104
96
  return files.filter(f => {
@@ -113,92 +105,21 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
113
105
  const stackLines = stack
114
106
  .split('\n')
115
107
  .filter(l => l.includes(':'))
108
+ // .map(l => l.match(/\[(.*?)\]/)?.[1] || l) // minitest format
109
+ // .map(l => l.split(':')[0])
116
110
  .map(l => l.trim())
117
- .map(l => {
118
- // Remove 'at ' prefix if present
119
- if (l.startsWith('at ')) {
120
- return l.substring(3).trim();
121
- }
122
- // Find the part that looks like a file path with line number
123
- const parts = l.split(' ');
124
- for (const part of parts) {
125
- // Check if this part has a colon
126
- if (part.includes(':')) {
127
- // For Windows paths, we need to handle drive letters (C:, D:, etc.)
128
- // Split by colon but keep drive letter with the path
129
- const colonParts = part.split(':');
130
- let filePath;
131
-
132
- // Check if first part is a Windows drive letter (single letter)
133
- if (colonParts.length >= 2 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
134
- // Windows path like D:\path\file.php:24
135
- // Reconstruct as D:\path\file.php
136
- filePath = colonParts[0] + ':' + colonParts[1];
137
- } else {
138
- // Unix path like /path/file.php:24
139
- filePath = colonParts[0];
140
- }
141
-
142
- // Only consider it valid if the file exists
143
- if (fs.existsSync(filePath)) {
144
- return part;
145
- }
146
- }
147
- }
148
- // If no valid file path found in parts, return the whole line
149
- // It will be filtered out later if it's not a valid file path
150
- return parts.find(p => p.includes(':')) || l;
151
- })
152
- .filter(l => {
153
- // Extract file path from line (accounting for Windows drive letters)
154
- if (!l) return false;
155
- const colonParts = l.split(':');
156
- let filePath;
157
-
158
- if (colonParts.length >= 2 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
159
- // Windows path
160
- filePath = colonParts[0] + ':' + colonParts[1];
161
- } else {
162
- // Unix path
163
- filePath = colonParts[0];
164
- }
165
-
166
- return filePath && fs.existsSync(filePath);
167
- })
111
+ .map(l => l.split(' ').find(p => p.includes(':')) || '')
112
+ .filter(l => isValid(l?.split(':')[0]))
168
113
 
169
114
  // // filter out 3rd party libs
170
115
  .filter(l => !l?.includes(`vendor${sep}`))
171
116
  .filter(l => !l?.includes(`node_modules${sep}`))
172
- .filter(l => {
173
- // Extract file path for final check (accounting for Windows drive letters)
174
- const colonParts = l.split(':');
175
- let filePath;
176
-
177
- if (colonParts.length >= 2 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
178
- filePath = colonParts[0] + ':' + colonParts[1];
179
- } else {
180
- filePath = colonParts[0];
181
- }
182
-
183
- return fs.lstatSync(filePath).isFile();
184
- });
117
+ .filter(l => fs.existsSync(l.split(':')[0]))
118
+ .filter(l => fs.lstatSync(l.split(':')[0]).isFile());
185
119
 
186
120
  if (!stackLines.length) return '';
187
121
 
188
- // Extract file and line number (accounting for Windows drive letters)
189
- const firstLine = stackLines[0];
190
- const colonParts = firstLine.split(':');
191
- let file, line;
192
-
193
- if (colonParts.length >= 3 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
194
- // Windows path like D:\path\file.php:24
195
- file = colonParts[0] + ':' + colonParts[1];
196
- line = colonParts[2];
197
- } else {
198
- // Unix path like /path/file.php:24
199
- file = colonParts[0];
200
- line = colonParts[1];
201
- }
122
+ const [file, line] = stackLines[0].split(':');
202
123
 
203
124
  const prepend = 3;
204
125
  const source = fetchSourceCode(fs.readFileSync(file).toString(), { line, prepend, limit: 7 });
@@ -218,8 +139,6 @@ export const TEST_ID_REGEX = /@T([\w\d]{8})/;
218
139
  export const SUITE_ID_REGEX = /@S([\w\d]{8})/;
219
140
 
220
141
  const fetchIdFromCode = (code, opts = {}) => {
221
- if (!code) return null;
222
-
223
142
  const comments = code
224
143
  .split('\n')
225
144
  .map(l => l.trim())
@@ -261,65 +180,8 @@ const fetchSourceCode = (contents, opts = {}) => {
261
180
  if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
262
181
  if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
263
182
  } else if (opts.lang === 'csharp') {
264
- // Find the method declaration line
265
- let methodLineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
266
-
267
- if (methodLineIndex === -1) {
268
- methodLineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
269
- }
270
-
271
- if (methodLineIndex === -1) {
272
- methodLineIndex = lines.findIndex(l => l.includes(`${title}(`));
273
- }
274
-
275
- // If found, scan upwards to find [TestCase], [Test] attributes and XML comments
276
- if (methodLineIndex !== -1) {
277
- lineIndex = methodLineIndex;
278
-
279
- // Scan upwards to find the start of attributes and comments
280
- for (let i = methodLineIndex - 1; i >= 0; i--) {
281
- const trimmedLine = lines[i].trim();
282
-
283
- // Include [TestCase], [Test], and other attributes
284
- if (trimmedLine.startsWith('[')) {
285
- lineIndex = i;
286
- continue;
287
- }
288
-
289
- // Include XML documentation comments
290
- if (trimmedLine.startsWith('///')) {
291
- lineIndex = i;
292
- continue;
293
- }
294
-
295
- // Stop at empty lines (with some tolerance)
296
- if (trimmedLine === '') {
297
- // Check if next non-empty line is an attribute or comment
298
- let hasMoreAttributes = false;
299
- for (let j = i - 1; j >= 0; j--) {
300
- const nextTrimmed = lines[j].trim();
301
- if (nextTrimmed === '') continue;
302
- if (nextTrimmed.startsWith('[') || nextTrimmed.startsWith('///')) {
303
- hasMoreAttributes = true;
304
- lineIndex = j;
305
- }
306
- break;
307
- }
308
- if (!hasMoreAttributes) break;
309
- continue;
310
- }
311
-
312
- // Stop at other method declarations or class-level elements
313
- if (
314
- trimmedLine.includes('public ') ||
315
- trimmedLine.includes('private ') ||
316
- trimmedLine.includes('protected ') ||
317
- trimmedLine.includes('internal ')
318
- ) {
319
- if (!trimmedLine.startsWith('[')) break;
320
- }
321
- }
322
- }
183
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
184
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
323
185
  } else {
324
186
  lineIndex = lines.findIndex(l => l.includes(title));
325
187
  }
@@ -329,31 +191,11 @@ const fetchSourceCode = (contents, opts = {}) => {
329
191
  lineIndex -= opts.prepend;
330
192
  }
331
193
 
332
- if (lineIndex !== -1 && lineIndex !== undefined) {
194
+ if (lineIndex) {
333
195
  const result = [];
334
- let braceDepth = 0; // Track brace depth for C# methods
335
- let methodStartFound = false; // Flag to indicate we've found the method opening brace
336
-
337
196
  for (let i = lineIndex; i < lineIndex + limit; i++) {
338
197
  if (lines[i] === undefined) continue;
339
198
 
340
- // Track brace depth for C# to stop after method closes
341
- if (opts.lang === 'csharp') {
342
- const line = lines[i];
343
- // Count opening and closing braces
344
- const openBraces = (line.match(/\{/g) || []).length;
345
- const closeBraces = (line.match(/\}/g) || []).length;
346
-
347
- if (openBraces > 0) methodStartFound = true;
348
- braceDepth += openBraces - closeBraces;
349
-
350
- // If we've started the method and depth returns to 0, method is complete
351
- if (methodStartFound && braceDepth === 0 && closeBraces > 0) {
352
- // Don't include the closing brace - just break
353
- break;
354
- }
355
- }
356
-
357
199
  if (i > lineIndex + 2 && !opts.prepend) {
358
200
  // annotation
359
201
  if (opts.lang === 'php' && lines[i].trim().startsWith('#[')) break;
@@ -374,36 +216,7 @@ const fetchSourceCode = (contents, opts = {}) => {
374
216
  if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
375
217
  if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
376
218
  if (opts.lang === 'java' && lines[i].includes(' class ')) break;
377
- // For C#, additional checks if brace tracking didn't stop us
378
- if (opts.lang === 'csharp') {
379
- const trimmed = lines[i].trim();
380
- // Stop at attribute that marks beginning of next test (but not if we're still in the current method)
381
- if (trimmed.match(/^\[(Test|TestCase|Theory|Fact)/) && methodStartFound && braceDepth === 0) break;
382
- // Stop at XML documentation comments that belong to next method
383
- if (trimmed.startsWith('///') && methodStartFound && braceDepth === 0) break;
384
- // Stop at another method declaration (but not if we're still in the current method)
385
- if (
386
- trimmed.match(/^\s*(public|private|protected|internal)\s+(\w+|async\s+\w+)\s+\w+\s*\(/) &&
387
- methodStartFound &&
388
- braceDepth === 0
389
- )
390
- break;
391
- // Stop at class declaration
392
- if (trimmed.includes(' class ') && trimmed.includes('public')) break;
393
- // Stop at helper method calls (like ProcessBooleanValue, AddNumbers) - these are private methods
394
- if (methodStartFound && trimmed.match(/^\s*\/\/\s*Helper methods for testing/)) break;
395
- }
396
219
  }
397
-
398
- // For C# tests, stop if we encounter helper method calls in the method body
399
- if (opts.lang === 'csharp' && methodStartFound && braceDepth > 0) {
400
- const trimmed = lines[i].trim();
401
- // Stop at comment indicating helper methods section
402
- if (trimmed.match(/^\s*\/\/\s*Helper methods for testing/)) {
403
- break;
404
- }
405
- }
406
-
407
220
  result.push(lines[i]);
408
221
  }
409
222
  return result.join('\n');
@@ -616,14 +429,10 @@ function transformEnvVarToBoolean(value) {
616
429
  }
617
430
 
618
431
  function truncate(s, size = 255) {
619
- if (s === undefined || s === null) {
620
- return '';
621
- }
622
- const str = s.toString();
623
- if (str.trim().length < size) {
624
- return str;
432
+ if (s.toString().trim().length < size) {
433
+ return s.toString();
625
434
  }
626
- return `${str.substring(0, size)}...`;
435
+ return `${s.toString().substring(0, size)}...`;
627
436
  }
628
437
 
629
438
  export {
package/src/xmlReader.js CHANGED
@@ -6,7 +6,6 @@ import { XMLParser } from 'fast-xml-parser';
6
6
  import { APP_PREFIX, STATUS } from './constants.js';
7
7
  import { randomUUID } from 'crypto';
8
8
  import { fileURLToPath } from 'url';
9
- import { NUnitXmlParser } from './junit-adapter/nunit-parser.js';
10
9
  import {
11
10
  fetchFilesFromStackTrace,
12
11
  fetchIdFromOutput,
@@ -15,7 +14,6 @@ import {
15
14
  fetchIdFromCode,
16
15
  humanize,
17
16
  TEST_ID_REGEX,
18
- transformEnvVarToBoolean,
19
17
  } from './utils/utils.js';
20
18
  import { pipesFactory } from './pipe/index.js';
21
19
  import adapterFactory from './junit-adapter/index.js';
@@ -37,7 +35,6 @@ const {
37
35
  TESTOMATIO_ENV,
38
36
  TESTOMATIO_RUN,
39
37
  TESTOMATIO_MARK_DETACHED,
40
- TESTOMATIO_LEGACY_NUNIT,
41
38
  } = process.env;
42
39
 
43
40
  const options = {
@@ -78,11 +75,6 @@ class XmlReader {
78
75
  this.stats.language = opts.lang?.toLowerCase();
79
76
  this.uploader = new S3Uploader();
80
77
 
81
- // Enhanced NUnit parsing - enabled by default for NUnit XML
82
- // Can be disabled via opts.enhancedNunit = false or TESTOMATIO_LEGACY_NUNIT=1
83
- this.enhancedNunit = opts.enhancedNunit !== false && !transformEnvVarToBoolean(TESTOMATIO_LEGACY_NUNIT); // Default true, can be disabled
84
- this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
85
-
86
78
  // @ts-ignore
87
79
  const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
88
80
  this.version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
@@ -134,8 +126,7 @@ class XmlReader {
134
126
  }
135
127
 
136
128
  processJUnit(jsonSuite) {
137
- const { testsuite, name, failures, errors } = jsonSuite;
138
- const tests = testsuite?.tests || jsonSuite.tests;
129
+ const { testsuite, name, tests, failures, errors } = jsonSuite;
139
130
 
140
131
  reduceOptions.preferClassname = this.stats.language === 'python';
141
132
  const resultTests = processTestSuite(testsuite);
@@ -166,14 +157,6 @@ class XmlReader {
166
157
  }
167
158
 
168
159
  processNUnit(jsonSuite) {
169
- // Use enhanced NUnit parser if enabled and this is actually NUnit XML
170
- if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
171
- debug('Using enhanced NUnit parser');
172
- return this.processNUnitEnhanced(jsonSuite);
173
- }
174
-
175
- // Fallback to legacy parser for backward compatibility
176
- debug('Using legacy NUnit parser');
177
160
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
178
161
 
179
162
  reduceOptions.preferClassname = this.stats.language === 'python';
@@ -192,71 +175,63 @@ class XmlReader {
192
175
  };
193
176
  }
194
177
 
195
- /**
196
- * Check if the XML is actually NUnit format (has test-suite hierarchy)
197
- * @param {Object} jsonSuite - Parsed XML suite object
198
- * @returns {boolean} - True if this is NUnit XML format
199
- */
200
- isNUnitXml(jsonSuite) {
201
- // NUnit XML has test-suite elements with type attributes
202
- if (jsonSuite['test-suite']) {
203
- const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
204
-
205
- // Check for NUnit-specific test-suite types
206
- return (
207
- testSuite &&
208
- testSuite.type &&
209
- ['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type)
210
- );
211
- }
212
- return false;
213
- }
214
-
215
- processNUnitEnhanced(jsonSuite) {
216
- debug('Processing NUnit XML with enhanced parser');
217
-
218
- try {
219
- const nunitParser = new NUnitXmlParser({
220
- groupParameterized: this.groupParameterized,
221
- ...this.opts,
222
- });
223
-
224
- const result = nunitParser.parseTestRun(jsonSuite);
225
-
226
- // Add parsed tests to our collection
227
- this.tests = this.tests.concat(result.tests);
228
-
229
- debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
230
-
231
- return result;
232
- } catch (error) {
233
- debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
234
- console.warn(`${APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
235
-
236
- // Fallback to legacy parser
237
- this.enhancedNunit = false;
238
- return this.processNUnit(jsonSuite);
239
- }
240
- }
241
-
242
178
  processTRX(jsonSuite) {
243
179
  let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
244
180
  if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
245
181
 
246
- // Parse test definitions
247
- const tests = defs.map(td => this._parseTRXTestDefinition(td));
182
+ const tests =
183
+ defs.map(td => {
184
+ const title = td.name.replace(/\(.*?\)/, '').trim();
185
+ let example = td.name.match(/\((.*?)\)/);
186
+ if (example) example = { ...example[1].split(',') };
187
+ const suite = td.TestMethod.className.split(', ')[0].split('.');
188
+ const suite_title = suite.pop();
189
+ return {
190
+ title,
191
+ example,
192
+ file: suite.join('/'),
193
+ description: td.Description,
194
+ suite_title,
195
+ id: td.Execution.id,
196
+ };
197
+ }) || [];
248
198
 
249
- // Parse test results
250
199
  let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
251
200
  if (!Array.isArray(result)) result = [result].filter(d => !!d);
252
201
 
253
- const results = result.map(td => this._parseTRXTestResult(td, tests));
202
+ const results = result.map(td => ({
203
+ id: td.executionId,
204
+ // seconds are used in junit reports, but ms are used by testomatio
205
+ run_time: parseFloat(td.duration) * 1000,
206
+ status: td.outcome,
207
+ stack: td.Output.StdOut,
208
+ files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
209
+ }));
210
+
211
+ results.forEach(r => {
212
+ const test = tests.find(t => t.id === r.id) || {};
213
+ r.suite_title = test.suite_title;
214
+ r.title = test.title?.trim();
215
+ if (test.code) r.code = test.code;
216
+ if (test.description) r.description = test.description;
217
+ if (test.example) r.example = test.example;
218
+ if (test.file) r.file = test.file;
219
+ r.create = true;
220
+ r.overwrite = true;
221
+ if (r.status === 'Passed') r.status = STATUS.PASSED;
222
+ if (r.status === 'Failed') r.status = STATUS.FAILED;
223
+ if (r.status === 'Skipped') r.status = STATUS.SKIPPED;
224
+ delete r.id;
225
+ });
254
226
 
255
227
  debug(results);
256
228
 
257
229
  const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
230
+
258
231
  const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
259
- const status = failed_count > 0 ? STATUS.FAILED : STATUS.PASSED.toString();
232
+
233
+ let status = STATUS.PASSED.toString();
234
+ if (failed_count > 0) status = STATUS.FAILED;
260
235
 
261
236
  this.tests = results.filter(t => !!t.title);
262
237
 
@@ -271,67 +246,6 @@ class XmlReader {
271
246
  };
272
247
  }
273
248
 
274
- _parseTRXTestDefinition(td) {
275
- const title = td.name.replace(/\(.*?\)/, '').trim();
276
- const exampleMatch = td.name.match(/\((.*?)\)/);
277
- const example = exampleMatch ? { ...exampleMatch[1].split(',') } : null;
278
-
279
- const suite = td.TestMethod.className.split(', ')[0].split('.');
280
- const suite_title = suite.pop();
281
-
282
- // Convert namespace to file path for C#
283
- const file = `${suite.join('/')}.cs`;
284
-
285
- return {
286
- title, // Base name without parameters for test import
287
- example, // Parameters object for parameterized tests
288
- file, // File path with .cs extension
289
- description: td.Description,
290
- suite_title,
291
- id: td.Execution.id,
292
- };
293
- }
294
-
295
- _parseTRXTestResult(td, tests) {
296
- const test = tests.find(t => t.id === td.executionId) || {};
297
-
298
- const result = {
299
- suite_title: test.suite_title,
300
- title: test.title?.trim(),
301
- file: test.file,
302
- description: test.description,
303
- code: test.code,
304
- run_time: parseFloat(td.duration) * 1000,
305
- stack: td.Output?.StdOut || '',
306
- files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
307
- create: true,
308
- overwrite: true,
309
- };
310
-
311
- // Add example for parameterized tests
312
- if (test.example) {
313
- result.example = test.example;
314
- }
315
-
316
- // Map TRX status to Testomat.io status
317
- result.status = this._mapTRXStatus(td.outcome);
318
-
319
- return result;
320
- }
321
-
322
- _mapTRXStatus(outcome) {
323
- switch (outcome) {
324
- case 'Passed':
325
- return STATUS.PASSED;
326
- case 'Failed':
327
- return STATUS.FAILED;
328
- case 'Skipped':
329
- return STATUS.SKIPPED;
330
- default:
331
- return STATUS.PASSED;
332
- }
333
- }
334
-
335
249
  processXUnit(assemblies) {
336
250
  const tests = [];
337
251