@testomatio/reporter 1.4.5 → 1.4.6-proxy-support
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/vitest.js +182 -0
- package/lib/bin/reportXml.js +9 -4
- package/lib/client.js +23 -8
- package/lib/pipe/gitlab.js +4 -4
- package/lib/pipe/testomatio.js +9 -0
- package/lib/utils/utils.js +1 -1
- package/lib/xmlReader.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { TestomatioClient } = require('../client');
|
|
3
|
+
const { STATUS } = require('../constants');
|
|
4
|
+
const { getTestomatIdFromTestTitle } = require('../utils/utils');
|
|
5
|
+
const debug = require('debug')('@testomatio/reporter:adapter:vitest');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('../../types').VitestTest} VitestTest
|
|
9
|
+
* @typedef {import('../../types').VitestTestFile} VitestTestFile
|
|
10
|
+
* @typedef {import('../../types').VitestSuite} VitestSuite
|
|
11
|
+
* @typedef {import('../../types').VitestTestLogs} VitestTestLogs
|
|
12
|
+
* @typedef {import('../../vitest.types').ErrorWithDiff} ErrorWithDiff
|
|
13
|
+
* @typedef {typeof import('../constants').STATUS} STATUS
|
|
14
|
+
* @typedef {import('../../types').TestData} TestData
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
class VitestReporter {
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
20
|
+
/**
|
|
21
|
+
* @type {(TestData & {status: string})[]} tests
|
|
22
|
+
*/
|
|
23
|
+
this.tests = [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// on run start
|
|
27
|
+
onInit() {
|
|
28
|
+
this.client.createRun();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {VitestTestFile[] | undefined} files // array with results;
|
|
33
|
+
* @param {unknown[] | undefined} errors // errors does not contain errors from tests; probably its testrunner errors
|
|
34
|
+
*/
|
|
35
|
+
async onFinished(files, errors) {
|
|
36
|
+
if (!files || !files.length) console.info('No tests executed');
|
|
37
|
+
|
|
38
|
+
files.forEach(file => {
|
|
39
|
+
// task could be test or suite
|
|
40
|
+
file.tasks.forEach(taskOrSuite => {
|
|
41
|
+
if (taskOrSuite.type === 'test') {
|
|
42
|
+
const test = taskOrSuite;
|
|
43
|
+
this.tests.push(this.#getDataFromTest(test));
|
|
44
|
+
} else if (taskOrSuite.type === 'suite') {
|
|
45
|
+
const suite = taskOrSuite;
|
|
46
|
+
this.#processTasksOfSuite(suite);
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error('Unprocessed case. Unknown task type');
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
debug(this.tests.length, 'tests collected');
|
|
54
|
+
|
|
55
|
+
// send tests to Testomat.io
|
|
56
|
+
for (const test of this.tests) {
|
|
57
|
+
await this.client.addTestRun(test.status, test);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log('finished');
|
|
61
|
+
if (errors.length) console.error('Vitest adapter errors:', errors);
|
|
62
|
+
|
|
63
|
+
await this.client.updateRunStatus(getRunStatusFromResults(files));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* non-used listeners
|
|
67
|
+
onUserConsoleLog(log) {}
|
|
68
|
+
onPathsCollected(paths) {} // paths array to files with tests
|
|
69
|
+
onCollected(files) {} // files array with tests (but without results)
|
|
70
|
+
onTaskUpdate(packs) {} // some updates come here on afterAll block execution
|
|
71
|
+
onTestRemoved(trigger) {}
|
|
72
|
+
onWatcherStart(files, errors) {}
|
|
73
|
+
onWatcherRerun(files, trigger) {}
|
|
74
|
+
onServerRestart(reason) {}
|
|
75
|
+
onProcessTimeout() {}
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Recursively gets all tasks from suite and pushes them to "tests" array
|
|
80
|
+
*
|
|
81
|
+
* @param {VitestSuite} suite
|
|
82
|
+
*/
|
|
83
|
+
#processTasksOfSuite(suite) {
|
|
84
|
+
suite.tasks.forEach(taskOrSuite => {
|
|
85
|
+
if (taskOrSuite.type === 'test') {
|
|
86
|
+
const test = taskOrSuite;
|
|
87
|
+
this.tests.push(this.#getDataFromTest(test));
|
|
88
|
+
} else if (taskOrSuite.type === 'suite') {
|
|
89
|
+
const theSuite = taskOrSuite;
|
|
90
|
+
this.#processTasksOfSuite(theSuite);
|
|
91
|
+
} else {
|
|
92
|
+
throw new Error('Unprocessed case. Unknown task type');
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Processes task and returns test data ready to be sent to Testomat.io
|
|
99
|
+
*
|
|
100
|
+
* @param {VitestTest} test
|
|
101
|
+
*
|
|
102
|
+
* @returns {TestData & {status: string}}
|
|
103
|
+
*/
|
|
104
|
+
#getDataFromTest(test) {
|
|
105
|
+
return {
|
|
106
|
+
error: test.result?.errors ? test.result.errors[0] : undefined,
|
|
107
|
+
file: test.file.name,
|
|
108
|
+
logs: test.logs ? transformLogsToString(test.logs) : '',
|
|
109
|
+
meta: test.meta,
|
|
110
|
+
status: getTestStatus(test),
|
|
111
|
+
suite_title: test.suite.name || test.file?.name,
|
|
112
|
+
test_id: getTestomatIdFromTestTitle(test.name),
|
|
113
|
+
time: test.result?.duration || 0,
|
|
114
|
+
title: test.name,
|
|
115
|
+
// testomatio functions (artifacts, logs, steps, meta) are not supported
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns run status based on test results
|
|
122
|
+
*
|
|
123
|
+
* @param {VitestTestFile[]} files
|
|
124
|
+
* @returns {'passed' | 'failed' | 'finished'}
|
|
125
|
+
*/
|
|
126
|
+
function getRunStatusFromResults(files) {
|
|
127
|
+
/**
|
|
128
|
+
* @type {'passed' | 'failed' | 'finished'}
|
|
129
|
+
*/
|
|
130
|
+
let status = 'finished'; // default status (if no failed or passed tests)
|
|
131
|
+
|
|
132
|
+
files.forEach(file => {
|
|
133
|
+
// search for failed tests
|
|
134
|
+
file.tasks.forEach(taskOrSuite => {
|
|
135
|
+
if (taskOrSuite.result?.state === 'fail') {
|
|
136
|
+
status = 'failed'; // set status to failed if any test failed
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// if there are no failed tests > search for passed tests
|
|
141
|
+
if (status !== 'failed') {
|
|
142
|
+
file.tasks.forEach(taskOrSuite => {
|
|
143
|
+
if (taskOrSuite.result?.state === 'pass') {
|
|
144
|
+
status = 'passed'; // set status to passed if any test passed (and there are no failed tests)
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return status;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Returns test status in Testomat.io format
|
|
155
|
+
*
|
|
156
|
+
* @param {VitestTest} test
|
|
157
|
+
* @returns 'passed' | 'failed' | 'skipped'
|
|
158
|
+
*/
|
|
159
|
+
function getTestStatus(test) {
|
|
160
|
+
if (test.result?.state === 'fail') return STATUS.FAILED;
|
|
161
|
+
if (test.result?.state === 'pass') return STATUS.PASSED;
|
|
162
|
+
if (!test.result && test.mode === 'skip') return STATUS.SKIPPED;
|
|
163
|
+
console.error(chalk.red('Unprocessed case for defining test status. Contact dev team. Test:'), test);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @param {VitestTestLogs[]} logs
|
|
168
|
+
* @returns string
|
|
169
|
+
*/
|
|
170
|
+
function transformLogsToString(logs) {
|
|
171
|
+
if (!logs) return '';
|
|
172
|
+
let logsStr = '';
|
|
173
|
+
logs.forEach(log => {
|
|
174
|
+
if (log.type === 'stdout') logsStr += `${log.content}\n`;
|
|
175
|
+
if (log.type === 'stderr') logsStr += `${chalk.red(log.content)}\n`;
|
|
176
|
+
});
|
|
177
|
+
return logsStr;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports.VitestReporter = VitestReporter;
|
|
181
|
+
module.exports.default = VitestReporter;
|
|
182
|
+
module.exports = VitestReporter;
|
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/client.js
CHANGED
|
@@ -123,7 +123,7 @@ class Client {
|
|
|
123
123
|
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
124
124
|
|
|
125
125
|
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
126
|
-
debug('Skipping test from report', testData?.title)
|
|
126
|
+
debug('Skipping test from report', testData?.title);
|
|
127
127
|
return []; // do not log skipped tests
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -133,10 +133,13 @@ class Client {
|
|
|
133
133
|
suite_title: 'Unknown suite',
|
|
134
134
|
};
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* @type {TestData}
|
|
138
|
+
*/
|
|
136
139
|
const {
|
|
137
140
|
rid,
|
|
138
141
|
error = null,
|
|
139
|
-
time =
|
|
142
|
+
time = 0,
|
|
140
143
|
example = null,
|
|
141
144
|
files = [],
|
|
142
145
|
filesBuffers = [],
|
|
@@ -198,7 +201,7 @@ class Client {
|
|
|
198
201
|
suite_id,
|
|
199
202
|
test_id,
|
|
200
203
|
message,
|
|
201
|
-
run_time: parseFloat(time),
|
|
204
|
+
run_time: typeof time === 'number' ? time : parseFloat(time),
|
|
202
205
|
artifacts,
|
|
203
206
|
meta,
|
|
204
207
|
};
|
|
@@ -224,7 +227,8 @@ class Client {
|
|
|
224
227
|
/**
|
|
225
228
|
*
|
|
226
229
|
* Updates the status of the current test run and finishes the run.
|
|
227
|
-
* @param {
|
|
230
|
+
* @param {'passed' | 'failed' | 'finished'} status - The status of the current test run.
|
|
231
|
+
* Must be one of "passed", "failed", or "finished"
|
|
228
232
|
* @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
|
|
229
233
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
230
234
|
*/
|
|
@@ -286,13 +290,23 @@ class Client {
|
|
|
286
290
|
if (!message) message = error.message;
|
|
287
291
|
if (error.inspect) message = error.inspect() || '';
|
|
288
292
|
|
|
289
|
-
let stack =
|
|
293
|
+
let stack = '';
|
|
294
|
+
if (error.name) stack += `${chalk.red(error.name)}`;
|
|
295
|
+
if (error.operator) stack += ` (${chalk.red(error.operator)})`;
|
|
296
|
+
// add new line if something was added to stack
|
|
297
|
+
if (stack) stack += ': ';
|
|
290
298
|
|
|
291
|
-
|
|
292
|
-
|
|
299
|
+
stack += `${message}\n`;
|
|
300
|
+
|
|
301
|
+
if (error.diff) {
|
|
302
|
+
// diff for vitest
|
|
303
|
+
stack += error.diff;
|
|
304
|
+
stack += '\n\n';
|
|
305
|
+
} else if (error.actual && error.expected && error.actual !== error.expected) {
|
|
306
|
+
// diffs for mocha, cypress, codeceptjs style
|
|
293
307
|
stack += `\n\n${chalk.bold.green('+ expected')} ${chalk.bold.red('- actual')}`;
|
|
294
|
-
stack += `\n${chalk.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
|
|
295
308
|
stack += `\n${chalk.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
|
|
309
|
+
stack += `\n${chalk.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
|
|
296
310
|
stack += '\n\n';
|
|
297
311
|
}
|
|
298
312
|
|
|
@@ -365,3 +379,4 @@ function isTestShouldBeExculedFromReport(testData) {
|
|
|
365
379
|
}
|
|
366
380
|
|
|
367
381
|
module.exports = Client;
|
|
382
|
+
module.exports.TestomatioClient = Client;
|
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/testomatio.js
CHANGED
|
@@ -42,6 +42,10 @@ class TestomatioPipe {
|
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
debug('Testomatio Pipe: Enabled');
|
|
45
|
+
|
|
46
|
+
const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
|
|
47
|
+
const proxy = proxyUrl ? new URL(proxyUrl) : null;
|
|
48
|
+
|
|
45
49
|
this.parallel = params.parallel;
|
|
46
50
|
this.store = store || {};
|
|
47
51
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
@@ -53,6 +57,11 @@ class TestomatioPipe {
|
|
|
53
57
|
this.axios = axios.create({
|
|
54
58
|
baseURL: `${this.url.trim()}`,
|
|
55
59
|
timeout: AXIOS_TIMEOUT,
|
|
60
|
+
proxy: proxy ? {
|
|
61
|
+
host: proxy.hostname,
|
|
62
|
+
port: proxy.port,
|
|
63
|
+
protocol: proxy.protocol,
|
|
64
|
+
} : false,
|
|
56
65
|
});
|
|
57
66
|
|
|
58
67
|
// Pass the axios instance to the retry function
|
package/lib/utils/utils.js
CHANGED
|
@@ -248,7 +248,7 @@ const foundedTestLog = (app, tests) => {
|
|
|
248
248
|
const humanize = text => {
|
|
249
249
|
// if there are no spaces, decamelize
|
|
250
250
|
if (!text.trim().includes(' ')) text = decamelize(text);
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
return text
|
|
253
253
|
.replace(/_./g, match => ` ${match.charAt(1).toUpperCase()}`)
|
|
254
254
|
.trim()
|
package/lib/xmlReader.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6-proxy-support",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
|
|
56
56
|
"test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
|
|
57
57
|
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
|
|
58
|
+
"test:adapter:vitest:example": "vitest --config='./tests/adapter/examples/vitest/vitest.config.js'",
|
|
58
59
|
"test:storage": "npx mocha tests-storage/artifact-storage.test.js && npx mocha tests-storage/data-storage.test.js && TESTOMATIO_INTERCEPT_CONSOLE_LOGS=true npx mocha tests-storage/logger.test.js && npx mocha tests-storage/logger-2.test.js && npx mocha tests-storage/reporter-functions.test.js"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|