@testomatio/reporter 2.0.0-beta.1-xml → 2.0.0-beta.2-gaxios

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,12 +1,6 @@
1
1
  import createDebugMessages from 'debug';
2
2
  import pc from 'picocolors';
3
-
4
- // Retry interceptor function
5
- import axiosRetry from 'axios-retry';
6
-
7
- // Default axios instance
8
- import axios from 'axios';
9
-
3
+ import { Gaxios } from 'gaxios';
10
4
  import JsonCycle from 'json-cycle';
11
5
  import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
12
6
  import { isValidUrl, foundedTestLog } from '../utils/utils.js';
@@ -57,43 +51,31 @@ class TestomatioPipe {
57
51
  this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
58
52
  this.env = process.env.TESTOMATIO_ENV;
59
53
  this.label = process.env.TESTOMATIO_LABEL;
60
- // Create a new instance of axios with a custom config
61
- this.axios = axios.create({
54
+
55
+ // Create a new instance of gaxios with a custom config
56
+ this.client = new Gaxios({
62
57
  baseURL: `${this.url.trim()}`,
63
58
  timeout: AXIOS_TIMEOUT,
64
- proxy: proxy
65
- ? {
66
- host: proxy.hostname,
67
- port: parseInt(proxy.port, 10),
68
- protocol: proxy.protocol,
59
+ proxy: proxy ? proxy.toString() : undefined,
60
+ retry: true,
61
+ agent: new (require('https').Agent)({ rejectUnauthorized: false, keepAlive: false }),
62
+ retryConfig: {
63
+ retry: REPORTER_REQUEST_RETRIES.retriesPerRequest,
64
+ retryDelay: REPORTER_REQUEST_RETRIES.retryTimeout,
65
+ shouldRetry: (error) => {
66
+ if (!error.response) return false;
67
+ switch (error.response?.status) {
68
+ case 400: // Bad request (probably wrong API key)
69
+ case 404: // Test not matched
70
+ case 429: // Rate limit exceeded
71
+ case 500: // Internal server error
72
+ return false;
73
+ default:
74
+ break;
69
75
  }
70
- : false,
71
- });
72
-
73
- // Pass the axios instance to the retry function
74
- axiosRetry(this.axios, {
75
- // do not use retries for unit tests
76
- retries: REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
77
- shouldResetTimeout: true,
78
- retryCondition: error => {
79
- if (!error.response) return false;
80
- switch (error.response?.status) {
81
- case 400: // Bad request (probably wrong API key)
82
- case 404: // Test not matched
83
- case 429: // Rate limit exceeded
84
- case 500: // Internal server error
85
- return false;
86
- default:
87
- break;
76
+ return error.response?.status >= 401; // Retry on 401+ and 5xx
88
77
  }
89
- return error.response?.status >= 401; // Retry on 401+ and 5xx
90
- },
91
- retryDelay: () => REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
92
- onRetry: async (retryCount, error) => {
93
- this.retriesTimestamps.push(Date.now());
94
-
95
- debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
96
- },
78
+ }
97
79
  });
98
80
 
99
81
  this.isEnabled = true;
@@ -134,12 +116,15 @@ class TestomatioPipe {
134
116
  return;
135
117
  }
136
118
 
137
- const resp = await this.axios.get('/api/test_grep', q);
138
- const { data } = resp;
119
+ const resp = await this.client.request({
120
+ method: 'GET',
121
+ url: '/api/test_grep',
122
+ params: q
123
+ });
139
124
 
140
- if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
141
- foundedTestLog(APP_PREFIX, data.tests);
142
- return data.tests;
125
+ if (Array.isArray(resp.data?.tests) && resp.data?.tests?.length > 0) {
126
+ foundedTestLog(APP_PREFIX, resp.data.tests);
127
+ return resp.data.tests;
143
128
  }
144
129
 
145
130
  console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
@@ -163,7 +148,6 @@ class TestomatioPipe {
163
148
 
164
149
  // GitHub Actions Url
165
150
  if (!buildUrl && process.env.GITHUB_RUN_ID) {
166
- // eslint-disable-next-line max-len
167
151
  buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
168
152
  }
169
153
 
@@ -199,16 +183,23 @@ class TestomatioPipe {
199
183
  if (this.runId) {
200
184
  this.store.runId = this.runId;
201
185
  debug(`Run with id ${this.runId} already created, updating...`);
202
- const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
186
+ const resp = await this.client.request({
187
+ method: 'PUT',
188
+ url: `/api/reporter/${this.runId}`,
189
+ data: runParams
190
+ });
203
191
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
204
192
  return;
205
193
  }
206
194
 
207
195
  debug('Creating run...');
208
196
  try {
209
- const resp = await this.axios.post(`/api/reporter`, runParams, {
197
+ const resp = await this.client.request({
198
+ method: 'POST',
199
+ url: '/api/reporter',
200
+ data: runParams,
210
201
  maxContentLength: Infinity,
211
- maxBodyLength: Infinity,
202
+ responseType: 'json'
212
203
  });
213
204
 
214
205
  this.runId = resp.data.uid;
@@ -225,6 +216,7 @@ class TestomatioPipe {
225
216
  debug('Run created', this.runId);
226
217
  } catch (err) {
227
218
  const errorText = err.response?.data?.message || err.message;
219
+ debug('Error creating run', err);
228
220
  console.log(errorText || err);
229
221
  if (!this.apiKey) console.error('Testomat.io API key is not set');
230
222
  if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
@@ -271,7 +263,15 @@ class TestomatioPipe {
271
263
 
272
264
  debug('Adding test', json);
273
265
 
274
- return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, axiosAddTestrunRequestConfig).catch(err => {
266
+ return this.client.request({
267
+ method: 'POST',
268
+ url: `/api/reporter/${this.runId}/testrun`,
269
+ data: json,
270
+ headers: {
271
+ 'Content-Type': 'application/json',
272
+ },
273
+ maxContentLength: Infinity
274
+ }).catch(err => {
275
275
  this.requestFailures++;
276
276
  this.notReportedTestsCount++;
277
277
  if (err.response) {
@@ -323,38 +323,43 @@ class TestomatioPipe {
323
323
  const testsToSend = this.batch.tests.splice(0);
324
324
  debug('📨 Batch upload', testsToSend.length, 'tests');
325
325
 
326
- return this.axios
327
- .post(
328
- `/api/reporter/${this.runId}/testrun`,
329
- { api_key: this.apiKey, tests: testsToSend, batch_index: this.batch.batchIndex },
330
- axiosAddTestrunRequestConfig,
331
- )
332
- .catch(err => {
333
- this.requestFailures++;
334
- this.notReportedTestsCount += testsToSend.length;
335
- if (err.response) {
336
- if (err.response.status >= 400) {
337
- const responseData = err.response.data || { message: '' };
338
- console.log(
339
- APP_PREFIX,
340
- pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
341
- // pc.grey(data?.title || ''),
342
- );
343
- if (err.response?.data?.message?.includes('could not be matched')) {
344
- this.hasUnmatchedTests = true;
345
- }
346
- return;
347
- }
326
+ return this.client.request({
327
+ method: 'POST',
328
+ url: `/api/reporter/${this.runId}/testrun`,
329
+ data: {
330
+ api_key: this.apiKey,
331
+ tests: testsToSend,
332
+ batch_index: this.batch.batchIndex
333
+ },
334
+ headers: {
335
+ 'Content-Type': 'application/json',
336
+ },
337
+ maxContentLength: Infinity
338
+ }).catch(err => {
339
+ this.requestFailures++;
340
+ this.notReportedTestsCount += testsToSend.length;
341
+ if (err.response) {
342
+ if (err.response.status >= 400) {
343
+ const responseData = err.response.data || { message: '' };
348
344
  console.log(
349
345
  APP_PREFIX,
350
- pc.yellow(`Warning: (${err.response?.status})`),
351
- `Report couldn't be processed: ${err?.response?.data?.message}`,
346
+ pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
352
347
  );
353
- printCreateIssue(err);
354
- } else {
355
- console.log(APP_PREFIX, "Report couldn't be processed", err);
348
+ if (err.response?.data?.message?.includes('could not be matched')) {
349
+ this.hasUnmatchedTests = true;
350
+ }
351
+ return;
356
352
  }
357
- });
353
+ console.log(
354
+ APP_PREFIX,
355
+ pc.yellow(`Warning: (${err.response?.status})`),
356
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
357
+ );
358
+ printCreateIssue(err);
359
+ } else {
360
+ console.log(APP_PREFIX, "Report couldn't be processed", err);
361
+ }
362
+ });
358
363
  };
359
364
 
360
365
  /**
@@ -413,12 +418,16 @@ class TestomatioPipe {
413
418
 
414
419
  try {
415
420
  if (this.runId && !this.proceed) {
416
- await this.axios.put(`/api/reporter/${this.runId}`, {
417
- api_key: this.apiKey,
418
- duration: params.duration,
419
- status_event,
420
- detach: params.detach,
421
- tests: params.tests,
421
+ await this.client.request({
422
+ method: 'PUT',
423
+ url: `/api/reporter/${this.runId}`,
424
+ data: {
425
+ api_key: this.apiKey,
426
+ duration: params.duration,
427
+ status_event,
428
+ detach: params.detach,
429
+ tests: params.tests,
430
+ }
422
431
  });
423
432
  if (this.runUrl) {
424
433
  console.log(APP_PREFIX, '📊 Report Saved. Report URL:', pc.magenta(this.runUrl));
@@ -478,20 +487,11 @@ function printCreateIssue(err) {
478
487
  if (!err.config) return;
479
488
 
480
489
  const time = new Date().toUTCString();
481
- const { data, url, baseURL, method } = err?.config || {};
490
+ const { body, url, baseURL, method } = err?.config || {};
482
491
  console.log('```js');
483
- console.log({ data: data?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
492
+ console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
484
493
  console.log('```');
485
494
  });
486
495
  }
487
496
 
488
- const axiosAddTestrunRequestConfig = {
489
- maxContentLength: Infinity,
490
- maxBodyLength: Infinity,
491
- headers: {
492
- // Overwrite Axios's automatically set Content-Type
493
- 'Content-Type': 'application/json',
494
- },
495
- };
496
-
497
497
  export default TestomatioPipe;
@@ -5,13 +5,9 @@ import fs from 'fs';
5
5
  import isValid from 'is-valid-path';
6
6
  import createDebugMessages from 'debug';
7
7
  import os from 'os';
8
- import { fileURLToPath } from 'url';
9
8
 
10
9
  const debug = createDebugMessages('@testomatio/reporter:util');
11
10
 
12
- // Use __dirname directly since we're compiling to CommonJS
13
- const __dirname = path.resolve();
14
-
15
11
  /**
16
12
  * @param {String} testTitle - Test title
17
13
  *
@@ -111,7 +107,7 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
111
107
  .join('\n');
112
108
  };
113
109
 
114
- export const TEST_ID_REGEX = /@T([\w\d]{8})/;
110
+ const TEST_ID_REGEX = /@T([\w\d]{8})/;
115
111
 
116
112
  const fetchIdFromCode = (code, opts = {}) => {
117
113
  const comments = code
@@ -154,9 +150,6 @@ const fetchSourceCode = (contents, opts = {}) => {
154
150
  if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
155
151
  if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
156
152
  if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
157
- } else if (opts.lang === 'csharp') {
158
- if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
159
- if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
160
153
  } else {
161
154
  lineIndex = lines.findIndex(l => l.includes(title));
162
155
  }
@@ -360,12 +353,6 @@ function formatStep(step, shift = 0) {
360
353
  return lines;
361
354
  }
362
355
 
363
- export function getPackageVersion() {
364
- const packageJsonPath = path.resolve(__dirname, '../../package.json');
365
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
366
- return packageJson.version;
367
- }
368
-
369
356
  export {
370
357
  ansiRegExp,
371
358
  isSameTest,
package/src/xmlReader.js CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  fetchSourceCodeFromStackTrace,
14
14
  fetchIdFromCode,
15
15
  humanize,
16
- TEST_ID_REGEX,
17
16
  } from './utils/utils.js';
18
17
  import { pipesFactory } from './pipe/index.js';
19
18
  import adapterFactory from './junit-adapter/index.js';
@@ -27,9 +26,8 @@ const debug = createDebugMessages('@testomatio/reporter:xml');
27
26
  const ridRunId = randomUUID();
28
27
 
29
28
  const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
30
- const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE,
31
- TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV,
32
- TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } = process.env;
29
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } =
30
+ process.env;
33
31
 
34
32
  const options = {
35
33
  ignoreDeclaration: true,
@@ -39,8 +37,6 @@ const options = {
39
37
  parseTagValue: true,
40
38
  };
41
39
 
42
- const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
43
-
44
40
  const reduceOptions = {};
45
41
 
46
42
  class XmlReader {
@@ -95,7 +91,7 @@ class XmlReader {
95
91
  ];
96
92
 
97
93
  for (const regex of cutRegexes) {
98
- xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, MAX_OUTPUT_LENGTH)}${p3}`);
94
+ xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, 5000)}${p3}`);
99
95
  }
100
96
 
101
97
  const jsonResult = this.parser.parse(xmlData);
@@ -345,7 +341,6 @@ class XmlReader {
345
341
  if (file.endsWith('.rb')) this.stats.language = 'ruby';
346
342
  if (file.endsWith('.js')) this.stats.language = 'js';
347
343
  if (file.endsWith('.ts')) this.stats.language = 'ts';
348
- if (file.endsWith('.cs')) this.stats.language = 'csharp';
349
344
  }
350
345
 
351
346
  if (!fs.existsSync(file)) {
@@ -399,14 +394,13 @@ class XmlReader {
399
394
  async uploadArtifacts() {
400
395
  for (const test of this.tests.filter(t => !!t.stack)) {
401
396
  let files = [];
402
- if (!test.files?.length) continue;
403
-
404
- files = test.files.map(f => path.isAbsolute(f) ? f : path.join(process.cwd(), f));
397
+ if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
398
+ files = [...files, ...fetchFilesFromStackTrace(test.stack)];
405
399
 
406
400
  if (!files.length) continue;
407
401
 
408
402
  const runId = this.runId || this.store.runId || Date.now().toString();
409
- test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId, path.basename(f)])));
403
+ test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
410
404
  console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
411
405
  }
412
406
  }
@@ -477,7 +471,7 @@ function reduceTestCases(prev, item) {
477
471
  testCases
478
472
  .filter(t => !!t)
479
473
  .forEach(testCaseItem => {
480
- const file = testCaseItem.file || item.filepath || item.fullname || '';
474
+ const file = testCaseItem.file || item.filepath || '';
481
475
 
482
476
  let stack = '';
483
477
  let message = '';
@@ -511,38 +505,15 @@ function reduceTestCases(prev, item) {
511
505
  stack = `${
512
506
  testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
513
507
  }\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
514
- let testId = fetchIdFromOutput(stack);
515
-
516
- if (tags?.length && !testId) {
517
- testId = tags.filter(t => t.startsWith('T')).map(t => `@${t}`).find(t => t.match(TEST_ID_REGEX))?.slice(2);
518
- }
508
+ const testId = fetchIdFromOutput(stack);
519
509
 
520
510
  let status = STATUS.PASSED.toString();
521
511
  if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
522
512
  if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
523
- if (testCaseItem.result && Object.values(STATUS).includes(testCaseItem.result.toLowerCase())) {
524
- status = testCaseItem.result.toLowerCase();
525
- }
526
513
 
527
514
  let rid = null;
528
515
  if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
529
516
 
530
- // Extract attachments
531
- let files = [];
532
- if (testCaseItem.attachments) {
533
- const attachments = Array.isArray(testCaseItem.attachments.attachment)
534
- ? testCaseItem.attachments.attachment
535
- : [testCaseItem.attachments.attachment];
536
-
537
- files = attachments
538
- .filter(a => a && a.filePath)
539
- .map(a => a.filePath);
540
- }
541
-
542
- // Extract files from stack trace using existing utility
543
- const stackFiles = fetchFilesFromStackTrace(stack);
544
- files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
545
-
546
517
  prev.push({
547
518
  rid,
548
519
  file,
@@ -557,9 +528,7 @@ function reduceTestCases(prev, item) {
557
528
  run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
558
529
  status,
559
530
  title,
560
- root_suite_id: TESTOMATIO_SUITE,
561
531
  suite_title: suiteTitle,
562
- files,
563
532
  });
564
533
  });
565
534
  return prev;
@@ -586,15 +555,10 @@ function fetchProperties(item) {
586
555
 
587
556
  if (!item.properties) return {};
588
557
 
589
- // Handle both single property and array of properties
590
- const properties = Array.isArray(item.properties.property)
591
- ? item.properties.property
592
- : [item.properties.property].filter(Boolean);
593
-
594
- const prop = properties.find(p => p.name === 'Description');
558
+ const prop = [item.properties?.property].flat().find(p => p.name === 'Description');
595
559
  if (prop) title = prop.value;
596
-
597
- properties
560
+ [item.properties?.property]
561
+ .flat()
598
562
  .filter(p => p.name === 'Category')
599
563
  .forEach(p => tags.push(p.value));
600
564
  return { title, tags };