UNPKG

@gurglosa/playwright-zephyr-jira-reporter

Version:

A custom Playwright reporter that syncs test results to Zephyr Scale and creates Jira bugs on failures.

236 lines (235 loc) 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ZephyrServices_1 = require("./ZephyrServices"); const JiraServices_1 = require("./JiraServices"); class ZephyrJiraReporter { constructor(config) { this.zephyrService = null; // Service for interacting with Zephyr Scale this.jiraService = null; // Service for interacting with Jira this.testResults = new Map(); // Initialize configuration with environment variables as fallback try { this.config = { Zephyr_Base_URL: config.Zephyr_Base_URL, Zephyr_Access_Token: config.Zephyr_Access_Token, Zephyr_Test_Cycle_ID: config.Zephyr_Test_Cycle_ID, Zephyr_Test_Project_Key: config.Zephyr_Test_Project_Key, Zephyr_Enabled: config.Zephyr_Enabled, Jira_Access_Token: config.Jira_Access_Token, Jira_Base_URL: config.Jira_Base_URL, Jira_Email: config.Jira_Email, Jira_project_Key: config.Jira_project_Key, Jira_Enabled: config.Jira_Enabled, }; // Initialize the Zephyr Scale service if reporting is enabled if (this.config.Zephyr_Enabled) { this.zephyrService = new ZephyrServices_1.ZephyrServices({ Zephyr_Base_URL: this.config.Zephyr_Base_URL, Zephyr_Access_Token: this.config.Zephyr_Access_Token, Zephyr_Test_Cycle_ID: this.config.Zephyr_Test_Cycle_ID, Zephyr_Test_Project_Key: this.config.Zephyr_Test_Project_Key, Zephyr_Enabled: this.config.Zephyr_Enabled, }); } // Initialize the Jira service if reporting is enabled if (this.config.Jira_Enabled) { this.jiraService = new JiraServices_1.JiraServices({ Jira_Base_URL: this.config.Jira_Base_URL, Jira_Access_Token: this.config.Jira_Access_Token, Jira_Email: this.config.Jira_Email, Jira_project_Key: this.config.Jira_project_Key, Jira_Enabled: this.config.Jira_Enabled, }); } } catch (error) { console.error('Error initializing ZephyrJiraReporter configuration or services:', error); throw new Error('Failed to initialize ZephyrJiraReporter. Please check the provided configuration.'); } } onTestEnd(testCase, testResult) { // if (!this.config.Zephyr_Enabled) return; const { testCaseKey, testCycleKey } = this.extractKeys(testCase.title); if (!testCaseKey) { console.log(`No Jira test case key found in test title: ${testCase.title}`); return; } // Use test cycle key from the title or fall back to the default const cycleKey = testCycleKey || this.config.Zephyr_Test_Cycle_ID; if (!cycleKey) { console.log(`No test cycle key found for test: ${testCase.title}`); return; } const status = this.mapTestStatus(testResult.status); let errorMessage = ''; if (testResult.error) { errorMessage = testResult.error.stack || String(testResult.error); // Capture error details if present } // Extract test script details from the test result const testScript = this.extractTestScript(testResult); // Store the test result for later reporting this.testResults.set(testCaseKey, { title: testCase.title, status, error: errorMessage, testCycleKey: cycleKey, testScript, duration: testResult.duration, }); } async onEnd() { // console.log(this.config.Jira_Access_Token) // if (!this.config.Zephyr_Enabled || !this.zephyrService || this.testResults.size === 0) return; console.log(`\nUpdating ${this.testResults.size} test results in Zephyr Scale...`); const updatePromises = Array.from(this.testResults.entries()).map(async ([testCaseKey, result]) => { try { if (this.config.Zephyr_Enabled && this.zephyrService) { await this.zephyrService.updateTestExecutionStatus({ projectKey: this.config.Zephyr_Test_Project_Key, testCaseKey, testCycleKey: result.testCycleKey, statusName: result.status, testScriptResults: result.testScript, environmentName: 'TEST', actualEndDate: new Date().toISOString(), executedBy: 'Automated Test Runner', assignedToId: '712020:40b4e2a2-b204-427e-867a-041cd0510010', executionTime: result.duration, comment: result.error ? `Test failed with error: ${result.error}` : undefined, }); } if (this.config.Jira_Enabled && this.jiraService) { await this.handleJiraIssue(testCaseKey, result); } } catch (error) { if (error instanceof Error) { console.log(`Failed to update test case ${testCaseKey} in Zephyr Scale:`, error.message); } else { console.log(`Failed to update test case ${testCaseKey} in Zephyr Scale:`, String(error)); } } }); await Promise.all(updatePromises); console.log('Zephyr Scale update and Jira migration completed'); } async handleJiraIssue(testCaseKey, result) { if (!this.jiraService) return; try { const searchResult = await this.jiraService.searchIssueWithTitleUsingJQL(`${testCaseKey}`); const existingIssues = searchResult.sections[0]?.issues || []; if (existingIssues.length === 1) { const issue = existingIssues[0]; console.log(`⚠️ Jira issue already exists for ${testCaseKey}: ${issue.key}`); const currentStatus = (await this.jiraService.getJiraIssue(issue.key)).fields.status.name; console.log(`📌 Current Jira issue status: ${currentStatus}`); const transitionMap = { [ZephyrServices_1.TestExecutionStatus.failed]: ['Ready For Testing', 'testing'].includes(currentStatus) ? JiraServices_1.IssueStatusTransition.in_progress : undefined, [ZephyrServices_1.TestExecutionStatus.passed]: JiraServices_1.IssueStatusTransition.done }; const status = result.status; const transition = transitionMap[status]; if (currentStatus === "Done" || !transition) { if (result.status === ZephyrServices_1.TestExecutionStatus.failed) { await this.createNewJiraIssue(testCaseKey, result); console.log(`❌ Test case ${testCaseKey} failed, Jira issue is already in "Done" status. Creating a new issue. check it manually on duplication.`); } console.log(`🔄 ⚠️ No transition needed for "${currentStatus}"`); } else { await this.jiraService.transitionIssue(issue.key, transition); console.log(`✅ Transitioned ${issue.key} to ${this.getTransitionName(transition)}`); } } else if (result.status === ZephyrServices_1.TestExecutionStatus.failed && existingIssues.length === 0) { await this.createNewJiraIssue(testCaseKey, result); } else if (existingIssues.length > 1) { console.log(`⚠️ Multiple Jira issues found for ${testCaseKey}. Please check manually.`); } } catch (error) { console.error(`❌ Jira issue handling failed for test ${testCaseKey}`, error); } } ; async createNewJiraIssue(testCaseKey, result) { let stepNum = 1; const descriptionText = `Failed: ${result.title}\n` + result.testScript?.map((step) => `Step ${stepNum++}: ${step.actualResult} - Status: ${step.statusName}`).join('\n'); const issue = await this.jiraService.createIssue({ fields: { project: { key: this.config.Jira_project_Key }, summary: result.title, customfield_10069: testCaseKey, description: { type: 'doc', version: 1, content: [{ type: 'paragraph', content: [{ type: 'text', text: descriptionText }] }], }, issuetype: { id: '10002' }, reporter: { id: '6113c0ba9798100070110305' } } }); if (this.config.Zephyr_Enabled && this.zephyrService) { await this.zephyrService?.linkIssueToTestCase(testCaseKey, { issueId: issue.id }); } console.log(`✅ Jira issue created: ${issue.key} and linked to test case ${testCaseKey}`); } ; getTransitionName(value) { return Object.entries(JiraServices_1.IssueStatusTransition).find(([_, val]) => val === value)?.[0]; } ; extractTestScript(result) { try { // Filter out steps categorized as 'hook' (e.g., setup/teardown steps) const filteredSteps = result.steps.filter(step => step.category !== 'hook'); // Map each step to an object containing its execution status and result const stepScriptWithErrors = filteredSteps.map(step => ({ actualResult: step.error ? `Step "${step.title}" failed with error: ${this.stripAnsiCodes(step.error.stack || 'Unknown error')}` : "Step executed successfully", statusName: step.error ? 'fail' : 'pass' })); return stepScriptWithErrors; } catch (error) { console.error('Failed to extract test script:', error); return undefined; // Return undefined if extraction fails } } stripAnsiCodes(str) { return str.replace(/\x1B\[\d+m/g, ''); // Removes ANSI color codes } extractKeys(title) { const testCaseMatch = title.match(/\[([A-Z]+-T\d+)\]/); // Matches test case keys like [MYA-T123] const testCycleMatch = title.match(/\[([A-Z]+-R\d+)\]/); // Matches test cycle keys like [MYA-R9] return { testCaseKey: testCaseMatch ? testCaseMatch[1] : null, testCycleKey: testCycleMatch ? testCycleMatch[1] : null }; } mapTestStatus(status) { switch (status) { case 'passed': return ZephyrServices_1.TestExecutionStatus.passed; case 'failed': return ZephyrServices_1.TestExecutionStatus.failed; case 'skipped': return ZephyrServices_1.TestExecutionStatus.skipped; case 'timedOut': return ZephyrServices_1.TestExecutionStatus.timedOut; case 'interrupted': return ZephyrServices_1.TestExecutionStatus.interrupted; default: return ZephyrServices_1.TestExecutionStatus.UNKNOWN; } } } exports.default = ZephyrJiraReporter;