@testomatio/reporter 2.3.7-beta.1-xml-import → 2.3.7-beta.3-xml-import
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/lib/adapter/codecept.js +22 -2
- package/lib/bin/cli.js +1 -1
- package/lib/bin/reportXml.js +1 -1
- package/lib/bin/startTest.js +3 -3
- package/lib/client.js +7 -3
- package/lib/junit-adapter/csharp.js +10 -6
- package/lib/junit-adapter/nunit-parser.js +14 -2
- package/lib/pipe/testomatio.js +1 -2
- package/lib/template/testomatio.hbs +1366 -1026
- package/lib/utils/utils.d.ts +1 -0
- package/lib/utils/utils.js +89 -26
- package/lib/xmlReader.d.ts +21 -0
- package/lib/xmlReader.js +56 -49
- package/package.json +1 -1
- package/src/adapter/codecept.js +27 -3
- package/src/bin/cli.js +1 -1
- package/src/bin/reportXml.js +1 -1
- package/src/bin/startTest.js +5 -5
- package/src/client.js +8 -5
- package/src/junit-adapter/csharp.js +11 -6
- package/src/junit-adapter/nunit-parser.js +15 -2
- package/src/pipe/testomatio.js +1 -3
- package/src/template/testomatio.hbs +1366 -1026
- package/src/utils/utils.js +91 -22
- package/src/xmlReader.js +66 -44
package/src/utils/utils.js
CHANGED
|
@@ -178,30 +178,63 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
178
178
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
179
179
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
180
180
|
} else if (opts.lang === 'csharp') {
|
|
181
|
-
//
|
|
182
|
-
|
|
181
|
+
// Find the method declaration line
|
|
182
|
+
let methodLineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
|
|
183
183
|
|
|
184
|
-
if (
|
|
185
|
-
|
|
184
|
+
if (methodLineIndex === -1) {
|
|
185
|
+
methodLineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
if (
|
|
189
|
-
|
|
188
|
+
if (methodLineIndex === -1) {
|
|
189
|
+
methodLineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
//
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
192
|
+
// If found, scan upwards to find [TestCase], [Test] attributes and XML comments
|
|
193
|
+
if (methodLineIndex !== -1) {
|
|
194
|
+
lineIndex = methodLineIndex;
|
|
195
|
+
|
|
196
|
+
// Scan upwards to find the start of attributes and comments
|
|
197
|
+
for (let i = methodLineIndex - 1; i >= 0; i--) {
|
|
198
|
+
const trimmedLine = lines[i].trim();
|
|
199
|
+
|
|
200
|
+
// Include [TestCase], [Test], and other attributes
|
|
201
|
+
if (trimmedLine.startsWith('[')) {
|
|
202
|
+
lineIndex = i;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Include XML documentation comments
|
|
207
|
+
if (trimmedLine.startsWith('///')) {
|
|
208
|
+
lineIndex = i;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Stop at empty lines (with some tolerance)
|
|
213
|
+
if (trimmedLine === '') {
|
|
214
|
+
// Check if next non-empty line is an attribute or comment
|
|
215
|
+
let hasMoreAttributes = false;
|
|
216
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
217
|
+
const nextTrimmed = lines[j].trim();
|
|
218
|
+
if (nextTrimmed === '') continue;
|
|
219
|
+
if (nextTrimmed.startsWith('[') || nextTrimmed.startsWith('///')) {
|
|
220
|
+
hasMoreAttributes = true;
|
|
221
|
+
lineIndex = j;
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
if (!hasMoreAttributes) break;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Stop at other method declarations or class-level elements
|
|
230
|
+
if (
|
|
231
|
+
trimmedLine.includes('public ') ||
|
|
232
|
+
trimmedLine.includes('private ') ||
|
|
233
|
+
trimmedLine.includes('protected ') ||
|
|
234
|
+
trimmedLine.includes('internal ')
|
|
235
|
+
) {
|
|
236
|
+
if (!trimmedLine.startsWith('[')) break;
|
|
200
237
|
}
|
|
201
|
-
return false;
|
|
202
|
-
});
|
|
203
|
-
if (testAttributeIndex !== -1) {
|
|
204
|
-
lineIndex = testAttributeIndex;
|
|
205
238
|
}
|
|
206
239
|
}
|
|
207
240
|
} else {
|
|
@@ -215,9 +248,29 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
215
248
|
|
|
216
249
|
if (lineIndex !== -1 && lineIndex !== undefined) {
|
|
217
250
|
const result = [];
|
|
251
|
+
let braceDepth = 0; // Track brace depth for C# methods
|
|
252
|
+
let methodStartFound = false; // Flag to indicate we've found the method opening brace
|
|
253
|
+
|
|
218
254
|
for (let i = lineIndex; i < lineIndex + limit; i++) {
|
|
219
255
|
if (lines[i] === undefined) continue;
|
|
220
256
|
|
|
257
|
+
// Track brace depth for C# to stop after method closes
|
|
258
|
+
if (opts.lang === 'csharp') {
|
|
259
|
+
const line = lines[i];
|
|
260
|
+
// Count opening and closing braces
|
|
261
|
+
const openBraces = (line.match(/\{/g) || []).length;
|
|
262
|
+
const closeBraces = (line.match(/\}/g) || []).length;
|
|
263
|
+
|
|
264
|
+
if (openBraces > 0) methodStartFound = true;
|
|
265
|
+
braceDepth += openBraces - closeBraces;
|
|
266
|
+
|
|
267
|
+
// If we've started the method and depth returns to 0, method is complete
|
|
268
|
+
if (methodStartFound && braceDepth === 0 && closeBraces > 0) {
|
|
269
|
+
result.push(lines[i]);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
221
274
|
if (i > lineIndex + 2 && !opts.prepend) {
|
|
222
275
|
// annotation
|
|
223
276
|
if (opts.lang === 'php' && lines[i].trim().startsWith('#[')) break;
|
|
@@ -238,10 +291,18 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
238
291
|
if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
|
|
239
292
|
if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
|
|
240
293
|
if (opts.lang === 'java' && lines[i].includes(' class ')) break;
|
|
241
|
-
if
|
|
242
|
-
if (opts.lang === 'csharp'
|
|
243
|
-
|
|
244
|
-
|
|
294
|
+
// For C#, additional checks if brace tracking didn't stop us
|
|
295
|
+
if (opts.lang === 'csharp') {
|
|
296
|
+
const trimmed = lines[i].trim();
|
|
297
|
+
// Stop at attribute that marks beginning of next test
|
|
298
|
+
if (trimmed.match(/^\[(Test|TestCase|Theory|Fact)/)) break;
|
|
299
|
+
// Stop at XML documentation comments that belong to next method
|
|
300
|
+
if (trimmed.startsWith('///')) break;
|
|
301
|
+
// Stop at another method declaration
|
|
302
|
+
if (trimmed.match(/^\s*(public|private|protected|internal)\s+(\w+|async\s+\w+)\s+\w+\s*\(/)) break;
|
|
303
|
+
// Stop at class declaration
|
|
304
|
+
if (trimmed.includes(' class ') && trimmed.includes('public')) break;
|
|
305
|
+
}
|
|
245
306
|
}
|
|
246
307
|
result.push(lines[i]);
|
|
247
308
|
}
|
|
@@ -454,8 +515,16 @@ function transformEnvVarToBoolean(value) {
|
|
|
454
515
|
return Boolean(value);
|
|
455
516
|
}
|
|
456
517
|
|
|
518
|
+
function truncate(s, size = 255) {
|
|
519
|
+
if (s.toString().trim().length < size) {
|
|
520
|
+
return s.toString();
|
|
521
|
+
}
|
|
522
|
+
return `${s.toString().substring(0, size)}...`;
|
|
523
|
+
}
|
|
524
|
+
|
|
457
525
|
export {
|
|
458
526
|
ansiRegExp,
|
|
527
|
+
truncate,
|
|
459
528
|
cleanLatestRunId,
|
|
460
529
|
isSameTest,
|
|
461
530
|
fetchSourceCode,
|
package/src/xmlReader.js
CHANGED
|
@@ -240,59 +240,20 @@ class XmlReader {
|
|
|
240
240
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
241
241
|
if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
246
|
-
let example = td.name.match(/\((.*?)\)/);
|
|
247
|
-
if (example) example = { ...example[1].split(',') };
|
|
248
|
-
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
249
|
-
const suite_title = suite.pop();
|
|
250
|
-
return {
|
|
251
|
-
title,
|
|
252
|
-
example,
|
|
253
|
-
file: suite.join('/'),
|
|
254
|
-
description: td.Description,
|
|
255
|
-
suite_title,
|
|
256
|
-
id: td.Execution.id,
|
|
257
|
-
};
|
|
258
|
-
}) || [];
|
|
243
|
+
// Parse test definitions
|
|
244
|
+
const tests = defs.map(td => this._parseTRXTestDefinition(td));
|
|
259
245
|
|
|
246
|
+
// Parse test results
|
|
260
247
|
let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
|
|
261
248
|
if (!Array.isArray(result)) result = [result].filter(d => !!d);
|
|
262
249
|
|
|
263
|
-
const results = result.map(td => (
|
|
264
|
-
id: td.executionId,
|
|
265
|
-
// seconds are used in junit reports, but ms are used by testomatio
|
|
266
|
-
run_time: parseFloat(td.duration) * 1000,
|
|
267
|
-
status: td.outcome,
|
|
268
|
-
stack: td.Output.StdOut,
|
|
269
|
-
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
|
|
270
|
-
}));
|
|
271
|
-
|
|
272
|
-
results.forEach(r => {
|
|
273
|
-
const test = tests.find(t => t.id === r.id) || {};
|
|
274
|
-
r.suite_title = test.suite_title;
|
|
275
|
-
r.title = test.title?.trim();
|
|
276
|
-
if (test.code) r.code = test.code;
|
|
277
|
-
if (test.description) r.description = test.description;
|
|
278
|
-
if (test.example) r.example = test.example;
|
|
279
|
-
if (test.file) r.file = test.file;
|
|
280
|
-
r.create = true;
|
|
281
|
-
r.overwrite = true;
|
|
282
|
-
if (r.status === 'Passed') r.status = STATUS.PASSED;
|
|
283
|
-
if (r.status === 'Failed') r.status = STATUS.FAILED;
|
|
284
|
-
if (r.status === 'Skipped') r.status = STATUS.SKIPPED;
|
|
285
|
-
delete r.id;
|
|
286
|
-
});
|
|
250
|
+
const results = result.map(td => this._parseTRXTestResult(td, tests));
|
|
287
251
|
|
|
288
252
|
debug(results);
|
|
289
253
|
|
|
290
254
|
const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
|
|
291
|
-
|
|
292
255
|
const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
|
|
293
|
-
|
|
294
|
-
let status = STATUS.PASSED.toString();
|
|
295
|
-
if (failed_count > 0) status = STATUS.FAILED;
|
|
256
|
+
const status = failed_count > 0 ? STATUS.FAILED : STATUS.PASSED.toString();
|
|
296
257
|
|
|
297
258
|
this.tests = results.filter(t => !!t.title);
|
|
298
259
|
|
|
@@ -307,6 +268,67 @@ class XmlReader {
|
|
|
307
268
|
};
|
|
308
269
|
}
|
|
309
270
|
|
|
271
|
+
_parseTRXTestDefinition(td) {
|
|
272
|
+
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
273
|
+
const exampleMatch = td.name.match(/\((.*?)\)/);
|
|
274
|
+
const example = exampleMatch ? { ...exampleMatch[1].split(',') } : null;
|
|
275
|
+
|
|
276
|
+
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
277
|
+
const suite_title = suite.pop();
|
|
278
|
+
|
|
279
|
+
// Convert namespace to file path for C#
|
|
280
|
+
const file = `${suite.join('/')}.cs`;
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
title, // Base name without parameters for test import
|
|
284
|
+
example, // Parameters object for parameterized tests
|
|
285
|
+
file, // File path with .cs extension
|
|
286
|
+
description: td.Description,
|
|
287
|
+
suite_title,
|
|
288
|
+
id: td.Execution.id,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_parseTRXTestResult(td, tests) {
|
|
293
|
+
const test = tests.find(t => t.id === td.executionId) || {};
|
|
294
|
+
|
|
295
|
+
const result = {
|
|
296
|
+
suite_title: test.suite_title,
|
|
297
|
+
title: test.title?.trim(),
|
|
298
|
+
file: test.file,
|
|
299
|
+
description: test.description,
|
|
300
|
+
code: test.code,
|
|
301
|
+
run_time: parseFloat(td.duration) * 1000,
|
|
302
|
+
stack: td.Output?.StdOut || '',
|
|
303
|
+
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
|
|
304
|
+
create: true,
|
|
305
|
+
overwrite: true,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Add example for parameterized tests
|
|
309
|
+
if (test.example) {
|
|
310
|
+
result.example = test.example;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Map TRX status to Testomat.io status
|
|
314
|
+
result.status = this._mapTRXStatus(td.outcome);
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
_mapTRXStatus(outcome) {
|
|
320
|
+
switch (outcome) {
|
|
321
|
+
case 'Passed':
|
|
322
|
+
return STATUS.PASSED;
|
|
323
|
+
case 'Failed':
|
|
324
|
+
return STATUS.FAILED;
|
|
325
|
+
case 'Skipped':
|
|
326
|
+
return STATUS.SKIPPED;
|
|
327
|
+
default:
|
|
328
|
+
return STATUS.PASSED;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
310
332
|
processXUnit(assemblies) {
|
|
311
333
|
const tests = [];
|
|
312
334
|
|