@testomatio/reporter 2.0.1-beta-2-ignore-xml → 2.0.1-beta.1

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 (63) hide show
  1. package/lib/adapter/codecept.js +0 -2
  2. package/lib/adapter/cypress-plugin/index.js +0 -2
  3. package/lib/adapter/mocha.js +0 -1
  4. package/lib/adapter/nightwatch.d.ts +4 -0
  5. package/lib/adapter/nightwatch.js +80 -0
  6. package/lib/adapter/webdriver.d.ts +1 -1
  7. package/lib/adapter/webdriver.js +17 -8
  8. package/lib/bin/cli.js +126 -8
  9. package/lib/bin/reportXml.js +4 -2
  10. package/lib/bin/startTest.js +3 -2
  11. package/lib/bin/uploadArtifacts.js +5 -4
  12. package/lib/client.js +18 -9
  13. package/lib/config.js +2 -2
  14. package/lib/data-storage.d.ts +1 -1
  15. package/lib/data-storage.js +17 -7
  16. package/lib/junit-adapter/csharp.d.ts +1 -0
  17. package/lib/junit-adapter/csharp.js +11 -1
  18. package/lib/pipe/bitbucket.d.ts +2 -0
  19. package/lib/pipe/bitbucket.js +38 -26
  20. package/lib/pipe/debug.js +17 -3
  21. package/lib/pipe/github.d.ts +2 -2
  22. package/lib/pipe/github.js +35 -3
  23. package/lib/pipe/gitlab.d.ts +2 -0
  24. package/lib/pipe/gitlab.js +27 -9
  25. package/lib/pipe/html.d.ts +1 -0
  26. package/lib/pipe/html.js +1 -3
  27. package/lib/pipe/index.js +17 -7
  28. package/lib/pipe/testomatio.d.ts +2 -1
  29. package/lib/pipe/testomatio.js +79 -73
  30. package/lib/reporter.d.ts +12 -12
  31. package/lib/services/artifacts.d.ts +1 -1
  32. package/lib/services/key-values.d.ts +1 -1
  33. package/lib/services/logger.d.ts +1 -1
  34. package/lib/services/logger.js +1 -2
  35. package/lib/template/testomatio.hbs +443 -68
  36. package/lib/uploader.js +2 -2
  37. package/lib/utils/utils.d.ts +2 -0
  38. package/lib/utils/utils.js +41 -18
  39. package/lib/xmlReader.js +54 -19
  40. package/package.json +8 -9
  41. package/src/adapter/codecept.js +0 -2
  42. package/src/adapter/cypress-plugin/index.js +0 -2
  43. package/src/adapter/mocha.js +0 -1
  44. package/src/adapter/nightwatch.js +88 -0
  45. package/src/adapter/webdriver.js +1 -2
  46. package/src/bin/cli.js +131 -2
  47. package/src/bin/reportXml.js +4 -1
  48. package/src/bin/startTest.js +2 -1
  49. package/src/bin/uploadArtifacts.js +2 -1
  50. package/src/client.js +1 -2
  51. package/src/config.js +2 -2
  52. package/src/junit-adapter/csharp.js +13 -1
  53. package/src/pipe/bitbucket.js +22 -24
  54. package/src/pipe/debug.js +18 -3
  55. package/src/pipe/github.js +1 -2
  56. package/src/pipe/gitlab.js +27 -9
  57. package/src/pipe/html.js +3 -4
  58. package/src/pipe/testomatio.js +98 -103
  59. package/src/services/logger.js +1 -2
  60. package/src/template/testomatio.hbs +443 -68
  61. package/src/uploader.js +2 -2
  62. package/src/utils/utils.js +19 -9
  63. package/src/xmlReader.js +69 -17
package/src/xmlReader.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  fetchSourceCodeFromStackTrace,
14
14
  fetchIdFromCode,
15
15
  humanize,
16
+ TEST_ID_REGEX,
16
17
  } from './utils/utils.js';
17
18
  import { pipesFactory } from './pipe/index.js';
18
19
  import adapterFactory from './junit-adapter/index.js';
@@ -26,8 +27,15 @@ const debug = createDebugMessages('@testomatio/reporter:xml');
26
27
  const ridRunId = randomUUID();
27
28
 
28
29
  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;
30
+ const {
31
+ TESTOMATIO_RUNGROUP_TITLE,
32
+ TESTOMATIO_SUITE,
33
+ TESTOMATIO_MAX_STACK_TRACE,
34
+ TESTOMATIO_TITLE,
35
+ TESTOMATIO_ENV,
36
+ TESTOMATIO_RUN,
37
+ TESTOMATIO_MARK_DETACHED,
38
+ } = process.env;
31
39
 
32
40
  const options = {
33
41
  ignoreDeclaration: true,
@@ -37,6 +45,8 @@ const options = {
37
45
  parseTagValue: true,
38
46
  };
39
47
 
48
+ const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
49
+
40
50
  const reduceOptions = {};
41
51
 
42
52
  class XmlReader {
@@ -91,7 +101,7 @@ class XmlReader {
91
101
  ];
92
102
 
93
103
  for (const regex of cutRegexes) {
94
- xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, 5000)}${p3}`);
104
+ xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, MAX_OUTPUT_LENGTH)}${p3}`);
95
105
  }
96
106
 
97
107
  const jsonResult = this.parser.parse(xmlData);
@@ -341,6 +351,7 @@ class XmlReader {
341
351
  if (file.endsWith('.rb')) this.stats.language = 'ruby';
342
352
  if (file.endsWith('.js')) this.stats.language = 'js';
343
353
  if (file.endsWith('.ts')) this.stats.language = 'ts';
354
+ if (file.endsWith('.cs')) this.stats.language = 'csharp';
344
355
  }
345
356
 
346
357
  if (!fs.existsSync(file)) {
@@ -394,13 +405,14 @@ class XmlReader {
394
405
  async uploadArtifacts() {
395
406
  for (const test of this.tests.filter(t => !!t.stack)) {
396
407
  let files = [];
397
- if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
398
- files = [...files, ...fetchFilesFromStackTrace(test.stack)];
408
+ if (!test.files?.length) continue;
409
+
410
+ files = test.files.map(f => (path.isAbsolute(f) ? f : path.join(process.cwd(), f)));
399
411
 
400
412
  if (!files.length) continue;
401
413
 
402
414
  const runId = this.runId || this.store.runId || Date.now().toString();
403
- test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
415
+ test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId, path.basename(f)])));
404
416
  console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
405
417
  }
406
418
  }
@@ -460,14 +472,18 @@ function reduceTestCases(prev, item) {
460
472
  }
461
473
 
462
474
  // suite inside test case
463
- if (item['test-suite'] && item['test-suite']['test-case']) testCases.push(...item['test-suite']['test-case']);
475
+ const testCase = item['test-suite']?.['test-case'];
476
+ if (testCase) {
477
+ const nestedCases = Array.isArray(testCase) ? testCase : [testCase];
478
+ testCases.push(...nestedCases);
479
+ }
464
480
 
465
481
  const suiteOutput = item['system-out'] || item.output || item.log || '';
466
482
  const suiteErr = item['system-err'] || item.output || item.log || '';
467
483
  testCases
468
484
  .filter(t => !!t)
469
485
  .forEach(testCaseItem => {
470
- const file = testCaseItem.file || item.filepath || '';
486
+ const file = testCaseItem.file || item.filepath || item.fullname || '';
471
487
 
472
488
  let stack = '';
473
489
  let message = '';
@@ -485,7 +501,7 @@ function reduceTestCases(prev, item) {
485
501
  const preferClassname = reduceOptions.preferClassname || isParametrized;
486
502
 
487
503
  // SpecFlow config
488
- let { title, tags } = fetchProperties(isParametrized ? item : testCaseItem);
504
+ let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
489
505
  let example = null;
490
506
  const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
491
507
 
@@ -498,19 +514,44 @@ function reduceTestCases(prev, item) {
498
514
  title = title.replace(/\(.*?\)/, '').trim();
499
515
  }
500
516
 
501
- // eslint-disable-next-line
502
517
  stack = `${
503
518
  testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
504
519
  }\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
505
- const testId = fetchIdFromOutput(stack);
520
+
521
+ if (!testId) testId = fetchIdFromOutput(stack);
522
+
523
+ if (tags?.length && !testId) {
524
+ testId = tags
525
+ .filter(t => t.startsWith('T'))
526
+ .map(t => `@${t}`)
527
+ .find(t => t.match(TEST_ID_REGEX))
528
+ ?.slice(2);
529
+ }
506
530
 
507
531
  let status = STATUS.PASSED.toString();
508
532
  if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
509
533
  if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
534
+ if (testCaseItem.result && Object.values(STATUS).includes(testCaseItem.result.toLowerCase())) {
535
+ status = testCaseItem.result.toLowerCase();
536
+ }
510
537
 
511
538
  let rid = null;
512
539
  if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
513
540
 
541
+ // Extract attachments
542
+ let files = [];
543
+ if (testCaseItem.attachments) {
544
+ const attachments = Array.isArray(testCaseItem.attachments.attachment)
545
+ ? testCaseItem.attachments.attachment
546
+ : [testCaseItem.attachments.attachment];
547
+
548
+ files = attachments.filter(a => a && a.filePath).map(a => a.filePath);
549
+ }
550
+
551
+ // Extract files from stack trace using existing utility
552
+ const stackFiles = fetchFilesFromStackTrace(stack);
553
+ files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
554
+
514
555
  prev.push({
515
556
  rid,
516
557
  file,
@@ -525,7 +566,9 @@ function reduceTestCases(prev, item) {
525
566
  run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
526
567
  status,
527
568
  title,
569
+ root_suite_id: TESTOMATIO_SUITE,
528
570
  suite_title: suiteTitle,
571
+ files,
529
572
  });
530
573
  });
531
574
  return prev;
@@ -552,11 +595,20 @@ function fetchProperties(item) {
552
595
 
553
596
  if (!item.properties) return {};
554
597
 
555
- const prop = [item.properties?.property].flat().find(p => p.name === 'Description');
598
+ // Handle both single property and array of properties
599
+ const properties = Array.isArray(item.properties.property)
600
+ ? item.properties.property
601
+ : [item.properties.property].filter(Boolean);
602
+
603
+ const prop = properties.find(p => p.name === 'Description');
556
604
  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 };
605
+
606
+ let testId = properties.find(p => p.name === 'ID')?.value;
607
+
608
+ if (testId?.startsWith('@')) testId = testId.slice(1);
609
+ if (testId?.startsWith('T')) testId = testId.slice(1);
610
+
611
+ properties.filter(p => p.name === 'Category').forEach(p => tags.push(p.value));
612
+
613
+ return { title, tags, testId };
562
614
  }