@testomatio/reporter 2.3.7-beta.2-xml-import → 2.3.7-beta.4-stack-artifacts
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/bin/cli.js +1 -1
- package/lib/bin/reportXml.js +2 -5
- package/lib/bin/startTest.js +3 -3
- package/lib/client.js +48 -25
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +7 -40
- package/lib/pipe/debug.js +1 -1
- package/lib/pipe/testomatio.js +15 -19
- package/lib/reporter.d.ts +19 -9
- package/lib/reporter.js +40 -5
- package/lib/template/testomatio.hbs +1026 -1366
- package/lib/uploader.js +0 -4
- package/lib/utils/utils.js +11 -90
- package/lib/xmlReader.d.ts +26 -32
- package/lib/xmlReader.js +50 -106
- package/package.json +1 -1
- package/src/bin/cli.js +1 -1
- package/src/bin/reportXml.js +2 -5
- package/src/bin/startTest.js +5 -5
- package/src/client.js +80 -31
- package/src/junit-adapter/csharp.js +6 -45
- package/src/pipe/debug.js +3 -2
- package/src/pipe/testomatio.js +81 -75
- package/src/reporter.js +7 -4
- package/src/template/testomatio.hbs +1026 -1366
- package/src/uploader.js +0 -5
- package/src/utils/utils.js +9 -96
- package/src/xmlReader.js +45 -128
- package/lib/junit-adapter/nunit-parser.d.ts +0 -82
- package/lib/junit-adapter/nunit-parser.js +0 -369
- package/src/junit-adapter/nunit-parser.js +0 -404
package/src/client.js
CHANGED
|
@@ -10,7 +10,14 @@ import { glob } from 'glob';
|
|
|
10
10
|
import path, { sep } from 'path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { S3Uploader } from './uploader.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
formatStep,
|
|
15
|
+
truncate,
|
|
16
|
+
readLatestRunId,
|
|
17
|
+
storeRunId,
|
|
18
|
+
validateSuiteId,
|
|
19
|
+
transformEnvVarToBoolean
|
|
20
|
+
} from './utils/utils.js';
|
|
14
21
|
import { filesize as prettyBytes } from 'filesize';
|
|
15
22
|
|
|
16
23
|
const debug = createDebugMessages('@testomatio/reporter:client');
|
|
@@ -37,8 +44,9 @@ class Client {
|
|
|
37
44
|
this.runId = '';
|
|
38
45
|
this.queue = Promise.resolve();
|
|
39
46
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
47
|
+
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
48
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
const pathToPackageJSON = path.join(__dirname, '../package.json');
|
|
42
50
|
try {
|
|
43
51
|
this.version = JSON.parse(fs.readFileSync(pathToPackageJSON).toString()).version;
|
|
44
52
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
@@ -138,19 +146,6 @@ class Client {
|
|
|
138
146
|
* @returns {Promise<PipeResult[]>}
|
|
139
147
|
*/
|
|
140
148
|
async addTestRun(status, testData) {
|
|
141
|
-
if (!this.pipes || !this.pipes.length)
|
|
142
|
-
this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
143
|
-
|
|
144
|
-
// all pipes disabled, skipping
|
|
145
|
-
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
146
|
-
|
|
147
|
-
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
148
|
-
|
|
149
|
-
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
150
|
-
debug('Skipping test from report', testData?.title);
|
|
151
|
-
return []; // do not log skipped tests
|
|
152
|
-
}
|
|
153
|
-
|
|
154
149
|
if (!testData)
|
|
155
150
|
testData = {
|
|
156
151
|
title: 'Unknown test',
|
|
@@ -168,15 +163,61 @@ class Client {
|
|
|
168
163
|
const {
|
|
169
164
|
rid,
|
|
170
165
|
error = null,
|
|
166
|
+
steps: originalSteps,
|
|
167
|
+
title,
|
|
168
|
+
suite_title,
|
|
169
|
+
} = testData;
|
|
170
|
+
let steps = originalSteps;
|
|
171
|
+
|
|
172
|
+
const uploadedFiles = [];
|
|
173
|
+
const stackArtifactsEnabled = transformEnvVarToBoolean(process.env.TESTOMATIO_STACK_ARTIFACTS);
|
|
174
|
+
|
|
175
|
+
let formattedSteps;
|
|
176
|
+
if (stackArtifactsEnabled) {
|
|
177
|
+
const timestamp = +new Date;
|
|
178
|
+
formattedSteps = Array.isArray(steps) ? steps.map(step => formatStep(step)).flat().join('\n') : '';
|
|
179
|
+
|
|
180
|
+
if (error?.stack?.length > 5000) {
|
|
181
|
+
uploadedFiles.push(
|
|
182
|
+
this.uploader.uploadFileAsBuffer(
|
|
183
|
+
Buffer.from(error.stack, 'utf8'),
|
|
184
|
+
[this.runId, rid, `stack_${timestamp}.log`]
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (formattedSteps?.length > 10000) {
|
|
189
|
+
uploadedFiles.push(
|
|
190
|
+
this.uploader.uploadFileAsBuffer(
|
|
191
|
+
Buffer.from(JSON.stringify(steps, null, 2), 'utf8'),
|
|
192
|
+
[this.runId, rid, `steps_${timestamp}.json`]
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!this.pipes || !this.pipes.length)
|
|
198
|
+
this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
199
|
+
|
|
200
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) {
|
|
201
|
+
if (uploadedFiles.length > 0) {
|
|
202
|
+
await Promise.all(uploadedFiles);
|
|
203
|
+
}
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
208
|
+
|
|
209
|
+
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
210
|
+
debug('Skipping test from report', testData?.title);
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const {
|
|
171
215
|
time = 0,
|
|
172
216
|
example = null,
|
|
173
217
|
files = [],
|
|
174
218
|
filesBuffers = [],
|
|
175
|
-
steps,
|
|
176
219
|
code = null,
|
|
177
|
-
title,
|
|
178
220
|
file,
|
|
179
|
-
suite_title,
|
|
180
221
|
suite_id,
|
|
181
222
|
test_id,
|
|
182
223
|
timestamp,
|
|
@@ -187,7 +228,6 @@ class Client {
|
|
|
187
228
|
} = testData;
|
|
188
229
|
let { message = '', meta = {} } = testData;
|
|
189
230
|
|
|
190
|
-
// stringify meta values and limit keys and values length to 255
|
|
191
231
|
meta = Object.entries(meta)
|
|
192
232
|
.filter(([, value]) => value !== null && value !== undefined)
|
|
193
233
|
.reduce((acc, [key, value]) => {
|
|
@@ -195,7 +235,6 @@ class Client {
|
|
|
195
235
|
return acc;
|
|
196
236
|
}, {});
|
|
197
237
|
|
|
198
|
-
// Get links from storage using the test context
|
|
199
238
|
const testContext = suite_title ? `${suite_title} ${title}` : title;
|
|
200
239
|
|
|
201
240
|
let errorFormatted = '';
|
|
@@ -204,13 +243,27 @@ class Client {
|
|
|
204
243
|
message = error?.message;
|
|
205
244
|
}
|
|
206
245
|
|
|
207
|
-
|
|
208
|
-
|
|
246
|
+
if (stackArtifactsEnabled) {
|
|
247
|
+
if (error?.stack?.length > 5000) errorFormatted = `[Large stack saved as artifact]`;
|
|
248
|
+
if (formattedSteps?.length > 10000) steps = null;
|
|
249
|
+
} else {
|
|
250
|
+
formattedSteps = Array.isArray(steps) ? steps.map(step => formatStep(step)).flat().join('\n') : '';
|
|
251
|
+
}
|
|
209
252
|
|
|
210
|
-
|
|
211
|
-
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
253
|
+
let fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
212
254
|
|
|
213
|
-
|
|
255
|
+
if (stackArtifactsEnabled && fullLogs.length > 5000) {
|
|
256
|
+
const timestamp = +new Date;
|
|
257
|
+
uploadedFiles.push(
|
|
258
|
+
this.uploader.uploadFileAsBuffer(
|
|
259
|
+
Buffer.from(fullLogs, 'utf8'),
|
|
260
|
+
[this.runId, rid, `logs_${timestamp}.log`]
|
|
261
|
+
)
|
|
262
|
+
);
|
|
263
|
+
fullLogs = fullLogs.slice(0, 5000) + '\n\n[Full logs saved as artifact]';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
214
267
|
|
|
215
268
|
for (let f of files) {
|
|
216
269
|
if (!f) continue; // f === null
|
|
@@ -386,11 +439,7 @@ class Client {
|
|
|
386
439
|
*/
|
|
387
440
|
formatLogs({ error, steps, logs }) {
|
|
388
441
|
error = error?.trim();
|
|
389
|
-
logs = logs
|
|
390
|
-
?.trim()
|
|
391
|
-
.split('\n')
|
|
392
|
-
.map(l => truncate(l))
|
|
393
|
-
.join('\n');
|
|
442
|
+
logs = logs?.trim().split('\n').map(l => truncate(l)).join('\n');
|
|
394
443
|
|
|
395
444
|
if (Array.isArray(steps)) {
|
|
396
445
|
steps = steps
|
|
@@ -3,50 +3,18 @@ import Adapter from './adapter.js';
|
|
|
3
3
|
|
|
4
4
|
class CSharpAdapter extends Adapter {
|
|
5
5
|
formatTest(t) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (exampleMatch) {
|
|
10
|
-
// Extract parameters as object with numeric keys for API
|
|
11
|
-
const params = exampleMatch[1].split(',').map(param => param.trim());
|
|
12
|
-
t.example = {};
|
|
13
|
-
params.forEach((param, index) => {
|
|
14
|
-
t.example[index] = param;
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// For runs: keep full title with parameters for display
|
|
20
|
-
// The example field will be used for grouping on import
|
|
21
|
-
// Do NOT remove parameters from title
|
|
22
|
-
|
|
6
|
+
const title = t.title.replace(/\(.*?\)/, '').trim();
|
|
7
|
+
const example = t.title.match(/\((.*?)\)/);
|
|
8
|
+
if (example) t.example = { ...example[1].split(',') };
|
|
23
9
|
const suite = t.suite_title.split('.');
|
|
24
10
|
t.suite_title = suite.pop();
|
|
25
11
|
t.file = namespaceToFileName(t.file);
|
|
12
|
+
t.title = title.trim();
|
|
26
13
|
return t;
|
|
27
14
|
}
|
|
28
15
|
|
|
29
16
|
getFilePath(t) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Normalize path separators for cross-platform compatibility
|
|
33
|
-
let filePath = t.file.replace(/\\/g, '/');
|
|
34
|
-
|
|
35
|
-
// If file already has .cs extension, use it directly
|
|
36
|
-
if (filePath.endsWith('.cs')) {
|
|
37
|
-
// Make relative path if it's absolute
|
|
38
|
-
if (path.isAbsolute(filePath)) {
|
|
39
|
-
// Try to find project-relative path
|
|
40
|
-
const cwd = process.cwd().replace(/\\/g, '/');
|
|
41
|
-
if (filePath.startsWith(cwd)) {
|
|
42
|
-
filePath = path.relative(cwd, filePath).replace(/\\/g, '/');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return filePath;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Convert namespace path to file path
|
|
49
|
-
const fileName = namespaceToFileName(filePath);
|
|
17
|
+
const fileName = namespaceToFileName(t.file);
|
|
50
18
|
return fileName;
|
|
51
19
|
}
|
|
52
20
|
}
|
|
@@ -54,14 +22,7 @@ class CSharpAdapter extends Adapter {
|
|
|
54
22
|
export default CSharpAdapter;
|
|
55
23
|
|
|
56
24
|
function namespaceToFileName(fileName) {
|
|
57
|
-
if (!fileName) return '';
|
|
58
|
-
|
|
59
|
-
// If already a .cs file path, clean it up
|
|
60
|
-
if (fileName.endsWith('.cs')) {
|
|
61
|
-
return fileName.replace(/\\/g, '/');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
25
|
const fileParts = fileName.split('.');
|
|
65
26
|
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
66
|
-
return `${fileParts.join(
|
|
27
|
+
return `${fileParts.join(path.sep)}.cs`;
|
|
67
28
|
}
|
package/src/pipe/debug.js
CHANGED
|
@@ -15,7 +15,7 @@ export class DebugPipe {
|
|
|
15
15
|
this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
|
|
16
16
|
if (this.isEnabled) {
|
|
17
17
|
this.batch = {
|
|
18
|
-
isEnabled: this.params.isBatchEnabled ??
|
|
18
|
+
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
19
19
|
intervalFunction: null,
|
|
20
20
|
intervalTime: 5000,
|
|
21
21
|
tests: [],
|
|
@@ -93,7 +93,8 @@ export class DebugPipe {
|
|
|
93
93
|
const logData = { action: 'addTest', testId: data };
|
|
94
94
|
if (this.store.runId) logData.runId = this.store.runId;
|
|
95
95
|
this.logToFile(logData);
|
|
96
|
-
}
|
|
96
|
+
}
|
|
97
|
+
else this.batch.tests.push(data);
|
|
97
98
|
|
|
98
99
|
if (!this.batch.intervalFunction) await this.batchUpload();
|
|
99
100
|
}
|
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 ?? 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',
|
|
64
|
-
shouldRetry: error => {
|
|
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;
|
|
@@ -104,6 +104,7 @@ class TestomatioPipe {
|
|
|
104
104
|
// add test ID + run ID
|
|
105
105
|
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
106
106
|
|
|
107
|
+
|
|
107
108
|
if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
|
|
108
109
|
data.stack = null;
|
|
109
110
|
}
|
|
@@ -119,6 +120,7 @@ class TestomatioPipe {
|
|
|
119
120
|
return data;
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
|
|
122
124
|
/**
|
|
123
125
|
* Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
|
|
124
126
|
* @param {Object} opts - The options for preparing the test grepList.
|
|
@@ -213,7 +215,7 @@ class TestomatioPipe {
|
|
|
213
215
|
method: 'PUT',
|
|
214
216
|
url: `/api/reporter/${this.runId}`,
|
|
215
217
|
data: runParams,
|
|
216
|
-
responseType: 'json'
|
|
218
|
+
responseType: 'json'
|
|
217
219
|
});
|
|
218
220
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
219
221
|
return;
|
|
@@ -226,7 +228,7 @@ class TestomatioPipe {
|
|
|
226
228
|
url: '/api/reporter',
|
|
227
229
|
data: runParams,
|
|
228
230
|
maxContentLength: Infinity,
|
|
229
|
-
responseType: 'json'
|
|
231
|
+
responseType: 'json'
|
|
230
232
|
});
|
|
231
233
|
|
|
232
234
|
this.runId = resp.data.uid;
|
|
@@ -285,44 +287,44 @@ class TestomatioPipe {
|
|
|
285
287
|
|
|
286
288
|
debug('Adding test', json);
|
|
287
289
|
|
|
288
|
-
return this.client
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
}
|
|
290
|
+
return this.client.request({
|
|
291
|
+
method: 'POST',
|
|
292
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
293
|
+
data: json,
|
|
294
|
+
headers: {
|
|
295
|
+
'Content-Type': 'application/json',
|
|
296
|
+
},
|
|
297
|
+
maxContentLength: Infinity
|
|
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: '' };
|
|
314
304
|
console.log(
|
|
315
305
|
APP_PREFIX,
|
|
316
|
-
pc.yellow(`Warning: ${
|
|
317
|
-
|
|
306
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
307
|
+
pc.gray(data?.title || ''),
|
|
318
308
|
);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
309
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
310
|
+
this.hasUnmatchedTests = true;
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
322
313
|
}
|
|
323
|
-
|
|
314
|
+
console.log(
|
|
315
|
+
APP_PREFIX,
|
|
316
|
+
pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
317
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
318
|
+
);
|
|
319
|
+
printCreateIssue(err);
|
|
320
|
+
} else {
|
|
321
|
+
console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
324
|
};
|
|
325
325
|
|
|
326
|
+
|
|
327
|
+
|
|
326
328
|
/**
|
|
327
329
|
* Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
|
|
328
330
|
*/
|
|
@@ -347,42 +349,43 @@ class TestomatioPipe {
|
|
|
347
349
|
const testsToSend = this.batch.tests.splice(0);
|
|
348
350
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
349
351
|
|
|
350
|
-
return this.client
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
+
return this.client.request({
|
|
353
|
+
method: 'POST',
|
|
354
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
355
|
+
data: {
|
|
356
|
+
api_key: this.apiKey,
|
|
357
|
+
tests: testsToSend,
|
|
358
|
+
batch_index: this.batch.batchIndex
|
|
359
|
+
},
|
|
360
|
+
headers: {
|
|
361
|
+
'Content-Type': 'application/json',
|
|
362
|
+
},
|
|
363
|
+
maxContentLength: Infinity
|
|
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: '' };
|
|
376
370
|
console.log(
|
|
377
371
|
APP_PREFIX,
|
|
378
|
-
pc.yellow(`Warning: (${err.response
|
|
379
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
372
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
380
373
|
);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
374
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
375
|
+
this.hasUnmatchedTests = true;
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
384
378
|
}
|
|
385
|
-
|
|
379
|
+
console.log(
|
|
380
|
+
APP_PREFIX,
|
|
381
|
+
pc.yellow(`Warning: (${err.response?.status})`),
|
|
382
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
383
|
+
);
|
|
384
|
+
printCreateIssue(err);
|
|
385
|
+
} else {
|
|
386
|
+
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
386
389
|
};
|
|
387
390
|
|
|
388
391
|
/**
|
|
@@ -405,9 +408,9 @@ class TestomatioPipe {
|
|
|
405
408
|
else this.batch.tests.push(data);
|
|
406
409
|
|
|
407
410
|
// if test is added after run which is already finished
|
|
408
|
-
|
|
411
|
+
if (!this.batch.intervalFunction) uploading = this.#batchUpload();
|
|
409
412
|
|
|
410
|
-
|
|
413
|
+
// return promise to be able to wait for it
|
|
411
414
|
return uploading;
|
|
412
415
|
}
|
|
413
416
|
|
|
@@ -456,7 +459,7 @@ class TestomatioPipe {
|
|
|
456
459
|
status_event,
|
|
457
460
|
detach: params.detach,
|
|
458
461
|
tests: params.tests,
|
|
459
|
-
}
|
|
462
|
+
}
|
|
460
463
|
});
|
|
461
464
|
|
|
462
465
|
if (this.runUrl) {
|
|
@@ -469,7 +472,7 @@ class TestomatioPipe {
|
|
|
469
472
|
if (this.runUrl && this.proceed) {
|
|
470
473
|
const notFinishedMessage = pc.yellow(pc.bold('Run was not finished because of $TESTOMATIO_PROCEED'));
|
|
471
474
|
console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${pc.magenta(this.runUrl)}`);
|
|
472
|
-
console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx
|
|
475
|
+
console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
|
|
473
476
|
}
|
|
474
477
|
|
|
475
478
|
if (this.hasUnmatchedTests) {
|
|
@@ -522,6 +525,9 @@ function printCreateIssue(err) {
|
|
|
522
525
|
console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
|
|
523
526
|
console.log('```');
|
|
524
527
|
});
|
|
528
|
+
|
|
525
529
|
}
|
|
526
530
|
|
|
531
|
+
|
|
532
|
+
|
|
527
533
|
export default TestomatioPipe;
|
package/src/reporter.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import Client from './client.js';
|
|
2
|
+
import * as TestomatioConstants from './constants.js';
|
|
3
3
|
import { services } from './services/index.js';
|
|
4
4
|
import reporterFunctions from './reporter-functions.js';
|
|
5
5
|
|
|
6
|
+
export { Client };
|
|
7
|
+
export const STATUS = TestomatioConstants.STATUS;
|
|
6
8
|
export const artifact = reporterFunctions.artifact;
|
|
7
9
|
export const log = reporterFunctions.log;
|
|
8
10
|
export const logger = services.logger;
|
|
@@ -35,6 +37,7 @@ export default {
|
|
|
35
37
|
linkTest: reporterFunctions.linkTest,
|
|
36
38
|
linkJira: reporterFunctions.linkJira,
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
TestomatioClient: Client,
|
|
41
|
+
STATUS,
|
|
42
|
+
|
|
40
43
|
};
|