UNPKG

cypress-testrail-paper

Version:

Easy and decoupled Cypress TestRail reporter

301 lines (253 loc) 11.2 kB
const TestRail = require('./components/TestRail/TestRail'); const TestCaseParser = require('./services/TestCaseParser'); const Result = require('./components/TestRail/Result'); const ConfigService = require('./services/ConfigService'); const TestData = require('./components/Cypress/TestData'); const ColorConsole = require('./services/ColorConsole'); const packageData = require('../package.json'); class Reporter { /** * * @param on * @param config * @param customComment provide a custom comment if you want to add something to the result comment */ constructor(on, config, customComment) { this.on = on; this.testCaseParser = new TestCaseParser(); /* eslint-disable no-undef */ const configService = new ConfigService(config.env); this.enabled = configService.isApiValid(); this.domain = configService.getDomain(); this.projectId = configService.getProjectId(); this.milestoneId = configService.getMilestoneId(); this.suiteId = configService.getSuiteId(); this.runId = configService.getRunId(); this.runName = configService.getRunName(); this.screenshotsEnabled = configService.isScreenshotsEnabled(); this.includeAllCasesDuringCreation = configService.includeAllCasesDuringCreation(); this.includeAllFailedScreenshots = configService.includeAllFailedScreenshots(); this.modeCreateRun = !configService.hasRunID(); this.closeRun = configService.shouldCloseRun(); this.foundCaseIds = []; this.statusPassed = configService.getStatusPassed(); this.statusFailed = configService.getStatusFailed(); this.customComment = customComment !== undefined && customComment !== null ? customComment : ''; this.testrail = new TestRail(configService.getDomain(), configService.getUsername(), configService.getPassword(), configService.isScreenshotsEnabled()); } /** * */ register() { // if our config is not valid // then do not even register anything if (!this.enabled) { return; } this.on('before:run', async (details) => { await this._beforeRun(details); }); this.on('after:spec', async (spec, results) => { await this._afterSpec(spec, results); }); this.on('after:run', async () => { await this._afterRun(); }); } /** * * @param details * @private */ async _beforeRun(details) { this.cypressVersion = details.cypressVersion; this.browser = details.browser.displayName + ' (' + details.browser.version + ')'; this.system = details.system.osName + ' (' + details.system.osVersion + ')'; this.baseURL = details.config.baseUrl; ColorConsole.success(' Starting TestRail Integration v' + packageData.version); ColorConsole.info(' ....................................................'); ColorConsole.info(' Cypress: ' + this.cypressVersion); ColorConsole.info(' Browser: ' + this.browser); ColorConsole.info(' System: ' + this.system); ColorConsole.info(' Base URL: ' + this.baseURL); ColorConsole.info(' TestRail Domain: ' + this.domain); if (this.modeCreateRun) { ColorConsole.info(' TestRail Mode: Create Run'); ColorConsole.info(' TestRail Project ID: ' + this.projectId); ColorConsole.info(' TestRail Milestone ID: ' + this.milestoneId); ColorConsole.info(' TestRail Suite ID: ' + this.suiteId); ColorConsole.info(' TestRail Run Name: ' + this.runName); ColorConsole.info(' TestRail Include All Cases: ' + this.includeAllCasesDuringCreation); } else { ColorConsole.info(' TestRail Mode: Use existing Run'); ColorConsole.info(' TestRail Run ID: ' + this.runId); } ColorConsole.info(' Screenshots: ' + this.screenshotsEnabled); ColorConsole.info(' Include All Failed Screenshots: ' + this.includeAllFailedScreenshots); // if we don't have a runID, then we need to create one if (this.runId === '') { const today = new Date(); const dateTime = today.toLocaleString(); let runName = this.runName === '' ? 'Cypress Run (__datetime__)' : this.runName; // now use our current date time if // that placeholder has been used runName = runName.replace('__datetime__', dateTime); let description = ''; description += 'Tested by Cypress'; description += '\nCypress: ' + this.cypressVersion; description += '\nBrowser: ' + this.browser; description += '\nBase URL: ' + this.baseURL; description += '\nSystem: ' + this.system; if (this.customComment !== '') { description += '\n' + this.customComment; } await this.testrail.createRun(this.projectId, this.milestoneId, this.suiteId, runName, description, this.includeAllCasesDuringCreation, (runId) => { // run created this.runId = runId; /* eslint-disable no-console */ ColorConsole.debug(' New TestRail Run: R' + this.runId); }); } } /** * * @param spec * @param results * @private */ async _afterSpec(spec, results) { if (this.modeCreateRun && !this.includeAllCasesDuringCreation) { // if we are in the mode to dynamically create runs // then we also need to add the newly found runs to our created test run // but only if we don't want to associate all cases during creation await results.tests.forEach((test) => { const testData = new TestData(test); const foundCaseIDs = this.testCaseParser.searchCaseId(testData.getTitle()); foundCaseIDs.forEach((singleCase) => { this.foundCaseIds.push(singleCase); }); }); await this.testrail.updateRun(this.runId, this.foundCaseIds); } await this._sendSpecResults(spec, results); } /** * * @private */ async _afterRun() { if (this.modeCreateRun) { if (this.closeRun) { // if we have just created a run then automatically close it await this.testrail.closeRun(this.runId, () => { /* eslint-disable no-console */ console.log(' TestRail Run: R' + this.runId + ' is now closed'); }); } else { /* eslint-disable no-console */ console.log(' Skipping closing of Test Run'); } } } /** * * @param spec * @param results * @returns {Promise<void>} * @private */ async _sendSpecResults(spec, results) { const allRequests = []; const allResults = []; // iterate through all our test results // and send the data to TestRail if(results.tests && results.tests.length > 0 ) { await results.tests.forEach(async (test) => { const testData = new TestData(test); const foundCaseIDs = this.testCaseParser.searchCaseId(testData.getTitle()); foundCaseIDs.forEach((caseId) => { let status = this.statusPassed; // if we have a pending status, then do not // send data to testrail if (testData.getState() === 'pending') { return; } let screenshotPaths = []; if (testData.getState() !== 'passed') { status = this.statusFailed; screenshotPaths = this._getScreenshotByTestId(test.testId, results.screenshots); if (screenshotPaths === null) { screenshotPaths = []; } } let comment = 'Tested by Cypress'; // this is already part of the run description // if it was created dynamically. // otherwise add it to the result if (!this.modeCreateRun) { comment += '\nCypress: ' + this.cypressVersion; comment += '\nBrowser: ' + this.browser; comment += '\nBase URL: ' + this.baseURL; comment += '\nSystem: ' + this.system; comment += '\nSpec: ' + spec.name; if (this.customComment !== '') { comment += '\n' + this.customComment; } } if (testData.getError() !== '') { comment += '\nError: ' + testData.getError(); } const result = new Result(caseId, status, comment, testData.getDurationMS(), screenshotPaths); allResults.push(result); }); }); } if(allResults.length > 0 ) { // now send all results in a single request const request = this.testrail.sendBatchResults(this.runId, allResults); allRequests.push(request); await Promise.all(allRequests); } } /** * { * screenshotId: 'snzdd', * name: null, * testId: 'r4', * testAttemptIndex: 3, * takenAt: '2022-12-23T08:03:08.888Z', * path: '/.../Test-Case ABC (failed) (attempt 4).png', * height: 720, * width: 1280 * } * @param testId * @param screenshots * @returns {null} * @private */ _getScreenshotByTestId(testId, screenshots) { var highestFoundAttemptId = -1; var foundScreenshots = []; var highestFoundScreenshot = []; screenshots.forEach((screenshot) => { // only use images of our current test. // screenshots would include all test images if (screenshot.testId === testId) { // only use images with '(failed)' in it. Other images might be custom // images taken by the developer if (screenshot.path.includes('(failed')) { foundScreenshots.push(screenshot); // only use the image of the latest test-attempt for now const currentAttempt = screenshot.testAttemptIndex; if (currentAttempt > highestFoundAttemptId) { highestFoundScreenshot = []; highestFoundScreenshot.push(screenshot); highestFoundAttemptId = currentAttempt; } } } }); return this.includeAllFailedScreenshots ? foundScreenshots : highestFoundScreenshot; } } module.exports = Reporter;