@testomatio/reporter 1.3.0-ignore-stack-for-passed.1 → 1.3.0
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/bin/reportXml.js +9 -4
- package/lib/constants.js +15 -2
- package/lib/fileUploader.js +7 -1
- package/lib/pipe/gitlab.js +4 -4
- package/lib/pipe/html.js +82 -45
- package/lib/pipe/testomatio.js +88 -15
- package/lib/template/testomatio.hbs +1185 -337
- package/lib/utils/pipe_utils.js +1 -0
- package/package.json +3 -3
- package/lib/template/template-draft.hbs +0 -249
package/lib/bin/reportXml.js
CHANGED
|
@@ -44,10 +44,15 @@ program
|
|
|
44
44
|
|
|
45
45
|
let timeoutTimer;
|
|
46
46
|
if (opts.timelimit) {
|
|
47
|
-
timeoutTimer = setTimeout(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
timeoutTimer = setTimeout(
|
|
48
|
+
() => {
|
|
49
|
+
console.log(
|
|
50
|
+
`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`,
|
|
51
|
+
);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
},
|
|
54
|
+
parseInt(opts.timelimit, 10) * 1000,
|
|
55
|
+
);
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
try {
|
package/lib/constants.js
CHANGED
|
@@ -4,7 +4,6 @@ const path = require('path');
|
|
|
4
4
|
|
|
5
5
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
6
6
|
const AXIOS_TIMEOUT = 20 * 1000; // sum = 20sec
|
|
7
|
-
const AXIOS_RETRY_TIMEOUT = 5 * 1000; // sum = 5sec
|
|
8
7
|
|
|
9
8
|
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
10
9
|
|
|
@@ -31,6 +30,19 @@ const HTML_REPORT = {
|
|
|
31
30
|
|
|
32
31
|
const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
|
|
33
32
|
|
|
33
|
+
const REPORTER_REQUEST_RETRIES = {
|
|
34
|
+
retryTimeout: 5 * 1000, // sum = 5sec
|
|
35
|
+
retriesPerRequest: 2,
|
|
36
|
+
maxTotalRetries: Number(process.env.TESTOMATIO_MAX_REQUEST_FAILURES_COUNT) || 10,
|
|
37
|
+
withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const RunStatus = {
|
|
41
|
+
Passed: 'passed',
|
|
42
|
+
Failed: 'failed',
|
|
43
|
+
Finished: 'finished',
|
|
44
|
+
};
|
|
45
|
+
|
|
34
46
|
module.exports = {
|
|
35
47
|
APP_PREFIX,
|
|
36
48
|
TESTOMAT_TMP_STORAGE_DIR,
|
|
@@ -38,6 +50,7 @@ module.exports = {
|
|
|
38
50
|
STATUS,
|
|
39
51
|
HTML_REPORT,
|
|
40
52
|
AXIOS_TIMEOUT,
|
|
41
|
-
AXIOS_RETRY_TIMEOUT,
|
|
42
53
|
testomatLogoURL,
|
|
54
|
+
REPORTER_REQUEST_RETRIES,
|
|
55
|
+
RunStatus,
|
|
43
56
|
};
|
package/lib/fileUploader.js
CHANGED
|
@@ -20,6 +20,7 @@ const keys = [
|
|
|
20
20
|
'S3_BUCKET',
|
|
21
21
|
'S3_ACCESS_KEY_ID',
|
|
22
22
|
'S3_SECRET_ACCESS_KEY',
|
|
23
|
+
'S3_SESSION_TOKEN',
|
|
23
24
|
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
24
25
|
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
25
26
|
'S3_FORCE_PATH_STYLE',
|
|
@@ -74,7 +75,8 @@ const _getFileExtBase64 = str => {
|
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
const _getS3Config = () => {
|
|
77
|
-
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
|
|
78
|
+
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
|
|
79
|
+
getConfig();
|
|
78
80
|
|
|
79
81
|
const cfg = {
|
|
80
82
|
region: S3_REGION,
|
|
@@ -85,6 +87,10 @@ const _getS3Config = () => {
|
|
|
85
87
|
},
|
|
86
88
|
};
|
|
87
89
|
|
|
90
|
+
if (S3_SESSION_TOKEN) {
|
|
91
|
+
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
if (S3_ENDPOINT) {
|
|
89
95
|
cfg.endpoint = S3_ENDPOINT;
|
|
90
96
|
}
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -79,13 +79,13 @@ class GitLabPipe {
|
|
|
79
79
|
let summary = `${this.hiddenCommentData}
|
|
80
80
|
|
|
81
81
|
| [](https://testomat.io) | ${statusEmoji(
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
runParams.status,
|
|
83
|
+
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
84
84
|
| --- | --- |
|
|
85
85
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
86
86
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
'passed',
|
|
88
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
89
89
|
| Duration | 🕐 **${humanizeDuration(
|
|
90
90
|
parseInt(
|
|
91
91
|
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
package/lib/pipe/html.js
CHANGED
|
@@ -91,7 +91,7 @@ class HtmlPipe {
|
|
|
91
91
|
// GENERATE HTML reports based on the results data
|
|
92
92
|
this.buildReport({
|
|
93
93
|
runParams,
|
|
94
|
-
// TODO: this.tests=[] in case of Mocha
|
|
94
|
+
// TODO: this.tests=[] in case of Mocha, need retest by Vitalii
|
|
95
95
|
tests: this.tests,
|
|
96
96
|
outputPath: this.htmlOutputPath,
|
|
97
97
|
templatePath: this.templateHtmlPath,
|
|
@@ -123,30 +123,31 @@ class HtmlPipe {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
tests.forEach(test => {
|
|
126
|
-
if (!test.message
|
|
126
|
+
if (!test.message?.trim()) {
|
|
127
127
|
test.message = "This test has no 'message' code";
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
if (!test.suite_title
|
|
130
|
+
if (!test.suite_title?.trim()) {
|
|
131
131
|
test.suite_title = 'Unknown suite';
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
if (!test.title
|
|
134
|
+
if (!test.title?.trim()) {
|
|
135
135
|
test.title = 'Unknown test title';
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
if (!test.files
|
|
138
|
+
if (!test.files?.length) {
|
|
139
139
|
test.files = 'This test has no files';
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
if (test.steps) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
test.steps = removeAnsiColorCodes(test.steps);
|
|
147
|
-
}
|
|
142
|
+
if (!test.steps?.trim()) {
|
|
143
|
+
test.steps = "This test has no 'steps' code";
|
|
144
|
+
} else {
|
|
145
|
+
test.steps = removeAnsiColorCodes(test.steps);
|
|
148
146
|
}
|
|
149
147
|
|
|
148
|
+
// TODO: future-proof: currently there is no need to display Artifacts and Metadata in HTML
|
|
149
|
+
test.artifacts = test.artifacts || [];
|
|
150
|
+
test.meta = test.meta || {};
|
|
150
151
|
// TODO: u can added an additional test values to this checks in the future
|
|
151
152
|
});
|
|
152
153
|
|
|
@@ -199,7 +200,8 @@ class HtmlPipe {
|
|
|
199
200
|
|
|
200
201
|
return template(data);
|
|
201
202
|
} catch (e) {
|
|
202
|
-
console.log('
|
|
203
|
+
console.log(chalk.red('❌ Oops! An unknown error occurred when generating an HTML report'));
|
|
204
|
+
console.log(chalk.red(e));
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
|
|
@@ -209,42 +211,77 @@ class HtmlPipe {
|
|
|
209
211
|
(tests, status) => tests.filter(test => test.status.toLowerCase() === status.toLowerCase()).length,
|
|
210
212
|
);
|
|
211
213
|
|
|
212
|
-
handlebars.registerHelper(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
214
|
+
handlebars.registerHelper(
|
|
215
|
+
'selectComponent',
|
|
216
|
+
() =>
|
|
217
|
+
new handlebars.SafeString(
|
|
218
|
+
`<select style="width: 70px;height: 38px;" class="form-select" aria-label="Tests counter on page">
|
|
219
|
+
<option value="0">10</option>
|
|
220
|
+
<option value="1">25</option>
|
|
221
|
+
<option value="2">50</option>
|
|
222
|
+
</select>`,
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
handlebars.registerHelper(
|
|
227
|
+
'emptyDataComponent',
|
|
228
|
+
() =>
|
|
229
|
+
new handlebars.SafeString(
|
|
230
|
+
`<div class="noData m-0">
|
|
231
|
+
NO MATCHING TESTS
|
|
232
|
+
</div>`,
|
|
233
|
+
),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
handlebars.registerHelper('pageDispleyElements', tests => {
|
|
237
|
+
// We wrapp the lines to the HTML format we need
|
|
238
|
+
const totalTests = JSON.parse(
|
|
239
|
+
JSON.stringify(tests)
|
|
240
|
+
.replace(/<script>/g, '<script>')
|
|
241
|
+
.replace(/<\/script>/g, '</script>'), // eslint-disable-line
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const paginationOptions = {
|
|
245
|
+
0: 10,
|
|
246
|
+
1: 25,
|
|
247
|
+
2: 50,
|
|
248
|
+
};
|
|
249
|
+
const statuses = ['all', 'passed', 'failed', 'skipped'];
|
|
250
|
+
const pageItemGroups = {
|
|
251
|
+
all: {},
|
|
252
|
+
passed: {},
|
|
253
|
+
failed: {},
|
|
254
|
+
skipped: {},
|
|
255
|
+
totalTests,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
function paginateItems(items, pageSize) {
|
|
259
|
+
const paginatedItems = [];
|
|
260
|
+
for (let i = 0; i < items.length; i += pageSize) {
|
|
261
|
+
paginatedItems.push(items.slice(i, i + pageSize));
|
|
262
|
+
}
|
|
263
|
+
return paginatedItems;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
statuses.forEach(status => {
|
|
267
|
+
for (const option in paginationOptions) {
|
|
268
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
269
|
+
if (paginationOptions.hasOwnProperty(option)) {
|
|
270
|
+
const pageSize = paginationOptions[option];
|
|
271
|
+
let filteredItems = totalTests;
|
|
272
|
+
|
|
273
|
+
if (status !== 'all') {
|
|
274
|
+
filteredItems = totalTests.filter(item => item.status === status);
|
|
239
275
|
}
|
|
276
|
+
|
|
277
|
+
pageItemGroups[status][option] = paginateItems(filteredItems, pageSize);
|
|
240
278
|
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
241
281
|
|
|
242
|
-
|
|
243
|
-
});
|
|
244
|
-
}
|
|
282
|
+
pageItemGroups.totalTests = totalTests;
|
|
245
283
|
|
|
246
|
-
|
|
247
|
-
return JSON.stringify(replaceScriptTagsInArray(tests));
|
|
284
|
+
return JSON.stringify(pageItemGroups);
|
|
248
285
|
});
|
|
249
286
|
}
|
|
250
287
|
|
|
@@ -261,7 +298,7 @@ class HtmlPipe {
|
|
|
261
298
|
*/
|
|
262
299
|
function testExecutionSumTime(tests) {
|
|
263
300
|
const totalMilliseconds = tests.reduce((sum, test) => {
|
|
264
|
-
if (typeof test.run_time === 'number') {
|
|
301
|
+
if (typeof test.run_time === 'number' && !Number.isNaN(test.run_time)) {
|
|
265
302
|
return sum + test.run_time;
|
|
266
303
|
}
|
|
267
304
|
return sum;
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -6,7 +6,7 @@ const axiosRetry = require('axios-retry');
|
|
|
6
6
|
const axios = require('axios');
|
|
7
7
|
const JsonCycle = require('json-cycle');
|
|
8
8
|
|
|
9
|
-
const { APP_PREFIX, STATUS, AXIOS_TIMEOUT,
|
|
9
|
+
const { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } = require('../constants');
|
|
10
10
|
const { isValidUrl, foundedTestLog } = require('../utils/utils');
|
|
11
11
|
const { parseFilterParams, generateFilterRequestParams, setS3Credentials } = require('../utils/pipe_utils');
|
|
12
12
|
const config = require('../config');
|
|
@@ -23,6 +23,10 @@ if (process.env.TESTOMATIO_RUN) {
|
|
|
23
23
|
*/
|
|
24
24
|
class TestomatioPipe {
|
|
25
25
|
constructor(params, store) {
|
|
26
|
+
this.retriesTimestamps = [];
|
|
27
|
+
this.reportingCanceledDueToReqFailures = false;
|
|
28
|
+
this.notReportedTestsCount = 0;
|
|
29
|
+
|
|
26
30
|
this.isEnabled = false;
|
|
27
31
|
this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
28
32
|
this.apiKey = params.apiKey || config.TESTOMATIO;
|
|
@@ -43,25 +47,30 @@ class TestomatioPipe {
|
|
|
43
47
|
baseURL: `${this.url.trim()}`,
|
|
44
48
|
timeout: AXIOS_TIMEOUT,
|
|
45
49
|
});
|
|
50
|
+
|
|
46
51
|
// Pass the axios instance to the retry function
|
|
47
52
|
axiosRetry(this.axios, {
|
|
48
|
-
|
|
53
|
+
// do not use retries for unit tests
|
|
54
|
+
retries: REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
|
|
49
55
|
shouldResetTimeout: true,
|
|
50
56
|
retryCondition: error => {
|
|
51
|
-
|
|
52
|
-
switch (error.response
|
|
53
|
-
case
|
|
54
|
-
case
|
|
55
|
-
case
|
|
56
|
-
case
|
|
57
|
-
return
|
|
57
|
+
if (!error.response) return false;
|
|
58
|
+
switch (error.response?.status) {
|
|
59
|
+
case 400: // Bad request (probably wrong API key)
|
|
60
|
+
case 404: // Test not matched
|
|
61
|
+
case 429: // Rate limit exceeded
|
|
62
|
+
case 500: // Internal server error
|
|
63
|
+
return false;
|
|
58
64
|
default:
|
|
59
|
-
|
|
65
|
+
break;
|
|
60
66
|
}
|
|
67
|
+
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
61
68
|
},
|
|
62
|
-
retryDelay:
|
|
63
|
-
onRetry: retryCount => {
|
|
64
|
-
|
|
69
|
+
retryDelay: () => REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
|
|
70
|
+
onRetry: async (retryCount, error) => {
|
|
71
|
+
this.retriesTimestamps.push(Date.now());
|
|
72
|
+
|
|
73
|
+
debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
|
|
65
74
|
},
|
|
66
75
|
});
|
|
67
76
|
|
|
@@ -190,13 +199,45 @@ class TestomatioPipe {
|
|
|
190
199
|
'Error creating Testomat.io report, please check if your API key is valid. Skipping report | ',
|
|
191
200
|
err?.response?.statusText || err?.status || err.message,
|
|
192
201
|
);
|
|
202
|
+
printCreateIssue(err);
|
|
193
203
|
}
|
|
194
204
|
debug('"createRun" function finished');
|
|
195
205
|
}
|
|
196
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Decides whether to skip test reporting in case of too many request failures
|
|
209
|
+
* @param {TestData} testData
|
|
210
|
+
* @returns {boolean}
|
|
211
|
+
*/
|
|
212
|
+
#cancelTestReportingInCaseOfTooManyReqFailures(testData) {
|
|
213
|
+
if (this.reportingCanceledDueToReqFailures) return true;
|
|
214
|
+
|
|
215
|
+
const retriesCountWithinTime = this.retriesTimestamps.filter(
|
|
216
|
+
timestamp => Date.now() - timestamp < REPORTER_REQUEST_RETRIES.withinTimeSeconds * 1000,
|
|
217
|
+
).length;
|
|
218
|
+
debug(`${retriesCountWithinTime} failed requests within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s`);
|
|
219
|
+
|
|
220
|
+
if (retriesCountWithinTime > REPORTER_REQUEST_RETRIES.maxTotalRetries) {
|
|
221
|
+
const errorMessage = chalk.yellow(
|
|
222
|
+
`${retriesCountWithinTime} requests were failed within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s,\
|
|
223
|
+
reporting for test "${testData.title}" to Testomat is skipped`,
|
|
224
|
+
);
|
|
225
|
+
console.warn(`${APP_PREFIX} ${errorMessage}`);
|
|
226
|
+
|
|
227
|
+
this.reportingCanceledDueToReqFailures = true;
|
|
228
|
+
this.notReportedTestsCount++;
|
|
229
|
+
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
197
236
|
addTest(data) {
|
|
198
237
|
if (!this.isEnabled) return;
|
|
199
238
|
if (!this.runId) return;
|
|
239
|
+
if (this.#cancelTestReportingInCaseOfTooManyReqFailures(data)) return;
|
|
240
|
+
|
|
200
241
|
data.api_key = this.apiKey;
|
|
201
242
|
data.create = this.createNewTests;
|
|
202
243
|
|
|
@@ -227,7 +268,7 @@ class TestomatioPipe {
|
|
|
227
268
|
chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
228
269
|
chalk.grey(data?.title || ''),
|
|
229
270
|
);
|
|
230
|
-
if (err.response
|
|
271
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
231
272
|
this.hasUnmatchedTests = true;
|
|
232
273
|
}
|
|
233
274
|
return;
|
|
@@ -237,6 +278,7 @@ class TestomatioPipe {
|
|
|
237
278
|
chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
238
279
|
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
239
280
|
);
|
|
281
|
+
printCreateIssue(err);
|
|
240
282
|
} else {
|
|
241
283
|
console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
242
284
|
}
|
|
@@ -248,8 +290,15 @@ class TestomatioPipe {
|
|
|
248
290
|
* @returns
|
|
249
291
|
*/
|
|
250
292
|
async finishRun(params) {
|
|
251
|
-
debug('Finishing run...');
|
|
252
293
|
if (!this.isEnabled) return;
|
|
294
|
+
debug('Finishing run...');
|
|
295
|
+
|
|
296
|
+
if (this.reportingCanceledDueToReqFailures) {
|
|
297
|
+
const errorMessage = chalk.red(
|
|
298
|
+
`⚠️ Due to request failures, ${this.notReportedTestsCount} test(s) were not reported to Testomat.io`,
|
|
299
|
+
);
|
|
300
|
+
console.warn(`${APP_PREFIX} ${errorMessage}`);
|
|
301
|
+
}
|
|
253
302
|
|
|
254
303
|
const { status, parallel } = params;
|
|
255
304
|
|
|
@@ -306,6 +355,7 @@ class TestomatioPipe {
|
|
|
306
355
|
}
|
|
307
356
|
} catch (err) {
|
|
308
357
|
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
358
|
+
printCreateIssue(err);
|
|
309
359
|
}
|
|
310
360
|
debug('Run finished');
|
|
311
361
|
}
|
|
@@ -315,4 +365,27 @@ class TestomatioPipe {
|
|
|
315
365
|
}
|
|
316
366
|
}
|
|
317
367
|
|
|
368
|
+
let registeredErrorHints = false;
|
|
369
|
+
function printCreateIssue(err) {
|
|
370
|
+
if (registeredErrorHints) return;
|
|
371
|
+
registeredErrorHints = true;
|
|
372
|
+
process.on('exit', () => {
|
|
373
|
+
console.log();
|
|
374
|
+
console.log(APP_PREFIX, 'There was an error reporting to Testomat.io:');
|
|
375
|
+
console.log(
|
|
376
|
+
APP_PREFIX,
|
|
377
|
+
'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new',
|
|
378
|
+
); // eslint-disable-line max-len
|
|
379
|
+
console.log(APP_PREFIX, 'Provide this information:');
|
|
380
|
+
console.log('Error:', err.message || err.code);
|
|
381
|
+
if (!err.config) return;
|
|
382
|
+
|
|
383
|
+
const time = new Date().toUTCString();
|
|
384
|
+
const { data, url, baseURL, method } = err?.config || {};
|
|
385
|
+
console.log('```js');
|
|
386
|
+
console.log({ data: data?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
|
|
387
|
+
console.log('```');
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
318
391
|
module.exports = TestomatioPipe;
|