@testomatio/reporter 2.7.3-beta.1-vitest → 2.7.3-beta.3.allure
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 +4 -2
- package/lib/adapter/vitest.d.ts +3 -22
- package/lib/adapter/vitest.js +26 -250
- package/lib/allureReader.d.ts +65 -0
- package/lib/allureReader.js +456 -0
- package/lib/bin/cli.js +28 -0
- package/lib/client.d.ts +1 -2
- package/lib/client.js +3 -4
- package/lib/junit-adapter/index.js +4 -0
- package/lib/junit-adapter/kotlin.d.ts +5 -0
- package/lib/junit-adapter/kotlin.js +46 -0
- package/lib/utils/log-formatter.d.ts +2 -2
- package/lib/utils/log-formatter.js +1 -1
- package/lib/utils/utils.js +9 -0
- package/package.json +1 -1
- package/src/adapter/vitest.js +26 -253
- package/src/allureReader.js +532 -0
- package/src/bin/cli.js +35 -0
- package/src/client.js +3 -4
- package/src/junit-adapter/index.js +4 -0
- package/src/junit-adapter/kotlin.js +48 -0
- package/src/utils/log-formatter.js +1 -1
- package/src/utils/utils.js +5 -0
package/src/adapter/vitest.js
CHANGED
|
@@ -19,37 +19,21 @@ const debug = createDebugMessages('@testomatio/reporter:adapter-jest');
|
|
|
19
19
|
class VitestReporter {
|
|
20
20
|
constructor(config = {}) {
|
|
21
21
|
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* @type {(TestData & {status: string})[]} tests
|
|
24
|
+
*/
|
|
23
25
|
this.tests = [];
|
|
24
26
|
this._finalized = false;
|
|
25
27
|
this._finalizing = false;
|
|
26
|
-
this._runStartedAtMs = null;
|
|
27
|
-
this._runStartedAtMicros = null;
|
|
28
|
-
this._reportedTestKeys = new Set();
|
|
29
|
-
this._liveQueue = Promise.resolve();
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
// on run start
|
|
33
31
|
onInit() {
|
|
34
|
-
const now = Date.now();
|
|
35
32
|
this._finalized = false;
|
|
36
33
|
this._finalizing = false;
|
|
37
|
-
this._runStartedAtMs = now;
|
|
38
|
-
this._runStartedAtMicros = now * 1000;
|
|
39
|
-
this._reportedTestKeys = new Set();
|
|
40
|
-
this._liveQueue = Promise.resolve();
|
|
41
34
|
this.client.createRun();
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
/**
|
|
45
|
-
* Vitest 3/4 callback fired when test run starts.
|
|
46
|
-
*/
|
|
47
|
-
onTestRunStart() {
|
|
48
|
-
const now = Date.now();
|
|
49
|
-
this._runStartedAtMs = now;
|
|
50
|
-
this._runStartedAtMicros = now * 1000;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
37
|
/**
|
|
54
38
|
* @param {VitestTestFile[] | undefined} files // array with results;
|
|
55
39
|
* @param {unknown[] | undefined} errors // errors does not contain errors from tests; probably its testrunner errors
|
|
@@ -84,18 +68,13 @@ class VitestReporter {
|
|
|
84
68
|
|
|
85
69
|
// send tests to Testomat.io
|
|
86
70
|
for (const test of this.tests) {
|
|
87
|
-
if (test._reportKey && this._reportedTestKeys.has(test._reportKey)) continue;
|
|
88
|
-
if (test._reportKey) this._reportedTestKeys.add(test._reportKey);
|
|
89
71
|
await this.client.addTestRun(test.status, test);
|
|
90
72
|
}
|
|
91
|
-
await this._liveQueue;
|
|
92
73
|
|
|
93
74
|
console.log('finished');
|
|
94
75
|
if (errors.length) console.error('Vitest adapter errors:', errors);
|
|
95
76
|
|
|
96
|
-
|
|
97
|
-
const duration = Math.max(0, (Date.now() - startedAtMs) / 1000);
|
|
98
|
-
await this.client.updateRunStatus(getRunStatusFromResults(files), { duration });
|
|
77
|
+
await this.client.updateRunStatus(getRunStatusFromResults(files));
|
|
99
78
|
this._finalized = true;
|
|
100
79
|
} finally {
|
|
101
80
|
this._finalizing = false;
|
|
@@ -115,28 +94,6 @@ class VitestReporter {
|
|
|
115
94
|
await this.onFinished(files, errors);
|
|
116
95
|
}
|
|
117
96
|
|
|
118
|
-
/**
|
|
119
|
-
* Vitest 4 callback fired when single test case is finished.
|
|
120
|
-
*
|
|
121
|
-
* @param {unknown} testCase
|
|
122
|
-
*/
|
|
123
|
-
async onTestCaseResult(testCase) {
|
|
124
|
-
await this.#reportLive(testCase);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Vitest 3 fallback callback with task updates.
|
|
129
|
-
*
|
|
130
|
-
* @param {unknown[] | undefined} packs
|
|
131
|
-
*/
|
|
132
|
-
async onTaskUpdate(packs) {
|
|
133
|
-
if (!Array.isArray(packs) || !packs.length) return;
|
|
134
|
-
for (const pack of packs) {
|
|
135
|
-
const test = getTestFromTaskUpdatePack(pack);
|
|
136
|
-
if (test) await this.#reportLive(test);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
97
|
/* non-used listeners
|
|
141
98
|
onUserConsoleLog(log) {}
|
|
142
99
|
onPathsCollected(paths) {} // paths array to files with tests
|
|
@@ -171,52 +128,25 @@ class VitestReporter {
|
|
|
171
128
|
/**
|
|
172
129
|
* Processes task and returns test data ready to be sent to Testomat.io
|
|
173
130
|
*
|
|
174
|
-
* @param {
|
|
131
|
+
* @param {VitestTest} test
|
|
175
132
|
*
|
|
176
|
-
* @returns {TestData & {status: 'passed' | 'failed' | 'skipped'
|
|
133
|
+
* @returns {TestData & {status: 'passed' | 'failed' | 'skipped'}}
|
|
177
134
|
*/
|
|
178
135
|
#getDataFromTest(test) {
|
|
179
|
-
const normalized = normalizeVitestTest(test);
|
|
180
|
-
const reportKey = getReportKey(test, normalized);
|
|
181
|
-
const startMicros =
|
|
182
|
-
typeof normalized.startTime === 'number'
|
|
183
|
-
? Math.floor(normalized.startTime * 1000)
|
|
184
|
-
: this._runStartedAtMicros || undefined;
|
|
185
|
-
|
|
186
136
|
return {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
meta: normalized.meta,
|
|
137
|
+
error: test.result?.errors ? test.result.errors[0] : undefined,
|
|
138
|
+
file: test.file?.name || test.file?.filepath || '',
|
|
139
|
+
logs: test.logs ? transformLogsToString(test.logs) : '',
|
|
140
|
+
meta: test.meta,
|
|
192
141
|
// @ts-ignore - STATUS values are string literals but type system sees them as string
|
|
193
|
-
status: getTestStatus(
|
|
194
|
-
suite_title:
|
|
195
|
-
test_id: getTestomatIdFromTestTitle(
|
|
196
|
-
time:
|
|
197
|
-
|
|
198
|
-
title: normalized.name,
|
|
142
|
+
status: getTestStatus(test),
|
|
143
|
+
suite_title: test.suite?.name || test.file?.name || test.file?.filepath,
|
|
144
|
+
test_id: getTestomatIdFromTestTitle(test.name),
|
|
145
|
+
time: test.result?.duration || 0,
|
|
146
|
+
title: test.name,
|
|
199
147
|
// testomatio functions (artifacts, logs, steps, meta) are not supported
|
|
200
148
|
};
|
|
201
149
|
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* @param {unknown} testCase
|
|
205
|
-
*/
|
|
206
|
-
async #reportLive(testCase) {
|
|
207
|
-
if (this._finalized || this._finalizing) return;
|
|
208
|
-
const normalized = normalizeVitestTest(testCase);
|
|
209
|
-
if (!isLiveReportableState(normalized.state, normalized.mode)) return;
|
|
210
|
-
|
|
211
|
-
const data = this.#getDataFromTest(testCase);
|
|
212
|
-
if (!data._reportKey || this._reportedTestKeys.has(data._reportKey)) return;
|
|
213
|
-
this._reportedTestKeys.add(data._reportKey);
|
|
214
|
-
|
|
215
|
-
this._liveQueue = this._liveQueue
|
|
216
|
-
.then(() => this.client.addTestRun(data.status, data))
|
|
217
|
-
.catch(() => undefined);
|
|
218
|
-
await this._liveQueue;
|
|
219
|
-
}
|
|
220
150
|
}
|
|
221
151
|
|
|
222
152
|
/**
|
|
@@ -232,16 +162,17 @@ function getRunStatusFromResults(files) {
|
|
|
232
162
|
let status = 'finished'; // default status (if no failed or passed tests)
|
|
233
163
|
|
|
234
164
|
files.forEach(file => {
|
|
235
|
-
|
|
236
|
-
|
|
165
|
+
// search for failed tests
|
|
166
|
+
file.tasks.forEach(taskOrSuite => {
|
|
167
|
+
if (taskOrSuite.result?.state === 'fail') {
|
|
237
168
|
status = 'failed'; // set status to failed if any test failed
|
|
238
169
|
}
|
|
239
170
|
});
|
|
240
171
|
|
|
241
172
|
// if there are no failed tests > search for passed tests
|
|
242
173
|
if (status !== 'failed') {
|
|
243
|
-
|
|
244
|
-
if (
|
|
174
|
+
file.tasks.forEach(taskOrSuite => {
|
|
175
|
+
if (taskOrSuite.result?.state === 'pass') {
|
|
245
176
|
status = 'passed'; // set status to passed if any test passed (and there are no failed tests)
|
|
246
177
|
}
|
|
247
178
|
});
|
|
@@ -254,15 +185,14 @@ function getRunStatusFromResults(files) {
|
|
|
254
185
|
/**
|
|
255
186
|
* Returns test status in Testomat.io format
|
|
256
187
|
*
|
|
257
|
-
* @param {
|
|
258
|
-
* @param {string | undefined} mode
|
|
188
|
+
* @param {VitestTest} test
|
|
259
189
|
* @returns 'passed' | 'failed' | 'skipped'
|
|
260
190
|
*/
|
|
261
|
-
function getTestStatus(
|
|
262
|
-
if (
|
|
263
|
-
if (
|
|
264
|
-
if (
|
|
265
|
-
console.error(pc.red('Unprocessed case for defining test status. Contact dev team.
|
|
191
|
+
function getTestStatus(test) {
|
|
192
|
+
if (test.result?.state === 'fail') return STATUS.FAILED;
|
|
193
|
+
if (test.result?.state === 'pass') return STATUS.PASSED;
|
|
194
|
+
if (test.result?.state === 'skip' || (!test.result && test.mode === 'skip')) return STATUS.SKIPPED;
|
|
195
|
+
console.error(pc.red('Unprocessed case for defining test status. Contact dev team. Test:'), test);
|
|
266
196
|
return STATUS.SKIPPED;
|
|
267
197
|
}
|
|
268
198
|
|
|
@@ -290,166 +220,9 @@ function getTasks(node) {
|
|
|
290
220
|
if (!node) return [];
|
|
291
221
|
if (Array.isArray(node.tasks)) return node.tasks;
|
|
292
222
|
if (Array.isArray(node.children)) return node.children;
|
|
293
|
-
if (node.children && typeof node.children[Symbol.iterator] === 'function') return Array.from(node.children);
|
|
294
223
|
if (node.task) return [node.task];
|
|
295
224
|
return [];
|
|
296
225
|
}
|
|
297
226
|
|
|
298
|
-
/**
|
|
299
|
-
* @param {string | undefined} state
|
|
300
|
-
* @returns {boolean}
|
|
301
|
-
*/
|
|
302
|
-
function isFailedState(state) {
|
|
303
|
-
return state === 'fail' || state === 'failed';
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* @param {string | undefined} state
|
|
308
|
-
* @returns {boolean}
|
|
309
|
-
*/
|
|
310
|
-
function isPassedState(state) {
|
|
311
|
-
return state === 'pass' || state === 'passed';
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* @param {string | undefined} state
|
|
316
|
-
* @returns {boolean}
|
|
317
|
-
*/
|
|
318
|
-
function isSkippedState(state) {
|
|
319
|
-
return state === 'skip' || state === 'skipped' || state === 'todo';
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Accept only completed test states for live upload to avoid reporting
|
|
324
|
-
* intermediate task updates as skipped.
|
|
325
|
-
*
|
|
326
|
-
* @param {string | undefined} state
|
|
327
|
-
* @param {string | undefined} mode
|
|
328
|
-
* @returns {boolean}
|
|
329
|
-
*/
|
|
330
|
-
function isLiveReportableState(state, mode) {
|
|
331
|
-
if (isFailedState(state) || isPassedState(state) || isSkippedState(state)) return true;
|
|
332
|
-
if (!state && mode === 'skip') return true;
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* @param {VitestTestFile[] | undefined} files
|
|
338
|
-
* @returns {number | null}
|
|
339
|
-
*/
|
|
340
|
-
function getEarliestTestStartMs(files) {
|
|
341
|
-
let earliest = null;
|
|
342
|
-
const walk = node => {
|
|
343
|
-
if (!node) return;
|
|
344
|
-
const startTime = node?.result?.startTime;
|
|
345
|
-
if (typeof startTime === 'number' && !Number.isNaN(startTime)) {
|
|
346
|
-
if (earliest == null || startTime < earliest) earliest = startTime;
|
|
347
|
-
}
|
|
348
|
-
getTasks(node).forEach(walk);
|
|
349
|
-
};
|
|
350
|
-
(files || []).forEach(walk);
|
|
351
|
-
return earliest;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* @param {any} test
|
|
356
|
-
* @returns {{
|
|
357
|
-
* name: string,
|
|
358
|
-
* state: string | undefined,
|
|
359
|
-
* mode: string | undefined,
|
|
360
|
-
* duration: number,
|
|
361
|
-
* startTime: number | undefined,
|
|
362
|
-
* error: any,
|
|
363
|
-
* file: string,
|
|
364
|
-
* suiteTitle: string,
|
|
365
|
-
* logs: string,
|
|
366
|
-
* meta: any
|
|
367
|
-
* }}
|
|
368
|
-
*/
|
|
369
|
-
function normalizeVitestTest(test) {
|
|
370
|
-
if (test && typeof test.result === 'function') {
|
|
371
|
-
const result = test.result();
|
|
372
|
-
const diagnostic = typeof test.diagnostic === 'function' ? test.diagnostic() : undefined;
|
|
373
|
-
const state = result?.state;
|
|
374
|
-
const duration = diagnostic?.duration || 0;
|
|
375
|
-
const startTime = diagnostic?.startTime;
|
|
376
|
-
const error = Array.isArray(result?.errors) ? result.errors[0] : undefined;
|
|
377
|
-
const file =
|
|
378
|
-
test.module?.relativeModuleId ||
|
|
379
|
-
test.module?.moduleId ||
|
|
380
|
-
test.task?.file?.name ||
|
|
381
|
-
test.task?.file?.filepath ||
|
|
382
|
-
'';
|
|
383
|
-
const suiteTitle =
|
|
384
|
-
(test.parent?.type === 'suite' ? test.parent?.name : null) ||
|
|
385
|
-
test.task?.suite?.name ||
|
|
386
|
-
test.task?.file?.name ||
|
|
387
|
-
file;
|
|
388
|
-
|
|
389
|
-
return {
|
|
390
|
-
name: test.name || test.task?.name || '',
|
|
391
|
-
state,
|
|
392
|
-
mode: test.options?.mode || test.task?.mode,
|
|
393
|
-
duration,
|
|
394
|
-
startTime,
|
|
395
|
-
error,
|
|
396
|
-
file,
|
|
397
|
-
suiteTitle,
|
|
398
|
-
logs: '',
|
|
399
|
-
meta: typeof test.meta === 'function' ? test.meta() : {},
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return {
|
|
404
|
-
name: test?.name || '',
|
|
405
|
-
state: test?.result?.state,
|
|
406
|
-
mode: test?.mode,
|
|
407
|
-
duration: test?.result?.duration || 0,
|
|
408
|
-
startTime: test?.result?.startTime,
|
|
409
|
-
error: test?.result?.errors ? test.result.errors[0] : undefined,
|
|
410
|
-
file: test?.file?.name || test?.file?.filepath || '',
|
|
411
|
-
suiteTitle: test?.suite?.name || test?.file?.name || test?.file?.filepath || '',
|
|
412
|
-
logs: test?.logs ? transformLogsToString(test.logs) : '',
|
|
413
|
-
meta: test?.meta,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* @param {any} test
|
|
419
|
-
* @param {{file: string, suiteTitle: string, name: string, startTime?: number}} normalized
|
|
420
|
-
* @returns {string | null}
|
|
421
|
-
*/
|
|
422
|
-
function getReportKey(test, normalized) {
|
|
423
|
-
if (test?.id) return String(test.id);
|
|
424
|
-
if (test?.task?.id) return String(test.task.id);
|
|
425
|
-
if (!normalized?.name) return null;
|
|
426
|
-
const loc = test?.location || test?.task?.location;
|
|
427
|
-
const locationKey = loc ? `${loc.line || ''}:${loc.column || ''}` : '';
|
|
428
|
-
const startKey =
|
|
429
|
-
typeof normalized.startTime === 'number' && !Number.isNaN(normalized.startTime) ? String(normalized.startTime) : '';
|
|
430
|
-
return `${normalized.file}::${normalized.suiteTitle}::${normalized.name}::${locationKey}::${startKey}`;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Vitest can pass task updates as tuples. Try to extract a test-like object.
|
|
435
|
-
*
|
|
436
|
-
* @param {unknown} pack
|
|
437
|
-
* @returns {any | null}
|
|
438
|
-
*/
|
|
439
|
-
function getTestFromTaskUpdatePack(pack) {
|
|
440
|
-
if (!pack) return null;
|
|
441
|
-
|
|
442
|
-
if (Array.isArray(pack)) {
|
|
443
|
-
if (pack[2]?.type === 'test') return pack[2];
|
|
444
|
-
if (pack[1]?.type === 'test') return pack[1];
|
|
445
|
-
if (pack[0]?.type === 'test') return pack[0];
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const objectPack = /** @type {any} */ (pack);
|
|
450
|
-
if (typeof objectPack === 'object' && objectPack?.type === 'test') return objectPack;
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
227
|
export default VitestReporter;
|
|
455
228
|
export { VitestReporter };
|