@testomatio/reporter 2.0.0-beta-esm → 2.0.0-beta.1-xml
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/adapter/codecept.d.ts +2 -0
- package/lib/adapter/codecept.js +31 -26
- package/lib/adapter/cucumber/current.d.ts +14 -0
- package/lib/adapter/cucumber/legacy.d.ts +0 -0
- package/lib/adapter/cucumber.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.js +10 -10
- package/lib/adapter/jasmine.d.ts +11 -0
- package/lib/adapter/jest.d.ts +13 -0
- package/lib/adapter/mocha.d.ts +2 -0
- package/lib/adapter/mocha.js +4 -4
- package/lib/adapter/nightwatch.d.ts +4 -0
- package/lib/adapter/nightwatch.js +80 -0
- package/lib/adapter/playwright.d.ts +14 -0
- package/lib/adapter/playwright.js +58 -33
- package/lib/adapter/vitest.d.ts +35 -0
- package/lib/adapter/vitest.js +6 -6
- package/lib/adapter/webdriver.d.ts +24 -0
- package/lib/adapter/webdriver.js +51 -14
- package/lib/bin/cli.d.ts +2 -0
- package/lib/bin/cli.js +250 -0
- package/lib/bin/reportXml.d.ts +2 -0
- package/lib/bin/reportXml.js +15 -11
- package/lib/bin/startTest.d.ts +2 -0
- package/lib/bin/startTest.js +12 -7
- package/lib/bin/uploadArtifacts.d.ts +2 -0
- package/lib/bin/uploadArtifacts.js +82 -0
- package/lib/client.d.ts +76 -0
- package/lib/client.js +128 -53
- package/lib/config.d.ts +1 -0
- package/lib/config.js +2 -2
- package/lib/constants.d.ts +25 -0
- package/lib/constants.js +5 -1
- package/lib/data-storage.d.ts +34 -0
- package/lib/data-storage.js +19 -9
- package/lib/junit-adapter/adapter.d.ts +9 -0
- package/lib/junit-adapter/csharp.d.ts +5 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/junit-adapter/index.d.ts +3 -0
- package/lib/junit-adapter/java.d.ts +5 -0
- package/lib/junit-adapter/javascript.d.ts +4 -0
- package/lib/junit-adapter/python.d.ts +5 -0
- package/lib/junit-adapter/ruby.d.ts +4 -0
- package/lib/output.d.ts +11 -0
- package/lib/package.json +3 -1
- package/lib/pipe/bitbucket.d.ts +23 -0
- package/lib/pipe/bitbucket.js +19 -9
- package/lib/pipe/csv.d.ts +47 -0
- package/lib/pipe/csv.js +2 -2
- package/lib/pipe/debug.d.ts +29 -0
- package/lib/pipe/debug.js +108 -0
- package/lib/pipe/github.d.ts +30 -0
- package/lib/pipe/github.js +37 -5
- package/lib/pipe/gitlab.d.ts +23 -0
- package/lib/pipe/gitlab.js +2 -3
- package/lib/pipe/html.d.ts +35 -0
- package/lib/pipe/html.js +9 -4
- package/lib/pipe/index.d.ts +1 -0
- package/lib/pipe/index.js +20 -10
- package/lib/pipe/testomatio.d.ts +70 -0
- package/lib/pipe/testomatio.js +54 -39
- package/lib/reporter-functions.d.ts +34 -0
- package/lib/reporter-functions.js +17 -7
- package/lib/reporter.d.ts +232 -0
- package/lib/reporter.js +19 -33
- package/lib/services/artifacts.d.ts +33 -0
- package/lib/services/index.d.ts +9 -0
- package/lib/services/key-values.d.ts +27 -0
- package/lib/services/key-values.js +1 -1
- package/lib/services/logger.d.ts +64 -0
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +651 -1366
- package/lib/uploader.d.ts +60 -0
- package/lib/uploader.js +312 -0
- package/lib/utils/pipe_utils.d.ts +41 -0
- package/lib/utils/pipe_utils.js +3 -5
- package/lib/utils/utils.d.ts +47 -0
- package/lib/utils/utils.js +99 -12
- package/lib/xmlReader.d.ts +92 -0
- package/lib/xmlReader.js +64 -25
- package/package.json +19 -13
- package/src/adapter/codecept.js +30 -26
- package/src/adapter/cypress-plugin/index.js +5 -5
- package/src/adapter/mocha.cjs +1 -1
- package/src/adapter/mocha.js +4 -4
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/playwright.js +59 -31
- package/src/adapter/vitest.js +6 -6
- package/src/adapter/webdriver.js +42 -12
- package/src/bin/cli.js +303 -0
- package/src/bin/reportXml.js +19 -9
- package/src/bin/startTest.js +9 -4
- package/src/bin/uploadArtifacts.js +91 -0
- package/src/client.js +137 -57
- package/src/config.js +2 -2
- package/src/constants.js +5 -1
- package/src/data-storage.js +2 -2
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +2 -2
- package/src/pipe/csv.js +3 -3
- package/src/pipe/debug.js +104 -0
- package/src/pipe/github.js +3 -5
- package/src/pipe/gitlab.js +6 -7
- package/src/pipe/html.js +14 -7
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +75 -76
- package/src/reporter-functions.js +18 -7
- package/src/reporter.cjs_decprecated +21 -0
- package/src/reporter.js +20 -11
- package/src/services/key-values.js +1 -1
- package/src/services/logger.js +5 -4
- package/src/template/testomatio.hbs +651 -1366
- package/src/uploader.js +371 -0
- package/src/utils/pipe_utils.js +4 -12
- package/src/utils/utils.js +64 -15
- package/src/xmlReader.js +76 -26
- package/lib/adapter/jasmine/jasmine.js +0 -63
- package/lib/adapter/mocha/mocha.js +0 -125
- package/lib/fileUploader.js +0 -245
- package/lib/utils/chalk.js +0 -10
- package/src/fileUploader.js +0 -307
- package/src/reporter.cjs +0 -22
- package/src/utils/chalk.js +0 -13
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import createDebugMessages from 'debug';
|
|
5
|
+
import { APP_PREFIX } from '../constants.js';
|
|
6
|
+
import prettyMs from 'pretty-ms';
|
|
7
|
+
|
|
8
|
+
const debug = createDebugMessages('@testomatio/reporter:pipe:debug');
|
|
9
|
+
|
|
10
|
+
export class DebugPipe {
|
|
11
|
+
constructor(params, store) {
|
|
12
|
+
this.params = params || {};
|
|
13
|
+
this.store = store || {};
|
|
14
|
+
|
|
15
|
+
this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
|
|
16
|
+
if (this.isEnabled) {
|
|
17
|
+
this.batch = {
|
|
18
|
+
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
19
|
+
intervalFunction: null,
|
|
20
|
+
intervalTime: 5000,
|
|
21
|
+
tests: [],
|
|
22
|
+
batchIndex: 0,
|
|
23
|
+
};
|
|
24
|
+
this.logFilePath = path.join(os.tmpdir(), `testomatio.debug.${Date.now()}.json`);
|
|
25
|
+
|
|
26
|
+
debug('Creating debug file:', this.logFilePath);
|
|
27
|
+
fs.writeFileSync(this.logFilePath, '');
|
|
28
|
+
console.log(APP_PREFIX, '🪲. Debug created:');
|
|
29
|
+
this.testomatioEnvVars = Object.keys(process.env)
|
|
30
|
+
.filter(key => key.startsWith('TESTOMATIO_'))
|
|
31
|
+
.reduce((acc, key) => {
|
|
32
|
+
acc[key] = process.env[key];
|
|
33
|
+
return acc;
|
|
34
|
+
}, {});
|
|
35
|
+
this.logToFile({ datetime: new Date().toISOString(), timestamp: Date.now() });
|
|
36
|
+
this.logToFile({ data: 'variables', testomatioEnvVars: this.testomatioEnvVars });
|
|
37
|
+
this.logToFile({ data: 'store', store: this.store || {} });
|
|
38
|
+
// Bind batchUpload to the instance
|
|
39
|
+
this.batchUpload = this.batchUpload.bind(this);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Logs data to a file if logging is enabled.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} logData - The data to be logged.
|
|
47
|
+
* @returns {Promise<void>} A promise that resolves when the log data has been appended to the file.
|
|
48
|
+
*/
|
|
49
|
+
logToFile(logData) {
|
|
50
|
+
if (!this.isEnabled) return;
|
|
51
|
+
const timePassedFromLastAction = Date.now() - (this.lastActionTimestamp || Date.now());
|
|
52
|
+
this.lastActionTimestamp = Date.now();
|
|
53
|
+
|
|
54
|
+
const logLine = JSON.stringify({ t: `+${prettyMs(timePassedFromLastAction)}`, ...logData });
|
|
55
|
+
fs.appendFileSync(this.logFilePath, `${logLine}\n`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async prepareRun(opts) {
|
|
59
|
+
if (!this.isEnabled) return [];
|
|
60
|
+
|
|
61
|
+
this.logToFile({ action: 'prepareRun', data: opts });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async createRun(params = {}) {
|
|
65
|
+
if (!this.isEnabled) return;
|
|
66
|
+
if (params.isBatchEnabled === true || params.isBatchEnabled === false) this.batch.isEnabled = params.isBatchEnabled;
|
|
67
|
+
|
|
68
|
+
if (!this.isEnabled) return {};
|
|
69
|
+
if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.batchUpload, this.batch.intervalTime);
|
|
70
|
+
|
|
71
|
+
this.logToFile({ action: 'createRun', params });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async addTest(data) {
|
|
75
|
+
if (!this.isEnabled) return;
|
|
76
|
+
|
|
77
|
+
if (!this.batch.isEnabled) this.logToFile({ action: 'addTest', testId: data });
|
|
78
|
+
else this.batch.tests.push(data);
|
|
79
|
+
|
|
80
|
+
if (!this.batch.intervalFunction) await this.batchUpload();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async batchUpload() {
|
|
84
|
+
this.batch.batchIndex++;
|
|
85
|
+
if (!this.batch.isEnabled) return;
|
|
86
|
+
if (!this.batch.tests.length) return;
|
|
87
|
+
|
|
88
|
+
const testsToSend = this.batch.tests.splice(0);
|
|
89
|
+
|
|
90
|
+
this.logToFile({ action: 'addTestsBatch', tests: testsToSend });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async finishRun(params) {
|
|
94
|
+
if (!this.isEnabled) return;
|
|
95
|
+
this.logToFile({ actions: 'finishRun', params });
|
|
96
|
+
await this.batchUpload();
|
|
97
|
+
if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
|
|
98
|
+
console.log(APP_PREFIX, '🪲. Debug Saved to', this.logFilePath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
toString() {
|
|
102
|
+
return 'Debug Reporter';
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/pipe/github.js
CHANGED
|
@@ -3,17 +3,15 @@ import path from 'path';
|
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import humanizeDuration from 'humanize-duration';
|
|
5
5
|
import merge from 'lodash.merge';
|
|
6
|
-
import { Octokit } from '@octokit/rest';
|
|
7
6
|
import { APP_PREFIX, testomatLogoURL } from '../constants.js';
|
|
8
7
|
import { ansiRegExp, isSameTest } from '../utils/utils.js';
|
|
9
8
|
import { statusEmoji, fullName } from '../utils/pipe_utils.js';
|
|
10
9
|
|
|
11
10
|
const debug = createDebugMessages('@testomatio/reporter:pipe:github');
|
|
12
11
|
|
|
13
|
-
|
|
14
12
|
/**
|
|
15
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
16
|
-
* @typedef {import('../../types').TestData} TestData
|
|
13
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
14
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
17
15
|
* @class GitHubPipe
|
|
18
16
|
* @implements {Pipe}
|
|
19
17
|
*/
|
|
@@ -65,6 +63,7 @@ class GitHubPipe {
|
|
|
65
63
|
if (!this.issue) return;
|
|
66
64
|
|
|
67
65
|
if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
|
|
66
|
+
const { Octokit } = await import('@octokit/rest');
|
|
68
67
|
|
|
69
68
|
this.octokit = new Octokit({
|
|
70
69
|
auth: this.token,
|
|
@@ -155,7 +154,6 @@ class GitHubPipe {
|
|
|
155
154
|
if (this.tests.length > 0) {
|
|
156
155
|
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
157
156
|
body += this.tests
|
|
158
|
-
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
159
157
|
.sort((a, b) => b?.run_time - a?.run_time)
|
|
160
158
|
.slice(0, 5)
|
|
161
159
|
.map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
|
package/src/pipe/gitlab.js
CHANGED
|
@@ -15,8 +15,8 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @class GitLabPipe
|
|
18
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
19
|
-
* @typedef {import('../../types').TestData} TestData
|
|
18
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
19
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
20
20
|
*/
|
|
21
21
|
class GitLabPipe {
|
|
22
22
|
constructor(params, store = {}) {
|
|
@@ -81,13 +81,13 @@ class GitLabPipe {
|
|
|
81
81
|
let summary = `${this.hiddenCommentData}
|
|
82
82
|
|
|
83
83
|
| [](https://testomat.io) | ${statusEmoji(
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
runParams.status,
|
|
85
|
+
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
86
86
|
| --- | --- |
|
|
87
87
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
88
88
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
'passed',
|
|
90
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
91
91
|
| Duration | 🕐 **${humanizeDuration(
|
|
92
92
|
parseInt(
|
|
93
93
|
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
|
@@ -149,7 +149,6 @@ class GitLabPipe {
|
|
|
149
149
|
if (this.tests.length > 0) {
|
|
150
150
|
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
151
151
|
body += this.tests
|
|
152
|
-
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
153
152
|
.sort((a, b) => b?.run_time - a?.run_time)
|
|
154
153
|
.slice(0, 5)
|
|
155
154
|
.map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
|
package/src/pipe/html.js
CHANGED
|
@@ -5,9 +5,9 @@ import path from 'path';
|
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import handlebars from 'handlebars';
|
|
7
7
|
import fileUrl from 'file-url';
|
|
8
|
-
import { fileSystem, isSameTest, ansiRegExp } from '../utils/utils.js';
|
|
8
|
+
import { fileSystem, isSameTest, ansiRegExp, formatStep } from '../utils/utils.js';
|
|
9
9
|
import { HTML_REPORT } from '../constants.js';
|
|
10
|
-
import { fileURLToPath } from
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
12
|
const debug = createDebugMessages('@testomatio/reporter:pipe:html');
|
|
13
13
|
|
|
@@ -66,13 +66,15 @@ class HtmlPipe {
|
|
|
66
66
|
// empty
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
async prepareRun() {}
|
|
70
|
+
|
|
69
71
|
updateRun() {
|
|
70
72
|
// empty
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
|
74
76
|
* Add test data to the result array for saving. As a result of this function, we get a result object to save.
|
|
75
|
-
* @param {import('../../types').RunData} test - object which includes each test entry.
|
|
77
|
+
* @param {import('../../types/types.js').RunData} test - object which includes each test entry.
|
|
76
78
|
*/
|
|
77
79
|
addTest(test) {
|
|
78
80
|
if (!this.isEnabled) return;
|
|
@@ -128,6 +130,14 @@ class HtmlPipe {
|
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
tests.forEach(test => {
|
|
133
|
+
// steps could be an array or a string
|
|
134
|
+
test.steps = Array.isArray(test.steps)
|
|
135
|
+
? (test.steps = test.steps
|
|
136
|
+
.map(step => formatStep(step))
|
|
137
|
+
.flat()
|
|
138
|
+
.join('\n'))
|
|
139
|
+
: test.steps;
|
|
140
|
+
|
|
131
141
|
if (!test.message?.trim()) {
|
|
132
142
|
test.message = "This test has no 'message' code";
|
|
133
143
|
}
|
|
@@ -227,7 +237,6 @@ class HtmlPipe {
|
|
|
227
237
|
</select>`,
|
|
228
238
|
),
|
|
229
239
|
);
|
|
230
|
-
/* eslint-disable */
|
|
231
240
|
handlebars.registerHelper('emptyDataComponent', () => {
|
|
232
241
|
const svgFilePath = path.join(__dirname, '..', 'template', 'emptyData.svg');
|
|
233
242
|
const svgContent = fs.readFileSync(svgFilePath, 'utf8');
|
|
@@ -244,13 +253,12 @@ class HtmlPipe {
|
|
|
244
253
|
<div>`,
|
|
245
254
|
);
|
|
246
255
|
});
|
|
247
|
-
/* eslint-enable */
|
|
248
256
|
handlebars.registerHelper('pageDispleyElements', tests => {
|
|
249
257
|
// We wrapp the lines to the HTML format we need
|
|
250
258
|
const totalTests = JSON.parse(
|
|
251
259
|
JSON.stringify(tests)
|
|
252
260
|
.replace(/<script>/g, '<script>')
|
|
253
|
-
.replace(/<\/script>/g, '</script>'),
|
|
261
|
+
.replace(/<\/script>/g, '</script>'),
|
|
254
262
|
);
|
|
255
263
|
|
|
256
264
|
const paginationOptions = {
|
|
@@ -277,7 +285,6 @@ class HtmlPipe {
|
|
|
277
285
|
|
|
278
286
|
statuses.forEach(status => {
|
|
279
287
|
for (const option in paginationOptions) {
|
|
280
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
281
288
|
if (paginationOptions.hasOwnProperty(option)) {
|
|
282
289
|
const pageSize = paginationOptions[option];
|
|
283
290
|
let filteredItems = totalTests;
|
package/src/pipe/index.js
CHANGED
|
@@ -7,7 +7,8 @@ import GitHubPipe from './github.js';
|
|
|
7
7
|
import GitLabPipe from './gitlab.js';
|
|
8
8
|
import CsvPipe from './csv.js';
|
|
9
9
|
import HtmlPipe from './html.js';
|
|
10
|
-
import {BitbucketPipe} from './bitbucket.js';
|
|
10
|
+
import { BitbucketPipe } from './bitbucket.js';
|
|
11
|
+
import { DebugPipe } from './debug.js';
|
|
11
12
|
|
|
12
13
|
export async function pipesFactory(params, opts) {
|
|
13
14
|
const extraPipes = [];
|
|
@@ -47,6 +48,7 @@ export async function pipesFactory(params, opts) {
|
|
|
47
48
|
new CsvPipe(params, opts),
|
|
48
49
|
new HtmlPipe(params, opts),
|
|
49
50
|
new BitbucketPipe(params, opts),
|
|
51
|
+
new DebugPipe(params, opts),
|
|
50
52
|
...extraPipes,
|
|
51
53
|
];
|
|
52
54
|
|
|
@@ -55,13 +57,9 @@ export async function pipesFactory(params, opts) {
|
|
|
55
57
|
console.log(
|
|
56
58
|
APP_PREFIX,
|
|
57
59
|
pc.cyan('Pipes:'),
|
|
58
|
-
pc.cyan(
|
|
59
|
-
pipesEnabled
|
|
60
|
-
.map(p => p.toString())
|
|
61
|
-
.join(', ') || 'No pipes enabled',
|
|
62
|
-
),
|
|
60
|
+
pc.cyan(pipesEnabled.map(p => p.toString()).join(', ') || 'No pipes enabled'),
|
|
63
61
|
);
|
|
64
|
-
|
|
62
|
+
|
|
65
63
|
if (!pipesEnabled.length) {
|
|
66
64
|
console.log(
|
|
67
65
|
APP_PREFIX,
|
package/src/pipe/testomatio.js
CHANGED
|
@@ -11,18 +11,15 @@ import JsonCycle from 'json-cycle';
|
|
|
11
11
|
import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
|
|
12
12
|
import { isValidUrl, foundedTestLog } from '../utils/utils.js';
|
|
13
13
|
import { parseFilterParams, generateFilterRequestParams, setS3Credentials } from '../utils/pipe_utils.js';
|
|
14
|
-
import {config} from '../config.js';
|
|
14
|
+
import { config } from '../config.js';
|
|
15
15
|
|
|
16
16
|
const debug = createDebugMessages('@testomatio/reporter:pipe:testomatio');
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
if (process.env.TESTOMATIO_RUN) {
|
|
20
|
-
// process.env.runId = process.env.TESTOMATIO_RUN;
|
|
21
|
-
}
|
|
18
|
+
if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
25
|
-
* @typedef {import('../../types').TestData} TestData
|
|
21
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
22
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
26
23
|
* @class TestomatioPipe
|
|
27
24
|
* @implements {Pipe}
|
|
28
25
|
*/
|
|
@@ -64,11 +61,13 @@ class TestomatioPipe {
|
|
|
64
61
|
this.axios = axios.create({
|
|
65
62
|
baseURL: `${this.url.trim()}`,
|
|
66
63
|
timeout: AXIOS_TIMEOUT,
|
|
67
|
-
proxy: proxy
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
proxy: proxy
|
|
65
|
+
? {
|
|
66
|
+
host: proxy.hostname,
|
|
67
|
+
port: parseInt(proxy.port, 10),
|
|
68
|
+
protocol: proxy.protocol,
|
|
69
|
+
}
|
|
70
|
+
: false,
|
|
72
71
|
});
|
|
73
72
|
|
|
74
73
|
// Pass the axios instance to the retry function
|
|
@@ -101,9 +100,10 @@ class TestomatioPipe {
|
|
|
101
100
|
// do not finish this run (for parallel testing)
|
|
102
101
|
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
103
102
|
this.jiraId = process.env.TESTOMATIO_JIRA_ID;
|
|
104
|
-
this.runId = params.runId || process.env.
|
|
103
|
+
this.runId = params.runId || process.env.TESTOMATIO_RUN;
|
|
105
104
|
this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
|
|
106
105
|
this.hasUnmatchedTests = false;
|
|
106
|
+
this.requestFailures = 0;
|
|
107
107
|
|
|
108
108
|
if (!isValidUrl(this.url.trim())) {
|
|
109
109
|
this.isEnabled = false;
|
|
@@ -155,9 +155,9 @@ class TestomatioPipe {
|
|
|
155
155
|
*/
|
|
156
156
|
async createRun(params = {}) {
|
|
157
157
|
this.batch.isEnabled = params.isBatchEnabled ?? this.batch.isEnabled;
|
|
158
|
-
debug('Creating run...');
|
|
159
158
|
if (!this.isEnabled) return;
|
|
160
|
-
if (this.batch.isEnabled
|
|
159
|
+
if (this.batch.isEnabled && this.isEnabled)
|
|
160
|
+
this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
|
|
161
161
|
|
|
162
162
|
let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
|
|
163
163
|
|
|
@@ -197,12 +197,14 @@ class TestomatioPipe {
|
|
|
197
197
|
debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
|
|
198
198
|
|
|
199
199
|
if (this.runId) {
|
|
200
|
+
this.store.runId = this.runId;
|
|
200
201
|
debug(`Run with id ${this.runId} already created, updating...`);
|
|
201
202
|
const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
|
|
202
203
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
203
204
|
return;
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
debug('Creating run...');
|
|
206
208
|
try {
|
|
207
209
|
const resp = await this.axios.post(`/api/reporter`, runParams, {
|
|
208
210
|
maxContentLength: Infinity,
|
|
@@ -222,10 +224,14 @@ class TestomatioPipe {
|
|
|
222
224
|
process.env.runId = this.runId;
|
|
223
225
|
debug('Run created', this.runId);
|
|
224
226
|
} catch (err) {
|
|
227
|
+
const errorText = err.response?.data?.message || err.message;
|
|
228
|
+
console.log(errorText || err);
|
|
229
|
+
if (!this.apiKey) console.error('Testomat.io API key is not set');
|
|
230
|
+
if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
|
|
231
|
+
|
|
225
232
|
console.error(
|
|
226
233
|
APP_PREFIX,
|
|
227
|
-
'Error creating Testomat.io report, please check if your API key is valid. Skipping report
|
|
228
|
-
err?.response?.statusText || err?.status || err.message,
|
|
234
|
+
'Error creating Testomat.io report (see details above), please check if your API key is valid. Skipping report',
|
|
229
235
|
);
|
|
230
236
|
printCreateIssue(err);
|
|
231
237
|
}
|
|
@@ -234,37 +240,25 @@ class TestomatioPipe {
|
|
|
234
240
|
|
|
235
241
|
/**
|
|
236
242
|
* Decides whether to skip test reporting in case of too many request failures
|
|
237
|
-
* @param {TestData} testData
|
|
238
243
|
* @returns {boolean}
|
|
239
244
|
*/
|
|
240
|
-
#cancelTestReportingInCaseOfTooManyReqFailures(
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
const retriesCountWithinTime = this.retriesTimestamps.filter(
|
|
244
|
-
timestamp => Date.now() - timestamp < REPORTER_REQUEST_RETRIES.withinTimeSeconds * 1000,
|
|
245
|
-
).length;
|
|
246
|
-
debug(`${retriesCountWithinTime} failed requests within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s`);
|
|
247
|
-
|
|
248
|
-
if (retriesCountWithinTime > REPORTER_REQUEST_RETRIES.maxTotalRetries) {
|
|
249
|
-
const errorMessage = pc.yellow(
|
|
250
|
-
`${retriesCountWithinTime} requests were failed within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s,\
|
|
251
|
-
reporting for test "${testData.title}" to Testomat is skipped`,
|
|
252
|
-
);
|
|
253
|
-
console.warn(`${APP_PREFIX} ${errorMessage}`);
|
|
245
|
+
#cancelTestReportingInCaseOfTooManyReqFailures() {
|
|
246
|
+
if (!process.env.TESTOMATIO_MAX_REQUEST_FAILURES) return;
|
|
254
247
|
|
|
248
|
+
const cancelReporting = this.requestFailures >= parseInt(process.env.TESTOMATIO_MAX_REQUEST_FAILURES, 10);
|
|
249
|
+
if (cancelReporting) {
|
|
255
250
|
this.reportingCanceledDueToReqFailures = true;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
251
|
+
let errorMessage = `⚠️ ${process.env.TESTOMATIO_MAX_REQUEST_FAILURES}`;
|
|
252
|
+
errorMessage += ' requests were failed, reporting to Testomat aborted.';
|
|
253
|
+
console.warn(`${APP_PREFIX} ${pc.yellow(errorMessage)}`);
|
|
259
254
|
}
|
|
260
|
-
|
|
261
|
-
return false;
|
|
255
|
+
return cancelReporting;
|
|
262
256
|
}
|
|
263
257
|
|
|
264
258
|
#uploadSingleTest = async data => {
|
|
265
259
|
if (!this.isEnabled) return;
|
|
266
260
|
if (!this.runId) return;
|
|
267
|
-
if (this.#cancelTestReportingInCaseOfTooManyReqFailures(
|
|
261
|
+
if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
|
|
268
262
|
|
|
269
263
|
data.api_key = this.apiKey;
|
|
270
264
|
data.create = this.createNewTests;
|
|
@@ -277,40 +271,41 @@ class TestomatioPipe {
|
|
|
277
271
|
|
|
278
272
|
debug('Adding test', json);
|
|
279
273
|
|
|
280
|
-
return this.axios
|
|
281
|
-
|
|
282
|
-
.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
console.log(
|
|
287
|
-
APP_PREFIX,
|
|
288
|
-
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
289
|
-
pc.gray(data?.title || ''),
|
|
290
|
-
);
|
|
291
|
-
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
292
|
-
this.hasUnmatchedTests = true;
|
|
293
|
-
}
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
274
|
+
return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, axiosAddTestrunRequestConfig).catch(err => {
|
|
275
|
+
this.requestFailures++;
|
|
276
|
+
this.notReportedTestsCount++;
|
|
277
|
+
if (err.response) {
|
|
278
|
+
if (err.response.status >= 400) {
|
|
279
|
+
const responseData = err.response.data || { message: '' };
|
|
296
280
|
console.log(
|
|
297
281
|
APP_PREFIX,
|
|
298
|
-
pc.yellow(`Warning: ${
|
|
299
|
-
|
|
282
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
283
|
+
pc.gray(data?.title || ''),
|
|
300
284
|
);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
285
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
286
|
+
this.hasUnmatchedTests = true;
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
304
289
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
290
|
+
console.log(
|
|
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
|
+
});
|
|
300
|
+
};
|
|
308
301
|
|
|
309
302
|
/**
|
|
310
303
|
* Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
|
|
311
304
|
*/
|
|
312
305
|
#batchUpload = async () => {
|
|
313
306
|
if (!this.batch.isEnabled) return;
|
|
307
|
+
if (!this.batch.tests.length) return;
|
|
308
|
+
if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
|
|
314
309
|
// prevent infinite loop
|
|
315
310
|
if (this.batch.numberOfTimesCalledWithoutTests > 10) {
|
|
316
311
|
debug('📨 Batch upload: no tests to send for 10 times, stopping batch');
|
|
@@ -322,7 +317,7 @@ class TestomatioPipe {
|
|
|
322
317
|
this.batch.numberOfTimesCalledWithoutTests++;
|
|
323
318
|
return;
|
|
324
319
|
}
|
|
325
|
-
|
|
320
|
+
|
|
326
321
|
this.batch.batchIndex++;
|
|
327
322
|
// get tests from batch and clear batch
|
|
328
323
|
const testsToSend = this.batch.tests.splice(0);
|
|
@@ -335,6 +330,8 @@ class TestomatioPipe {
|
|
|
335
330
|
axiosAddTestrunRequestConfig,
|
|
336
331
|
)
|
|
337
332
|
.catch(err => {
|
|
333
|
+
this.requestFailures++;
|
|
334
|
+
this.notReportedTestsCount += testsToSend.length;
|
|
338
335
|
if (err.response) {
|
|
339
336
|
if (err.response.status >= 400) {
|
|
340
337
|
const responseData = err.response.data || { message: '' };
|
|
@@ -380,14 +377,21 @@ class TestomatioPipe {
|
|
|
380
377
|
}
|
|
381
378
|
|
|
382
379
|
/**
|
|
383
|
-
* @param {import('../../types').RunData} params
|
|
380
|
+
* @param {import('../../types/types.js').RunData} params
|
|
384
381
|
* @returns
|
|
385
382
|
*/
|
|
386
383
|
async finishRun(params) {
|
|
387
384
|
if (!this.isEnabled) return;
|
|
388
|
-
|
|
389
|
-
if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
|
|
385
|
+
|
|
390
386
|
await this.#batchUpload();
|
|
387
|
+
if (this.batch.intervalFunction) {
|
|
388
|
+
clearInterval(this.batch.intervalFunction);
|
|
389
|
+
// this code is required in case test is added after run is finished
|
|
390
|
+
// (e.g. if test has artifacts, add test function will be invoked only after artifacts are uploaded)
|
|
391
|
+
// batch stops working after run is finished; thus, disable it to use single test uploading
|
|
392
|
+
this.batch.intervalFunction = null;
|
|
393
|
+
this.batch.isEnabled = false;
|
|
394
|
+
}
|
|
391
395
|
|
|
392
396
|
debug('Finishing run...');
|
|
393
397
|
|
|
@@ -411,7 +415,9 @@ class TestomatioPipe {
|
|
|
411
415
|
if (this.runId && !this.proceed) {
|
|
412
416
|
await this.axios.put(`/api/reporter/${this.runId}`, {
|
|
413
417
|
api_key: this.apiKey,
|
|
418
|
+
duration: params.duration,
|
|
414
419
|
status_event,
|
|
420
|
+
detach: params.detach,
|
|
415
421
|
tests: params.tests,
|
|
416
422
|
});
|
|
417
423
|
if (this.runUrl) {
|
|
@@ -426,16 +432,14 @@ class TestomatioPipe {
|
|
|
426
432
|
console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${pc.magenta(this.runUrl)}`);
|
|
427
433
|
console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
|
|
428
434
|
}
|
|
435
|
+
|
|
429
436
|
if (this.hasUnmatchedTests) {
|
|
430
437
|
console.log('');
|
|
431
|
-
// eslint-disable-next-line max-len
|
|
432
438
|
console.log(APP_PREFIX, pc.yellow(pc.bold('⚠️ Some reported tests were not found in Testomat.io project')));
|
|
433
|
-
// eslint-disable-next-line max-len
|
|
434
439
|
console.log(
|
|
435
440
|
APP_PREFIX,
|
|
436
441
|
`If you use Testomat.io as a reporter only, please re-run tests using ${pc.bold('TESTOMATIO_CREATE=1')}`,
|
|
437
442
|
);
|
|
438
|
-
// eslint-disable-next-line max-len
|
|
439
443
|
console.log(
|
|
440
444
|
APP_PREFIX,
|
|
441
445
|
`But to keep your tests consistent it is recommended to ${pc.bold('import tests first')}`,
|
|
@@ -444,12 +448,7 @@ class TestomatioPipe {
|
|
|
444
448
|
console.log(APP_PREFIX, 'You can do that automatically via command line tools:');
|
|
445
449
|
console.log(APP_PREFIX, pc.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
|
|
446
450
|
console.log(APP_PREFIX, 'or for Cucumber:');
|
|
447
|
-
|
|
448
|
-
console.log(
|
|
449
|
-
APP_PREFIX,
|
|
450
|
-
pc.bold('npx check-cucumber ... --update-ids'),
|
|
451
|
-
'See: https://bit.ly/bdd-update-ids',
|
|
452
|
-
);
|
|
451
|
+
console.log(APP_PREFIX, pc.bold('npx check-cucumber ... --update-ids'), 'See: https://bit.ly/bdd-update-ids');
|
|
453
452
|
}
|
|
454
453
|
} catch (err) {
|
|
455
454
|
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
@@ -473,7 +472,7 @@ function printCreateIssue(err) {
|
|
|
473
472
|
console.log(
|
|
474
473
|
APP_PREFIX,
|
|
475
474
|
'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new',
|
|
476
|
-
);
|
|
475
|
+
);
|
|
477
476
|
console.log(APP_PREFIX, 'Provide this information:');
|
|
478
477
|
console.log('Error:', err.message || err.code);
|
|
479
478
|
if (!err.config) return;
|
|
@@ -1,38 +1,49 @@
|
|
|
1
1
|
import { services } from './services/index.js';
|
|
2
|
-
import { initPlaywrightForStorage } from './adapter/playwright.js';
|
|
3
|
-
|
|
4
|
-
if (process.env.PLAYWRIGHT_TEST_BASE_URL) initPlaywrightForStorage();
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* Stores path to file as artifact and uploads it to the S3 storage
|
|
8
5
|
* @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
|
|
9
6
|
*/
|
|
10
7
|
function saveArtifact(data, context = null) {
|
|
8
|
+
if (process.env.IS_PLAYWRIGHT)
|
|
9
|
+
throw new Error(`This function is not available in Playwright framework.
|
|
10
|
+
/Playwright supports artifacts out of the box`);
|
|
11
11
|
if (!data) return;
|
|
12
12
|
services.artifacts.put(data, context);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Attach log message(s) to the test report
|
|
17
|
-
* @param
|
|
17
|
+
* @param string
|
|
18
18
|
*/
|
|
19
19
|
function logMessage(...args) {
|
|
20
|
+
if (process.env.IS_PLAYWRIGHT) throw new Error('This function is not available in Playwright framework');
|
|
20
21
|
services.logger._templateLiteralLog(...args);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Similar to "log" function but marks message in report as a step
|
|
25
|
-
* @param {
|
|
26
|
+
* @param {string} message
|
|
26
27
|
*/
|
|
27
28
|
function addStep(message) {
|
|
29
|
+
if (process.env.IS_PLAYWRIGHT)
|
|
30
|
+
throw new Error('This function is not available in Playwright framework. Use playwright steps');
|
|
31
|
+
|
|
28
32
|
services.logger.step(message);
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
36
|
* Add key-value pair(s) to the test report
|
|
33
|
-
* @param {
|
|
37
|
+
* @param {{[key: string]: string} | string} keyValue object { key: value } (multiple props allowed) or key (string)
|
|
38
|
+
* @param {string?} value
|
|
34
39
|
*/
|
|
35
|
-
function setKeyValue(keyValue) {
|
|
40
|
+
function setKeyValue(keyValue, value = null) {
|
|
41
|
+
if (process.env.IS_PLAYWRIGHT)
|
|
42
|
+
throw new Error('This function is not available in Playwright framework. Use test tag instead.');
|
|
43
|
+
|
|
44
|
+
if (typeof keyValue === 'string') {
|
|
45
|
+
keyValue = { [keyValue]: value };
|
|
46
|
+
}
|
|
36
47
|
services.keyValues.put(keyValue);
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// const _reporter = require('../lib/reporter.js');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// /**
|
|
5
|
+
// * @typedef {import('../lib/reporter-functions.js').saveArtifact} artifact
|
|
6
|
+
// * @typedef {import('../lib/reporter-functions.js').log} log
|
|
7
|
+
// * @typedef {import('../lib/reporter-functions.js').logger} logger
|
|
8
|
+
// * @typedef {import('../lib/reporter-functions.js')} meta
|
|
9
|
+
// * @typedef {import('../lib/reporter-functions.js')} step
|
|
10
|
+
// *
|
|
11
|
+
// * "Reporter" type which is object containing all types from the above
|
|
12
|
+
// * @typedef {{artifact: artifact, log: log, logger: logger, meta: meta, step: step, }} Reporter
|
|
13
|
+
// */
|
|
14
|
+
|
|
15
|
+
// // const reporter = _reporter;
|
|
16
|
+
|
|
17
|
+
// /**
|
|
18
|
+
// * @type {Reporter}
|
|
19
|
+
// */
|
|
20
|
+
// const reporter = _reporter;
|
|
21
|
+
// module.exports = reporter;
|