Skip to content

Commit

Permalink
Merge pull request #1050 from mikepenz/feature/summary_output
Browse files Browse the repository at this point in the history
Provide summary and detailed summary as action output
  • Loading branch information
mikepenz authored Mar 13, 2024
2 parents 5f47764 + 438e756 commit 754051f
Show file tree
Hide file tree
Showing 9 changed files with 647 additions and 584 deletions.
7 changes: 3 additions & 4 deletions __tests__/testParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1103,14 +1103,13 @@ action.surefire.report.email.InvalidEmailAddressException: Invalid email address
)

expect(result).toStrictEqual({
checkName: "",
summary: "",
checkName: '',
summary: '',
totalCount: 0,
skipped: 0,
failed: 0,
passed: 0,
annotations: [
],
annotations: []
})
})
})
25 changes: 19 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,19 @@ inputs:
job_summary:
description: 'Enables the publishing of a JOB_SUMMARY with the report.'
required: false
default: true
default: 'true'
detailed_summary:
description: 'Include table with all test results in summary'
required: false
default: false
default: 'false'
annotate_notice:
description: 'Annotate passed tests along with warning and failed ones'
required: false
default: false
default: 'false'
follow_symlink:
description: 'Enables the file globber to follow symlinks. Default: false'
required: false
default: false
default: 'false'
job_name:
description: 'Specify the name of a check to update'
required: false
Expand All @@ -101,9 +101,22 @@ inputs:
description: 'Specify the limit for annotations. This will also interrupt parsing all test-suites if the limit is reached.'
required: false
truncate_stack_traces:
descrption: 'Truncate stack traces from test output to 2 lines in annotations'
description: 'Truncate stack traces from test output to 2 lines in annotations'
required: false
default: true
default: 'true'
outputs:
total:
description: 'The total count of all checks'
passed:
description: 'The count of all passed tests'
skipped:
description: 'The count of all skipped tests'
failed:
description: 'The count of all failed tests'
summary:
description: 'The short summary of the junit report. In html format (as also constructed by GitHub for the summary).'
detailed_summary:
description: 'The full table with all test results in a summary. In html format (as also constructed by GitHub for the summary).'
runs:
using: 'node20'
main: 'dist/index.js'
1,063 changes: 526 additions & 537 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

60 changes: 32 additions & 28 deletions src/annotator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,10 @@ async function updateChecks(
await octokit.rest.checks.update(updateCheckRequest)
}

export async function attachSummary(
export function buildSummaryTables(
testResults: TestResult[],
detailedSummary: boolean,
includePassed: boolean
): Promise<void> {
): [SummaryTableRow[], SummaryTableRow[]] {
const table: SummaryTableRow[] = [
[
{data: '', header: true},
Expand Down Expand Up @@ -155,36 +154,41 @@ export async function attachSummary(
`${testResult.failed} failed`
])

if (detailedSummary) {
const annotations = testResult.annotations.filter(
annotation => includePassed || annotation.annotation_level !== 'notice'
)
const annotations = testResult.annotations.filter(
annotation => includePassed || annotation.annotation_level !== 'notice'
)

if (annotations.length === 0) {
if (!includePassed) {
core.info(
`⚠️ No annotations found for ${testResult.checkName}. If you want to include passed results in this table please configure 'include_passed' as 'true'`
)
}
detailsTable.push([`-`, `No test annotations available`, `-`])
} else {
for (const annotation of annotations) {
detailsTable.push([
`${testResult.checkName}`,
`${annotation.title}`,
`${
annotation.status === 'success'
? '✅ pass'
: annotation.status === 'skipped'
? `⏭️ skipped`
: `❌ ${annotation.annotation_level}`
}`
])
}
if (annotations.length === 0) {
if (!includePassed) {
core.info(
`⚠️ No annotations found for ${testResult.checkName}. If you want to include passed results in this table please configure 'include_passed' as 'true'`
)
}
detailsTable.push([`-`, `No test annotations available`, `-`])
} else {
for (const annotation of annotations) {
detailsTable.push([
`${testResult.checkName}`,
`${annotation.title}`,
`${
annotation.status === 'success'
? '✅ pass'
: annotation.status === 'skipped'
? `⏭️ skipped`
: `❌ ${annotation.annotation_level}`
}`
])
}
}
}
return [table, detailsTable]
}

export async function attachSummary(
table: SummaryTableRow[],
detailedSummary: boolean,
detailsTable: SummaryTableRow[]
): Promise<void> {
await core.summary.addTable(table).write()
if (detailedSummary) {
await core.summary.addTable(detailsTable).write()
Expand Down
10 changes: 7 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import {annotateTestResult, attachSummary} from './annotator'
import {annotateTestResult, attachSummary, buildSummaryTables} from './annotator'
import {parseTestReports, TestResult} from './testParser'
import {readTransformers, retrieve} from './utils'
import {buildTable, readTransformers, retrieve} from './utils'

export async function run(): Promise<void> {
try {
Expand Down Expand Up @@ -129,9 +129,10 @@ export async function run(): Promise<void> {
}

const supportsJobSummary = process.env['GITHUB_STEP_SUMMARY']
const [table, detailTable] = buildSummaryTables(testResults, includePassed)
if (jobSummary && supportsJobSummary) {
try {
await attachSummary(testResults, detailedSummary, includePassed)
await attachSummary(table, detailedSummary, detailTable)
} catch (error) {
core.error(`❌ Failed to set the summary using the provided token. (${error})`)
}
Expand All @@ -141,6 +142,9 @@ export async function run(): Promise<void> {
core.info('⏩ Skipped creation of job summary')
}

core.setOutput('summary', buildTable(table))
core.setOutput('detailed_summary', buildTable(detailTable))

if (failOnFailure && conclusion === 'failure') {
core.setFailed(`❌ Tests reported ${mergedResult.failed} failures`)
}
Expand Down
8 changes: 4 additions & 4 deletions src/testParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ async function parseSuite(
? suite.testsuite
: [suite.testsuite]
: Array.isArray(suite.testsuites.testsuite)
? suite.testsuites.testsuite
: [suite.testsuites.testsuite]
? suite.testsuites.testsuite
: [suite.testsuites.testsuite]

for (const testsuite of testsuites) {
if (!testsuite) {
Expand Down Expand Up @@ -264,8 +264,8 @@ async function parseSuite(
let testcases = Array.isArray(testsuite.testcase)
? testsuite.testcase
: testsuite.testcase
? [testsuite.testcase]
: []
? [testsuite.testcase]
: []

if (checkRetries) {
// identify duplicates, in case of flaky tests, and remove them
Expand Down
54 changes: 54 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as core from '@actions/core'
import {Transformer} from './testParser'
import {SummaryTableRow} from '@actions/core/lib/summary'

export function retrieve(name: string, items: string[], index: number, total: number): string {
if (total > 1) {
Expand Down Expand Up @@ -50,3 +51,56 @@ export function applyTransformer(transformer: Transformer, string: string): stri
return string.replace(transformer.searchValue, transformer.replaceValue)
}
}

/**
* Function extracted from: https://github.com/actions/toolkit/blob/main/packages/core/src/summary.ts#L229
*/
export function buildTable(rows: SummaryTableRow[]): string {
const tableBody = rows
.map(row => {
const cells = row
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.map((cell: any) => {
if (typeof cell === 'string') {
return wrap('td', cell)
}

const {header, data, colspan, rowspan} = cell
const tag = header ? 'th' : 'td'
const attrs = {
...(colspan && {colspan}),
...(rowspan && {rowspan})
}

return wrap(tag, data, attrs)
})
.join('')

return wrap('tr', cells)
})
.join('')

const element = wrap('table', tableBody)
return element
}

/**
* Wraps content in an HTML tag, adding any HTML attributes
*
* @param {string} tag HTML tag to wrap
* @param {string | null} content content within the tag
* @param {[attribute: string]: string} attrs key-value list of HTML attributes to add
*
* @returns {string} content wrapped in HTML element
*/
function wrap(tag: string, content: string | null, attrs: {[attribute: string]: string} = {}): string {
const htmlAttrs = Object.entries(attrs)
.map(([key, value]) => ` ${key}="${value}"`)
.join('')

if (!content) {
return `<${tag}${htmlAttrs}>`
}

return `<${tag}${htmlAttrs}>${content}</${tag}>`
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"outDir": "./lib", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
Expand Down

0 comments on commit 754051f

Please sign in to comment.