diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6dc6b7d..fc8c4b1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,4 +20,5 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm i && npx playwright install chromium + - run: npm run lint - run: npm test diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..9519626 --- /dev/null +++ b/biome.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/constants/codeceptjsTypes.js b/constants/codeceptjsTypes.js index e2bd2ed..a5fa13f 100644 --- a/constants/codeceptjsTypes.js +++ b/constants/codeceptjsTypes.js @@ -1,24 +1,24 @@ const screenshotHelpers = [ - 'WebDriver', - 'Appium', - 'Puppeteer', - 'TestCafe', - 'Playwright', + 'WebDriver', + 'Appium', + 'Puppeteer', + 'TestCafe', + 'Playwright', ]; const PREFIX_PASSED_TEST = '✅ [TEST]'; const PREFIX_FAILED_TEST = '❌ [TEST]'; -const PREFIX_SKIPPED_TEST = '⏩ [SKIPPED TEST]' +const PREFIX_SKIPPED_TEST = '⏩ [SKIPPED TEST]'; const PREFIX_PASSED_STEP = '✅ [STEP]'; const PREFIX_FAILED_STEP = '❌ [STEP]'; -const PREFIX_BUG = '🐞🐞 LOGS --->' +const PREFIX_BUG = '🐞🐞 LOGS --->'; module.exports = { - screenshotHelpers, - PREFIX_PASSED_TEST, - PREFIX_FAILED_TEST, - PREFIX_SKIPPED_TEST, - PREFIX_PASSED_STEP, - PREFIX_FAILED_STEP, - PREFIX_BUG -} + screenshotHelpers, + PREFIX_PASSED_TEST, + PREFIX_FAILED_TEST, + PREFIX_SKIPPED_TEST, + PREFIX_PASSED_STEP, + PREFIX_FAILED_STEP, + PREFIX_BUG, +}; diff --git a/constants/launchModes.js b/constants/launchModes.js index 3689083..1eff17d 100644 --- a/constants/launchModes.js +++ b/constants/launchModes.js @@ -1,8 +1,8 @@ const LAUNCH_MODES = { - DEFAULT: 'DEFAULT', - DEBUG: 'DEBUG', -} + DEFAULT: 'DEFAULT', + DEBUG: 'DEBUG', +}; module.exports = { - LAUNCH_MODES -} + LAUNCH_MODES, +}; diff --git a/constants/logLevels.js b/constants/logLevels.js index 2a4e633..c6576d5 100644 --- a/constants/logLevels.js +++ b/constants/logLevels.js @@ -1,12 +1,12 @@ const LOG_LEVELS = { - TRACE: 'TRACE', - DEBUG: 'DEBUG', - WARN: 'WARN', - INFO: 'INFO', - ERROR: 'ERROR', - FATAL: 'FATAL', -} + TRACE: 'TRACE', + DEBUG: 'DEBUG', + WARN: 'WARN', + INFO: 'INFO', + ERROR: 'ERROR', + FATAL: 'FATAL', +}; module.exports = { - LOG_LEVELS -} + LOG_LEVELS, +}; diff --git a/constants/statuses.js b/constants/statuses.js index 8efcdc7..fd9f406 100644 --- a/constants/statuses.js +++ b/constants/statuses.js @@ -1,14 +1,14 @@ const STATUSES = { - FAILED: 'failed', - PASSED: 'passed', - SKIPPED: 'skipped', - STOPPED: 'stopped', - INTERRUPTED: 'interrupted', - CANCELLED: 'cancelled', - INFO: 'info', - WARN: 'warn', -} + FAILED: 'failed', + PASSED: 'passed', + SKIPPED: 'skipped', + STOPPED: 'stopped', + INTERRUPTED: 'interrupted', + CANCELLED: 'cancelled', + INFO: 'info', + WARN: 'warn', +}; module.exports = { - STATUSES -} + STATUSES, +}; diff --git a/constants/testItemTypes.js b/constants/testItemTypes.js index 94022be..4361a74 100644 --- a/constants/testItemTypes.js +++ b/constants/testItemTypes.js @@ -1,9 +1,9 @@ const TEST_ITEM_TYPES = { - SUITE: 'SUITE', - TEST: 'TEST', - STEP: 'STEP', -} + SUITE: 'SUITE', + TEST: 'TEST', + STEP: 'STEP', +}; module.exports = { - TEST_ITEM_TYPES -} + TEST_ITEM_TYPES, +}; diff --git a/helpers/restClient.js b/helpers/restClient.js index f095d45..6472052 100644 --- a/helpers/restClient.js +++ b/helpers/restClient.js @@ -1,41 +1,41 @@ const axios = require('axios').default; class RestClient { - constructor() { - this.axios = axios.create(); - } + constructor() { + this.axios = axios.create(); + } - async makePostRequest (url, payload = {}, headers = {}) { - try { - return this.axios.post(url, payload, { headers }); - } catch (e) { - throw new Error(e.message); - } - } + async makePostRequest(url, payload = {}, headers = {}) { + try { + return this.axios.post(url, payload, { headers }); + } catch (e) { + throw new Error(e.message); + } + } - async makePutRequest (url, payload = {}, headers = {}) { - try { - return this.axios.put(url, payload, { headers }); - } catch (e) { - throw new Error(e.message); - } - } + async makePutRequest(url, payload = {}, headers = {}) { + try { + return this.axios.put(url, payload, { headers }); + } catch (e) { + throw new Error(e.message); + } + } - async makeGetRequest(url, headers = {}) { - try { - return this.axios.get(url, { headers }); - } catch (e) { - throw new Error(e.message); - } - } + async makeGetRequest(url, headers = {}) { + try { + return this.axios.get(url, { headers }); + } catch (e) { + throw new Error(e.message); + } + } - async makeDeleteRequest(url, headers = {}) { - try { - return this.axios.delete(url, { headers }); - } catch (e) { - throw new Error(e.message); - } - } + async makeDeleteRequest(url, headers = {}) { + try { + return this.axios.delete(url, { headers }); + } catch (e) { + throw new Error(e.message); + } + } } module.exports = RestClient; diff --git a/helpers/rpHelpers.js b/helpers/rpHelpers.js index 7bdfde3..c5da8c8 100644 --- a/helpers/rpHelpers.js +++ b/helpers/rpHelpers.js @@ -1,198 +1,213 @@ -const RPClient = require("@reportportal/client-javascript"); -const {LAUNCH_MODES} = require("../constants/launchModes"); -const {TEST_ITEM_TYPES} = require("../constants/testItemTypes"); -const {STATUSES} = require("../constants/statuses"); -const fs = require("fs"); -const path = require("path"); -const RestClient = require("./restClient"); +const RPClient = require('@reportportal/client-javascript'); +const { LAUNCH_MODES } = require('../constants/launchModes'); +const { TEST_ITEM_TYPES } = require('../constants/testItemTypes'); +const { STATUSES } = require('../constants/statuses'); +const fs = require('fs'); +const path = require('path'); +const RestClient = require('./restClient'); const debug = require('debug')('codeceptjs:reportportal'); const restClient = new RestClient(); const { output, event } = codeceptjs; let rpClient; async function startLaunch(config, suiteTitle) { - rpClient = new RPClient({ - apiKey: config.token, - endpoint: config.endpoint, - project: config.projectName, - debug: config.debug, - }); - - return rpClient.startLaunch({ - name: config.launchName || suiteTitle, - description: config.launchDescription, - attributes: config.launchAttributes, - rerun: config.rerun, - rerunOf: config.rerunOf, - mode: LAUNCH_MODES.DEFAULT - }); + rpClient = new RPClient({ + apiKey: config.token, + endpoint: config.endpoint, + project: config.projectName, + debug: config.debug, + }); + + return rpClient.startLaunch({ + name: config.launchName || suiteTitle, + description: config.launchDescription, + attributes: config.launchAttributes, + rerun: config.rerun, + rerunOf: config.rerunOf, + mode: LAUNCH_MODES.DEFAULT, + }); } async function finishLaunch(launchObj, launchStatus) { - try { - debug(`${launchObj.tempId} Finished launch: ${launchStatus}`) - const launch = rpClient.finishLaunch(launchObj.tempId, { - status: launchStatus, - }); - - const response = await launch.promise; - event.emit('reportportal.result', response); - } catch (error) { - console.log(error); - debug(error); - } + try { + debug(`${launchObj.tempId} Finished launch: ${launchStatus}`); + const launch = rpClient.finishLaunch(launchObj.tempId, { + status: launchStatus, + }); + + const response = await launch.promise; + event.emit('reportportal.result', response); + } catch (error) { + console.log(error); + debug(error); + } } async function getRPLink(config, launchId) { - try { - const res = await restClient.makeGetRequest(`${config.endpoint}/${config.projectName}/launch?page.page=1&page.size=50&page.sort=startTime%2Cnumber%2CDESC`, { Authorization: `Bearer ${config.token}`}); - const launch = res.data.content.filter(item => item.uuid === launchId); - return `${config.endpoint.split('api')[0]}ui/#${config.projectName}/launches/all/${launch[0].id}`; - } catch (e) { - console.log(e); - } - + try { + const res = await restClient.makeGetRequest( + `${config.endpoint}/${config.projectName}/launch?page.page=1&page.size=50&page.sort=startTime%2Cnumber%2CDESC`, + { Authorization: `Bearer ${config.token}` }, + ); + const launch = res.data.content.filter((item) => item.uuid === launchId); + return `${config.endpoint.split('api')[0]}ui/#${ + config.projectName + }/launches/all/${launch[0].id}`; + } catch (e) { + console.log(e); + } } function writePRInfo(launchLink, config) { - output.print(`📋 Writing results to ReportPortal: Project Name: ${config.projectName} > RP Endpoint: ${config.endpoint}`); - output.print(`📋 ReportPortal Launch Link: ${launchLink}`); + output.print( + `📋 Writing results to ReportPortal: Project Name: ${config.projectName} > RP Endpoint: ${config.endpoint}`, + ); + output.print(`📋 ReportPortal Launch Link: ${launchLink}`); } -async function startTestItem(launchId, testTitle, method, parentId = null, parentTitle) { - try { - if (method === TEST_ITEM_TYPES.SUITE) { - const payload = suitePayload(testTitle) - return rpClient.startTestItem(payload, launchId, parentId); - } - - if (method === TEST_ITEM_TYPES.TEST) { - const payload = testPayload(testTitle, parentTitle) - return rpClient.startTestItem(payload, launchId, parentId); - } - - if ( method === TEST_ITEM_TYPES.STEP) { - const payload = stepPayload(testTitle, parentTitle) - return rpClient.startTestItem(payload, launchId, parentId); - } - - } catch (error) { - console.log(error); - } +async function startTestItem( + launchId, + testTitle, + method, + parentId, + parentTitle, +) { + try { + if (method === TEST_ITEM_TYPES.SUITE) { + const payload = suitePayload(testTitle); + return rpClient.startTestItem(payload, launchId, parentId); + } + + if (method === TEST_ITEM_TYPES.TEST) { + const payload = testPayload(testTitle, parentTitle); + return rpClient.startTestItem(payload, launchId, parentId); + } + + if (method === TEST_ITEM_TYPES.STEP) { + const payload = stepPayload(testTitle, parentTitle); + return rpClient.startTestItem(payload, launchId, parentId); + } + } catch (error) { + console.log(error); + } } async function finishTestItem(test, issueObject) { - if (!test) return; + if (!test) return; - debug(`Finishing '${test.toString()}' Test`); - const testItemRQ = { - endTime: rpClient.helpers.now(), - status: rpStatus(test.status), - } + debug(`Finishing '${test.toString()}' Test`); + const testItemRQ = { + endTime: rpClient.helpers.now(), + status: rpStatus(test.status), + }; - if (issueObject) { - testItemRQ.issue = issueObject; - } + if (issueObject) { + testItemRQ.issue = issueObject; + } - rpClient.finishTestItem(test.tempId, testItemRQ); + rpClient.finishTestItem(test.tempId, testItemRQ); } async function finishStepItem(step) { - if (!step) return; + if (!step) return; - debug(`Finishing '${step.toString()}' step`); + debug(`Finishing '${step.toString()}' step`); - return rpClient.finishTestItem(step.tempId, { - endTime: rpClient.helpers.now(), - status: rpStatus(step.status) || STATUSES.PASSED, - }); + return rpClient.finishTestItem(step.tempId, { + endTime: rpClient.helpers.now(), + status: rpStatus(step.status) || STATUSES.PASSED, + }); } function logCurrent(data, file) { - const obj = stepObj || testObj; - if (obj) rpClient.sendLog(obj.tempId, data, file); + const obj = stepObj || testObj; + if (obj) rpClient.sendLog(obj.tempId, data, file); } function rpStatus(status) { - if (status.toLowerCase() === 'success') return STATUSES.PASSED; - if (status.toLowerCase() === 'failed') return STATUSES.FAILED; - return status; + if (status.toLowerCase() === 'success') return STATUSES.PASSED; + if (status.toLowerCase() === 'failed') return STATUSES.FAILED; + return status; } function suitePayload(title) { - return { - name: title, - type: TEST_ITEM_TYPES.SUITE, - codeRef: '' - } + return { + name: title, + type: TEST_ITEM_TYPES.SUITE, + codeRef: '', + }; } function testPayload(testTitle, suiteTitle) { - return { - name: testTitle, - type: TEST_ITEM_TYPES.STEP, - codeRef: `${suiteTitle}/${testTitle}`, - testCaseId: `${suiteTitle}/${testTitle}`, - hasStats: true - } + return { + name: testTitle, + type: TEST_ITEM_TYPES.STEP, + codeRef: `${suiteTitle}/${testTitle}`, + testCaseId: `${suiteTitle}/${testTitle}`, + hasStats: true, + }; } function stepPayload(stepTitle, testTitle) { - return { - name: stepTitle, - type: TEST_ITEM_TYPES.STEP, - codeRef: `${testTitle}/${stepTitle}`, - testCaseId: `${testTitle}/${stepTitle}`, - hasStats: false - } + return { + name: stepTitle, + type: TEST_ITEM_TYPES.STEP, + codeRef: `${testTitle}/${stepTitle}`, + testCaseId: `${testTitle}/${stepTitle}`, + hasStats: false, + }; } -async function sendLogToRP({tempId, level, message, screenshotData}) { - debug(`📷 Attaching screenshot & error to failed step...`); - return rpClient.sendLog(tempId, { - level, - message, - }, screenshotData).promise; +async function sendLogToRP({ tempId, level, message, screenshotData }) { + debug('📷 Attaching screenshot & error to failed step...'); + return rpClient.sendLog( + tempId, + { + level, + message, + }, + screenshotData, + ).promise; } async function attachScreenshot(helper, fileName) { - if (!helper) return undefined; - let content; - - if (!fileName) { - fileName = `${rpClient.helpers.now()}_failed.png`; - try { - await helper.saveScreenshot(fileName); - content = fs.readFileSync(path.join(global.output_dir, fileName)); - fs.unlinkSync(path.join(global.output_dir, fileName)); - } catch (err) { - output.error('Couldn\'t save screenshot'); - return undefined; - } - } else { - content = fs.readFileSync(path.join(global.output_dir, fileName)); - } - - return { - name: fileName, - type: 'image/png', - content, - }; + if (!helper) return undefined; + let content; + + if (!fileName) { + const _fileName = `${rpClient.helpers.now()}_failed.png`; + try { + await helper.saveScreenshot(_fileName); + content = fs.readFileSync(path.join(global.output_dir, _fileName)); + fs.unlinkSync(path.join(global.output_dir, _fileName)); + } catch (err) { + output.error("Couldn't save screenshot"); + return undefined; + } + } else { + content = fs.readFileSync(path.join(global.output_dir, fileName)); + } + + return { + name: fileName, + type: 'image/png', + content, + }; } module.exports = { - startLaunch, - getRPLink, - writePRInfo, - startTestItem, - finishTestItem, - finishStepItem, - logCurrent, - rpStatus, - suitePayload, - testPayload, - stepPayload, - sendLogToRP, - attachScreenshot, - finishLaunch -} + startLaunch, + getRPLink, + writePRInfo, + startTestItem, + finishTestItem, + finishStepItem, + logCurrent, + rpStatus, + suitePayload, + testPayload, + stepPayload, + sendLogToRP, + attachScreenshot, + finishLaunch, +}; diff --git a/index.js b/index.js index 4066fd7..778ff07 100644 --- a/index.js +++ b/index.js @@ -1,22 +1,31 @@ const debug = require('debug')('codeceptjs:reportportal'); -const {event, recorder, output, container} = codeceptjs; -const {clearString} = require('codeceptjs/lib/utils'); -const sleep = require("sleep-promise"); +const { event, recorder, output, container } = codeceptjs; +const { clearString } = require('codeceptjs/lib/utils'); +const sleep = require('sleep-promise'); const { - screenshotHelpers, - PREFIX_PASSED_STEP, - PREFIX_SKIPPED_TEST, - PREFIX_FAILED_TEST, - PREFIX_PASSED_TEST, PREFIX_FAILED_STEP, PREFIX_BUG -} = require("./constants/codeceptjsTypes"); -const {STATUSES} = require("./constants/statuses"); -const {TEST_ITEM_TYPES} = require("./constants/testItemTypes"); -const {LOG_LEVELS} = require("./constants/logLevels"); + screenshotHelpers, + PREFIX_PASSED_STEP, + PREFIX_SKIPPED_TEST, + PREFIX_FAILED_TEST, + PREFIX_PASSED_TEST, + PREFIX_FAILED_STEP, + PREFIX_BUG, +} = require('./constants/codeceptjsTypes'); +const { STATUSES } = require('./constants/statuses'); +const { TEST_ITEM_TYPES } = require('./constants/testItemTypes'); +const { LOG_LEVELS } = require('./constants/logLevels'); const { - startLaunch, getRPLink, writePRInfo, startTestItem, logCurrent, rpStatus, finishStepItem, sendLogToRP, - attachScreenshot, finishLaunch -} = require("./helpers/rpHelpers"); -const {finishTestItem} = require("./helpers/rpHelpers"); + startLaunch, + getRPLink, + writePRInfo, + startTestItem, + logCurrent, + finishStepItem, + sendLogToRP, + attachScreenshot, + finishLaunch, +} = require('./helpers/rpHelpers'); +const { finishTestItem } = require('./helpers/rpHelpers'); const { version } = require('./package.json'); const deepMerge = require('lodash.merge'); @@ -24,279 +33,366 @@ const helpers = container.helpers(); let helper; for (const helperName of screenshotHelpers) { - if (Object.keys(helpers).indexOf(helperName) > -1) { - helper = helpers[helperName]; - } + if (Object.keys(helpers).indexOf(helperName) > -1) { + helper = helpers[helperName]; + } } const defaultConfig = { - token: '', - endpoint: '', - projectName: '', - launchName: 'codeceptjs tests', - launchDescription: '', - launchAttributes: [{ - key: 'platform', - value: process.platform - },{ - key: 'rphelper-version', - value: version, - }], - debug: false, - rerun: undefined, - enabled: false + token: '', + endpoint: '', + projectName: '', + launchName: 'codeceptjs tests', + launchDescription: '', + launchAttributes: [ + { + key: 'platform', + value: process.platform, + }, + { + key: 'rphelper-version', + value: version, + }, + ], + debug: false, + rerun: undefined, + enabled: false, }; const requiredFields = ['projectName', 'token', 'endpoint']; -module.exports = (config) => { - config = deepMerge(defaultConfig, config); - - for (let field of requiredFields) { - if (!config[field]) throw new Error(`ReportPortal config is invalid. Key ${field} is missing in config.\nRequired fields: ${requiredFields} `) - } - - let launchObj; - let suiteObj; - let testObj; - let launchStatus = STATUSES.PASSED; - let currentMetaSteps = []; - let suiteArr = new Set(); - let testArr = []; - let testResults = { - suites: [], - tests: { - passed: [], - failed: [], - skipped: [] - } - }; - - let currenTestTitle; - let currentSuiteTitle; - - event.dispatcher.on(event.suite.before, async (suite) => { - await recorder.add(async () => { - testResults.suites.push(suite); - }); - }); - - event.dispatcher.on(event.test.failed, async (test, err) => { - await recorder.add(async () => { - if (!process.env.RUNS_WITH_WORKERS) { - testResults.tests.failed.push(test); - } - }); - - }); - - event.dispatcher.on(event.test.passed, async (test) => { - await recorder.add(async () => { - if (!process.env.RUNS_WITH_WORKERS) { - testResults.tests.passed.push(test); - } - }); - }); - - event.dispatcher.on(event.all.result, async () => { - await recorder.add(async () => { - if (!process.env.RUNS_WITH_WORKERS) { - debug('Finishing launch...'); - await _sendResultsToRP(testResults); - } - }); - }); - - event.dispatcher.on(event.workers.result, async (result) => { - await recorder.add(async () => { - await _sendResultsToRP(result); - }); - }); - - async function _sendResultsToRP(result) { - for (suite of result.suites) { - suiteArr.add(suite.title); - } - testArr = result.tests; - - launchObj = await startLaunch(config); - const launchId = (await launchObj.promise).id; - const launchLink = await getRPLink(config, launchId); - writePRInfo(launchLink, config); - - const suiteTempIdArr = []; - const testTempIdArr = []; - - for (suite of suiteArr) { - suiteObj = await startTestItem(launchObj.tempId, suite, TEST_ITEM_TYPES.SUITE); - suiteObj.status = (testArr.failed.length > 0) ? STATUSES.FAILED : STATUSES.PASSED; - suiteTempIdArr.push({ - suiteTitle: suite, - suiteTempId: suiteObj.tempId, - }); - currentSuiteTitle = suite; - await finishStepItem(suiteObj); - } - - - for (test of testArr.passed) { - currenTestTitle = test.title; - testObj = await startTestItem(launchObj.tempId, test.title, TEST_ITEM_TYPES.TEST, suiteTempIdArr.find((element) => element.suiteTitle === test.parent.title).suiteTempId, currentSuiteTitle); - testObj.status = STATUSES.PASSED; - - testTempIdArr.push({ - testTitle: test.title, - testTempId: testObj.tempId, - testError: test.err, - testSteps: test.steps, - }); - - const message = `${PREFIX_PASSED_TEST} - ${test.title}`; - await sendLogToRP({tempId: testObj.tempId, level: LOG_LEVELS.INFO, message}); - await finishTestItem(testObj); - } - - for (test of testArr.failed) { - currenTestTitle = test.title; - testObj = await startTestItem(launchObj.tempId, test.title, TEST_ITEM_TYPES.TEST, suiteTempIdArr.find((element) => element.suiteTitle === test.parent.title).suiteTempId, currentSuiteTitle); - testObj.status = STATUSES.FAILED; - launchStatus = STATUSES.FAILED; - - testTempIdArr.push({ - testTitle: test.title, - testTempId: testObj.tempId, - testError: test.err, - testSteps: test.steps, - }); - - const message = `${PREFIX_FAILED_TEST} - ${test.title}\n${test.err.stack ? test.err.stack : JSON.stringify(test.err)}`; - await sendLogToRP({tempId: testObj.tempId, level: LOG_LEVELS.ERROR, message}); - await finishTestItem(testObj, config.issue); - } - - for (test of testArr.skipped) { - currenTestTitle = test.title; - testObj = await startTestItem(launchObj.tempId, test.title, TEST_ITEM_TYPES.TEST, suiteTempIdArr.find((element) => element.suiteTitle === test.parent.title).suiteTempId, currentSuiteTitle); - testObj.status = STATUSES.SKIPPED; - - testTempIdArr.push({ - testTitle: test.title, - testTempId: testObj.tempId, - testError: test.err, - testSteps: test.steps, - }); - - const message = `${PREFIX_SKIPPED_TEST} - ${test.title}`; - await sendLogToRP({tempId: testObj.tempId, level: LOG_LEVELS.INFO, message}); - await finishTestItem(testObj); - } - - for (test of testTempIdArr) { - for (step of test.testSteps) { - if (!step) { - debug(`The ${test.testTitle} has no steps.`); - break; - } - const stepArgs = step.agrs ? step.agrs : step.args; - const prefix = step.status === STATUSES.FAILED ? PREFIX_FAILED_STEP : PREFIX_PASSED_STEP; - const stepTitle = stepArgs ? `${prefix}: ${step.actor} ${step.name} ${JSON.stringify(stepArgs.map(item => item && item._secret ? '*****' : JSON.stringify(item)).join(' '))}` : `${prefix}: - ${step.actor} ${step.name}`; - - await sleep(1); - const stepObj = await startTestItem(launchObj.tempId, stepTitle.slice(0, 300), TEST_ITEM_TYPES.STEP, test.testTempId, currenTestTitle); - - stepObj.status = step.status === STATUSES.FAILED ? STATUSES.FAILED : STATUSES.PASSED; - - if (stepObj.status === STATUSES.FAILED) { - let stepMessage; - if (step.err) { - stepMessage = `${PREFIX_BUG}: ${(step.err.stack ? step.err.stack : JSON.stringify(step.err))}`; - } else if (step.helper.currentRunningTest.err) { - stepMessage = `${PREFIX_BUG}: ${JSON.stringify(step.helper.currentRunningTest.err)}`; - } - await sendLogToRP({tempId: stepObj.tempId, level: LOG_LEVELS.ERROR, message: stepMessage}); - - if (helper) { - const screenshot = await attachScreenshot(helper, `${clearString(test.testTitle)}.failed.png`); - await sendLogToRP({ - tempId: stepObj.tempId, - level: LOG_LEVELS.DEBUG, - message: '📷 Last seen screenshot', - screenshotData: screenshot, - }); - } - } - - await finishStepItem(stepObj); - } - } - - await finishLaunch(launchObj, launchStatus); - } - - async function startMetaSteps(step, parentTitle) { - let metaStepObj = {}; - const metaSteps = metaStepsToArray(step.metaStep); - - // close current metasteps - for (let j = currentMetaSteps.length - 1; j >= metaSteps.length; j--) { - await finishStep(currentMetaSteps[j]); - } - - for (const i in metaSteps) { - const metaStep = metaSteps[i]; - if (isEqualMetaStep(metaStep, currentMetaSteps[i])) { - metaStep.tempId = currentMetaSteps[i].tempId; - continue; - } - // close metasteps other than current - for (let j = currentMetaSteps.length - 1; j >= i; j--) { - await finishStep(currentMetaSteps[j]); - delete currentMetaSteps[j]; - } - - metaStepObj = currentMetaSteps[i - 1] || metaStepObj; - - const isNested = !!metaStepObj.tempId; - metaStepObj = startTestItem(launchObj.tempId, metaStep.toString(), TEST_ITEM_TYPES.STEP, metaStepObj.tempId || testObj.tempId, parentTitle); - metaStep.tempId = metaStepObj.tempId; - debug(`${metaStep.tempId}: The stepId '${metaStep.toString()}' is started. Nested: ${isNested}`); - } - - currentMetaSteps = metaSteps; - return currentMetaSteps[currentMetaSteps.length - 1] || testObj; - } - - function finishStep(step) { - if (!step.tempId) { - debug(`WARNING: '${step.toString()}' step can't be closed, it has no tempId`); - return; - } - debug(`Finishing '${step.toString()}' step`); - - return finishStepItem(step); - } - - return { - addLog: logCurrent, - }; +module.exports = (passedConfig) => { + const config = deepMerge(defaultConfig, passedConfig); + + for (const field of requiredFields) { + if (!config[field]) + throw new Error( + `ReportPortal config is invalid. Key ${field} is missing in config.\nRequired fields: ${requiredFields} `, + ); + } + + let launchObj; + let suiteObj; + let testObj; + let launchStatus = STATUSES.PASSED; + let currentMetaSteps = []; + const suiteArr = new Set(); + let testArr = []; + const testResults = { + suites: [], + tests: { + passed: [], + failed: [], + skipped: [], + }, + }; + + let currenTestTitle; + let currentSuiteTitle; + + event.dispatcher.on(event.suite.before, async (suite) => { + await recorder.add(async () => { + testResults.suites.push(suite); + }); + }); + + event.dispatcher.on(event.test.failed, async (test, err) => { + await recorder.add(async () => { + if (!process.env.RUNS_WITH_WORKERS) { + testResults.tests.failed.push(test); + } + }); + }); + + event.dispatcher.on(event.test.passed, async (test) => { + await recorder.add(async () => { + if (!process.env.RUNS_WITH_WORKERS) { + testResults.tests.passed.push(test); + } + }); + }); + + event.dispatcher.on(event.all.result, async () => { + await recorder.add(async () => { + if (!process.env.RUNS_WITH_WORKERS) { + debug('Finishing launch...'); + await _sendResultsToRP(testResults); + } + }); + }); + + event.dispatcher.on(event.workers.result, async (result) => { + await recorder.add(async () => { + await _sendResultsToRP(result); + }); + }); + + async function _sendResultsToRP(result) { + for (suite of result.suites) { + suiteArr.add(suite.title); + } + testArr = result.tests; + + launchObj = await startLaunch(config); + const launchId = (await launchObj.promise).id; + const launchLink = await getRPLink(config, launchId); + writePRInfo(launchLink, config); + + const suiteTempIdArr = []; + const testTempIdArr = []; + + for (suite of suiteArr) { + suiteObj = await startTestItem( + launchObj.tempId, + suite, + TEST_ITEM_TYPES.SUITE, + ); + suiteObj.status = + testArr.failed.length > 0 ? STATUSES.FAILED : STATUSES.PASSED; + suiteTempIdArr.push({ + suiteTitle: suite, + suiteTempId: suiteObj.tempId, + }); + currentSuiteTitle = suite; + await finishStepItem(suiteObj); + } + + for (test of testArr.passed) { + currenTestTitle = test.title; + testObj = await startTestItem( + launchObj.tempId, + test.title, + TEST_ITEM_TYPES.TEST, + suiteTempIdArr.find( + (element) => element.suiteTitle === test.parent.title, + ).suiteTempId, + currentSuiteTitle, + ); + testObj.status = STATUSES.PASSED; + + testTempIdArr.push({ + testTitle: test.title, + testTempId: testObj.tempId, + testError: test.err, + testSteps: test.steps, + }); + + const message = `${PREFIX_PASSED_TEST} - ${test.title}`; + await sendLogToRP({ + tempId: testObj.tempId, + level: LOG_LEVELS.INFO, + message, + }); + await finishTestItem(testObj); + } + + for (test of testArr.failed) { + currenTestTitle = test.title; + testObj = await startTestItem( + launchObj.tempId, + test.title, + TEST_ITEM_TYPES.TEST, + suiteTempIdArr.find( + (element) => element.suiteTitle === test.parent.title, + ).suiteTempId, + currentSuiteTitle, + ); + testObj.status = STATUSES.FAILED; + launchStatus = STATUSES.FAILED; + + testTempIdArr.push({ + testTitle: test.title, + testTempId: testObj.tempId, + testError: test.err, + testSteps: test.steps, + }); + + const message = `${PREFIX_FAILED_TEST} - ${test.title}\n${ + test.err.stack ? test.err.stack : JSON.stringify(test.err) + }`; + await sendLogToRP({ + tempId: testObj.tempId, + level: LOG_LEVELS.ERROR, + message, + }); + await finishTestItem(testObj, config.issue); + } + + for (test of testArr.skipped) { + currenTestTitle = test.title; + testObj = await startTestItem( + launchObj.tempId, + test.title, + TEST_ITEM_TYPES.TEST, + suiteTempIdArr.find( + (element) => element.suiteTitle === test.parent.title, + ).suiteTempId, + currentSuiteTitle, + ); + testObj.status = STATUSES.SKIPPED; + + testTempIdArr.push({ + testTitle: test.title, + testTempId: testObj.tempId, + testError: test.err, + testSteps: test.steps, + }); + + const message = `${PREFIX_SKIPPED_TEST} - ${test.title}`; + await sendLogToRP({ + tempId: testObj.tempId, + level: LOG_LEVELS.INFO, + message, + }); + await finishTestItem(testObj); + } + + for (test of testTempIdArr) { + for (step of test.testSteps) { + if (!step) { + debug(`The ${test.testTitle} has no steps.`); + break; + } + const stepArgs = step.agrs ? step.agrs : step.args; + const prefix = + step.status === STATUSES.FAILED + ? PREFIX_FAILED_STEP + : PREFIX_PASSED_STEP; + const stepTitle = stepArgs + ? `${prefix}: ${step.actor} ${step.name} ${JSON.stringify( + stepArgs + .map((item) => (item?._secret ? '*****' : JSON.stringify(item))) + .join(' '), + )}` + : `${prefix}: - ${step.actor} ${step.name}`; + + await sleep(1); + const stepObj = await startTestItem( + launchObj.tempId, + stepTitle.slice(0, 300), + TEST_ITEM_TYPES.STEP, + test.testTempId, + currenTestTitle, + ); + + stepObj.status = + step.status === STATUSES.FAILED ? STATUSES.FAILED : STATUSES.PASSED; + + if (stepObj.status === STATUSES.FAILED) { + let stepMessage; + if (step.err) { + stepMessage = `${PREFIX_BUG}: ${ + step.err.stack ? step.err.stack : JSON.stringify(step.err) + }`; + } else if (step.helper.currentRunningTest.err) { + stepMessage = `${PREFIX_BUG}: ${JSON.stringify( + step.helper.currentRunningTest.err, + )}`; + } + await sendLogToRP({ + tempId: stepObj.tempId, + level: LOG_LEVELS.ERROR, + message: stepMessage, + }); + + if (helper) { + const screenshot = await attachScreenshot( + helper, + `${clearString(test.testTitle)}.failed.png`, + ); + await sendLogToRP({ + tempId: stepObj.tempId, + level: LOG_LEVELS.DEBUG, + message: '📷 Last seen screenshot', + screenshotData: screenshot, + }); + } + } + + await finishStepItem(stepObj); + } + } + + await finishLaunch(launchObj, launchStatus); + } + + async function startMetaSteps(step, parentTitle) { + let metaStepObj = {}; + const metaSteps = metaStepsToArray(step.metaStep); + + // close current metasteps + for (let j = currentMetaSteps.length - 1; j >= metaSteps.length; j--) { + await finishStep(currentMetaSteps[j]); + } + + for (const i in metaSteps) { + const metaStep = metaSteps[i]; + if (isEqualMetaStep(metaStep, currentMetaSteps[i])) { + metaStep.tempId = currentMetaSteps[i].tempId; + continue; + } + // close metasteps other than current + for (let j = currentMetaSteps.length - 1; j >= i; j--) { + await finishStep(currentMetaSteps[j]); + delete currentMetaSteps[j]; + } + + metaStepObj = currentMetaSteps[i - 1] || metaStepObj; + + const isNested = !!metaStepObj.tempId; + metaStepObj = startTestItem( + launchObj.tempId, + metaStep.toString(), + TEST_ITEM_TYPES.STEP, + metaStepObj.tempId || testObj.tempId, + parentTitle, + ); + metaStep.tempId = metaStepObj.tempId; + debug( + `${ + metaStep.tempId + }: The stepId '${metaStep.toString()}' is started. Nested: ${isNested}`, + ); + } + + currentMetaSteps = metaSteps; + return currentMetaSteps[currentMetaSteps.length - 1] || testObj; + } + + function finishStep(step) { + if (!step.tempId) { + debug( + `WARNING: '${step.toString()}' step can't be closed, it has no tempId`, + ); + return; + } + debug(`Finishing '${step.toString()}' step`); + + return finishStepItem(step); + } + + return { + addLog: logCurrent, + }; }; function metaStepsToArray(step) { - let metaSteps = []; - iterateMetaSteps(step, metaStep => metaSteps.push(metaStep)); - return metaSteps; + const metaSteps = []; + iterateMetaSteps(step, (metaStep) => metaSteps.push(metaStep)); + return metaSteps; } function iterateMetaSteps(step, fn) { - if (step && step.metaStep) iterateMetaSteps(step.metaStep, fn); - if (step) fn(step); + if (step?.metaStep) iterateMetaSteps(step.metaStep, fn); + if (step) fn(step); } - const isEqualMetaStep = (metastep1, metastep2) => { - if (!metastep1 && !metastep2) return true; - if (!metastep1 || !metastep2) return false; - return metastep1.actor === metastep2.actor - && metastep1.name === metastep2.name - && metastep1.args.join(',') === metastep2.args.join(','); + if (!metastep1 && !metastep2) return true; + if (!metastep1 || !metastep2) return false; + return ( + metastep1.actor === metastep2.actor && + metastep1.name === metastep2.name && + metastep1.args.join(',') === metastep2.args.join(',') + ); }; diff --git a/package.json b/package.json index 3f1f78d..ee57c5b 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,12 @@ "scripts": { "acceptance_test": "mocha test/acceptance_test.js --timeout 50000", "bdd_acceptance_test": "mocha test/bdd_acceptance_test.js --timeout 50000", - "test": "npm run acceptance_test && npm run bdd_acceptance_test" + "test": "npm run acceptance_test && npm run bdd_acceptance_test", + "lint": "biome lint constants/*.js helpers/*.js index.js && biome format constants/*.js helpers/*.js index.js", + "lint:fix": "biome check constants/*.js helpers/*.js index.js --apply-unsafe && biome format constants/*.js helpers/*.js index.js --write" }, "devDependencies": { + "@biomejs/biome": "1.5.3", "@faker-js/faker": "8.3.1", "@types/node": "20.10.5", "eslint": "6.6.0",