@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.
- 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 -24
- package/lib/utils/utils.js +33 -4
- package/lib/xmlReader.d.ts +7 -0
- package/lib/xmlReader.js +312 -10
- 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 -83
- package/src/utils/utils.js +33 -3
- package/src/xmlReader.js +360 -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,51 +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
|
-
}
|
|
282
|
+
this.#formatData(data);
|
|
261
283
|
|
|
262
284
|
const json = JsonCycle.stringify(data);
|
|
263
285
|
|
|
264
286
|
debug('Adding test', json);
|
|
265
287
|
|
|
266
|
-
return this.client
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
+
}
|
|
280
314
|
console.log(
|
|
281
315
|
APP_PREFIX,
|
|
282
|
-
pc.yellow(`Warning: ${
|
|
283
|
-
|
|
316
|
+
pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
317
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
284
318
|
);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return;
|
|
319
|
+
printCreateIssue(err);
|
|
320
|
+
} else {
|
|
321
|
+
console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
289
322
|
}
|
|
290
|
-
|
|
291
|
-
APP_PREFIX,
|
|
292
|
-
pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
293
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
294
|
-
);
|
|
295
|
-
printCreateIssue(err);
|
|
296
|
-
} else {
|
|
297
|
-
console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
323
|
+
});
|
|
300
324
|
};
|
|
301
325
|
|
|
302
326
|
/**
|
|
@@ -323,43 +347,42 @@ class TestomatioPipe {
|
|
|
323
347
|
const testsToSend = this.batch.tests.splice(0);
|
|
324
348
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
325
349
|
|
|
326
|
-
return this.client
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
}
|
|
344
376
|
console.log(
|
|
345
377
|
APP_PREFIX,
|
|
346
|
-
pc.yellow(`Warning:
|
|
378
|
+
pc.yellow(`Warning: (${err.response?.status})`),
|
|
379
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
347
380
|
);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return;
|
|
381
|
+
printCreateIssue(err);
|
|
382
|
+
} else {
|
|
383
|
+
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
352
384
|
}
|
|
353
|
-
|
|
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
|
-
});
|
|
385
|
+
});
|
|
363
386
|
};
|
|
364
387
|
|
|
365
388
|
/**
|
|
@@ -375,19 +398,16 @@ class TestomatioPipe {
|
|
|
375
398
|
return;
|
|
376
399
|
}
|
|
377
400
|
|
|
378
|
-
|
|
379
|
-
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
380
|
-
data.api_key = this.apiKey;
|
|
381
|
-
data.create = this.createNewTests;
|
|
401
|
+
this.#formatData(data);
|
|
382
402
|
|
|
383
403
|
let uploading = null;
|
|
384
404
|
if (!this.batch.isEnabled) uploading = this.#uploadSingleTest(data);
|
|
385
405
|
else this.batch.tests.push(data);
|
|
386
406
|
|
|
387
407
|
// if test is added after run which is already finished
|
|
388
|
-
|
|
408
|
+
if (!this.batch.intervalFunction) uploading = this.#batchUpload();
|
|
389
409
|
|
|
390
|
-
|
|
410
|
+
// return promise to be able to wait for it
|
|
391
411
|
return uploading;
|
|
392
412
|
}
|
|
393
413
|
|
|
@@ -436,7 +456,7 @@ class TestomatioPipe {
|
|
|
436
456
|
status_event,
|
|
437
457
|
detach: params.detach,
|
|
438
458
|
tests: params.tests,
|
|
439
|
-
}
|
|
459
|
+
},
|
|
440
460
|
});
|
|
441
461
|
|
|
442
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
|
}
|