UNPKG

@shelex/cypress-allure-plugin

Version:
443 lines (392 loc) 14.6 kB
require('./commands'); const { setTestIdToScreenshot, reorderAllureHooks, buildPendingResults, loadPendingResults, clearAllureHookStepsFromTests, clearAllureHookStepsFromHook, ALLURE_HOOK_NAME } = require('./patching.js'); const { EVENT_TEST_BEGIN, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING, EVENT_SUITE_BEGIN, EVENT_SUITE_END, EVENT_TEST_END, EVENT_HOOK_BEGIN, EVENT_HOOK_END, EVENT_TEST_RETRY } = Mocha.Runner.constants; const path = require('path-browserify'); const { AllureRuntime, InMemoryAllureWriter, ContentType } = require('@shelex/allure-js-commons-browser'); const AllureReporter = require('./allure-cypress/AllureReporter'); const stubbedAllure = require('./stubbedAllure'); const logger = require('./debug'); const { shouldUseAfterSpec } = require('../writer/useAfterSpec'); const { commandIsGherkinStep } = require('./allure-cypress/CucumberHandler'); const { env } = Cypress; const shouldEnableGherkinLogging = () => { const logCypress = env('allureLogCypress'); const logGherkin = env('allureLogGherkin'); const isLogCypressDefined = typeof logCypress !== 'undefined'; const isLogGherkinDefined = typeof logGherkin !== 'undefined'; if (isLogGherkinDefined) { return logGherkin; } // in case logGherkin is not defined use logCypress value or true by default return isLogCypressDefined ? logCypress : true; }; const config = { allureEnabled: () => env('allure'), resultsPath: () => env('allureResultsPath') || 'allure-results', shouldLogCypress: () => env('allureLogCypress') !== false, shouldAttachRequests: () => env('allureAttachRequests'), shouldLogGherkinSteps: () => shouldEnableGherkinLogging(), clearFilesForPreviousAttempt: () => env('allureOmitPreviousAttemptScreenshots'), clearSkipped: () => env('allureClearSkippedTests') === true, addAnalyticLabels: () => env('allureAddAnalyticLabels'), addVideoOnPass: () => env('allureAddVideoOnPass'), skipAutomaticScreenshots: () => env('allureSkipAutomaticScreenshots') === true, loggingCommandStepsEnabled: true, avoidLoggingCommands: () => { const input = env('allureAvoidLoggingCommands'); if (Array.isArray(input)) { return input; } if (input && typeof input === 'string') { return input.startsWith('[') ? JSON.parse(input) : input.split(','); } return []; }, logAllureHooksEnabled: () => env('allureLogHooks') === true }; const invokeResultsWriter = (allure, isGlobal) => { if (!config || !config.allureEnabled()) { return; } try { clearAllureHookStepsFromTests( allure.reporter.runtime.config.writer.tests, config.logAllureHooksEnabled() ); return cy .task( 'writeAllureResults', { pendingResults: buildPendingResults({ currentSpec: Cypress.spec.relative || Cypress.spec.absolute, allure }), files: allure.reporter.files || [], clearSkipped: config.clearSkipped(), isGlobal, defineHistoryId: allure.reporter.defineHistoryId }, { log: config.logAllureHooksEnabled() } ) .then((result) => { 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); } }; const skipAlreadyRunTest = (test) => { if (test && test._ALREADY_RAN) { if (!(test.id in Cypress.Allure.reporter.mochaIdToAllure)) { test['_ALLURE_RUN'] = true; return false; } return !test._ALLURE_RUN; } return false; }; class CypressAllureReporter { constructor() { logger.allure( `creating allure reporter instance, cypress env: %O`, env() ); this.createAllureReporter(config); this.config = config; const onTestFail = (test, err) => { logger.mocha(`EVENT_TEST_FAIL: %s %O`, test.title, test); this.reporter.failTestCase(test, err); attachVideo(this.reporter, test, 'failed'); }; const onTestBegin = (test) => { logger.mocha(`EVENT_TEST_BEGIN: %s %O`, test.title, test); this.reporter.startCase(test, config); }; const onTestEnd = (test) => { logger.mocha(`EVENT_TEST_END: %s %O`, test.title, test); attachVideo(this.reporter, test, 'finished'); this.reporter.gherkin.checkLinksInExamplesTable(); this.reporter.gherkin.checkTags(); this.reporter.endTest(test); }; Cypress.mocha .getRunner() .on(EVENT_SUITE_BEGIN, (suite) => { logger.mocha(`EVENT_SUITE_BEGIN: %s %O`, suite.title, suite); reorderAllureHooks(suite); this.reporter.startSuite(suite); }) .on(EVENT_SUITE_END, (suite) => { logger.mocha(`EVENT_SUITE_END: %s %O`, suite.title, suite); /** * only global cypress file suite end * should be triggered from here * others are handled on suite start event */ const isGlobal = suite.title === ''; this.reporter.endSuite(isGlobal); }) .on(EVENT_TEST_BEGIN, (test) => { if (skipAlreadyRunTest(test)) { return; } onTestBegin(test); }) .on(EVENT_TEST_FAIL, (test, err) => { if (skipAlreadyRunTest(test)) { return; } onTestFail(test, err); }) .on(EVENT_TEST_PASS, (test) => { logger.mocha(`EVENT_TEST_PASS: %s %O`, test.title, test); if (skipAlreadyRunTest(test)) { return; } this.reporter.passTestCase(test); }) .on(EVENT_TEST_RETRY, (test) => { logger.mocha(`EVENT_TEST_RETRY: %s %O`, test.title, test); onTestFail(test, test.err); onTestEnd(test); }) .on(EVENT_TEST_PENDING, (test) => { logger.mocha(`EVENT_TEST_PENDING: %s %O`, test.title, test); if (skipAlreadyRunTest(test)) { return; } this.reporter.pendingTestCase(test); }) .on(EVENT_TEST_END, (test) => { if (skipAlreadyRunTest(test)) { return; } onTestEnd(test); }) .on(EVENT_HOOK_BEGIN, (hook) => { logger.mocha(`EVENT_HOOK_BEGIN: %s %O`, hook.title, hook); this.reporter.startHook(hook); }) .on(EVENT_HOOK_END, (hook) => { logger.mocha(`EVENT_HOOK_END: %s %O`, hook.title, hook); clearAllureHookStepsFromHook({ hook, reporter: this.reporter, allureLogHooks: config.logAllureHooksEnabled() }); this.reporter.endHook(hook); }); Cypress.on('command:enqueued', (command) => { if (this.commandShouldBeLogged(command)) { logger.cy(`command:enqueued %O`, command); this.reporter.cy.enqueued(command); } }); Cypress.on('command:start', (command) => { if (this.commandShouldBeLogged(command)) { logger.cy(`command:start %O`, command); this.reporter.cy.started(command.attributes); } }); Cypress.on('command:end', (command) => { if (this.commandShouldBeLogged(command)) { logger.cy(`command:end %O`, command); this.reporter.cy.finished(command.attributes); } }); Cypress.on('log:added', (log) => { if (log.state === 'failed') { logger.cy('found failed log:added %O', log); if ( this.reporter.currentExecutable && this.reporter.currentExecutable.info ) { this.reporter.currentExecutable.info.status = 'failed'; } } if (log.groupStart && Cypress.spec.fileExtension !== '.feature') { return; } // handle new implementation of log grouped gherkin steps logger.allure('caught added log group for gherkin step: %O', log); const command = this.reporter.cy.chain.currentChainer; if ( !command || !this.reporter.config.shouldLogGherkinSteps() || !commandIsGherkinStep(command) ) { return; } logger.cy(`found gherkin step, creating allure entity`); const step = this.reporter.cy.startStep(command, { ...log, displayName: (log && log.displayName) || log.name, name: 'step' }); this.reporter.cy.chain.currentChainer.step = step; this.reporter.steps.push(step); this.reporter.parentStep = step; }); } createAllureReporter(config) { this.reporter = Cypress.Allure ? Cypress.Allure.reporter : new AllureReporter( new AllureRuntime({ resultsDir: config.resultsPath(), writer: new InMemoryAllureWriter() }), config ); } commandShouldBeLogged(command) { if (!this.config.loggingCommandStepsEnabled) { logger.cy(`command tracking is disabled`); return; } if (this.config.avoidLoggingCommands().includes(command.name)) { logger.cy(`command %s tracking is disabled`, command.name); return; } if ( !this.config.shouldLogCypress() && !this.config.shouldLogGherkinSteps() ) { return; } return true; } } // 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 // https://github.com/Shelex/cypress-allure-plugin/issues/125 if (!Cypress.Allure) { Cypress.Allure = config.allureEnabled() ? new CypressAllureReporter() : stubbedAllure; } Cypress.Screenshot.defaults({ onAfterScreenshot(_, details) { logger.cy(`onAfterScreenshot: %O`, details); setTestIdToScreenshot({ allureEnabled: config.allureEnabled(), allure: Cypress.Allure, details }); if ( config.allureEnabled() && !shouldUseAfterSpec(Cypress.config()) && !config.skipAutomaticScreenshots() ) { logger.allure(`allure enabled, attaching screenshot`); Cypress.Allure.reporter.files.push({ name: details.name || `${details.specName}:${details.takenAt}`, path: details.path, type: ContentType.PNG, testName: Cypress.Allure.reporter.testNameForAttachment }); } } }); const attachVideo = (reporter, test, status) => { if (shouldUseAfterSpec(Cypress.config())) { logger.allure( `video attachment will be handled in after:spec plugins event` ); return; } const shouldAttach = status === 'failed' ? true : test.state !== 'failed' && config.addVideoOnPass(); logger.allure(`check video attachment`); if (Cypress.config().video && reporter.currentTest) { // add video to failed test case or for passed in case addVideoOnPass is true if (!shouldAttach) { return; } const absoluteVideoPath = Cypress.config() .videosFolder.split(config.resultsPath()) .pop(); const relativeVideoPath = path.isAbsolute(absoluteVideoPath) ? path.join( '..', path.relative( Cypress.config().fileServerFolder, absoluteVideoPath ) ) : absoluteVideoPath; const fileName = `${Cypress.spec.name}.mp4`; // avoid duplicating videos, especially for after all hook when test is passed if ( reporter.currentTest.info.attachments.some( (attachment) => attachment.name === fileName ) ) { return; } const videoFilePath = path.join(relativeVideoPath, fileName); if (!videoFilePath) { return; } logger.allure(`attaching video %s`, videoFilePath); reporter.currentTest.addAttachment( fileName, 'video/mp4', videoFilePath ); } }; if (config.allureEnabled()) { before(ALLURE_HOOK_NAME['before'], () => { logger.allure('########### before all ################'); cy.task('getPendingAllureResults', undefined, { log: config.logAllureHooksEnabled() }).then(loadPendingResults); }); afterEach(ALLURE_HOOK_NAME['afterEach'], () => { logger.allure('########### after each ################'); cy.then(() => invokeResultsWriter(Cypress.Allure, false)); }); after(ALLURE_HOOK_NAME['after'], () => { logger.allure('########### after all ################'); cy.then(() => { Cypress.Allure.reporter.prepareAllureReport( cy.state('runnable'), config.logAllureHooksEnabled() ); invokeResultsWriter(Cypress.Allure, true); }); }); } // need empty after hook to prohibit cypress stop the runner when there are skipped tests in the end after(() => {});