UNPKG

cypress-xporter

Version:
263 lines (232 loc) โ€ข 6.63 kB
require( "dotenv" ).config(); const axios = require( "axios" ); const fs = require( "fs" ); const FormData = require( "form-data" ); const path = require( "path" ); const { uploadScreenshotAndGetUrl } = require( "../utils/uploadUtils" ); const { findScreenshotForTest } = require( "../utils/screenshotUtils" ); const { JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN, JIRA_PROJECT_KEY } = process.env; const AUTH = { username: JIRA_EMAIL, password: JIRA_API_TOKEN, }; function normalizeTitleForComparison ( text ) { return text ?.toLowerCase() .replace( /[^a-z0-9 ]/gi, "" ) .replace( /\s+/g, " " ) .trim(); } // ๐Ÿง  Fallback search to compare summaries manually async function issueAlreadyExists ( testTitle ) { const normalizedTitle = normalizeTitleForComparison( testTitle ); try { console.info( `๐Ÿ” Fetching all Jira bugs in project ${ JIRA_PROJECT_KEY }...` ); const res = await axios.get( `${ JIRA_BASE_URL }/rest/api/3/search`, { params: { jql: `project = "${ JIRA_PROJECT_KEY }" AND issuetype = Bug`, fields: "summary,status", maxResults: 1000, }, auth: AUTH, headers: { Accept: "application/json" }, } ); for ( const issue of res.data.issues || [] ) { const issueSummary = normalizeTitleForComparison( issue.fields?.summary || "" ); const status = issue.fields?.status?.name?.toLowerCase() || ""; if ( issueSummary.includes( normalizedTitle ) && !["done", "closed", "won't fix", "wontfix"].includes( status ) ) { console.log( `โš ๏ธ Skipping duplicate: found ${ issue.key }` ); return true; } } return false; } catch ( err ) { console.warn( `โš ๏ธ Failed to fetch Jira issues: ${ err.response?.status } ${ err.response?.statusText }` ); return false; } } function createADFDescription ( test, screenshotUrl ) { const adfContent = [ { type: "paragraph", content: [{ type: "text", text: "โŒ Cypress Test Failed" }], }, ]; if ( test.file ) { adfContent.push( { type: "paragraph", content: [{ type: "text", text: `๐Ÿ“„ Spec File: ${ test.file }` }], } ); } if ( test.title ) { adfContent.push( { type: "paragraph", content: [{ type: "text", text: `๐Ÿงช Test Name: ${ test.title }` }], } ); } adfContent.push( { type: "paragraph", content: [{ type: "text", text: "๐Ÿ’ฅ Error:" }], }, { type: "paragraph", content: [{ type: "text", text: test.error || "No error message provided" }], }, { type: "paragraph", content: [{ type: "text", text: "๐Ÿงฌ Test Body:" }], }, { type: "codeBlock", attrs: { language: "javascript" }, content: [{ type: "text", text: test.body?.slice( 0, 1000 ) || "No body available" }], } ); if ( screenshotUrl?.content ) { adfContent.push( { type: "paragraph", content: [ { type: "text", text: "๐Ÿ–ผ๏ธ View Screenshot: " }, { type: "text", text: "Click Here", marks: [ { type: "link", attrs: { href: screenshotUrl.content, }, }, ], }, ], } ); } return { version: 1, type: "doc", content: adfContent, }; } async function attachLogsToIssue ( issueKey, logString ) { const tempFile = `.tmp-cypress-log-${ Date.now() }.txt`; fs.writeFileSync( tempFile, logString ); const form = new FormData(); form.append( "file", fs.createReadStream( tempFile ) ); try { await axios.post( `${ JIRA_BASE_URL }/rest/api/3/issue/${ issueKey }/attachments`, form, { auth: AUTH, headers: { ...form.getHeaders(), "X-Atlassian-Token": "no-check", }, } ); console.log( `๐Ÿ“Ž Attached log file to Jira issue: ${ issueKey }` ); } catch ( err ) { console.error( `โŒ Failed to attach log file to ${ issueKey }` ); console.error( err.response?.data || err.message ); } finally { fs.unlinkSync( tempFile ); } } async function createJiraBug ( test ) { const title = test.title?.trim(); const summary = `โŒ [Cypress] ${ title }`; const exists = await issueAlreadyExists( title ); if ( exists ) { console.log( `โš ๏ธ Skipping duplicate Jira bug for: ${ title }` ); return null; } try { const issueRes = await axios.post( `${ JIRA_BASE_URL }/rest/api/3/issue`, { fields: { project: { key: JIRA_PROJECT_KEY }, summary, issuetype: { name: "Bug" }, labels: ["automated-test", "cypress"], }, }, { auth: AUTH, headers: { Accept: "application/json", "Content-Type": "application/json", }, } ); const issueKey = issueRes.data.key; console.log( `โœ… Created Jira issue: ${ issueKey }` ); await attachLogsToIssue( issueKey, test.error || test.body || "No log data." ); const screenshotPath = findScreenshotForTest( test ); let screenshotUrl = null; if ( screenshotPath ) { screenshotUrl = await uploadScreenshotAndGetUrl( issueKey, screenshotPath ); } const updatedDescription = createADFDescription( test, screenshotUrl ); await axios.put( `${ JIRA_BASE_URL }/rest/api/3/issue/${ issueKey }`, { fields: { description: updatedDescription } }, { auth: AUTH, headers: { Accept: "application/json", "Content-Type": "application/json", }, } ); console.log( `๐Ÿ“ Updated Jira description for: ${ issueKey }` ); return issueKey; } catch ( err ) { console.error( `โŒ Failed to create Jira issue for test: ${ test.title }` ); console.error( err.response?.data || err.message ); return null; } } exports.reportToJira = async ( failedTests = [] ) => { if ( !JIRA_BASE_URL || !JIRA_API_TOKEN || !JIRA_PROJECT_KEY || !JIRA_EMAIL ) { console.log( "โš ๏ธ Jira not fully configured in .env" ); return failedTests; } if ( !failedTests.length ) { console.log( "โœ… No failed tests to report to Jira." ); return failedTests; } console.log( `๐Ÿž Creating Jira issues for ${ failedTests.length } failed test(s)...` ); const updatedTests = []; for ( const test of failedTests ) { test.title = test.title?.trim(); const issueKey = await createJiraBug( test ); test.jira = issueKey || "N/A"; updatedTests.push( test ); } return updatedTests; };