UNPKG

@testomatio/reporter

Version:
184 lines (163 loc) 5.65 kB
import pc from 'picocolors'; import { Client as TestomatioClient } from '../client.js'; import { STATUS } from '../constants.js'; import { getTestomatIdFromTestTitle } from '../utils/utils.js'; import createDebugMessages from 'debug'; const debug = createDebugMessages('@testomatio/reporter:adapter-jest'); /** * @typedef {import('../../types/types.js').VitestTest} VitestTest * @typedef {import('../../types/types.js').VitestTestFile} VitestTestFile * @typedef {import('../../types/types.js').VitestSuite} VitestSuite * @typedef {import('../../types/types.js').VitestTestLogs} VitestTestLogs * @typedef {import('../../types/vitest.types.js').ErrorWithDiff} ErrorWithDiff * @typedef {typeof import('../constants.js').STATUS} STATUS * @typedef {import('../../types/types.js').TestData} TestData */ class VitestReporter { constructor(config = {}) { this.client = new TestomatioClient({ apiKey: config?.apiKey }); /** * @type {(TestData & {status: string})[]} tests */ this.tests = []; } // on run start onInit() { this.client.createRun(); } /** * @param {VitestTestFile[] | undefined} files // array with results; * @param {unknown[] | undefined} errors // errors does not contain errors from tests; probably its testrunner errors */ async onFinished(files, errors) { if (!files || !files.length) console.info('No tests executed'); files.forEach(file => { // task could be test or suite file.tasks.forEach(taskOrSuite => { if (taskOrSuite.type === 'test') { const test = taskOrSuite; this.tests.push(this.#getDataFromTest(test)); } else if (taskOrSuite.type === 'suite') { const suite = taskOrSuite; this.#processTasksOfSuite(suite); } else { throw new Error('Unprocessed case. Unknown task type'); } }); }); debug(this.tests.length, 'tests collected'); // send tests to Testomat.io for (const test of this.tests) { await this.client.addTestRun(test.status, test); } console.log('finished'); if (errors.length) console.error('Vitest adapter errors:', errors); await this.client.updateRunStatus(getRunStatusFromResults(files)); } /* non-used listeners onUserConsoleLog(log) {} onPathsCollected(paths) {} // paths array to files with tests onCollected(files) {} // files array with tests (but without results) onTaskUpdate(packs) {} // some updates come here on afterAll block execution onTestRemoved(trigger) {} onWatcherStart(files, errors) {} onWatcherRerun(files, trigger) {} onServerRestart(reason) {} onProcessTimeout() {} */ /** * Recursively gets all tasks from suite and pushes them to "tests" array * * @param {VitestSuite} suite */ #processTasksOfSuite(suite) { suite.tasks.forEach(taskOrSuite => { if (taskOrSuite.type === 'test') { const test = taskOrSuite; this.tests.push(this.#getDataFromTest(test)); } else if (taskOrSuite.type === 'suite') { const theSuite = taskOrSuite; this.#processTasksOfSuite(theSuite); } else { throw new Error('Unprocessed case. Unknown task type'); } }); } /** * Processes task and returns test data ready to be sent to Testomat.io * * @param {VitestTest} test * * @returns {TestData & {status: string}} */ #getDataFromTest(test) { return { error: test.result?.errors ? test.result.errors[0] : undefined, file: test.file.name, logs: test.logs ? transformLogsToString(test.logs) : '', meta: test.meta, status: getTestStatus(test), suite_title: test.suite.name || test.file?.name, test_id: getTestomatIdFromTestTitle(test.name), time: test.result?.duration || 0, title: test.name, // testomatio functions (artifacts, logs, steps, meta) are not supported }; } } /** * Returns run status based on test results * * @param {VitestTestFile[]} files * @returns {'passed' | 'failed' | 'finished'} */ function getRunStatusFromResults(files) { /** * @type {'passed' | 'failed' | 'finished'} */ let status = 'finished'; // default status (if no failed or passed tests) files.forEach(file => { // search for failed tests file.tasks.forEach(taskOrSuite => { if (taskOrSuite.result?.state === 'fail') { status = 'failed'; // set status to failed if any test failed } }); // if there are no failed tests > search for passed tests if (status !== 'failed') { file.tasks.forEach(taskOrSuite => { if (taskOrSuite.result?.state === 'pass') { status = 'passed'; // set status to passed if any test passed (and there are no failed tests) } }); } }); return status; } /** * Returns test status in Testomat.io format * * @param {VitestTest} test * @returns 'passed' | 'failed' | 'skipped' */ function getTestStatus(test) { if (test.result?.state === 'fail') return STATUS.FAILED; if (test.result?.state === 'pass') return STATUS.PASSED; if (!test.result && test.mode === 'skip') return STATUS.SKIPPED; console.error(pc.red('Unprocessed case for defining test status. Contact dev team. Test:'), test); } /** * @param {VitestTestLogs[]} logs * @returns string */ function transformLogsToString(logs) { if (!logs) return ''; let logsStr = ''; logs.forEach(log => { if (log.type === 'stdout') logsStr += `${log.content}\n`; if (log.type === 'stderr') logsStr += `${pc.red(log.content)}\n`; }); return logsStr; } export default VitestReporter; export { VitestReporter };