@testomatio/reporter 2.3.3 → 2.3.5-beta-5-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.
@@ -1,5 +1,4 @@
1
1
  export default CSharpAdapter;
2
2
  declare class CSharpAdapter extends Adapter {
3
- getFilePath(t: any): string;
4
3
  }
5
4
  import Adapter from './adapter.js';
@@ -7,24 +7,53 @@ const path_1 = __importDefault(require("path"));
7
7
  const adapter_js_1 = __importDefault(require("./adapter.js"));
8
8
  class CSharpAdapter extends adapter_js_1.default {
9
9
  formatTest(t) {
10
- const title = t.title.replace(/\(.*?\)/, '').trim();
11
- const example = t.title.match(/\((.*?)\)/);
12
- if (example)
13
- t.example = { ...example[1].split(',') };
10
+ // Don't override example if it already exists from NUnit XML processing
11
+ // The xmlReader.js already extracts parameters correctly from <arguments>
12
+ if (!t.example) {
13
+ const title = t.title.replace(/\(.*?\)/, '').trim();
14
+ const exampleMatch = t.title.match(/\((.*?)\)/);
15
+ if (exampleMatch) {
16
+ // Keep as array for consistency with NUnit XML processing
17
+ t.example = exampleMatch[1].split(',').map(param => param.trim());
18
+ }
19
+ t.title = title.trim();
20
+ }
14
21
  const suite = t.suite_title.split('.');
15
22
  t.suite_title = suite.pop();
16
23
  t.file = namespaceToFileName(t.file);
17
- t.title = title.trim();
18
24
  return t;
19
25
  }
20
26
  getFilePath(t) {
21
- const fileName = namespaceToFileName(t.file);
27
+ if (!t.file)
28
+ return null;
29
+ // Normalize path separators for cross-platform compatibility
30
+ let filePath = t.file.replace(/\\/g, '/');
31
+ // If file already has .cs extension, use it directly
32
+ if (filePath.endsWith('.cs')) {
33
+ // Make relative path if it's absolute
34
+ if (path_1.default.isAbsolute(filePath)) {
35
+ // Try to find project-relative path
36
+ const cwd = process.cwd().replace(/\\/g, '/');
37
+ if (filePath.startsWith(cwd)) {
38
+ filePath = path_1.default.relative(cwd, filePath).replace(/\\/g, '/');
39
+ }
40
+ }
41
+ return filePath;
42
+ }
43
+ // Convert namespace path to file path
44
+ const fileName = namespaceToFileName(filePath);
22
45
  return fileName;
23
46
  }
24
47
  }
25
48
  module.exports = CSharpAdapter;
26
49
  function namespaceToFileName(fileName) {
50
+ if (!fileName)
51
+ return '';
52
+ // If already a .cs file path, clean it up
53
+ if (fileName.endsWith('.cs')) {
54
+ return fileName.replace(/\\/g, '/');
55
+ }
27
56
  const fileParts = fileName.split('.');
28
57
  fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
29
- return `${fileParts.join(path_1.default.sep)}.cs`;
58
+ return `${fileParts.join('/')}.cs`;
30
59
  }
package/lib/pipe/debug.js CHANGED
@@ -18,7 +18,7 @@ class DebugPipe {
18
18
  this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
19
19
  if (this.isEnabled) {
20
20
  this.batch = {
21
- isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
21
+ isEnabled: this.params.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
22
22
  intervalFunction: null,
23
23
  intervalTime: 5000,
24
24
  tests: [],
@@ -23,7 +23,7 @@ if (process.env.TESTOMATIO_RUN)
23
23
  class TestomatioPipe {
24
24
  constructor(params, store) {
25
25
  this.batch = {
26
- isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
26
+ isEnabled: params?.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
27
27
  intervalFunction: null, // will be created in createRun by setInterval function
28
28
  intervalTime: 5000, // how often tests are sent
29
29
  tests: [], // array of tests in batch
@@ -60,7 +60,7 @@ class TestomatioPipe {
60
60
  retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
61
61
  retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
62
62
  httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
63
- shouldRetry: (error) => {
63
+ shouldRetry: error => {
64
64
  if (!error.response)
65
65
  return false;
66
66
  switch (error.response?.status) {
@@ -73,8 +73,8 @@ class TestomatioPipe {
73
73
  break;
74
74
  }
75
75
  return error.response?.status >= 401; // Retry on 401+ and 5xx
76
- }
77
- }
76
+ },
77
+ },
78
78
  });
79
79
  this.isEnabled = true;
80
80
  // do not finish this run (for parallel testing)
@@ -89,6 +89,28 @@ class TestomatioPipe {
89
89
  console.error(constants_js_1.APP_PREFIX, picocolors_1.default.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`));
90
90
  }
91
91
  }
92
+ /**
93
+ * Prepares data for sending to Testomat.io.
94
+ * @param {*} data - The data to be formatted.
95
+ * @returns
96
+ */
97
+ #formatData(data) {
98
+ data.api_key = this.apiKey;
99
+ data.create = this.createNewTests;
100
+ // add test ID + run ID
101
+ if (data.rid)
102
+ data.rid = `${this.runId}-${data.rid}`;
103
+ if (!process.env.TESTOMATIO_STACK_PASSED && data.status === constants_js_1.STATUS.PASSED) {
104
+ data.stack = null;
105
+ }
106
+ if (!process.env.TESTOMATIO_STEPS_PASSED && data.status === constants_js_1.STATUS.PASSED) {
107
+ data.steps = null;
108
+ }
109
+ if (process.env.TESTOMATIO_NO_STEPS) {
110
+ data.steps = null;
111
+ }
112
+ return data;
113
+ }
92
114
  /**
93
115
  * Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
94
116
  * @param {Object} opts - The options for preparing the test grepList.
@@ -171,7 +193,7 @@ class TestomatioPipe {
171
193
  method: 'PUT',
172
194
  url: `/api/reporter/${this.runId}`,
173
195
  data: runParams,
174
- responseType: 'json'
196
+ responseType: 'json',
175
197
  });
176
198
  if (resp.data.artifacts)
177
199
  (0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
@@ -184,7 +206,7 @@ class TestomatioPipe {
184
206
  url: '/api/reporter',
185
207
  data: runParams,
186
208
  maxContentLength: Infinity,
187
- responseType: 'json'
209
+ responseType: 'json',
188
210
  });
189
211
  this.runId = resp.data.uid;
190
212
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
@@ -234,22 +256,20 @@ class TestomatioPipe {
234
256
  return;
235
257
  if (this.#cancelTestReportingInCaseOfTooManyReqFailures())
236
258
  return;
237
- data.api_key = this.apiKey;
238
- data.create = this.createNewTests;
239
- if (!process.env.TESTOMATIO_STACK_PASSED && data.status === constants_js_1.STATUS.PASSED) {
240
- data.stack = null;
241
- }
259
+ this.#formatData(data);
242
260
  const json = json_cycle_1.default.stringify(data);
243
261
  debug('Adding test', json);
244
- return this.client.request({
262
+ return this.client
263
+ .request({
245
264
  method: 'POST',
246
265
  url: `/api/reporter/${this.runId}/testrun`,
247
266
  data: json,
248
267
  headers: {
249
268
  'Content-Type': 'application/json',
250
269
  },
251
- maxContentLength: Infinity
252
- }).catch(err => {
270
+ maxContentLength: Infinity,
271
+ })
272
+ .catch(err => {
253
273
  this.requestFailures++;
254
274
  this.notReportedTestsCount++;
255
275
  if (err.response) {
@@ -294,19 +314,21 @@ class TestomatioPipe {
294
314
  // get tests from batch and clear batch
295
315
  const testsToSend = this.batch.tests.splice(0);
296
316
  debug('📨 Batch upload', testsToSend.length, 'tests');
297
- return this.client.request({
317
+ return this.client
318
+ .request({
298
319
  method: 'POST',
299
320
  url: `/api/reporter/${this.runId}/testrun`,
300
321
  data: {
301
322
  api_key: this.apiKey,
302
323
  tests: testsToSend,
303
- batch_index: this.batch.batchIndex
324
+ batch_index: this.batch.batchIndex,
304
325
  },
305
326
  headers: {
306
327
  'Content-Type': 'application/json',
307
328
  },
308
- maxContentLength: Infinity
309
- }).catch(err => {
329
+ maxContentLength: Infinity,
330
+ })
331
+ .catch(err => {
310
332
  this.requestFailures++;
311
333
  this.notReportedTestsCount += testsToSend.length;
312
334
  if (err.response) {
@@ -338,11 +360,7 @@ class TestomatioPipe {
338
360
  console.warn(constants_js_1.APP_PREFIX, picocolors_1.default.red('Run ID is not set, skipping test reporting'));
339
361
  return;
340
362
  }
341
- // add test ID + run ID
342
- if (data.rid)
343
- data.rid = `${this.runId}-${data.rid}`;
344
- data.api_key = this.apiKey;
345
- data.create = this.createNewTests;
363
+ this.#formatData(data);
346
364
  let uploading = null;
347
365
  if (!this.batch.isEnabled)
348
366
  uploading = this.#uploadSingleTest(data);
@@ -394,7 +412,7 @@ class TestomatioPipe {
394
412
  status_event,
395
413
  detach: params.detach,
396
414
  tests: params.tests,
397
- }
415
+ },
398
416
  });
399
417
  console.log(constants_js_1.APP_PREFIX, '✅ Testrun finished');
400
418
  if (this.runUrl) {
@@ -173,6 +173,8 @@ exports.fetchSourceCodeFromStackTrace = fetchSourceCodeFromStackTrace;
173
173
  exports.TEST_ID_REGEX = /@T([\w\d]{8})/;
174
174
  exports.SUITE_ID_REGEX = /@S([\w\d]{8})/;
175
175
  const fetchIdFromCode = (code, opts = {}) => {
176
+ if (!code)
177
+ return null;
176
178
  const comments = code
177
179
  .split('\n')
178
180
  .map(l => l.trim())
@@ -215,10 +217,29 @@ const fetchSourceCode = (contents, opts = {}) => {
215
217
  lineIndex = lines.findIndex(l => l.includes(`${title}(`));
216
218
  }
217
219
  else if (opts.lang === 'csharp') {
218
- if (lineIndex === -1)
219
- lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
220
- if (lineIndex === -1)
220
+ // Enhanced C# method detection for NUnit tests
221
+ lineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
222
+ if (lineIndex === -1) {
223
+ lineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
224
+ }
225
+ if (lineIndex === -1) {
221
226
  lineIndex = lines.findIndex(l => l.includes(`${title}(`));
227
+ }
228
+ // Look for TestCase or Test attributes above the method
229
+ if (lineIndex === -1) {
230
+ const testAttributeIndex = lines.findIndex((l, index) => {
231
+ if (l.includes('[TestCase') || l.includes('[Test')) {
232
+ // Check next few lines for the method
233
+ const nextLines = lines.slice(index, Math.min(lines.length, index + 5));
234
+ const hasMethod = nextLines.some(nextLine => nextLine.includes(`${title}(`));
235
+ return hasMethod;
236
+ }
237
+ return false;
238
+ });
239
+ if (testAttributeIndex !== -1) {
240
+ lineIndex = testAttributeIndex;
241
+ }
242
+ }
222
243
  }
223
244
  else {
224
245
  lineIndex = lines.findIndex(l => l.includes(title));
@@ -227,7 +248,7 @@ const fetchSourceCode = (contents, opts = {}) => {
227
248
  if (opts.prepend) {
228
249
  lineIndex -= opts.prepend;
229
250
  }
230
- if (lineIndex) {
251
+ if (lineIndex !== -1 && lineIndex !== undefined) {
231
252
  const result = [];
232
253
  for (let i = lineIndex; i < lineIndex + limit; i++) {
233
254
  if (lines[i] === undefined)
@@ -270,6 +291,14 @@ const fetchSourceCode = (contents, opts = {}) => {
270
291
  break;
271
292
  if (opts.lang === 'java' && lines[i].includes(' class '))
272
293
  break;
294
+ if (opts.lang === 'csharp' && lines[i].trim().match(/^\[Test/))
295
+ break;
296
+ if (opts.lang === 'csharp' && lines[i].includes(' public void '))
297
+ break;
298
+ if (opts.lang === 'csharp' && lines[i].includes(' public async Task '))
299
+ break;
300
+ if (opts.lang === 'csharp' && lines[i].includes(' class ') && lines[i].includes('public'))
301
+ break;
273
302
  }
274
303
  result.push(lines[i]);
275
304
  }
@@ -77,6 +77,13 @@ declare class XmlReader {
77
77
  skipped_count: number;
78
78
  tests: any[];
79
79
  };
80
+ deduplicateTestsByFQN(tests: any): any[];
81
+ generateFQN(test: any): string;
82
+ generateNormalizedFQN(test: any): string;
83
+ extractAssemblyName(test: any): any;
84
+ extractNamespace(test: any): any;
85
+ extractClassName(test: any): any;
86
+ extractCsFileFromPath(test: any): any;
80
87
  calculateStats(): {};
81
88
  fetchSourceCode(): void;
82
89
  formatTests(): void;