diff --git a/src/Reporter.js b/src/Reporter.js index 7cd880f..7fc06cc 100644 --- a/src/Reporter.js +++ b/src/Reporter.js @@ -44,6 +44,7 @@ class Reporter { this.includeAllCasesDuringCreation = configService.includeAllCasesDuringCreation(); this.includeAllFailedScreenshots = configService.includeAllFailedScreenshots(); this.ignorePendingTests = configService.ignorePendingCypressTests(); + this.ignoreMissingCaseIDs = configService.ignoreMissingCaseIds(); this.modeCreateRun = !configService.hasRunID(); this.closeRun = configService.shouldCloseRun(); @@ -256,7 +257,11 @@ class Reporter { if (allResults.length > 0) { for (let i = 0; i < this.runIds.length; i += 1) { - const request = this.testrail.sendBatchResults(this.runIds[i], allResults); + const request = this.testrail.sendBatchResults( + this.runIds[i], + allResults, + this.ignoreMissingCaseIDs, + ); allRequests.push(request); } @@ -371,4 +376,4 @@ class Reporter { } } -module.exports = Reporter; +module.exports = Reporter; \ No newline at end of file diff --git a/src/components/TestRail/ApiClient.js b/src/components/TestRail/ApiClient.js index 8a4d34c..05a9bcb 100644 --- a/src/components/TestRail/ApiClient.js +++ b/src/components/TestRail/ApiClient.js @@ -2,6 +2,7 @@ const axios = require('axios'); const ApiError = require('./ApiError'); const FormData = require('form-data'); const fs = require('fs'); +// const ColorConsole = require('../../services/ColorConsole'); class ApiClient { /** @@ -15,6 +16,35 @@ class ApiClient { this.baseUrl = `https://${domain}/index.php?/api/v2`; } +/** + * + * @param slug + * @returns {Promise>} + */ +getData(slug, params={}) { + const fullUrl = this.baseUrl + slug; + return axios({ + method: 'get', + url: fullUrl, + auth: { + username: this.username, + password: this.password, + }, + params, + }) + .then((response) => { + // ColorConsole.debug('>> getData response: ' + JSON.stringify(response.data)); + return response; + }) + .catch((error) => { + // Extract and handle the error + const apiError = new ApiError(error); + throw new Error( + `Error fetching data: ${apiError.getStatusCode()} ${apiError.getStatusText()} >> ${apiError.getErrorText()}` + ); + }); +} + /** * * @param slug @@ -87,4 +117,4 @@ class ApiClient { } } -module.exports = ApiClient; +module.exports = ApiClient; \ No newline at end of file diff --git a/src/components/TestRail/TestRail.js b/src/components/TestRail/TestRail.js index 9e09f40..169745f 100644 --- a/src/components/TestRail/TestRail.js +++ b/src/components/TestRail/TestRail.js @@ -1,6 +1,8 @@ const ColorConsole = require('../../services/ColorConsole'); const ApiClient = require('./ApiClient'); + + class TestRail { /** * @@ -25,7 +27,15 @@ class TestRail { * @param callback * @returns {Promise>} */ - createRun(projectId, milestoneId, suiteId, name, description, includeAllCasesDuringCreation, callback) { + createRun( + projectId, + milestoneId, + suiteId, + name, + description, + includeAllCasesDuringCreation, + callback, + ) { if (typeof includeAllCasesDuringCreation !== 'boolean') { includeAllCasesDuringCreation = false; //preserving existing functionality } @@ -170,61 +180,104 @@ class TestRail { * * @param {string} runID * @param {Result[]} testResults + * @param {boolean} ignoreMissingCaseIds * @returns {Promise>} */ - sendBatchResults(runID, testResults) { - const url = '/add_results_for_cases/' + runID; - - const postData = { - results: [], - }; - - ColorConsole.debug('TestRail >> Sending case results to run R' + runID + ': ' + testResults.map((r) => 'C' + r.getCaseId())); + async sendBatchResults(runID, testResults, ignoreMissingCaseIds) { + if (typeof ignoreMissingCaseIds !== 'boolean') { + ignoreMissingCaseIds = false; //preserving existing functionality + } + + // Check if there are any test results to send + ColorConsole.info('\n Cypress-TestRail send batch results:'); + let validCaseIds = []; + + if (ignoreMissingCaseIds) { // validCaseIds is only needed if Validating case_id + // Fetch valid case IDs for the given runID + let testsCaseIdPage = []; + // Pagination variables + let offset = 0; + const limit = 250; // Maximum allowed by TestRail + + // Fetch valid case IDs from TestRail API + try { + // eslint-disable-next-line no-constant-condition + while (true) { + const getDataResponse = await this.client.getData( + `/get_tests/${runID}`, + {offset, limit}, + ); + // Extract valid case IDs from the response data + testsCaseIdPage = getDataResponse.data.tests.map((test) => test.case_id); + + if (testsCaseIdPage.length === 0) break; // we've retrieved all case IDs + validCaseIds = validCaseIds.concat(testsCaseIdPage); + offset += limit; // Move to the next page + } + } catch (error) { + ColorConsole.error(`Could not fetch valid case IDs for run R${runID}: ${error.message}`); + return; // Exit the function if fetching valid case IDs fails + } + // ColorConsole.debug('>> testResults validCaseIds: ' + JSON.stringify(validCaseIds)); + ColorConsole.info(` In run R${runID} there are ${validCaseIds.length} Valid Testrail cases\n`); + } + const url = '/add_results_for_cases/' + runID; + const postData = { results: [] }; + + // Filter testResults to include only those with valid case IDs testResults.forEach((result) => { var resultEntry = { case_id: result.getCaseId(), status_id: result.getStatusId(), comment: result.getComment().trim(), + screenshotPaths: result.getScreenshotPaths(), }; - // only add an elapsed time, if a valid value exists // otherwise TestRail will throw an error if (result.hasElapsedTime()) { resultEntry.elapsed = result.getElapsed(); } - - postData.results.push(resultEntry); + + // Check if the case_id is valid ie in the run + if (ignoreMissingCaseIds && + !validCaseIds.includes(parseInt(resultEntry.case_id))) { // Check if the case_id is valid + ColorConsole.error(`Test case C${resultEntry.case_id} is not valid for run R${runID}. Skipping.`); + } else { + postData.results.push(resultEntry); + } }); + return this.client.sendData( url, postData, (response) => { - ColorConsole.success('Results sent to TestRail R' + runID + ' for: ' + testResults.map((r) => 'C' + r.getCaseId())); + + ColorConsole.success('Cypress results sent to TestRail R' + + runID + ' for: ' + postData.results.map((r) => 'C' + r.case_id).join(', ')); if (this.isScreenshotsEnabled) { const allRequests = []; - - testResults.forEach((result, i) => { - const screenshotPaths = result.getScreenshotPaths(); + + postData.results.forEach((result, i) => { // cypress-testrail-greenwich mod + const screenshotPaths = result.screenshotPaths; // cypress-testrail-greenwich mod if (screenshotPaths.length) { // there is no identifier, to match both, but // we usually get the same order back as we sent it to TestRail const matchingResultId = response.data[i].id; - screenshotPaths.forEach((screenshot) => { - ColorConsole.debug('sending screenshot to TestRail for TestCase C' + result.getCaseId()); + ColorConsole.debug(' sending screenshot to TestRail for TestCase C' + result.case_id); const addScreenShotRequest = this.client.sendScreenshot( matchingResultId, screenshot.path, () => { - ColorConsole.success('created screenshot'); + ColorConsole.success(' created screenshot'); }, (error) => { - ColorConsole.error(`could not create screenshot: ${error}`); + ColorConsole.error(` could not create screenshot: ${error}`); ColorConsole.debug(''); } ); @@ -238,11 +291,12 @@ class TestRail { } }, (statusCode, statusText, errorText) => { - ColorConsole.error('Could not send list of TestRail results: ' + statusCode + ' ' + statusText + ' >> ' + errorText); + ColorConsole.error(' Could not send list of Cypress results to TestRail : ' + + statusCode + ' ' + statusText + ' >> ' + errorText); ColorConsole.debug(''); } ); } } -module.exports = TestRail; +module.exports = TestRail; \ No newline at end of file diff --git a/src/services/Config/ConfigService.js b/src/services/Config/ConfigService.js index e9adc96..aceeeb3 100644 --- a/src/services/Config/ConfigService.js +++ b/src/services/Config/ConfigService.js @@ -177,6 +177,14 @@ class ConfigService { return this._valueLoader.getBooleanValue('TESTRAIL_RUN_INCLUDE_ALL', 'runIncludeAll', false); } + /** + * + * @returns {boolean} + */ + ignoreMissingCaseIds() { + return this._valueLoader.getBooleanValue('TESTRAIL_IGNORE_MISSING_CASE_IDS', 'ignoreMissingCaseIds', false); + } + /** * * @returns {boolean}