@testomatio/reporter 2.3.4 → 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 +1 -1
- package/lib/junit-adapter/csharp.d.ts +0 -1
- package/lib/junit-adapter/csharp.js +36 -7
- package/lib/pipe/debug.js +1 -1
- package/lib/pipe/testomatio.js +42 -30
- package/lib/utils/utils.js +33 -4
- package/lib/xmlReader.d.ts +9 -0
- package/lib/xmlReader.js +357 -11
- package/package.json +1 -1
- package/src/junit-adapter/csharp.js +40 -6
- package/src/pipe/debug.js +2 -3
- package/src/pipe/testomatio.js +103 -91
- package/src/utils/utils.js +33 -3
- package/src/xmlReader.js +409 -10
package/src/pipe/testomatio.js
CHANGED
|
@@ -20,7 +20,7 @@ if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
|
|
|
20
20
|
class TestomatioPipe {
|
|
21
21
|
constructor(params, store) {
|
|
22
22
|
this.batch = {
|
|
23
|
-
isEnabled: params?.isBatchEnabled ??
|
|
23
|
+
isEnabled: params?.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
|
|
24
24
|
intervalFunction: null, // will be created in createRun by setInterval function
|
|
25
25
|
intervalTime: 5000, // how often tests are sent
|
|
26
26
|
tests: [], // array of tests in batch
|
|
@@ -60,8 +60,8 @@ class TestomatioPipe {
|
|
|
60
60
|
retryConfig: {
|
|
61
61
|
retry: REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
62
62
|
retryDelay: REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
63
|
-
httpMethodsToRetry: ['GET','PUT','HEAD','OPTIONS','DELETE','POST'],
|
|
64
|
-
shouldRetry:
|
|
63
|
+
httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
|
|
64
|
+
shouldRetry: error => {
|
|
65
65
|
if (!error.response) return false;
|
|
66
66
|
switch (error.response?.status) {
|
|
67
67
|
case 400: // Bad request (probably wrong API key)
|
|
@@ -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
|
|
|
80
80
|
this.isEnabled = true;
|
|
@@ -92,6 +92,33 @@ class TestomatioPipe {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Prepares data for sending to Testomat.io.
|
|
97
|
+
* @param {*} data - The data to be formatted.
|
|
98
|
+
* @returns
|
|
99
|
+
*/
|
|
100
|
+
#formatData(data) {
|
|
101
|
+
data.api_key = this.apiKey;
|
|
102
|
+
data.create = this.createNewTests;
|
|
103
|
+
|
|
104
|
+
// add test ID + run ID
|
|
105
|
+
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
106
|
+
|
|
107
|
+
if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
|
|
108
|
+
data.stack = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!process.env.TESTOMATIO_STEPS_PASSED && data.status === STATUS.PASSED) {
|
|
112
|
+
data.steps = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (process.env.TESTOMATIO_NO_STEPS) {
|
|
116
|
+
data.steps = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return data;
|
|
120
|
+
}
|
|
121
|
+
|
|
95
122
|
/**
|
|
96
123
|
* Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
|
|
97
124
|
* @param {Object} opts - The options for preparing the test grepList.
|
|
@@ -186,7 +213,7 @@ class TestomatioPipe {
|
|
|
186
213
|
method: 'PUT',
|
|
187
214
|
url: `/api/reporter/${this.runId}`,
|
|
188
215
|
data: runParams,
|
|
189
|
-
responseType: 'json'
|
|
216
|
+
responseType: 'json',
|
|
190
217
|
});
|
|
191
218
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
192
219
|
return;
|
|
@@ -199,7 +226,7 @@ class TestomatioPipe {
|
|
|
199
226
|
url: '/api/reporter',
|
|
200
227
|
data: runParams,
|
|
201
228
|
maxContentLength: Infinity,
|
|
202
|
-
responseType: 'json'
|
|
229
|
+
responseType: 'json',
|
|
203
230
|
});
|
|
204
231
|
|
|
205
232
|
this.runId = resp.data.uid;
|
|
@@ -252,59 +279,48 @@ class TestomatioPipe {
|
|
|
252
279
|
if (!this.runId) return;
|
|
253
280
|
if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
|
|
254
281
|
|
|
255
|
-
data
|
|
256
|
-
data.create = this.createNewTests;
|
|
257
|
-
|
|
258
|
-
if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
|
|
259
|
-
data.stack = null;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (!process.env.TESTOMATIO_STEPS_PASSED && data.status === STATUS.PASSED) {
|
|
263
|
-
data.steps = null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (process.env.TESTOMATIO_NO_STEPS) {
|
|
267
|
-
data.steps = null;
|
|
268
|
-
}
|
|
282
|
+
this.#formatData(data);
|
|
269
283
|
|
|
270
284
|
const json = JsonCycle.stringify(data);
|
|
271
285
|
|
|
272
286
|
debug('Adding test', json);
|
|
273
287
|
|
|
274
|
-
return this.client
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
+
return this.client
|
|
289
|
+
.request({
|
|
290
|
+
method: 'POST',
|
|
291
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
292
|
+
data: json,
|
|
293
|
+
headers: {
|
|
294
|
+
'Content-Type': 'application/json',
|
|
295
|
+
},
|
|
296
|
+
maxContentLength: Infinity,
|
|
297
|
+
})
|
|
298
|
+
.catch(err => {
|
|
299
|
+
this.requestFailures++;
|
|
300
|
+
this.notReportedTestsCount++;
|
|
301
|
+
if (err.response) {
|
|
302
|
+
if (err.response.status >= 400) {
|
|
303
|
+
const responseData = err.response.data || { message: '' };
|
|
304
|
+
console.log(
|
|
305
|
+
APP_PREFIX,
|
|
306
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
307
|
+
pc.gray(data?.title || ''),
|
|
308
|
+
);
|
|
309
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
310
|
+
this.hasUnmatchedTests = true;
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
288
314
|
console.log(
|
|
289
315
|
APP_PREFIX,
|
|
290
|
-
pc.yellow(`Warning: ${
|
|
291
|
-
|
|
316
|
+
pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
317
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
292
318
|
);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return;
|
|
319
|
+
printCreateIssue(err);
|
|
320
|
+
} else {
|
|
321
|
+
console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
297
322
|
}
|
|
298
|
-
|
|
299
|
-
APP_PREFIX,
|
|
300
|
-
pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
301
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
302
|
-
);
|
|
303
|
-
printCreateIssue(err);
|
|
304
|
-
} else {
|
|
305
|
-
console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
306
|
-
}
|
|
307
|
-
});
|
|
323
|
+
});
|
|
308
324
|
};
|
|
309
325
|
|
|
310
326
|
/**
|
|
@@ -331,43 +347,42 @@ class TestomatioPipe {
|
|
|
331
347
|
const testsToSend = this.batch.tests.splice(0);
|
|
332
348
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
333
349
|
|
|
334
|
-
return this.client
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
return this.client
|
|
351
|
+
.request({
|
|
352
|
+
method: 'POST',
|
|
353
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
354
|
+
data: {
|
|
355
|
+
api_key: this.apiKey,
|
|
356
|
+
tests: testsToSend,
|
|
357
|
+
batch_index: this.batch.batchIndex,
|
|
358
|
+
},
|
|
359
|
+
headers: {
|
|
360
|
+
'Content-Type': 'application/json',
|
|
361
|
+
},
|
|
362
|
+
maxContentLength: Infinity,
|
|
363
|
+
})
|
|
364
|
+
.catch(err => {
|
|
365
|
+
this.requestFailures++;
|
|
366
|
+
this.notReportedTestsCount += testsToSend.length;
|
|
367
|
+
if (err.response) {
|
|
368
|
+
if (err.response.status >= 400) {
|
|
369
|
+
const responseData = err.response.data || { message: '' };
|
|
370
|
+
console.log(APP_PREFIX, pc.yellow(`Warning: ${responseData.message} (${err.response.status})`));
|
|
371
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
372
|
+
this.hasUnmatchedTests = true;
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
352
376
|
console.log(
|
|
353
377
|
APP_PREFIX,
|
|
354
|
-
pc.yellow(`Warning:
|
|
378
|
+
pc.yellow(`Warning: (${err.response?.status})`),
|
|
379
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
355
380
|
);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return;
|
|
381
|
+
printCreateIssue(err);
|
|
382
|
+
} else {
|
|
383
|
+
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
360
384
|
}
|
|
361
|
-
|
|
362
|
-
APP_PREFIX,
|
|
363
|
-
pc.yellow(`Warning: (${err.response?.status})`),
|
|
364
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
365
|
-
);
|
|
366
|
-
printCreateIssue(err);
|
|
367
|
-
} else {
|
|
368
|
-
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
385
|
+
});
|
|
371
386
|
};
|
|
372
387
|
|
|
373
388
|
/**
|
|
@@ -383,19 +398,16 @@ class TestomatioPipe {
|
|
|
383
398
|
return;
|
|
384
399
|
}
|
|
385
400
|
|
|
386
|
-
|
|
387
|
-
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
388
|
-
data.api_key = this.apiKey;
|
|
389
|
-
data.create = this.createNewTests;
|
|
401
|
+
this.#formatData(data);
|
|
390
402
|
|
|
391
403
|
let uploading = null;
|
|
392
404
|
if (!this.batch.isEnabled) uploading = this.#uploadSingleTest(data);
|
|
393
405
|
else this.batch.tests.push(data);
|
|
394
406
|
|
|
395
407
|
// if test is added after run which is already finished
|
|
396
|
-
|
|
408
|
+
if (!this.batch.intervalFunction) uploading = this.#batchUpload();
|
|
397
409
|
|
|
398
|
-
|
|
410
|
+
// return promise to be able to wait for it
|
|
399
411
|
return uploading;
|
|
400
412
|
}
|
|
401
413
|
|
|
@@ -444,7 +456,7 @@ class TestomatioPipe {
|
|
|
444
456
|
status_event,
|
|
445
457
|
detach: params.detach,
|
|
446
458
|
tests: params.tests,
|
|
447
|
-
}
|
|
459
|
+
},
|
|
448
460
|
});
|
|
449
461
|
|
|
450
462
|
console.log(APP_PREFIX, '✅ Testrun finished');
|
package/src/utils/utils.js
CHANGED
|
@@ -139,6 +139,8 @@ export const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
|
139
139
|
export const SUITE_ID_REGEX = /@S([\w\d]{8})/;
|
|
140
140
|
|
|
141
141
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
142
|
+
if (!code) return null;
|
|
143
|
+
|
|
142
144
|
const comments = code
|
|
143
145
|
.split('\n')
|
|
144
146
|
.map(l => l.trim())
|
|
@@ -180,8 +182,32 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
180
182
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
181
183
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
182
184
|
} else if (opts.lang === 'csharp') {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
// Enhanced C# method detection for NUnit tests
|
|
186
|
+
lineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
|
|
187
|
+
|
|
188
|
+
if (lineIndex === -1) {
|
|
189
|
+
lineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (lineIndex === -1) {
|
|
193
|
+
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Look for TestCase or Test attributes above the method
|
|
197
|
+
if (lineIndex === -1) {
|
|
198
|
+
const testAttributeIndex = lines.findIndex((l, index) => {
|
|
199
|
+
if (l.includes('[TestCase') || l.includes('[Test')) {
|
|
200
|
+
// Check next few lines for the method
|
|
201
|
+
const nextLines = lines.slice(index, Math.min(lines.length, index + 5));
|
|
202
|
+
const hasMethod = nextLines.some(nextLine => nextLine.includes(`${title}(`));
|
|
203
|
+
return hasMethod;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
});
|
|
207
|
+
if (testAttributeIndex !== -1) {
|
|
208
|
+
lineIndex = testAttributeIndex;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
185
211
|
} else {
|
|
186
212
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
187
213
|
}
|
|
@@ -191,7 +217,7 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
191
217
|
lineIndex -= opts.prepend;
|
|
192
218
|
}
|
|
193
219
|
|
|
194
|
-
if (lineIndex) {
|
|
220
|
+
if (lineIndex !== -1 && lineIndex !== undefined) {
|
|
195
221
|
const result = [];
|
|
196
222
|
for (let i = lineIndex; i < lineIndex + limit; i++) {
|
|
197
223
|
if (lines[i] === undefined) continue;
|
|
@@ -216,6 +242,10 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
216
242
|
if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
|
|
217
243
|
if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
|
|
218
244
|
if (opts.lang === 'java' && lines[i].includes(' class ')) break;
|
|
245
|
+
if (opts.lang === 'csharp' && lines[i].trim().match(/^\[Test/)) break;
|
|
246
|
+
if (opts.lang === 'csharp' && lines[i].includes(' public void ')) break;
|
|
247
|
+
if (opts.lang === 'csharp' && lines[i].includes(' public async Task ')) break;
|
|
248
|
+
if (opts.lang === 'csharp' && lines[i].includes(' class ') && lines[i].includes('public')) break;
|
|
219
249
|
}
|
|
220
250
|
result.push(lines[i]);
|
|
221
251
|
}
|