@testomatio/reporter 2.3.5-beta-5-xml-import β†’ 2.3.5-beta-6-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/README.md CHANGED
@@ -13,7 +13,7 @@ Testomat.io Reporter (this npm package) supports:
13
13
  - πŸ”Ž [Stack traces](./docs/stacktrace.md) and error messages
14
14
  - πŸ™ [GitHub](./docs/pipes/github.md), [GitLab](./docs/pipes/gitlab.md) & [Bitbucket](./docs/pipes/bitbucket.md) integration
15
15
  - πŸš… Realtime reports
16
- - πŸ—ƒοΈ Other test frameworks supported via [JUNit XML](./docs/junit.md)
16
+ - πŸ—ƒοΈ Other test frameworks supported via [JUnit XML](./docs/junit.md) with [XML import configuration](./docs/xml-imports.md)
17
17
  - πŸšΆβ€β™€οΈ Steps _(work in progress)_
18
18
  - πŸ“„ [Logger](./docs/logger.md) _(work in progress, supports Jest for now)_
19
19
  - ☁️ Custom properties and metadata _(work in progress)_
@@ -13,6 +13,8 @@ declare class XmlReader {
13
13
  runId: any;
14
14
  adapter: import("./junit-adapter/adapter.js").default;
15
15
  opts: {};
16
+ disableSourceCodeFetching: any;
17
+ suiteOrganization: any;
16
18
  store: {};
17
19
  pipesPromise: Promise<any[]>;
18
20
  parser: XMLParser;
package/lib/xmlReader.js CHANGED
@@ -20,7 +20,7 @@ const uploader_js_1 = require("./uploader.js");
20
20
  const debug = (0, debug_1.default)('@testomatio/reporter:xml');
21
21
  const ridRunId = (0, crypto_1.randomUUID)();
22
22
  const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
23
- const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED, } = process.env;
23
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED, TESTOMATIO_DISABLE_SOURCE_CODE, } = process.env;
24
24
  const options = {
25
25
  ignoreDeclaration: true,
26
26
  ignoreAttributes: false,
@@ -47,6 +47,10 @@ class XmlReader {
47
47
  if (!this.adapter)
48
48
  throw new Error('XML adapter for this format not found');
49
49
  this.opts = opts || {};
50
+ // Check if source code fetching should be disabled
51
+ this.disableSourceCodeFetching = opts.disableSourceCodeFetching || TESTOMATIO_DISABLE_SOURCE_CODE;
52
+ // Control suite organization strategy: 'classname' (default) or 'fullpath'
53
+ this.suiteOrganization = opts.suiteOrganization || process.env.TESTOMATIO_SUITE_ORGANIZATION || 'classname';
50
54
  this.store = {};
51
55
  this.pipesPromise = (0, index_js_1.pipesFactory)(opts, this.store);
52
56
  this.parser = new fast_xml_parser_1.XMLParser(options);
@@ -58,6 +62,15 @@ class XmlReader {
58
62
  const packageJsonPath = path_1.default.resolve(__dirname, '..', 'package.json');
59
63
  this.version = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()).version;
60
64
  console.log(constants_js_1.APP_PREFIX, `Testomatio Reporter v${this.version}`);
65
+ if (this.disableSourceCodeFetching) {
66
+ console.log(constants_js_1.APP_PREFIX, '🚫 Source code fetching is disabled');
67
+ }
68
+ if (this.suiteOrganization === 'fullpath') {
69
+ console.log(constants_js_1.APP_PREFIX, 'πŸ“ Using fullpath suite organization (may create nested structure)');
70
+ }
71
+ else {
72
+ console.log(constants_js_1.APP_PREFIX, 'πŸ“‹ Using classname suite organization (avoids duplicates)');
73
+ }
61
74
  }
62
75
  connectAdapter() {
63
76
  if (this.opts.javaTests) {
@@ -130,6 +143,7 @@ class XmlReader {
130
143
  processNUnit(jsonSuite) {
131
144
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
132
145
  reduceOptions.preferClassname = this.stats.language === 'python';
146
+ reduceOptions.suiteOrganization = this.suiteOrganization;
133
147
  const resultTests = processTestSuite(jsonSuite['test-suite']);
134
148
  debug('Raw tests extracted from NUnit XML:', resultTests.length);
135
149
  debug('Raw tests:', resultTests.map(t => ({ title: t.title, example: t.example, file: t.file })));
@@ -477,6 +491,11 @@ class XmlReader {
477
491
  return this.stats;
478
492
  }
479
493
  fetchSourceCode() {
494
+ // Skip source code fetching if disabled
495
+ if (this.disableSourceCodeFetching) {
496
+ debug('Source code fetching is disabled');
497
+ return;
498
+ }
480
499
  this.tests.forEach(t => {
481
500
  try {
482
501
  const file = this.adapter.getFilePath(t);
@@ -640,11 +659,8 @@ function reduceTestCases(prev, item) {
640
659
  // SpecFlow config
641
660
  let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
642
661
  let example = null;
643
- // Simple suite title extraction (version 2.1.1 approach) with fallback to enhanced
644
- let suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
645
- if (!suiteTitle && item.fullname) {
646
- suiteTitle = extractTestExplorerSuiteTitle(testCaseItem, item);
647
- }
662
+ // Smart suite title extraction to avoid duplicates
663
+ const suiteTitle = getSuiteTitle(testCaseItem, item, isParametrized, reduceOptions.suiteOrganization);
648
664
  title ||= testCaseItem.name || testCaseItem.methodname || testCaseItem.classname;
649
665
  tags ||= [];
650
666
  // Store original test name for enhanced parameter extraction
@@ -832,6 +848,34 @@ function processTestSuite(testsuite) {
832
848
  const subSuites = suites.filter(s => s['test-suite'] && !s['test-case']);
833
849
  return [...subSuites.map(s => processTestSuite(s['test-suite'])), ...suites.reduce(reduceTestCases, [])].flat();
834
850
  }
851
+ function getSuiteTitle(testCaseItem, item, isParametrized, suiteOrganization = 'classname') {
852
+ let suiteTitle;
853
+ if (suiteOrganization === 'fullpath') {
854
+ // Use full namespace path (old behavior that creates detailed structure)
855
+ if (item.fullname) {
856
+ return item.fullname;
857
+ }
858
+ suiteTitle = testCaseItem.classname || item.name;
859
+ }
860
+ else {
861
+ // Use classname approach (default - avoids duplicates)
862
+ if (isParametrized) {
863
+ // For parameterized tests, use the class name to group them
864
+ suiteTitle = item.name || testCaseItem.classname;
865
+ }
866
+ else {
867
+ // For regular tests, prefer classname over fullname to avoid long paths
868
+ suiteTitle = testCaseItem.classname || item.name;
869
+ }
870
+ // If still no suite title and we have fullname, extract just the class name
871
+ if (!suiteTitle && item.fullname) {
872
+ const fullnameParts = item.fullname.split('.');
873
+ suiteTitle = fullnameParts[fullnameParts.length - 1]; // Just the class name
874
+ }
875
+ }
876
+ // Fallback
877
+ return suiteTitle || 'UnknownClass';
878
+ }
835
879
  function fetchProperties(item) {
836
880
  const tags = [];
837
881
  let title = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.5-beta-5-xml-import",
3
+ "version": "2.3.5-beta-6-xml-import",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
package/src/xmlReader.js CHANGED
@@ -35,6 +35,7 @@ const {
35
35
  TESTOMATIO_ENV,
36
36
  TESTOMATIO_RUN,
37
37
  TESTOMATIO_MARK_DETACHED,
38
+ TESTOMATIO_DISABLE_SOURCE_CODE,
38
39
  } = process.env;
39
40
 
40
41
  const options = {
@@ -66,6 +67,10 @@ class XmlReader {
66
67
  if (!this.adapter) throw new Error('XML adapter for this format not found');
67
68
 
68
69
  this.opts = opts || {};
70
+ // Check if source code fetching should be disabled
71
+ this.disableSourceCodeFetching = opts.disableSourceCodeFetching || TESTOMATIO_DISABLE_SOURCE_CODE;
72
+ // Control suite organization strategy: 'classname' (default) or 'fullpath'
73
+ this.suiteOrganization = opts.suiteOrganization || process.env.TESTOMATIO_SUITE_ORGANIZATION || 'classname';
69
74
  this.store = {};
70
75
  this.pipesPromise = pipesFactory(opts, this.store);
71
76
 
@@ -79,6 +84,16 @@ class XmlReader {
79
84
  const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
80
85
  this.version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
81
86
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
87
+
88
+ if (this.disableSourceCodeFetching) {
89
+ console.log(APP_PREFIX, '🚫 Source code fetching is disabled');
90
+ }
91
+
92
+ if (this.suiteOrganization === 'fullpath') {
93
+ console.log(APP_PREFIX, 'πŸ“ Using fullpath suite organization (may create nested structure)');
94
+ } else {
95
+ console.log(APP_PREFIX, 'πŸ“‹ Using classname suite organization (avoids duplicates)');
96
+ }
82
97
  }
83
98
 
84
99
  connectAdapter() {
@@ -160,6 +175,7 @@ class XmlReader {
160
175
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
161
176
 
162
177
  reduceOptions.preferClassname = this.stats.language === 'python';
178
+ reduceOptions.suiteOrganization = this.suiteOrganization;
163
179
  const resultTests = processTestSuite(jsonSuite['test-suite']);
164
180
 
165
181
  debug('Raw tests extracted from NUnit XML:', resultTests.length);
@@ -551,6 +567,12 @@ class XmlReader {
551
567
  }
552
568
 
553
569
  fetchSourceCode() {
570
+ // Skip source code fetching if disabled
571
+ if (this.disableSourceCodeFetching) {
572
+ debug('Source code fetching is disabled');
573
+ return;
574
+ }
575
+
554
576
  this.tests.forEach(t => {
555
577
  try {
556
578
  const file = this.adapter.getFilePath(t);
@@ -728,11 +750,8 @@ function reduceTestCases(prev, item) {
728
750
  let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
729
751
  let example = null;
730
752
 
731
- // Simple suite title extraction (version 2.1.1 approach) with fallback to enhanced
732
- let suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
733
- if (!suiteTitle && item.fullname) {
734
- suiteTitle = extractTestExplorerSuiteTitle(testCaseItem, item);
735
- }
753
+ // Smart suite title extraction to avoid duplicates
754
+ const suiteTitle = getSuiteTitle(testCaseItem, item, isParametrized, reduceOptions.suiteOrganization);
736
755
 
737
756
  title ||= testCaseItem.name || testCaseItem.methodname || testCaseItem.classname;
738
757
  tags ||= [];
@@ -942,6 +961,36 @@ function processTestSuite(testsuite) {
942
961
  return [...subSuites.map(s => processTestSuite(s['test-suite'])), ...suites.reduce(reduceTestCases, [])].flat();
943
962
  }
944
963
 
964
+ function getSuiteTitle(testCaseItem, item, isParametrized, suiteOrganization = 'classname') {
965
+ let suiteTitle;
966
+
967
+ if (suiteOrganization === 'fullpath') {
968
+ // Use full namespace path (old behavior that creates detailed structure)
969
+ if (item.fullname) {
970
+ return item.fullname;
971
+ }
972
+ suiteTitle = testCaseItem.classname || item.name;
973
+ } else {
974
+ // Use classname approach (default - avoids duplicates)
975
+ if (isParametrized) {
976
+ // For parameterized tests, use the class name to group them
977
+ suiteTitle = item.name || testCaseItem.classname;
978
+ } else {
979
+ // For regular tests, prefer classname over fullname to avoid long paths
980
+ suiteTitle = testCaseItem.classname || item.name;
981
+ }
982
+
983
+ // If still no suite title and we have fullname, extract just the class name
984
+ if (!suiteTitle && item.fullname) {
985
+ const fullnameParts = item.fullname.split('.');
986
+ suiteTitle = fullnameParts[fullnameParts.length - 1]; // Just the class name
987
+ }
988
+ }
989
+
990
+ // Fallback
991
+ return suiteTitle || 'UnknownClass';
992
+ }
993
+
945
994
  function fetchProperties(item) {
946
995
  const tags = [];
947
996
  let title = '';