@testomatio/reporter 2.7.6 → 2.7.9-beta.1-markdown
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/cli.js +1 -1
- package/lib/constants.d.ts +9 -1
- package/lib/constants.js +22 -4
- package/lib/pipe/coverage.js +1 -1
- package/lib/pipe/debug.d.ts +2 -0
- package/lib/pipe/debug.js +29 -13
- package/lib/pipe/html.js +21 -108
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/markdown.d.ts +25 -0
- package/lib/pipe/markdown.js +698 -0
- package/lib/pipe/testomatio.js +20 -17
- package/lib/replay.d.ts +2 -1
- package/lib/replay.js +20 -15
- package/lib/template/testomatio.hbs +0 -2
- package/lib/utils/debug.d.ts +12 -0
- package/lib/utils/debug.js +27 -0
- package/lib/xmlReader.js +25 -3
- package/package.json +1 -2
- package/src/bin/cli.js +2 -2
- package/src/constants.js +17 -2
- package/src/pipe/coverage.js +2 -2
- package/src/pipe/debug.js +27 -13
- package/src/pipe/html.js +18 -108
- package/src/pipe/index.js +2 -0
- package/src/pipe/markdown.js +743 -0
- package/src/pipe/testomatio.js +28 -14
- package/src/replay.js +23 -17
- package/src/template/testomatio.hbs +0 -2
- package/src/utils/debug.js +20 -0
- package/src/xmlReader.js +27 -3
- package/types/types.d.ts +5 -0
package/src/pipe/testomatio.js
CHANGED
|
@@ -2,7 +2,13 @@ import createDebugMessages from 'debug';
|
|
|
2
2
|
import pc from 'picocolors';
|
|
3
3
|
import { Gaxios } from 'gaxios';
|
|
4
4
|
import JsonCycle from 'json-cycle';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
APP_PREFIX,
|
|
7
|
+
STATUS,
|
|
8
|
+
REQUEST_TIMEOUT,
|
|
9
|
+
getCreateRunRequestTimeout,
|
|
10
|
+
REPORTER_REQUEST_RETRIES,
|
|
11
|
+
} from '../constants.js';
|
|
6
12
|
import {
|
|
7
13
|
isValidUrl,
|
|
8
14
|
foundedTestLog,
|
|
@@ -81,7 +87,7 @@ class TestomatioPipe {
|
|
|
81
87
|
// Create a new instance of gaxios with a custom config
|
|
82
88
|
this.client = new Gaxios({
|
|
83
89
|
baseURL: `${this.url.trim()}`,
|
|
84
|
-
timeout:
|
|
90
|
+
timeout: REQUEST_TIMEOUT,
|
|
85
91
|
proxy: proxy ? proxy.toString() : undefined,
|
|
86
92
|
retry: true,
|
|
87
93
|
retryConfig: {
|
|
@@ -256,6 +262,7 @@ class TestomatioPipe {
|
|
|
256
262
|
method: 'PUT',
|
|
257
263
|
url: `/api/reporter/${this.runId}`,
|
|
258
264
|
data: runParams,
|
|
265
|
+
timeout: getCreateRunRequestTimeout(),
|
|
259
266
|
responseType: 'json',
|
|
260
267
|
});
|
|
261
268
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
@@ -277,6 +284,7 @@ class TestomatioPipe {
|
|
|
277
284
|
method: 'POST',
|
|
278
285
|
url: '/api/reporter',
|
|
279
286
|
data: runParams,
|
|
287
|
+
timeout: getCreateRunRequestTimeout(),
|
|
280
288
|
maxContentLength: Infinity,
|
|
281
289
|
responseType: 'json',
|
|
282
290
|
});
|
|
@@ -294,14 +302,13 @@ class TestomatioPipe {
|
|
|
294
302
|
process.env.runId = this.runId;
|
|
295
303
|
debug('Run created', this.runId);
|
|
296
304
|
} catch (err) {
|
|
305
|
+
if (!this.apiKey) console.error('Testomat.io API key is not set');
|
|
297
306
|
const errorText = err.response?.data?.message || err.message;
|
|
298
307
|
debug('Error creating run', err);
|
|
299
|
-
console.log(errorText || err);
|
|
308
|
+
console.log(APP_PREFIX, errorText || err);
|
|
300
309
|
if (err.response?.status === 403) this.#disablePipe();
|
|
301
|
-
if (!this.apiKey) console.error('Testomat.io API key is not set');
|
|
302
|
-
if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
|
|
303
310
|
|
|
304
|
-
|
|
311
|
+
this.#logFailedResponse(err);
|
|
305
312
|
|
|
306
313
|
console.error(
|
|
307
314
|
APP_PREFIX,
|
|
@@ -532,8 +539,8 @@ class TestomatioPipe {
|
|
|
532
539
|
);
|
|
533
540
|
}
|
|
534
541
|
} catch (err) {
|
|
535
|
-
log
|
|
536
|
-
|
|
542
|
+
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
543
|
+
this.#logFailedResponse(err);
|
|
537
544
|
printCreateIssue();
|
|
538
545
|
}
|
|
539
546
|
debug('Run finished');
|
|
@@ -558,19 +565,26 @@ class TestomatioPipe {
|
|
|
558
565
|
responseBody = hideTestomatioToken(responseBody);
|
|
559
566
|
|
|
560
567
|
const statusCode = error.status || error.code || error.response?.status || '<unknown status code>';
|
|
561
|
-
const method = error.response?.config
|
|
562
|
-
const url = error.response?.config
|
|
568
|
+
const method = error.response?.config?.method || '<unknown method>';
|
|
569
|
+
const url = String(error.response?.config?.url || '<unknown url>');
|
|
570
|
+
|
|
571
|
+
let message = pc.yellow('⚠️ Request to Testomat.io failed:\n');
|
|
572
|
+
message += pc.bold(`${pc.red(statusCode)} ${method} ${pc.gray(url)}\n`);
|
|
573
|
+
|
|
574
|
+
if (statusCode === 403) {
|
|
575
|
+
message += `\t${pc.red('Please check your API token. It might be invalid or expired.')}\n`;
|
|
576
|
+
}
|
|
563
577
|
|
|
564
|
-
let message = pc.yellow('\n⚠️ Request to Testomat.io failed:\n');
|
|
565
|
-
message += pc.bold(`${pc.red(statusCode)} ${method} ${url}\n`);
|
|
566
578
|
message += `\t${pc.bold('response: ')}${pc.gray(responseBody)}\n`;
|
|
567
579
|
|
|
568
580
|
const requestBody = hideTestomatioToken(stringify(error.response?.config?.data));
|
|
569
|
-
if (process.env.DEBUG || process.env.TESTOMATIO_DEBUG) {
|
|
581
|
+
if (process.env.DEBUG || process.env.TESTOMATIO_DEBUG || requestBody.length < 1000) {
|
|
582
|
+
// full body
|
|
570
583
|
message += `\t${pc.bold('request: ')}${pc.gray(requestBody)}\n`;
|
|
571
584
|
} else {
|
|
585
|
+
// cut body
|
|
572
586
|
const requestBodyCut = requestBody.slice(0, 1000);
|
|
573
|
-
message += `\t${pc.bold('request: ')}${pc.gray(`${requestBodyCut}
|
|
587
|
+
message += `\t${pc.bold('request: ')}${pc.gray(`${requestBodyCut}...`)}\n`;
|
|
574
588
|
message += '\trequest body is cut, run with TESTOMATIO_DEBUG=1 to see full body\n';
|
|
575
589
|
}
|
|
576
590
|
|
package/src/replay.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
3
|
import TestomatClient from './client.js';
|
|
5
|
-
import { STATUS } from './constants.js';
|
|
4
|
+
import { STATUS, DEBUG_FILE } from './constants.js';
|
|
6
5
|
import { config } from './config.js';
|
|
7
6
|
|
|
8
7
|
export class Replay {
|
|
@@ -15,11 +14,12 @@ export class Replay {
|
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
|
-
* Get the default debug file path
|
|
17
|
+
* Get the default debug file path.
|
|
18
|
+
* Returns ./testomatio.debug.json (actual file in CI, symlink to tmp file in local).
|
|
19
19
|
* @returns {string} Path to the latest debug file
|
|
20
20
|
*/
|
|
21
21
|
getDefaultDebugFile() {
|
|
22
|
-
return path.join(
|
|
22
|
+
return path.join(process.cwd(), `${DEBUG_FILE}.json`);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -119,8 +119,11 @@ export class Replay {
|
|
|
119
119
|
// Handle tests without rid (no deduplication)
|
|
120
120
|
testsWithoutRid.push({ ...test });
|
|
121
121
|
}
|
|
122
|
-
} else if (logEntry.
|
|
122
|
+
} else if (logEntry.action === 'finishRun') {
|
|
123
123
|
finishParams = logEntry.params || {};
|
|
124
|
+
if (logEntry.runId && !runId) {
|
|
125
|
+
runId = logEntry.runId;
|
|
126
|
+
}
|
|
124
127
|
}
|
|
125
128
|
} catch (err) {
|
|
126
129
|
parseErrors++;
|
|
@@ -203,23 +206,28 @@ export class Replay {
|
|
|
203
206
|
};
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
|
|
209
|
+
if (runId) {
|
|
210
|
+
this.onLog(`Using existing run ID: ${runId}`);
|
|
211
|
+
process.env.TESTOMATIO_RUN = runId;
|
|
212
|
+
} else {
|
|
213
|
+
this.onLog('Publishing to run...');
|
|
214
|
+
}
|
|
215
|
+
process.env.TESTOMATIO_REPLAY = '1';
|
|
216
|
+
|
|
207
217
|
const client = new TestomatClient({
|
|
208
218
|
apiKey: this.apiKey,
|
|
209
219
|
isBatchEnabled: true,
|
|
210
220
|
...runParams,
|
|
221
|
+
...(runId && { runId }),
|
|
211
222
|
});
|
|
212
223
|
|
|
213
|
-
// Use the stored runId if available, otherwise create a new run
|
|
214
224
|
if (runId) {
|
|
215
|
-
this.onLog(`Using existing run ID: ${runId}`);
|
|
216
225
|
client.runId = runId;
|
|
217
|
-
|
|
218
|
-
this.onLog('Publishing to run...');
|
|
219
|
-
await client.createRun(runParams);
|
|
226
|
+
client.pipeStore.runId = runId;
|
|
220
227
|
}
|
|
221
228
|
|
|
222
|
-
|
|
229
|
+
await client.createRun(runParams);
|
|
230
|
+
|
|
223
231
|
let successCount = 0;
|
|
224
232
|
let failureCount = 0;
|
|
225
233
|
|
|
@@ -248,7 +256,9 @@ export class Replay {
|
|
|
248
256
|
|
|
249
257
|
await client.updateRunStatus(finishParams.status || STATUS.FINISHED);
|
|
250
258
|
|
|
251
|
-
|
|
259
|
+
this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
|
|
260
|
+
|
|
261
|
+
return {
|
|
252
262
|
success: true,
|
|
253
263
|
testsCount: tests.length,
|
|
254
264
|
successCount,
|
|
@@ -258,10 +268,6 @@ export class Replay {
|
|
|
258
268
|
envVars,
|
|
259
269
|
runId: runId || client.runId,
|
|
260
270
|
};
|
|
261
|
-
|
|
262
|
-
this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
|
|
263
|
-
|
|
264
|
-
return result;
|
|
265
271
|
}
|
|
266
272
|
}
|
|
267
273
|
|
|
@@ -2242,7 +2242,6 @@
|
|
|
2242
2242
|
<div class='env-var-item {{#unless this.isSet}}env-var-unset{{/unless}}'>
|
|
2243
2243
|
<div class='env-var-info'>
|
|
2244
2244
|
<div class='env-var-key'>{{@key}}</div>
|
|
2245
|
-
<div class='env-var-description'>{{this.description}}</div>
|
|
2246
2245
|
</div>
|
|
2247
2246
|
<div class='env-var-value {{#unless this.isSet}}env-var-empty{{/unless}} {{#if this.isSensitive}}env-var-confidential{{/if}}'>
|
|
2248
2247
|
{{#if this.isSensitive}}
|
|
@@ -2267,7 +2266,6 @@
|
|
|
2267
2266
|
<div class='env-var-item {{#unless this.isSet}}env-var-unset{{/unless}}'>
|
|
2268
2267
|
<div class='env-var-info'>
|
|
2269
2268
|
<div class='env-var-key'>{{@key}}</div>
|
|
2270
|
-
<div class='env-var-description'>{{this.description}}</div>
|
|
2271
2269
|
</div>
|
|
2272
2270
|
<div class='env-var-value {{#unless this.isSet}}env-var-empty{{/unless}} {{#if this.isSensitive}}env-var-confidential{{/if}}'>
|
|
2273
2271
|
{{#if this.isSensitive}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { DEBUG_FILE } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the debug file path(s).
|
|
7
|
+
*
|
|
8
|
+
* Always creates a timestamped file in tmp dir and a symlink in project root.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} [suffix] - Optional suffix appended to the base name (e.g. 'replay').
|
|
11
|
+
* @returns {{root: string, tmp: string}} root path (symlink), tmp path (actual file)
|
|
12
|
+
*/
|
|
13
|
+
export function getDebugFilePath(suffix = '') {
|
|
14
|
+
const dateTime = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-');
|
|
15
|
+
const baseName = suffix ? `${DEBUG_FILE}-${suffix}` : DEBUG_FILE;
|
|
16
|
+
return {
|
|
17
|
+
root: path.join(process.cwd(), `${baseName}.json`),
|
|
18
|
+
tmp: path.join(os.tmpdir(), `${baseName}.${dateTime}.json`),
|
|
19
|
+
};
|
|
20
|
+
}
|
package/src/xmlReader.js
CHANGED
|
@@ -39,18 +39,29 @@ const {
|
|
|
39
39
|
TESTOMATIO_RUN,
|
|
40
40
|
TESTOMATIO_MARK_DETACHED,
|
|
41
41
|
TESTOMATIO_LEGACY_NUNIT,
|
|
42
|
+
TESTOMATIO_MAX_ENTITY_EXPANSIONS,
|
|
42
43
|
} = process.env;
|
|
43
44
|
|
|
45
|
+
const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
|
|
46
|
+
const MAX_ENTITY_EXPANSIONS = parseInt(TESTOMATIO_MAX_ENTITY_EXPANSIONS, 10) || 10000;
|
|
47
|
+
const ENTITY_EXPANSION_LIMIT_REGEXP = /Entity expansion limit exceeded/i;
|
|
48
|
+
|
|
44
49
|
const options = {
|
|
45
50
|
ignoreDeclaration: true,
|
|
46
51
|
ignoreAttributes: false,
|
|
47
52
|
alwaysCreateTextNode: false,
|
|
48
53
|
attributeNamePrefix: '',
|
|
49
54
|
parseTagValue: true,
|
|
55
|
+
processEntities: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
maxEntitySize: 10000,
|
|
58
|
+
maxExpansionDepth: 10,
|
|
59
|
+
maxTotalExpansions: MAX_ENTITY_EXPANSIONS,
|
|
60
|
+
maxExpandedLength: 100000,
|
|
61
|
+
maxEntityCount: 10000,
|
|
62
|
+
},
|
|
50
63
|
};
|
|
51
64
|
|
|
52
|
-
const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
|
|
53
|
-
|
|
54
65
|
const reduceOptions = {};
|
|
55
66
|
|
|
56
67
|
class XmlReader {
|
|
@@ -113,7 +124,20 @@ class XmlReader {
|
|
|
113
124
|
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, MAX_OUTPUT_LENGTH)}${p3}`);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
|
-
|
|
127
|
+
let jsonResult;
|
|
128
|
+
try {
|
|
129
|
+
jsonResult = this.parser.parse(xmlData);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (ENTITY_EXPANSION_LIMIT_REGEXP.test(error.message)) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`${error.message}\n\n` +
|
|
134
|
+
`XML report contains more entity references than the current limit (${MAX_ENTITY_EXPANSIONS}). ` +
|
|
135
|
+
'If this XML report is trusted, increase the limit with TESTOMATIO_MAX_ENTITY_EXPANSIONS, for example:\n' +
|
|
136
|
+
`TESTOMATIO_MAX_ENTITY_EXPANSIONS=${MAX_ENTITY_EXPANSIONS * 2} npx report-xml "{pattern}" --lang={lang}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
117
141
|
let jsonSuite;
|
|
118
142
|
|
|
119
143
|
if (jsonResult.testsuites) {
|
package/types/types.d.ts
CHANGED
|
@@ -231,6 +231,11 @@ export interface HtmlTestData extends TestData {
|
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Extended test data for Markdown reporter.
|
|
236
|
+
*/
|
|
237
|
+
export interface MarkdownTestData extends HtmlTestData {}
|
|
238
|
+
|
|
234
239
|
/**
|
|
235
240
|
* Object representing a result of a Run.
|
|
236
241
|
*/
|