UNPKG

zephyr-import

Version:

Parsing JSON data and import it to Zephyr as reports of autoamtion testing!

469 lines (468 loc) 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTestIT = getTestIT; exports.getJiraCrosId = getJiraCrosId; exports.getTestId = getTestId; exports.getIdOfVersion = getIdOfVersion; exports.createCycle = createCycle; exports.getCycleId = getCycleId; exports.getIsseuId = getIsseuId; exports.createAndAssignExecution = createAndAssignExecution; exports.createExecution = createExecution; exports.bulkEditExecs = bulkEditExecs; exports.bulkEditSteps = bulkEditSteps; exports.putStepResult = putStepResult; exports.updateStepResult = updateStepResult; exports.execs = execs; exports.getFilesData = getFilesData; exports.updateJiraIssueStatus = updateJiraIssueStatus; // get parent dir name of node modules const path = require("path"); const parent_dirname = path.join(__dirname, "../../.."); // get config from parent dir of node modules, so config.json should be placed there const configZephyr = require("/" + parent_dirname + "/configZephyr.json"); const ZephyrApiVersion = "/public/rest/api/" + configZephyr.zephyrDefaultOptions.zephyrApiVersion; const jiraProjectID = configZephyr.zephyrDefaultOptions.jiraProjectId; const apicall = require("./apicall"); const fs = require("fs"); // folder where jsons are placed let testFolder = "../reports/jsons/"; if (configZephyr.zephyrDefaultOptions.reportsDir != null) { testFolder = configZephyr.zephyrDefaultOptions.reportsDir + "/"; } async function retry(fn, retries = 3, delay = 5000) { try { return await fn(); } catch (e) { if (retries <= 0) throw e; if (e?.message?.includes("Rate limit exceeded")) { console.warn(`Rate limit hit. Retrying in ${delay}ms...`); await new Promise(res => setTimeout(res, delay)); return retry(fn, retries - 1, delay); } throw e; } } function getTestIT(description) { const start_pos = 0; const start_pos1 = description.indexOf("|"); const text_to_get = description.substring(start_pos, start_pos1); return text_to_get; } function getJiraCrosId(description) { const start_pos = description.indexOf("|") + 1; const end_pos = description.indexOf(" ", start_pos); const issueId = description.substring(start_pos, end_pos); return issueId; } function getTestId(description) { const crosid = getJiraCrosId(description); const start_pos = crosid.indexOf("|") + 1; const end_pos = crosid.length; const issueId = crosid.substring(start_pos, end_pos); if (issueId.length == 9) { return "1" + issueId.substring(5, issueId.length); } else if (issueId.length == 10) { return "2" + issueId.substring(6, issueId.length); } } async function getIdOfVersion(versionName, projectId = jiraProjectID) { let versions; let versionsJSON; let id = -1; if (versionName != "development") { try { versions = await apicall.getJiraData("project/" + projectId + "/versions"); versionsJSON = JSON.parse(versions); for (let i in versionsJSON) { if (versionsJSON[i].name.includes(versionName)) { id = versionsJSON[i].id; return id; } } } catch (err) { console.log("Versions call troubles!", err); } } else if (versionName === "development") { id = -1; } if (id === -1) { console.log("Version does not exist or u wanna run test as Ad Hoc!"); return id; } } async function createCycle(branch, custom_cycle_name = "", projectId = jiraProjectID) { let response; let version = ""; let environment = ""; let cycleName = ""; let description = "default description"; let cycle_id = ""; if (branch.toLowerCase() == "development") { cycleName = "DEVELOPMENT"; environment = "DEVELOPMENT"; description = "Tests was runned during development period!"; } if (branch.toLowerCase().includes("release")) { cycleName = "RELEASE"; environment = "TEST"; description = "Tests was runned during release period!"; } if (custom_cycle_name != "") { cycleName = custom_cycle_name; environment = "TEST"; description = "Tests was runned as custom release cycle!"; } version = branch.split("/").pop(); try { await getIdOfVersion(version, projectId).then(async function (versionID) { const body = { name: cycleName, environment: environment, description: description, versionId: versionID, projectId: projectId, }; response = await apicall.postData(ZephyrApiVersion + "/cycle", body); const data = JSON.parse(response); cycle_id = data.id; }); } catch (error) { console.log("Continue as Ad hoc reporting, because an error occured when founding version:", error); cycle_id = undefined; } return cycle_id; } async function getCycleId(branch, cycleName = "", skip_duplicity_verify = false, current_used_cycle_id = undefined, projectId = jiraProjectID) { let response; let cycle_id = -1; if (current_used_cycle_id == undefined) { if (skip_duplicity_verify == false) { const splittedVersion = branch.split("/", 2); const versionName = splittedVersion[1]; if (cycleName == "") { cycleName = splittedVersion[0]; } await getIdOfVersion(versionName).then(async function (versionID) { if (versionID != -1) { response = await apicall.getData(ZephyrApiVersion + "/cycles/search?versionId=" + versionID + "&" + "projectId=" + projectId); const cycleJSON = JSON.parse(response); if (!cycleJSON || !Array.isArray(cycleJSON)) { console.error("Unexpected response for cycles!"); return -1; } for (let i in cycleJSON) { const name = cycleJSON[i]?.name; if (!name) continue; if (name.toLowerCase() === cycleName.toLowerCase()) { cycle_id = cycleJSON[i].id; return cycle_id; } } if (cycle_id == -1) { console.log("Cycle does not exist!"); return cycle_id; } } else { console.error("Version does not Exist!"); } }); } else if (skip_duplicity_verify == true) { cycle_id = -1; } } else { cycle_id = current_used_cycle_id; } return cycle_id; } async function getIsseuId(jiraIssueID) { try { let issueIDJson = await apicall.getJiraData("issue/" + jiraIssueID); let data = JSON.parse(issueIDJson); data = data.id; data = data.toString(); return data; } catch (err) { console.log(err); } } async function createAndAssignExecution(jiraIssueID = "", cycleId, branch, custom_cycle_name = "") { const versionName = branch.split("/").pop(); const versionID = await this.getIdOfVersion(versionName); if (jiraIssueID == "") { console.error("No JIRA ID SET!"); } if (cycleId == -1) { console.log("Creating cycle ..."); const [exec_id, cycle_id] = await this.createExecution(jiraIssueID, await this.createCycle(branch, custom_cycle_name), versionID); return [exec_id, cycle_id]; } else { const [exec_id, cycle_id] = await this.createExecution(jiraIssueID, cycleId, versionID); return [exec_id, cycle_id]; } } async function createExecution(jiraIssueID = "", cycleId = -1, versionID = -1) { if (!jiraIssueID) throw new Error("No JIRA ID SET!"); const body = { status: { id: -1 }, projectId: jiraProjectID, issueId: jiraIssueID, cycleId, versionId: versionID, assigneeType: "currentUser", }; async function execWithRetry(retries = 5) { const data = await apicall.postData(ZephyrApiVersion + "/execution", body); let json; try { json = JSON.parse(data); } catch (err) { throw new Error("Failed to parse Zephyr response."); } // detect rate limit if (json?.error === "Rate limit exceeded") { const waitMatch = json.message?.match(/(\d+)\s*seconds/); const waitTime = waitMatch ? parseInt(waitMatch[1]) * 1000 : 5000; if (retries > 0) { console.warn(`Rate limited. Retrying after ${waitTime} ms...`); await new Promise((res) => setTimeout(res, waitTime)); return await execWithRetry(retries - 1); } else { throw new Error("Exceeded retry attempts due to rate limiting."); } } if (!json?.execution?.id) { console.error("Missing execution ID in response:", json); throw new Error("Missing execution ID in response"); } return [json.execution.id, cycleId]; } return await execWithRetry(); } // createExecution('24452','4c544096-cb8a-4d5f-9030-a31ae60e44a6','10737'); async function bulkEditExecs(execs, status, pending = false, unexecuted = false) { let body; if (unexecuted === true) { body = { executions: execs, status: -1, clearDefectMappingFlag: false, testStepStatusChangeFlag: false, stepStatus: -1, }; } else if (status === true && !pending) { body = { executions: execs, status: 1, clearDefectMappingFlag: false, testStepStatusChangeFlag: true, stepStatus: 1, }; } else if (status === false && !pending) { body = { executions: execs, status: 2, clearDefectMappingFlag: false, testStepStatusChangeFlag: false, stepStatus: -1, }; } else if (status === false && pending) { body = { executions: execs, status: 3, clearDefectMappingFlag: false, testStepStatusChangeFlag: false, stepStatus: -1, }; } await apicall.postData(ZephyrApiVersion + "/executions", body); } async function bulkEditSteps(exec, status) { let body; const execs = [exec]; if (status == true) { body = { executions: execs, status: -1, clearDefectMappingFlag: false, testStepStatusChangeFlag: true, stepStatus: 1, }; } else if (status == false) { body = { executions: execs, status: -1, clearDefectMappingFlag: false, testStepStatusChangeFlag: true, stepStatus: 2, }; } await apicall.postData(ZephyrApiVersion + "/executions", body); } async function putStepResult(execId, issueId, stepResultId, resultOfTest, console_log = "Passed.") { const body = { executionId: execId, issueId: issueId, comment: console_log, status: { id: resultOfTest, description: console_log }, }; const fn = async () => await apicall.putData(ZephyrApiVersion + "/stepresult/" + stepResultId, body); await retry(fn); } async function updateStepResult(obj, issueId, execId) { let dataRaw = await apicall.getData(ZephyrApiVersion + "/teststep/" + issueId + "?projectId=" + jiraProjectID); let stepResultRaw = await apicall.getData(ZephyrApiVersion + "/stepresult/search?executionId=" + execId + "&issueId=" + issueId + "&isOrdered=true"); let data, stepResult; try { data = JSON.parse(dataRaw); stepResult = JSON.parse(stepResultRaw); } catch (err) { console.error("Failed to parse step data or results:", err); return; } const stepName = getTestIT(obj["description"]); const console_log = Array.isArray(obj["message"]) ? obj["message"].join(" | ") : obj["message"]?.toString() ?? ""; const passed = obj["passed"]; const pending = obj["pending"]; const selectedSteps = data.map(({ step }) => step); const selectedStepsIds = data.map(({ id }) => id); console.log("Looking for step:", stepName); console.log("Available JIRA steps:", selectedSteps); const indexOfStep = selectedSteps.indexOf(stepName); if (indexOfStep === -1) { console.error("Not matched step: check test description vs JIRA steps!"); return; } const stepResultItem = stepResult?.stepResults?.[indexOfStep]; if (!stepResultItem) { console.error(`stepResult is undefined at index ${indexOfStep}`); return; } const stepResultId = stepResultItem.id; let resultOfTest; if (pending === true) { resultOfTest = 3; } else if (passed === true) { if (console_log.includes("screenshot did not match") || console_log.includes("Expected")) { resultOfTest = 2; } else { resultOfTest = 1; } } else { resultOfTest = 2; } await putStepResult(execId, issueId, stepResultId, resultOfTest, console_log); } async function execs(path = testFolder) { return new Promise((resolve, reject) => { fs.readdir(path, (err, files) => { if (err) return reject(err); resolve(files); }); }); } async function getFilesData(path = testFolder) { const files = await execs(path); let data = []; let crosids = []; function getJson(file) { return new Promise((resolve, reject) => { fs.readFile(path + file, "utf8", (err, data) => { if (err) reject(err); else resolve(data); }); }); } for (let i = 0; i < files.length; i++) { try { const content = await getJson(files[i]); const json = JSON.parse(content); if (!json.suites || !Array.isArray(json.suites)) continue; for (const suite of json.suites) { const description = suite.name || ""; const testId = getTestId(description); if (!testId) { console.warn(`Skipped file ${files[i]}: could not extract testId from suite name '${suite.name}'`); continue; } if (!suite.tests || !Array.isArray(suite.tests)) continue; for (const test of suite.tests) { if (!test.name) continue; test.suiteName = suite.name; test.description = `${test.name} | ${testId}`; // ensure description includes identifier data.push(JSON.stringify(test)); crosids.push(testId); } } } catch (err) { console.error(`Failed to parse file ${files[i]}:`, err); continue; } } return [data, crosids]; } async function updateJiraIssueStatus(issueCrosID, status) { const transitionMap = { 1: "51", // passed 0: "41", // fail 2: "91", // skipped }; const transitionId = transitionMap[status]; if (!transitionId) { console.error(`Invalid status: ${status}`); return false; } const body = { transition: { id: transitionId } }; const urlParams = `issue/${issueCrosID}/transitions`; try { await apicall.postJiraData(urlParams, body); return true; } catch (e) { console.error("Status of Jira issue is not updated!", e); return false; } }