Skip to content

Commit

Permalink
feat: add fail stats
Browse files Browse the repository at this point in the history
  • Loading branch information
Ma11hewThomas committed Oct 3, 2024
1 parent 1d8767d commit b69f11d
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 4 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/failed.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ jobs:
- name: Build
run: npx tsc
- name: Test failed with title
run: node dist/index.js failed ctrf-reports/ctrf-report.json --title Failed With Title
run: node dist/index.js failed ctrf-reports/ctrf-report.json --title "Failed With Title"
- name: Test failed no title
run: node dist/index.js failed ctrf-reports/ctrf-report.json
- name: Test failed stats
run: node dist/index.js failed-stats ctrf-reports/ctrf-report.json --title "Failed Stats"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload test results
uses: actions/upload-artifact@v4
with:
Expand Down
146 changes: 146 additions & 0 deletions src/fail-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { type CtrfReport } from '../types/ctrf'
import * as core from '@actions/core'
import { extractGithubProperties, getTestName } from './common'
import { fetchArtifactsFromPreviousBuilds } from './fetch-previous-runs'

export async function generateFailStatsSummary(
report: CtrfReport,
artifactName: string,
rows: number,
useSuiteName: boolean
): Promise<void> {
const token = process.env.GITHUB_TOKEN
if (!token) {
console.error(
'GITHUB_TOKEN is not set. This is required for fail-stats method'
)
return
}

const github = extractGithubProperties()
const reports = await fetchArtifactsFromPreviousBuilds(
github,
artifactName,
rows
)

if (github?.runId && github.runNumber && github.buildUrl) {
const extendedReport: CtrfReport & {
runId: string
runNumber: string
buildUrl: string
} = {
...report,
runId: github.runId,
runNumber: github.runNumber,
buildUrl: github.buildUrl,
}
reports.unshift(extendedReport)
}

const testFailMap = new Map<
string,
{
testName: string
totalRuns: number
pass: number
fail: number
failRate: number
}
>()

reports.forEach((run) => {
const { tests } = run.results

tests.forEach((test) => {
const testName = getTestName(test, useSuiteName)

let data = testFailMap.get(testName)
if (!data) {
data = {
testName,
totalRuns: 0,
pass: 0,
fail: 0,
failRate: 0,
}
testFailMap.set(testName, data)
}

if (test.status === 'passed' || test.status === 'failed') {
data.totalRuns += 1

if (test.status === 'passed') {
data.pass += 1
} else if (test.status === 'failed') {
data.fail += 1
}
}
})
})

const testFailArray = Array.from(testFailMap.values())

testFailArray.forEach((data) => {
data.failRate =
data.totalRuns > 0 ? (data.fail / data.totalRuns) * 100 : 0
})

const totalRunsAllTests = testFailArray.reduce(
(sum, data) => sum + data.totalRuns,
0
)
const totalFailsAllTests = testFailArray.reduce(
(sum, data) => sum + data.fail,
0
)
const overallFailRate =
totalRunsAllTests > 0 ? (totalFailsAllTests / totalRunsAllTests) * 100 : 0
const overallFailRateFormatted = overallFailRate.toFixed(2)
const overallFailRateMessage = `**Overall Fail Rate:** ${overallFailRateFormatted}%`

const testFailArrayNonZero = testFailArray.filter(
(data) => data.failRate > 0
)

const totalRuns = reports.length
const totalRunsMessage = `<sub><i>Measured over ${totalRuns} runs.</i></sub>`

if (testFailArrayNonZero.length === 0) {
const noFailMessage = `<sub><i>No failing tests detected over ${totalRuns} runs.</i></sub>`
const summary = `
${overallFailRateMessage}
${noFailMessage}
[Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf)
`
core.summary.addRaw(summary).write()
return
}

testFailArrayNonZero.sort((a, b) => b.failRate - a.failRate)

const failRows = testFailArrayNonZero.map((data) => {
const { testName, totalRuns, pass, fail, failRate } = data
return `| ${testName} | ${totalRuns} | ${pass} | ${fail} | ${failRate.toFixed(
2
)}% |`
})

const limitedSummaryRows = failRows.slice(0, rows)

const summaryTable = `
${overallFailRateMessage}
| Test 📝 | Total Runs 🎯 | Pass ✅ | Fail ❌ | Fail Rate 📉 |
| --- | --- | --- | --- | --- |
${limitedSummaryRows.join('\n')}
${totalRunsMessage}
[Github Actions Test Reporter CTRF](https://github.com/ctrf-io/github-actions-test-reporter-ctrf)
`

core.summary.addRaw(summaryTable).write()
}
45 changes: 42 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { generateHistoricSummary } from './historical'
import { extractGithubProperties, getTestName, stripAnsi } from './common'
import Convert = require('ansi-to-html')
import { generateFlakyStatsSummary } from './flaky-stats'
import { generateFailStatsSummary } from './fail-stats'

Handlebars.registerHelper('countFlaky', function (tests) {
return tests.filter((test: { flaky: boolean }) => test.flaky).length
Expand Down Expand Up @@ -63,6 +64,7 @@ interface Arguments {
onFailOnly?: boolean
domain?: string
useSuiteName?: boolean
results?: number
}

const argv: Arguments = yargs(hideBin(process.argv))
Expand Down Expand Up @@ -107,6 +109,21 @@ const argv: Arguments = yargs(hideBin(process.argv))
})
}
)
.command(
'failed-stats <file>',
'Generate a fail rate statistics test report from a CTRF report',
(yargs) => {
return yargs.positional('file', {
describe: 'Path to the CTRF file',
type: 'string',
})
.option('results', {
type: 'number',
description: 'Number of test results use for calculations',
default: 100,
})
}
)
.command(
'skipped <file>',
'Generate skipped or pending report from a CTRF report',
Expand Down Expand Up @@ -139,12 +156,17 @@ const argv: Arguments = yargs(hideBin(process.argv))
)
.command(
'flaky-stats <file>',
'Generate flaky statistics test report from a CTRF report',
'Generate a flaky rate statistics test report from a CTRF report',
(yargs) => {
return yargs.positional('file', {
describe: 'Path to the CTRF file',
type: 'string',
})
.option('results', {
type: 'number',
description: 'Number of test results use for calculations',
default: 100,
})
}
)
.command(
Expand Down Expand Up @@ -238,6 +260,7 @@ const annotate = argv.annotate ?? true
const file = argv.file || ''
const title = argv.title || 'Test Summary'
const rows = argv.rows || 10
const results = argv.results || 100
const artifactName = argv.artifactName || 'ctrf-report'
const useSuiteName = argv.useSuiteName ?? false

Expand Down Expand Up @@ -330,6 +353,23 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) {
} catch (error) {
console.error('Failed to read file:', error)
}
} else if (argv._.includes('failed-stats') && argv.file) {
try {
let report = validateCtrfFile(argv.file)
report = stripAnsiFromErrors(report)
if (report !== null) {
if (argv.title) {
addHeading(title)
}
generateFailStatsSummary(report, artifactName, results, useSuiteName)
write()
if (argv.prComment) {
postSummaryComment(report, apiUrl, prCommentMessage)
}
}
} catch (error) {
console.error('Failed to read file:', error)
}
} else if (argv._.includes('skipped') && argv.file) {
try {
let report = validateCtrfFile(argv.file)
Expand Down Expand Up @@ -382,7 +422,6 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) {
} catch (error) {
console.error('Failed to read file:', error)
}

} else if (argv._.includes('flaky-stats') && argv.file) {
try {
const data = fs.readFileSync(argv.file, 'utf8')
Expand All @@ -392,7 +431,7 @@ if ((commandUsed === 'all' || commandUsed === '') && argv.file) {
if (argv.title) {
addHeading(title)
}
generateFlakyStatsSummary(report, artifactName, rows, useSuiteName)
generateFlakyStatsSummary(report, artifactName, results, useSuiteName)
write()
if (argv.prComment) {
postSummaryComment(report, apiUrl, prCommentMessage)
Expand Down

0 comments on commit b69f11d

Please sign in to comment.