diff --git a/reporter/index.js b/reporter/index.js index fd870fc..281c49a 100644 --- a/reporter/index.js +++ b/reporter/index.js @@ -67,6 +67,37 @@ const config = { } }; +const invokeResultsWriter = (allure, isGlobal) => { + if (!config || !config.allureEnabled()) { + return; + } + try { + cy.now( + 'task', + 'writeAllureResults', + { + results: allure.reporter.runtime.config, + files: allure.reporter.files, + mapping: allure.reporter.mochaIdToAllure, + clearSkipped: config.clearSkipped(), + isGlobal + }, + { log: false } + ) + // eslint-disable-next-line no-console + .catch((e) => + logger.allure( + `failed to execute task to write allure results: %O`, + e + ) + ); + logger.allure(`writing allure results`); + } catch (e) { + // happens when cy.task could not be executed due to fired outside of it + logger.allure(`failed to write allure results: %O`, e); + } +}; + class CypressAllureReporter { constructor() { logger.allure( @@ -112,33 +143,7 @@ class CypressAllureReporter { */ const isGlobal = suite.title === ''; this.reporter.endSuite(isGlobal); - - if (config && config.allureEnabled() && isGlobal) { - try { - cy.now( - 'task', - 'writeAllureResults', - { - results: this.reporter.runtime.config, - files: this.reporter.files, - mapping: this.reporter.mochaIdToAllure, - clearSkipped: config.clearSkipped() - }, - { log: false } - ) - // eslint-disable-next-line no-console - .catch((e) => - logger.allure( - `failed to execute task to write allure results: %O`, - e - ) - ); - logger.allure(`writing allure results`); - } catch (e) { - // happens when cy.task could not be executed due to fired outside of it - logger.allure(`failed to write allure results: %O`, e); - } - } + isGlobal && invokeResultsWriter(this, isGlobal); }) .on(EVENT_TEST_BEGIN, (test) => { onTestBegin(test); @@ -240,6 +245,10 @@ class CypressAllureReporter { } } +Cypress.on('test:after:run', () => { + invokeResultsWriter(Cypress.Allure, false); +}); + // when different hosts used in same test // Cypress opens new host URL and loads index.js // so if we already have Allure data we should not replace it with new instance diff --git a/writer.js b/writer.js index 20d442d..97a42a0 100644 --- a/writer.js +++ b/writer.js @@ -54,7 +54,13 @@ function allureWriter(on, config) { } on('task', { - writeAllureResults: ({ results, files, mapping, clearSkipped }) => { + writeAllureResults: ({ + results, + files, + mapping, + clearSkipped, + isGlobal + }) => { const { resultsDir: relativeResultsDir, writer } = results; const resultsDir = config.projectRoot @@ -69,7 +75,8 @@ function allureWriter(on, config) { files, clearSkipped, writer, - allureMapping + allureMapping, + isGlobal }); return null; diff --git a/writer/handleMultiDomain.js b/writer/handleMultiDomain.js new file mode 100644 index 0000000..eca4d57 --- /dev/null +++ b/writer/handleMultiDomain.js @@ -0,0 +1,113 @@ +const fs = require('fs'); +const path = require('path'); +const logger = require('../reporter/debug'); + +const readAllureResults = (folder) => { + try { + logger.writer('parsing existing allure results'); + if (!fs.existsSync(folder)) { + return; + } + + const files = fs.readdirSync(folder); + + const fileMap = files.map((filePath) => { + const getType = (file) => { + const types = { + suite: (f) => + f.includes('-container') && f.endsWith('.json'), + test: (f) => f.includes('-result') && f.endsWith('.json') + }; + return Object.keys(types).find((type) => types[type](file)); + }; + + const resultType = getType(filePath); + + const fileContent = + resultType === 'suite' || resultType === 'test' + ? JSON.parse( + fs.readFileSync(path.join(folder, filePath), { + encoding: 'utf-8' + }) + ) + : filePath; + + return fileContent; + }); + + return fileMap; + } catch (e) { + return e; + } +}; + +const sanitizeSuites = (folder, files, isGlobal) => { + const suites = files.filter((file) => file.children); + + for (const suite of suites) { + logger.writer('checking suite %s children', suite.uuid); + for (const childID of suite.children) { + const child = files.find((file) => file.uuid === childID); + + if (child.steps.length) { + logger.writer('child %s %s has steps', child.uuid, child.name); + continue; + } + + const duplicates = files.filter( + (file) => + file.historyId === child.historyId && + file.uuid !== child.uuid && + file.steps.length + ); + + const earliestDuplicate = duplicates + .sort((a, b) => a.start - b.start) + .shift(); + + if (!earliestDuplicate) { + logger.writer('no duplicate executions found: %s', child.uuid); + continue; + } + + const newChild = `${earliestDuplicate.uuid}-result.json`; + const originalChild = `${childID}-result.json`; + + fs.existsSync(path.join(folder, originalChild)) && + fs.unlinkSync(path.join(folder, originalChild)); + + fs.renameSync( + path.join(folder, newChild), + path.join(folder, originalChild) + ); + } + + const suitePath = `${suite.uuid}-container.json`; + fs.writeFileSync(path.join(folder, suitePath), JSON.stringify(suite)); + } + + if (!isGlobal) { + return; + } + + logger.writer('suite run finished, checking tests'); + + const tests = files.filter((file) => !file.children && file.historyId); + + for (const test of tests) { + const isAttached = suites.find((suite) => + suite.children.includes(test.uuid) + ); + if (!isAttached) { + const fileName = `${test.uuid}-result.json`; + const filePath = path.join(folder, fileName); + logger.writer('found orphan test, removing %s', fileName); + fs.existsSync(filePath) && fs.unlinkSync(filePath); + } + } +}; + +module.exports = { + readAllureResults, + sanitizeSuites +}; diff --git a/writer/results.js b/writer/results.js index 74429fd..afd6a32 100644 --- a/writer/results.js +++ b/writer/results.js @@ -6,6 +6,7 @@ const logger = require('../reporter/debug'); const { overwriteTestNameMaybe } = require('./customTestName'); const { clearEmptyHookSteps } = require('./clearEmptyHookSteps'); const { writeInfoFile, writeEnvProperties } = require('./infoFiles'); +const { readAllureResults, sanitizeSuites } = require('./handleMultiDomain'); const writeAttachmentFiles = ({ files, resultsDir, tests }) => { if (!files || !files.length) { @@ -142,6 +143,11 @@ const writeAttachments = ({ attachments, resultsDir }) => { } }; +const handleAfterTestWrites = ({ resultsDir, isGlobal }) => { + const parsed = readAllureResults(resultsDir); + return sanitizeSuites(resultsDir, parsed, isGlobal); +}; + const catchError = (fn, ...args) => { try { fn(...args); @@ -156,7 +162,8 @@ const writeResultFiles = ({ files, clearSkipped, writer, - allureMapping + allureMapping, + isGlobal }) => { !fs.existsSync(resultsDir) && fs.mkdirSync(resultsDir, { recursive: true }); @@ -169,6 +176,7 @@ const writeResultFiles = ({ catchError(writeSuites, { groups, resultsDir, tests, clearSkipped }); catchError(writeTests, { tests, resultsDir, clearSkipped, allureMapping }); catchError(writeAttachments, { attachments, resultsDir }); + catchError(handleAfterTestWrites, { resultsDir, isGlobal }); const allureResultsPath = (file) => path.join(resultsDir, file);