diff --git a/src/common.ts b/src/common.ts index 8e445217..bbedc35f 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,80 +1,115 @@ import fs from 'fs' -import { type CtrfTest } from '../types/ctrf' +import { CtrfReport, type CtrfTest } from '../types/ctrf' + +export function validateCtrfFile(filePath: string): CtrfReport | null { + try { + const fileContent = fs.readFileSync(filePath, 'utf8') + const jsonData: CtrfReport = JSON.parse(fileContent) + + if (!jsonData.results?.summary || !jsonData.results.tests) { + console.warn('Warning: The file does not contain valid CTRF data.') + return null + } + return jsonData + } catch (error) { + console.error('Failed to read or process the file:', error) + console.warn( + 'Unable to generate GitHub Actions Summary, moving on without...' + ) + } + return null +} export function extractGithubProperties() { - const eventPath = process.env.GITHUB_EVENT_PATH - - if (!eventPath) { - console.error( - 'GITHUB_EVENT_PATH is not set. This is required to determine context.' - ) - return - } - - let context - try { - const eventData = fs.readFileSync(eventPath, 'utf8') - context = JSON.parse(eventData) - } catch (error) { - console.error('Failed to read or parse event data:', error) - return - } - - const { - GITHUB_REPOSITORY: repoName, - GITHUB_REF_NAME: branchName, - GITHUB_RUN_NUMBER: runNumber, - GITHUB_JOB: jobName, - GITHUB_WORKFLOW_ID: workflowId, - GITHUB_WORKFLOW: workflowName, - GITHUB_TRIGGERING_ACTOR: actorName, - GITHUB_EVENT_NAME: eventName, // push or pull_request - GITHUB_RUN_ID: runId, - GITHUB_API_URL: apiUrl, - GITHUB_SERVER_URL: baseUrl, - } = process.env - - const pullRequestNumber = context.pull_request?.number - const buildUrl = `${baseUrl}/${repoName}/actions/runs/${runId}#summary` - - return { - repoName, - branchName, - runNumber, - jobName, - workflowId, - workflowName, - actorName, - eventName, - runId, - pullRequestNumber, - apiUrl, - baseUrl, - buildUrl, - } + const eventPath = process.env.GITHUB_EVENT_PATH + + if (!eventPath) { + console.error( + 'GITHUB_EVENT_PATH is not set. This is required to determine context.' + ) + return + } + + let context + try { + const eventData = fs.readFileSync(eventPath, 'utf8') + context = JSON.parse(eventData) + } catch (error) { + console.error('Failed to read or parse event data:', error) + return + } + + const { + GITHUB_REPOSITORY: repoName, + GITHUB_REF_NAME: branchName, + GITHUB_RUN_NUMBER: runNumber, + GITHUB_JOB: jobName, + GITHUB_WORKFLOW_ID: workflowId, + GITHUB_WORKFLOW: workflowName, + GITHUB_TRIGGERING_ACTOR: actorName, + GITHUB_EVENT_NAME: eventName, // push or pull_request + GITHUB_RUN_ID: runId, + GITHUB_API_URL: apiUrl, + GITHUB_SERVER_URL: baseUrl, + } = process.env + + const pullRequestNumber = context.pull_request?.number + const buildUrl = `${baseUrl}/${repoName}/actions/runs/${runId}#summary` + + return { + repoName, + branchName, + runNumber, + jobName, + workflowId, + workflowName, + actorName, + eventName, + runId, + pullRequestNumber, + apiUrl, + baseUrl, + buildUrl, + } } export function ansiRegex({ onlyFirst = false } = {}) { - const pattern = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', - ].join('|') + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', + ].join('|') - return new RegExp(pattern, onlyFirst ? undefined : 'g') + return new RegExp(pattern, onlyFirst ? undefined : 'g') } export function stripAnsi(message: string) { - if (typeof message !== 'string') { - throw new TypeError(`Expected a \`string\`, got \`${typeof message}\``) - } + if (typeof message !== 'string') { + throw new TypeError(`Expected a \`string\`, got \`${typeof message}\``) + } - return message.replace(ansiRegex(), '') + return message.replace(ansiRegex(), '') } +export function stripAnsiFromErrors(report: CtrfReport | null): any { + if (!report?.results?.tests) { + return report + } + + report.results.tests.forEach((test) => { + if (test.message) { + test.message = stripAnsi(test.message) + } + if (test.trace) { + test.trace = stripAnsi(test.trace) + } + }) + + return report +} export function getTestName(test: CtrfTest, useSuiteName: boolean): string { - if (useSuiteName && test.suite) { - return `${test.suite}:${test.name}` - } - return test.name + if (useSuiteName && test.suite) { + return `${test.suite}:${test.name}` + } + return test.name } \ No newline at end of file diff --git a/src/handlebars.ts b/src/handlebars.ts new file mode 100644 index 00000000..5b285d5c --- /dev/null +++ b/src/handlebars.ts @@ -0,0 +1,40 @@ +import Convert from "ansi-to-html" +import { stripAnsi } from "./common" +import Handlebars from "handlebars" + +export function renderHandlebarsTemplate(template: any, context: any) { + try { + const compiledTemplate = Handlebars.compile(template) + return compiledTemplate(context) + } catch (error) { + console.error('Failed to render Handlebars template:', error) + return '' + } + } + +Handlebars.registerHelper('countFlaky', function (tests) { + return tests.filter((test: { flaky: boolean }) => test.flaky).length + }) + + Handlebars.registerHelper('formatDuration', function (start, stop) { + const durationInSeconds = (stop - start) / 1000 + const durationFormatted = + durationInSeconds < 1 + ? '<1s' + : `${new Date(durationInSeconds * 1000).toISOString().substr(11, 8)}` + + return `${durationFormatted}` + }) + + Handlebars.registerHelper('eq', function (arg1, arg2) { + return arg1 === arg2 + }) + + Handlebars.registerHelper('stripAnsi', function (message) { + return stripAnsi(message) + }) + + Handlebars.registerHelper('ansiToHtml', function (message) { + const convert = new Convert() + return convert.toHtml(message) + }) \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 957693e9..01e1d2df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,56 +1,25 @@ #!/usr/bin/env node import yargs from 'yargs/yargs' -import Handlebars from 'handlebars' import { hideBin } from 'yargs/helpers' import fs from 'fs' -import https from 'https' import * as core from '@actions/core' -import { type CtrfReport } from '../types/ctrf' import { - write, generateSummaryDetailsTable, - generateTestDetailsTable, - generateFailedTestsDetailsTable, - generateSkippedTestsDetailsTable, - generateFlakyTestsDetailsTable, - annotateFailed, - addHeading, - generateAIFailedTestsSummaryTable, - exitOnFail, } from './views/summary' import path from 'path' import { generateHistoricSummary } from './views/historical' -import { extractGithubProperties, getTestName, stripAnsi } from './common' -import Convert = require('ansi-to-html') +import { extractGithubProperties, stripAnsiFromErrors, validateCtrfFile } from './common' import { generateFlakyRateSummary } from './views/flaky-rate' import { generateFailedRateSummary } from './views/failed-rate' - -Handlebars.registerHelper('countFlaky', function (tests) { - return tests.filter((test: { flaky: boolean }) => test.flaky).length -}) - -Handlebars.registerHelper('formatDuration', function (start, stop) { - const durationInSeconds = (stop - start) / 1000 - const durationFormatted = - durationInSeconds < 1 - ? '<1s' - : `${new Date(durationInSeconds * 1000).toISOString().substr(11, 8)}` - - return `${durationFormatted}` -}) - -Handlebars.registerHelper('eq', function (arg1, arg2) { - return arg1 === arg2 -}) - -Handlebars.registerHelper('stripAnsi', function (message) { - return stripAnsi(message) -}) - -Handlebars.registerHelper('ansiToHtml', function (message) { - const convert = new Convert() - return convert.toHtml(message) -}) +import { renderHandlebarsTemplate } from './handlebars' +import { postPullRequestComment } from './views/pull-request' +import { exitActionOnFail, write, addHeading } from './views/common' +import { annotateFailed } from './views/annotate' +import { generateAIFailedTestsSummaryTable } from './views/ai' +import { generateTestDetailsTable } from './views/detailed' +import { generateFailedTestsDetailsTable } from './views/failed' +import { generateFlakyTestsDetailsTable } from './views/flaky' +import { generateSkippedTestsDetailsTable } from './views/skipped' interface Arguments { _: Array @@ -60,8 +29,8 @@ interface Arguments { annotate?: boolean rows?: number artifactName?: string + pullRequest: boolean, prComment?: boolean - prCommentMessage?: string onFailOnly?: boolean domain?: string useSuiteName?: boolean @@ -197,6 +166,16 @@ const argv: Arguments = yargs(hideBin(process.argv)) }) } ) + .command( + 'pull-request ', + 'Post a pull request comment', + (yargs) => { + return yargs.positional('file', { + describe: 'Path to the CTRF file', + type: 'string', + }) + } + ) .command( 'annotate ', 'Annotate failed tests from a CTRF report', @@ -226,16 +205,17 @@ const argv: Arguments = yargs(hideBin(process.argv)) description: 'Name of artifact for CTRF Report', default: 'ctrf-report', }) + .option('pull-request', { + type: 'boolean', + description: 'Post view to pull request comment', + default: false, + }) + // deprecated .option('pr-comment', { type: 'boolean', description: 'Post a Pull Request comment with the summary', default: false, }) - .option('pr-comment-message', { - type: 'string', - description: - 'Custom message for your Pull Request comment using a string or handlebars template file', - }) .option('on-fail-only', { type: 'boolean', description: 'Post a Pull Request comment only if there are failed tests', @@ -268,26 +248,10 @@ const title = argv.title || 'Test Summary' const rows = argv.rows || 10 const results = argv.results || 100 const artifactName = argv.artifactName || 'ctrf-report' +const onFailOnly = argv.onFailOnly ?? false +const exitOnFail = argv.exitOnFail ?? false const useSuiteName = argv.useSuiteName ?? false - -let prCommentMessage = argv.prCommentMessage -if (prCommentMessage) { - if (path.extname(prCommentMessage) === '.hbs') { - try { - const report = validateCtrfFile(file) - const template = fs.readFileSync(prCommentMessage, 'utf8') - if (report !== null) { - const reportContext = { ctrf: report.results, github: extractGithubProperties() } - prCommentMessage = renderHandlebarsTemplate(template, reportContext) - } - } catch (error) { - console.error('Failed to read prCommentMessage file:', error) - prCommentMessage = '' - } - } else { - console.log('Using provided string as the PR comment message') - } -} +const pullRequest = argv.pullRequest ?? false if ((commandUsed === 'all' || commandUsed === '') && argv.file) { try { @@ -302,10 +266,10 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { if (annotate) annotateFailed(report, useSuiteName) write() if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) } - if (argv.exitOnFail) { - exitOnFail(report) + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { @@ -322,10 +286,10 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { generateSummaryDetailsTable(report) write() if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) } - if (argv.exitOnFail) { - exitOnFail(report) + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { @@ -342,10 +306,13 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { generateTestDetailsTable(report.results.tests, useSuiteName) write() if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) } - if (argv.exitOnFail) { - exitOnFail(report) + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) + } + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { @@ -362,10 +329,13 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { generateFailedTestsDetailsTable(report.results.tests, useSuiteName) write() if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) + } + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) } - if (argv.exitOnFail) { - exitOnFail(report) + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { @@ -379,14 +349,18 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { if (argv.title) { addHeading(title) } - generateFailedRateSummary(report, artifactName, results, useSuiteName) - write() - if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) - } - if (argv.exitOnFail) { - exitOnFail(report) - } + generateFailedRateSummary(report, artifactName, results, useSuiteName).then(() => { + write() + if (argv.prComment) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) + } + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) + } + if (exitOnFail) { + exitActionOnFail(report) + } + }) } } catch (error) { console.error('Failed to read file:', error) @@ -402,10 +376,13 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { generateSkippedTestsDetailsTable(report.results.tests, useSuiteName) write() if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) } - if (argv.exitOnFail) { - exitOnFail(report) + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) + } + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { @@ -422,10 +399,13 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { generateAIFailedTestsSummaryTable(report.results.tests, useSuiteName) write() if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) + } + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) } - if (argv.exitOnFail) { - exitOnFail(report) + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { @@ -433,7 +413,6 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { } } else if (argv._.includes('flaky') && argv.file) { try { - const data = fs.readFileSync(argv.file, 'utf8') let report = validateCtrfFile(argv.file) report = stripAnsiFromErrors(report) if (report !== null) { @@ -443,10 +422,13 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { generateFlakyTestsDetailsTable(report.results.tests, useSuiteName) write() if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) } - if (argv.exitOnFail) { - exitOnFail(report) + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) + } + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { @@ -454,21 +436,24 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { } } else if (argv._.includes('flaky-rate') && argv.file) { try { - const data = fs.readFileSync(argv.file, 'utf8') let report = validateCtrfFile(argv.file) report = stripAnsiFromErrors(report) if (report !== null) { if (argv.title) { addHeading(title) } - generateFlakyRateSummary(report, artifactName, results, useSuiteName) - write() - if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) - } - if (argv.exitOnFail) { - exitOnFail(report) - } + generateFlakyRateSummary(report, artifactName, results, useSuiteName).then(() => { + write() + if (argv.prComment) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) + } + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) + } + if (exitOnFail) { + exitActionOnFail(report) + } + }) } } catch (error) { console.error('Failed to read file:', error) @@ -488,13 +473,15 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { ) core.summary.addRaw(customSummary) write() - if (argv.exitOnFail) { - exitOnFail(report) + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) + } + if (exitOnFail) { + exitActionOnFail(report) } } } catch (error) { console.error('Failed to read prCommentMessage file:', error) - prCommentMessage = '' } } else { core.summary.addRaw(argv.summary) @@ -512,246 +499,47 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) { if (argv.title) { addHeading(title) } - generateHistoricSummary(report, artifactName, rows, argv.exitOnFail || false) + generateHistoricSummary(report, artifactName, rows, exitOnFail).then(() => { + write() + if (argv.prComment) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) + } + if (pullRequest) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName, core.summary.stringify()) + } + if (exitOnFail) { + exitActionOnFail(report) + } + }) } } catch (error) { console.error('Failed to read file:', error) } -} else if (argv._.includes('annotate') && argv.file) { +} else if (argv._.includes('pull-request') && argv.file) { try { let report = validateCtrfFile(argv.file) report = stripAnsiFromErrors(report) if (report !== null) { - annotateFailed(report, useSuiteName) - if (argv.prComment) { - postSummaryComment(report, apiUrl, prCommentMessage) + if (argv.title) { + addHeading(title) } + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) } } catch (error) { console.error('Failed to read file:', error) } } - -function validateCtrfFile(filePath: string): CtrfReport | null { - try { - const fileContent = fs.readFileSync(filePath, 'utf8') - const jsonData: CtrfReport = JSON.parse(fileContent) - - if (!jsonData.results?.summary || !jsonData.results.tests) { - console.warn('Warning: The file does not contain valid CTRF data.') - return null - } - return jsonData - } catch (error) { - console.error('Failed to read or process the file:', error) - console.warn( - 'Unable to generate GitHub Actions Summary, moving on without...' - ) - } - return null -} - -function postSummaryComment( - report: CtrfReport, - apiUrl: string, - prCommentMessage?: string -) { - const token = process.env.GITHUB_TOKEN - if (!token) { - console.error( - 'GITHUB_TOKEN is not set. This is required for post-comment argument' - ) - return - } - - const eventPath = process.env.GITHUB_EVENT_PATH - if (!eventPath) { - console.error( - 'GITHUB_EVENT_PATH is not set. This is required to determine context.' - ) - return - } - - let context +else if (argv._.includes('annotate') && argv.file) { try { - const eventData = fs.readFileSync(eventPath, 'utf8') - context = JSON.parse(eventData) - } catch (error) { - console.error('Failed to read or parse event data:', error) - return - } - - const repo = context.repository.full_name - const pullRequest = context.pull_request?.number - - if (!pullRequest) { - console.log( - 'Action is not running in a pull request context. Skipping comment.' - ) - return - } - - if (argv.onFailOnly && report.results.summary.failed === 0) { - console.log( - 'On fail only is set to true and no tests failed. Skipping comment' - ) - return - } - - const run_id = process.env.GITHUB_RUN_ID - - const summaryUrl = `${baseUrl}/${repo}/actions/runs/${run_id}#summary` - const summaryMarkdown = - prCommentMessage || generateSummaryMarkdown(report, summaryUrl) - - const data = JSON.stringify({ body: summaryMarkdown.trim() }) - - const apiPath = `/repos/${repo}/issues/${pullRequest}/comments` - - const options = { - hostname: apiUrl.replace(/^https?:\/\//, '').split('/')[0], - path: apiPath, - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json', - 'Content-Type': 'application/json', - 'X-GitHub-Api-Version': '2022-11-28', - 'User-Agent': 'github-actions-ctrf', - }, - } - - const req = https.request(options, (res) => { - let responseBody = '' - - res.on('data', (chunk) => { - responseBody += chunk - }) - - res.on('end', () => { - if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { - console.log('Comment posted successfully.') - } else if (res.statusCode === 403) { - console.error(`Failed to post comment: 403 Forbidden - ${responseBody}`) - console.error( - `This may be due to insufficient permissions on the GitHub token.` - ) - console.error( - `Please check the permissions for the GITHUB_TOKEN and ensure it has the appropriate scopes.` - ) - console.error( - `For more information, visit: https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token` - ) - } else { - console.error( - `Failed to post comment: ${res.statusCode} - ${responseBody}` - ) + let report = validateCtrfFile(argv.file) + report = stripAnsiFromErrors(report) + if (report !== null) { + annotateFailed(report, useSuiteName) + if (argv.prComment) { + postPullRequestComment(report, apiUrl, baseUrl, onFailOnly, title, useSuiteName) } - }) - }) - - req.on('error', (error) => { - console.error(`Failed to post comment: ${error.message}`) - }) - - req.write(data) - req.end() -} - -export function generateSummaryMarkdown( - report: CtrfReport, - summaryUrl: string -): string { - const durationInSeconds = - (report.results.summary.stop - report.results.summary.start) / 1000 - const durationFormatted = - durationInSeconds < 1 - ? '<1s' - : new Date(durationInSeconds * 1000).toISOString().substr(11, 8) - - const runNumber = process.env.GITHUB_RUN_NUMBER - - const flakyCount = report.results.tests.filter((test) => test.flaky).length - const failedTests = report.results.tests.filter( - (test) => test.status === 'failed' - ) - const statusLine = - report.results.summary.failed > 0 - ? '❌ **Some tests failed!**' - : '🎉 **All tests passed!**' - - let failedTestsTable = '' - if (failedTests.length > 0) { - const failedTestsRows = failedTests - .slice(0, 5) - .map( - (test) => ` - -${getTestName(test, useSuiteName)} -failed ❌ -${stripAnsi(test.message || '') || 'No failure message'} -` - ) - .join('') - - const moreTestsText = - failedTests.length > 5 - ? `

See all failed tests here

` - : '' - - failedTestsTable = ` - - - - - - - - - - ${failedTestsRows} - -
NameStatusFailure Message
-${moreTestsText}` - } - - return ` -### ${title} - [Run #${runNumber}](${summaryUrl}) - -| **Tests 📝** | **Passed ✅** | **Failed ❌** | **Skipped ⏭️** | **Pending ⏳** | **Other ❓** | **Flaky 🍂** | **Duration ⏱️** | -| --- | --- | --- | --- | --- | --- | --- | --- | -| ${report.results.summary.tests} | ${report.results.summary.passed} | ${report.results.summary.failed} | ${report.results.summary.skipped} | ${report.results.summary.pending} | ${report.results.summary.other} | ${flakyCount} | ${durationFormatted} | - -### ${statusLine} -${failedTestsTable} - -[Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf) -` -} - -export function renderHandlebarsTemplate(template: any, context: any) { - try { - const compiledTemplate = Handlebars.compile(template) - return compiledTemplate(context) + } } catch (error) { - console.error('Failed to render Handlebars template:', error) - return '' - } -} - -function stripAnsiFromErrors(report: CtrfReport | null): any { - if (!report?.results?.tests) { - return report + console.error('Failed to read file:', error) } - - report.results.tests.forEach((test) => { - if (test.message) { - test.message = stripAnsi(test.message) - } - if (test.trace) { - test.trace = stripAnsi(test.trace) - } - }) - - return report -} +} \ No newline at end of file diff --git a/src/views/ai.ts b/src/views/ai.ts new file mode 100644 index 00000000..938d7c6b --- /dev/null +++ b/src/views/ai.ts @@ -0,0 +1,37 @@ +import * as core from '@actions/core' +import { CtrfTest } from '../../types/ctrf' +import { getTestName } from '../common' + +export function generateAIFailedTestsSummaryTable(tests: CtrfTest[], useSuiteName: boolean) { + try { + core.summary.addHeading(`AI Summary`, 3) + + const failedTests = tests.filter((test) => test.status === 'failed') + + if (failedTests.length > 0) { + core.summary + .addTable([ + [ + { data: 'Failed Test ❌', header: true }, + { data: 'AI Summary ✨', header: true }, + ], + ...failedTests.map((test) => [ + { data: `${getTestName(test, useSuiteName)}`, header: true }, + { data: `${test.ai || 'No summary'}`, header: false }, + ]), + ]) + .addLink( + 'Github Actions Test Reporter CTRF', + 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' + ) + } else { + core.summary.addRaw('No failed tests ✨') + } + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Failed to display failed test details: ${error.message}`) + } else { + core.setFailed('An unknown error occurred') + } + } + } \ No newline at end of file diff --git a/src/views/annotate.ts b/src/views/annotate.ts new file mode 100644 index 00000000..54e7812f --- /dev/null +++ b/src/views/annotate.ts @@ -0,0 +1,30 @@ +import * as core from '@actions/core' +import { CtrfReport } from "../../types/ctrf" +import { getTestName, stripAnsi } from "../common" + +export function annotateFailed(report: CtrfReport, useSuiteName: boolean): void { + try { + report.results.tests.forEach((test) => { + if (test.status === 'failed') { + const message = test.message + ? stripAnsi(test.message || '') + : 'No message provided' + const trace = test.trace ? stripAnsi(test.trace) : 'No trace available' + const annotation = `${getTestName(test, useSuiteName)}: ${stripAnsi(message)} - ${stripAnsi(trace)}` + + core.error(annotation, { + title: `Failed Test: ${getTestName(test, useSuiteName)}`, + file: test.filePath, + startLine: 0, + endLine: 0, + }) + } + }) + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Failed to annotate failed tests: ${error.message}`) + } else { + core.setFailed('An unknown error occurred') + } + } + } \ No newline at end of file diff --git a/src/views/common.ts b/src/views/common.ts new file mode 100644 index 00000000..3c3447bd --- /dev/null +++ b/src/views/common.ts @@ -0,0 +1,40 @@ +import * as core from '@actions/core' +import { CtrfReport, CtrfTestState } from '../../types/ctrf' + +export function write(): void { + core.summary.write() + } + + export function exitActionOnFail(report: CtrfReport): void { + if (report.results.summary.failed > 0) { + core.setFailed(`Github Test Reporter: ${report.results.summary.failed} failed tests found`) + } + } + + export function addHeading(title: string): void { + try { + core.summary.addHeading(`${title}`, 2) + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Failed to add title: ${error.message}`) + } else { + core.setFailed('An unknown error occurred') + } + } + } + + export function getEmojiForStatus(status: CtrfTestState): string { + switch (status) { + case 'passed': + return '✅' + case 'failed': + return '❌' + case 'skipped': + return '⏭️' + case 'pending': + return '⏳' + default: + return '❓' + } + } + \ No newline at end of file diff --git a/src/views/detailed.ts b/src/views/detailed.ts new file mode 100644 index 00000000..511813a5 --- /dev/null +++ b/src/views/detailed.ts @@ -0,0 +1,52 @@ +import * as core from '@actions/core' +import { CtrfTest } from "../../types/ctrf" +import { getTestName } from "../common" +import { getEmojiForStatus } from "./common" + +export function generateTestDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { + try { + core.summary.addHeading(`Detailed Test Results`, 3) + + const maxRows = 8000 + let limitedTests = tests + + if (tests.length > maxRows) { + limitedTests = tests.slice(0, maxRows) + } + + const headers = [ + { data: 'Name', header: true }, + { data: 'Status', header: true }, + { data: 'ms', header: true }, + { data: 'Flaky 🍂', header: true }, + ] + + const rows = limitedTests.map((test) => [ + { data: getTestName(test, useSuiteName), header: false }, + { + data: `${test.status} ${getEmojiForStatus(test.status)}`, + header: false, + }, + { data: test.duration.toString(), header: false }, + { data: test.flaky ? 'Yes' : '', header: false }, + ]) + + core.summary.addTable([headers, ...rows]) + + if (tests.length > maxRows) { + core.summary.addRaw( + `Note: You have a lot of tests. We've limited the number shown in the detailed breakdown to ${maxRows}.` + ) + } + core.summary.addLink( + 'Github Actions Test Reporter CTRF', + 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' + ) + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Failed to append to job summary: ${error.message}`) + } else { + core.setFailed('An unknown error occurred') + } + } + } \ No newline at end of file diff --git a/src/views/failed-rate.ts b/src/views/failed-rate.ts index f8afbfdc..8648a873 100644 --- a/src/views/failed-rate.ts +++ b/src/views/failed-rate.ts @@ -115,7 +115,7 @@ ${noFailMessage} [Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf) ` - core.summary.addRaw(summary).write() + core.summary.addRaw(summary) return } @@ -142,5 +142,5 @@ ${totalRunsMessage} [Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf) ` - core.summary.addRaw(summaryTable).write() + core.summary.addRaw(summaryTable) } diff --git a/src/views/failed.ts b/src/views/failed.ts new file mode 100644 index 00000000..f4f4bc87 --- /dev/null +++ b/src/views/failed.ts @@ -0,0 +1,50 @@ +import * as core from '@actions/core' +import { getTestName } from "../common" +import Convert from 'ansi-to-html' +import { CtrfTest } from '../../types/ctrf' + +export function generateFailedTestsDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { + try { + core.summary.addHeading(`Failed Tests`, 3) + const convert = new Convert() + + const failedTests = tests.filter((test) => test.status === 'failed') + + if (failedTests.length > 0) { + let tableHtml = ` + + + + + + + + + ` + failedTests.forEach((test) => { + tableHtml += ` + + + + + ` + }) + tableHtml += ` + +
NameStatusFailure Message
${getTestName(test, useSuiteName)}${test.status} ❌${convert.toHtml(test.message || '') || 'No failure message'}
` + core.summary.addRaw(tableHtml) + core.summary.addLink( + 'Github Actions Test Reporter CTRF', + 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' + ) + } else { + core.summary.addRaw('

No failed tests ✨

') + } + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Failed to display failed test details: ${error.message}`) + } else { + core.setFailed('An unknown error occurred') + } + } + } \ No newline at end of file diff --git a/src/views/flaky-rate.ts b/src/views/flaky-rate.ts index f6b76059..f833a4e3 100644 --- a/src/views/flaky-rate.ts +++ b/src/views/flaky-rate.ts @@ -130,7 +130,7 @@ ${noFlakyMessage} [Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf) ` - core.summary.addRaw(summary).write() + core.summary.addRaw(summary) return } @@ -156,5 +156,5 @@ ${totalRunsMessage} [Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf) ` - core.summary.addRaw(summaryTable).write() + core.summary.addRaw(summaryTable) } diff --git a/src/views/flaky.ts b/src/views/flaky.ts new file mode 100644 index 00000000..32f50209 --- /dev/null +++ b/src/views/flaky.ts @@ -0,0 +1,46 @@ +import * as core from '@actions/core' +import { getTestName } from "../common" +import { getEmojiForStatus } from "./common" +import { CtrfTest } from '../../types/ctrf' + +export function generateFlakyTestsDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { + try { + core.summary.addHeading(`Flaky Tests`, 3) + + const flakyTests = tests.filter((test) => test.flaky) + + if (flakyTests.length > 0) { + const headers = [ + { data: 'Name', header: true }, + { data: 'Status', header: true }, + { data: 'Retries', header: true }, + { data: 'Flaky 🍂', header: true }, + ] + + const rows = flakyTests.map((test) => [ + { data: getTestName(test, useSuiteName), header: false }, + { + data: test.status + ' ' + getEmojiForStatus(test.status), + header: false, + }, + { data: test.retries?.toString() || '0', header: false }, + { data: '🍂', header: false }, + ]) + + core.summary + .addTable([headers, ...rows]) + .addLink( + 'Github Actions Test Reporter CTRF', + 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' + ) + } else { + core.summary.addRaw('No flaky tests detected. ✨') + } + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Failed to display flaky test details: ${error.message}`) + } else { + core.setFailed('An unknown error occurred') + } + } + } \ No newline at end of file diff --git a/src/views/historical.ts b/src/views/historical.ts index 4cec30e6..6ce07481 100644 --- a/src/views/historical.ts +++ b/src/views/historical.ts @@ -2,7 +2,6 @@ import { type CtrfReport } from '../../types/ctrf' import * as core from '@actions/core' import { extractGithubProperties } from '../common' import { fetchArtifactsFromPreviousBuilds } from '../api/fetch-previous-runs' -import { exitOnFail } from './summary' export async function generateHistoricSummary( report: CtrfReport, @@ -65,10 +64,5 @@ ${limitedSummaryRows.join('\n')} ` core.summary.addHeading(`Previous Results`, 3) - core.summary.addRaw(summaryTable).write() - if (exitOnFail) { - if (report.results.summary.failed > 0) { - core.setFailed(`GitHub Test Reporter: ${report.results.summary.failed} failed tests found`) - } - } + core.summary.addRaw(summaryTable) } \ No newline at end of file diff --git a/src/views/pull-request.ts b/src/views/pull-request.ts new file mode 100644 index 00000000..d91d26f0 --- /dev/null +++ b/src/views/pull-request.ts @@ -0,0 +1,188 @@ +import { CtrfReport } from "../../types/ctrf" +import { getTestName, stripAnsi } from "../common" +import fs from 'fs' +import https from 'https' + +export function generatePullRequestComment( + report: CtrfReport, + summaryUrl: string, + title: string, + useSuiteName: boolean + ): string { + const durationInSeconds = + (report.results.summary.stop - report.results.summary.start) / 1000 + const durationFormatted = + durationInSeconds < 1 + ? '<1s' + : new Date(durationInSeconds * 1000).toISOString().substr(11, 8) + + const runNumber = process.env.GITHUB_RUN_NUMBER + + const flakyCount = report.results.tests.filter((test) => test.flaky).length + const failedTests = report.results.tests.filter( + (test) => test.status === 'failed' + ) + const statusLine = + report.results.summary.failed > 0 + ? '❌ **Some tests failed!**' + : '🎉 **All tests passed!**' + + let failedTestsTable = '' + if (failedTests.length > 0) { + const failedTestsRows = failedTests + .slice(0, 5) + .map( + (test) => ` + + ${getTestName(test, useSuiteName)} + failed ❌ + ${stripAnsi(test.message || '') || 'No failure message'} + ` + ) + .join('') + + const moreTestsText = + failedTests.length > 5 + ? `

See all failed tests here

` + : '' + + failedTestsTable = ` + + + + + + + + + + ${failedTestsRows} + +
NameStatusFailure Message
+ ${moreTestsText}` + } + + return ` + ### ${title} - [Run #${runNumber}](${summaryUrl}) + + | **Tests 📝** | **Passed ✅** | **Failed ❌** | **Skipped ⏭️** | **Pending ⏳** | **Other ❓** | **Flaky 🍂** | **Duration ⏱️** | + | --- | --- | --- | --- | --- | --- | --- | --- | + | ${report.results.summary.tests} | ${report.results.summary.passed} | ${report.results.summary.failed} | ${report.results.summary.skipped} | ${report.results.summary.pending} | ${report.results.summary.other} | ${flakyCount} | ${durationFormatted} | + + ### ${statusLine} + ${failedTestsTable} + + [Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf) + ` + } + + export function postPullRequestComment( + report: CtrfReport, + apiUrl: string, + baseUrl: string, + onFailOnly: boolean, + title: string, + useSuiteName: boolean, + prCommentMessage?: string, + ) { + const token = process.env.GITHUB_TOKEN + if (!token) { + console.error( + 'GITHUB_TOKEN is not set. This is required for post-comment argument' + ) + return + } + + const eventPath = process.env.GITHUB_EVENT_PATH + if (!eventPath) { + console.error( + 'GITHUB_EVENT_PATH is not set. This is required to determine context.' + ) + return + } + + let context + try { + const eventData = fs.readFileSync(eventPath, 'utf8') + context = JSON.parse(eventData) + } catch (error) { + console.error('Failed to read or parse event data:', error) + return + } + + const repo = context.repository.full_name + const pullRequest = context.pull_request?.number + + if (!pullRequest) { + console.log( + 'Action is not running in a pull request context. Skipping comment.' + ) + return + } + + if (onFailOnly && report.results.summary.failed === 0) { + console.log( + 'On fail only is set to true and no tests failed. Skipping comment' + ) + return + } + + const run_id = process.env.GITHUB_RUN_ID + + const summaryUrl = `${baseUrl}/${repo}/actions/runs/${run_id}#summary` + const summaryMarkdown = + prCommentMessage || generatePullRequestComment(report, summaryUrl, title, useSuiteName) + + const data = JSON.stringify({ body: summaryMarkdown.trim() }) + + const apiPath = `/repos/${repo}/issues/${pullRequest}/comments` + + const options = { + hostname: apiUrl.replace(/^https?:\/\//, '').split('/')[0], + path: apiPath, + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'github-actions-ctrf', + }, + } + + const req = https.request(options, (res) => { + let responseBody = '' + + res.on('data', (chunk: any) => { + responseBody += chunk + }) + + res.on('end', () => { + if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { + console.log('Comment posted successfully.') + } else if (res.statusCode === 403) { + console.error(`Failed to post comment: 403 Forbidden - ${responseBody}`) + console.error( + `This may be due to insufficient permissions on the GitHub token.` + ) + console.error( + `Please check the permissions for the GITHUB_TOKEN and ensure it has the appropriate scopes.` + ) + console.error( + `For more information, visit: https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token` + ) + } else { + console.error( + `Failed to post comment: ${res.statusCode} - ${responseBody}` + ) + } + }) + }) + + req.on('error', (error: any) => { + console.error(`Failed to post comment: ${error.message}`) + }) + + req.write(data) + req.end() + } \ No newline at end of file diff --git a/src/views/skipped.ts b/src/views/skipped.ts new file mode 100644 index 00000000..983470df --- /dev/null +++ b/src/views/skipped.ts @@ -0,0 +1,44 @@ +import * as core from '@actions/core' +import { CtrfTest } from "../../types/ctrf" +import { getTestName } from "../common" +import { getEmojiForStatus } from "./common" + +export function generateSkippedTestsDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { + try { + core.summary.addHeading(`Skipped and Pending Tests`, 3) + + const skippedTests = tests.filter( + (test) => test.status === 'skipped' || test.status === 'pending' + ) + + if (skippedTests.length > 0) { + const headers = [ + { data: 'Name', header: true }, + { data: 'Status', header: true }, + ] + + const rows = skippedTests.map((test) => [ + { data: getTestName(test, useSuiteName), header: false }, + { + data: test.status + ' ' + getEmojiForStatus(test.status), + header: false, + }, + ]) + + core.summary + .addTable([headers, ...rows]) + .addLink( + 'Github Actions Test Reporter CTRF', + 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' + ) + } else { + core.summary.addRaw('No skipped or pending tests detected. ✨') + } + } catch (error) { + if (error instanceof Error) { + core.setFailed(`Failed to display skipped/pending test details: ${error.message}`) + } else { + core.setFailed('An unknown error occurred') + } + } + } \ No newline at end of file diff --git a/src/views/summary.ts b/src/views/summary.ts index 12ce78bf..d56a3f16 100644 --- a/src/views/summary.ts +++ b/src/views/summary.ts @@ -1,222 +1,7 @@ import * as core from '@actions/core' import { - type CtrfTest, - type CtrfTestState, type CtrfReport, } from '../../types/ctrf' -import { getTestName, stripAnsi } from '../common' -import Convert from 'ansi-to-html' - -export function generateTestDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { - try { - core.summary.addHeading(`Detailed Test Results`, 3) - - const maxRows = 8000 - let limitedTests = tests - - if (tests.length > maxRows) { - limitedTests = tests.slice(0, maxRows) - } - - const headers = [ - { data: 'Name', header: true }, - { data: 'Status', header: true }, - { data: 'ms', header: true }, - { data: 'Flaky 🍂', header: true }, - ] - - const rows = limitedTests.map((test) => [ - { data: getTestName(test, useSuiteName), header: false }, - { - data: `${test.status} ${getEmojiForStatus(test.status)}`, - header: false, - }, - { data: test.duration.toString(), header: false }, - { data: test.flaky ? 'Yes' : '', header: false }, - ]) - - core.summary.addTable([headers, ...rows]) - - if (tests.length > maxRows) { - core.summary.addRaw( - `Note: You have a lot of tests. We've limited the number shown in the detailed breakdown to ${maxRows}.` - ) - } - core.summary.addLink( - 'Github Actions Test Reporter CTRF', - 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' - ) - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to append to job summary: ${error.message}`) - } else { - core.setFailed('An unknown error occurred') - } - } -} - -export function generateFlakyTestsDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { - try { - core.summary.addHeading(`Flaky Tests`, 3) - - const flakyTests = tests.filter((test) => test.flaky) - - if (flakyTests.length > 0) { - const headers = [ - { data: 'Name', header: true }, - { data: 'Status', header: true }, - { data: 'Retries', header: true }, - { data: 'Flaky 🍂', header: true }, - ] - - const rows = flakyTests.map((test) => [ - { data: getTestName(test, useSuiteName), header: false }, - { - data: test.status + ' ' + getEmojiForStatus(test.status), - header: false, - }, - { data: test.retries?.toString() || '0', header: false }, - { data: 'Yes', header: false }, - ]) - - core.summary - .addTable([headers, ...rows]) - .addLink( - 'Github Actions Test Reporter CTRF', - 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' - ) - } else { - core.summary.addRaw('No flaky tests detected. ✨') - } - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to display flaky test details: ${error.message}`) - } else { - core.setFailed('An unknown error occurred') - } - } -} - -export function generateSkippedTestsDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { - try { - core.summary.addHeading(`Skipped and Pending Tests`, 3) - - const skippedTests = tests.filter( - (test) => test.status === 'skipped' || test.status === 'pending' - ) - - if (skippedTests.length > 0) { - const headers = [ - { data: 'Name', header: true }, - { data: 'Status', header: true }, - ] - - const rows = skippedTests.map((test) => [ - { data: getTestName(test, useSuiteName), header: false }, - { - data: test.status + ' ' + getEmojiForStatus(test.status), - header: false, - }, - ]) - - core.summary - .addTable([headers, ...rows]) - .addLink( - 'Github Actions Test Reporter CTRF', - 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' - ) - } else { - core.summary.addRaw('No skipped or pending tests detected. ✨') - } - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to display skipped/pending test details: ${error.message}`) - } else { - core.setFailed('An unknown error occurred') - } - } -} - - -export function generateFailedTestsDetailsTable(tests: CtrfTest[], useSuiteName: boolean): void { - try { - core.summary.addHeading(`Failed Tests`, 3) - const convert = new Convert() - - const failedTests = tests.filter((test) => test.status === 'failed') - - if (failedTests.length > 0) { - let tableHtml = ` - - - - - - - - - ` - failedTests.forEach((test) => { - tableHtml += ` - - - - - ` - }) - tableHtml += ` - -
NameStatusFailure Message
${getTestName(test, useSuiteName)}${test.status} ❌${convert.toHtml(test.message || '') || 'No failure message'}
` - core.summary.addRaw(tableHtml) - core.summary.addLink( - 'Github Actions Test Reporter CTRF', - 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' - ) - } else { - core.summary.addRaw('

No failed tests ✨

') - } - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to display failed test details: ${error.message}`) - } else { - core.setFailed('An unknown error occurred') - } - } -} - -export function generateAIFailedTestsSummaryTable(tests: CtrfTest[], useSuiteName: boolean) { - try { - core.summary.addHeading(`AI Summary`, 3) - - const failedTests = tests.filter((test) => test.status === 'failed') - - if (failedTests.length > 0) { - core.summary - .addTable([ - [ - { data: 'Failed Test ❌', header: true }, - { data: 'AI Summary ✨', header: true }, - ], - ...failedTests.map((test) => [ - { data: `${getTestName(test, useSuiteName)}`, header: true }, - { data: `${test.ai || 'No summary'}`, header: false }, - ]), - ]) - .addLink( - 'Github Actions Test Reporter CTRF', - 'https://github.com/ctrf-io/github-actions-test-reporter-ctrf' - ) - } else { - core.summary.addRaw('No failed tests ✨') - } - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to display failed test details: ${error.message}`) - } else { - core.setFailed('An unknown error occurred') - } - } -} export function generateSummaryDetailsTable(report: CtrfReport): void { try { @@ -263,68 +48,4 @@ export function generateSummaryDetailsTable(report: CtrfReport): void { core.setFailed('An unknown error occurred') } } -} - -export function addHeading(title: string): void { - try { - core.summary.addHeading(`${title}`, 2) - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to add title: ${error.message}`) - } else { - core.setFailed('An unknown error occurred') - } - } -} - -export function annotateFailed(report: CtrfReport, useSuiteName: boolean): void { - try { - report.results.tests.forEach((test) => { - if (test.status === 'failed') { - const message = test.message - ? stripAnsi(test.message || '') - : 'No message provided' - const trace = test.trace ? stripAnsi(test.trace) : 'No trace available' - const annotation = `${getTestName(test, useSuiteName)}: ${stripAnsi(message)} - ${stripAnsi(trace)}` - - core.error(annotation, { - title: `Failed Test: ${getTestName(test, useSuiteName)}`, - file: test.filePath, - startLine: 0, - endLine: 0, - }) - } - }) - } catch (error) { - if (error instanceof Error) { - core.setFailed(`Failed to annotate failed tests: ${error.message}`) - } else { - core.setFailed('An unknown error occurred') - } - } -} - -export function write(): void { - core.summary.write() -} - -export function exitOnFail(report: CtrfReport): void { - if (report.results.summary.failed > 0) { - core.setFailed(`Github Test Reporter: ${report.results.summary.failed} failed tests found`) - } -} - -function getEmojiForStatus(status: CtrfTestState): string { - switch (status) { - case 'passed': - return '✅' - case 'failed': - return '❌' - case 'skipped': - return '⏭️' - case 'pending': - return '⏳' - default: - return '❓' - } -} +} \ No newline at end of file