1
0
Fork 0
mirror of synced 2026-06-05 17:48:19 +00:00

Compare commits

..

No commits in common. "main" and "update_packages" have entirely different histories.

10 changed files with 1783 additions and 2467 deletions

View file

@ -1,9 +1,5 @@
# Changelog # Changelog
## 3.1.0
* Feature: Add `list-files` input to control test report file listing https://github.com/dorny/test-reporter/pull/773
* Feature: Add `summary_file` output with the path to the generated summary in Markdown format https://github.com/dorny/test-reporter/pull/772
## 3.0.0 ## 3.0.0
* Feature: Use NodeJS 24 LTS as default runtime https://github.com/dorny/test-reporter/pull/738 * Feature: Use NodeJS 24 LTS as default runtime https://github.com/dorny/test-reporter/pull/738

View file

@ -47,7 +47,7 @@ jobs:
name: Build & Test name: Build & Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 # checkout the repo - uses: actions/checkout@v4 # checkout the repo
- run: npm ci # install packages - run: npm ci # install packages
- run: npm test # run tests (configured to use jest-junit reporter) - run: npm test # run tests (configured to use jest-junit reporter)
@ -78,10 +78,10 @@ jobs:
build-test: build-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 # checkout the repo - uses: actions/checkout@v4 # checkout the repo
- run: npm ci # install packages - run: npm ci # install packages
- run: npm test # run tests (configured to use jest-junit reporter) - run: npm test # run tests (configured to use jest-junit reporter)
- uses: actions/upload-artifact@v7 # upload test results - uses: actions/upload-artifact@v4 # upload test results
if: ${{ !cancelled() }} # run this step even if previous step failed if: ${{ !cancelled() }} # run this step even if previous step failed
with: with:
name: test-results name: test-results
@ -184,12 +184,6 @@ jobs:
# none # none
list-tests: 'all' list-tests: 'all'
# Limits which test result files are listed:
# all
# failed
# none
list-files: 'all'
# Limits number of created annotations with error message and stack trace captured during test execution. # Limits number of created annotations with error message and stack trace captured during test execution.
# Must be less or equal to 50. # Must be less or equal to 50.
max-annotations: '10' max-annotations: '10'
@ -225,7 +219,6 @@ jobs:
| time | Test execution time [ms] | | time | Test execution time [ms] |
| url | Check run URL | | url | Check run URL |
| url_html | Check run URL HTML | | url_html | Check run URL HTML |
| summary_file | Path to a file containing the generated test report summary in Markdown format |
| slug_prefix| Random anchor links slug prefix generated for the summary headers | | slug_prefix| Random anchor links slug prefix generated for the summary headers |
## Supported formats ## Supported formats

View file

@ -1,5 +1,4 @@
import {DEFAULT_OPTIONS, getBadge, getReport, ReportOptions} from '../../src/report/get-report.js' import {getBadge, DEFAULT_OPTIONS, ReportOptions} from '../../src/report/get-report.js'
import {TestCaseResult, TestGroupResult, TestRunResult, TestSuiteResult} from '../../src/test-results.js'
describe('getBadge', () => { describe('getBadge', () => {
describe('URI encoding with special characters', () => { describe('URI encoding with special characters', () => {
@ -132,147 +131,3 @@ describe('getBadge', () => {
}) })
}) })
}) })
describe('getReport', () => {
// Helper function to create test results
function createTestResult(path: string, passed: number, failed: number, skipped: number): TestRunResult {
const tests: TestCaseResult[] = []
for (let i = 0; i < passed; i++) {
tests.push(new TestCaseResult(`passed-test-${i}`, 'success', 100))
}
for (let i = 0; i < failed; i++) {
tests.push(
new TestCaseResult(`failed-test-${i}`, 'failed', 100, {
details: 'Test failed',
message: 'Assertion error'
})
)
}
for (let i = 0; i < skipped; i++) {
tests.push(new TestCaseResult(`skipped-test-${i}`, 'skipped', 0))
}
const group = new TestGroupResult('test-group', tests)
const suite = new TestSuiteResult('test-suite', [group])
return new TestRunResult(path, [suite])
}
describe('list-files parameter', () => {
const results = [
createTestResult('passing-file.spec.ts', 5, 0, 0),
createTestResult('failing-file.spec.ts', 3, 2, 1),
createTestResult('passing-with-skipped-file.spec.ts', 8, 0, 2)
]
it('shows all files when list-files is "all"', () => {
const report = getReport(results, {
...DEFAULT_OPTIONS,
listFiles: 'all',
listSuites: 'none',
listTests: 'none'
})
expect(report).toContain('|Report|Passed|Failed|Skipped|Time|')
expect(report).toContain('passing-file.spec.ts')
expect(report).toContain('failing-file.spec.ts')
expect(report).toContain('passing-with-skipped-file.spec.ts')
})
it('shows only failed files when list-files is "failed"', () => {
const report = getReport(results, {
...DEFAULT_OPTIONS,
listFiles: 'failed',
listSuites: 'none',
listTests: 'none'
})
expect(report).toContain('|Report|Passed|Failed|Skipped|Time|')
expect(report).not.toContain('passing-file.spec.ts')
expect(report).toContain('failing-file.spec.ts')
expect(report).not.toContain('passing-with-skipped-file.spec.ts')
})
it('shows no file details when list-files is "none"', () => {
const report = getReport(results, {
...DEFAULT_OPTIONS,
listFiles: 'none',
listSuites: 'none',
listTests: 'none'
})
expect(report).toContain('![')
expect(report).not.toContain('|Report|Passed|Failed|Skipped|Time|')
expect(report).not.toContain('passing-file.spec.ts')
expect(report).not.toContain('failing-file.spec.ts')
expect(report).not.toContain('passing-with-skipped-file.spec.ts')
})
it('does not show an empty summary table when list-files is "none" and only-summary is enabled', () => {
const report = getReport(results, {
...DEFAULT_OPTIONS,
listFiles: 'none',
listSuites: 'all',
onlySummary: true,
listTests: 'none'
})
expect(report).toContain('![')
expect(report).not.toContain('|Report|Passed|Failed|Skipped|Time|')
expect(report).not.toContain('passing-file.spec.ts')
expect(report).not.toContain('failing-file.spec.ts')
expect(report).not.toContain('passing-with-skipped-file.spec.ts')
})
it('works correctly with list-suites and list-tests when list-files is "failed"', () => {
const report = getReport(results, {
...DEFAULT_OPTIONS,
listFiles: 'failed',
listSuites: 'all',
listTests: 'all'
})
expect(report).toContain('|Report|Passed|Failed|Skipped|Time|')
expect(report).not.toContain('passing-file.spec.ts')
expect(report).toContain('failing-file.spec.ts')
expect(report).not.toContain('passing-with-skipped-file.spec.ts')
// Should show suite details for the failed file
expect(report).toContain('test-suite')
})
it('filters correctly when all files pass and list-files is "failed"', () => {
const allPassingResults = [
createTestResult('passing-file-1.spec.ts', 5, 0, 0),
createTestResult('passing-file-2.spec.ts', 8, 0, 2)
]
const report = getReport(allPassingResults, {
...DEFAULT_OPTIONS,
listFiles: 'failed',
listSuites: 'all',
listTests: 'none'
})
expect(report).not.toContain('passing-file-1.spec.ts')
expect(report).not.toContain('passing-file-2.spec.ts')
expect(report).toContain('![')
expect(report).not.toContain('|Report|Passed|Failed|Skipped|Time|')
})
it('filters correctly when all files fail and list-files is "failed"', () => {
const allFailingResults = [
createTestResult('failing-file-1.spec.ts', 0, 5, 0),
createTestResult('failing-file-2.spec.ts', 1, 2, 1)
]
const report = getReport(allFailingResults, {
...DEFAULT_OPTIONS,
listFiles: 'failed',
listSuites: 'all',
listTests: 'none'
})
expect(report).toContain('failing-file-1.spec.ts')
expect(report).toContain('failing-file-2.spec.ts')
})
})
})

View file

@ -54,14 +54,6 @@ inputs:
- none - none
required: false required: false
default: 'all' default: 'all'
list-files:
description: |
Limits which test result files are listed. Supported options:
- all
- failed
- none
required: false
default: 'all'
max-annotations: max-annotations:
description: | description: |
Limits number of created annotations with error message and stack trace captured during test execution. Limits number of created annotations with error message and stack trace captured during test execution.
@ -130,8 +122,6 @@ outputs:
description: Check run URL description: Check run URL
url_html: url_html:
description: Check run URL HTML description: Check run URL HTML
summary_file:
description: Path to a file containing the generated test report summary in Markdown format
slug_prefix: slug_prefix:
description: Random prefix added to generated report anchor slugs for this action run description: Random prefix added to generated report anchor slugs for this action run
runs: runs:

1523
dist/index.js generated vendored

File diff suppressed because it is too large Load diff

2
dist/licenses.txt generated vendored
View file

@ -889,7 +889,7 @@ reusify
MIT MIT
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015-2024 Matteo Collina Copyright (c) 2015 Matteo Collina
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

2436
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -37,9 +37,9 @@
"author": "Michal Dorner <dorner.michal@gmail.com>", "author": "Michal Dorner <dorner.michal@gmail.com>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^3.0.1", "@actions/core": "^3.0.0",
"@actions/exec": "^3.0.0", "@actions/exec": "^3.0.0",
"@actions/github": "^9.1.1", "@actions/github": "^9.0.0",
"adm-zip": "^0.5.17", "adm-zip": "^0.5.17",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
"picomatch": "^4.0.4", "picomatch": "^4.0.4",
@ -52,22 +52,22 @@
"@types/node": "^24.12.2", "@types/node": "^24.12.2",
"@types/picomatch": "^4.0.3", "@types/picomatch": "^4.0.3",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",
"@typescript-eslint/eslint-plugin": "^8.59.0", "@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.59.0", "@typescript-eslint/parser": "^8.58.0",
"@vercel/ncc": "^0.38.4", "@vercel/ncc": "^0.38.4",
"eol-converter-cli": "^1.1.0", "eol-converter-cli": "^1.1.0",
"eslint": "^9.39.4", "eslint": "^9.39.4",
"eslint-import-resolver-typescript": "^4.4.4", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-github": "^6.0.0", "eslint-plugin-github": "^6.0.0",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.15.2", "eslint-plugin-jest": "^29.15.1",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
"jest": "^30.3.0", "jest": "^30.3.0",
"jest-junit": "^17.0.0", "jest-junit": "^16.0.0",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"prettier": "^3.8.3", "prettier": "^3.8.1",
"ts-jest": "^29.4.9", "ts-jest": "^29.4.9",
"typescript": "^6.0.3" "typescript": "^5.9.3"
}, },
"overrides": { "overrides": {
"sax": "^1.4.3" "sax": "^1.4.3"

View file

@ -2,9 +2,6 @@ import * as core from '@actions/core'
import * as github from '@actions/github' import * as github from '@actions/github'
import {GitHub} from '@actions/github/lib/utils' import {GitHub} from '@actions/github/lib/utils'
import {randomBytes} from 'node:crypto' import {randomBytes} from 'node:crypto'
import {writeFileSync} from 'node:fs'
import {tmpdir} from 'node:os'
import {join} from 'node:path'
import {ArtifactProvider} from './input-providers/artifact-provider.js' import {ArtifactProvider} from './input-providers/artifact-provider.js'
import {LocalFileProvider} from './input-providers/local-file-provider.js' import {LocalFileProvider} from './input-providers/local-file-provider.js'
@ -47,7 +44,6 @@ class TestReporter {
readonly reporter = core.getInput('reporter', {required: true}) readonly reporter = core.getInput('reporter', {required: true})
readonly listSuites = core.getInput('list-suites', {required: true}) as 'all' | 'failed' | 'none' readonly listSuites = core.getInput('list-suites', {required: true}) as 'all' | 'failed' | 'none'
readonly listTests = core.getInput('list-tests', {required: true}) as 'all' | 'failed' | 'none' readonly listTests = core.getInput('list-tests', {required: true}) as 'all' | 'failed' | 'none'
readonly listFiles = core.getInput('list-files', {required: true}) as 'all' | 'failed' | 'none'
readonly maxAnnotations = parseInt(core.getInput('max-annotations', {required: true})) readonly maxAnnotations = parseInt(core.getInput('max-annotations', {required: true}))
readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true' readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
readonly failOnEmpty = core.getInput('fail-on-empty', {required: true}) === 'true' readonly failOnEmpty = core.getInput('fail-on-empty', {required: true}) === 'true'
@ -75,11 +71,6 @@ class TestReporter {
return return
} }
if (this.listFiles !== 'all' && this.listFiles !== 'failed' && this.listFiles !== 'none') {
core.setFailed(`Input parameter 'list-files' has invalid value`)
return
}
if (this.collapsed !== 'auto' && this.collapsed !== 'always' && this.collapsed !== 'never') { if (this.collapsed !== 'auto' && this.collapsed !== 'always' && this.collapsed !== 'never') {
core.setFailed(`Input parameter 'collapsed' has invalid value`) core.setFailed(`Input parameter 'collapsed' has invalid value`)
return return
@ -186,17 +177,7 @@ class TestReporter {
} }
} }
const { const {listSuites, listTests, slugPrefix, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed} = this
listSuites,
listTests,
slugPrefix,
listFiles,
onlySummary,
useActionsSummary,
badgeTitle,
reportTitle,
collapsed
} = this
const passed = results.reduce((sum, tr) => sum + tr.passed, 0) const passed = results.reduce((sum, tr) => sum + tr.passed, 0)
const failed = results.reduce((sum, tr) => sum + tr.failed, 0) const failed = results.reduce((sum, tr) => sum + tr.failed, 0)
@ -211,7 +192,6 @@ class TestReporter {
listSuites, listSuites,
listTests, listTests,
slugPrefix, slugPrefix,
listFiles,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
@ -224,7 +204,6 @@ class TestReporter {
core.info('Summary content:') core.info('Summary content:')
core.info(summary) core.info(summary)
this.writeSummaryFile(summary)
await core.summary.addRaw(summary).write() await core.summary.addRaw(summary).write()
} else { } else {
core.info(`Creating check run ${name}`) core.info(`Creating check run ${name}`)
@ -245,7 +224,6 @@ class TestReporter {
listSuites, listSuites,
listTests, listTests,
slugPrefix, slugPrefix,
listFiles,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
@ -256,7 +234,6 @@ class TestReporter {
core.info('Creating annotations') core.info('Creating annotations')
const annotations = getAnnotations(results, this.maxAnnotations) const annotations = getAnnotations(results, this.maxAnnotations)
this.writeSummaryFile(summary)
const isFailed = this.failOnError && results.some(tr => tr.result === 'failed') const isFailed = this.failOnError && results.some(tr => tr.result === 'failed')
const conclusion = isFailed ? 'failure' : 'success' const conclusion = isFailed ? 'failure' : 'success'
@ -283,14 +260,6 @@ class TestReporter {
return results return results
} }
writeSummaryFile(summary: string): void {
const dir = process.env.RUNNER_TEMP || tmpdir()
const file = join(dir, `test-reporter-summary-${randomBytes(8).toString('hex')}.md`)
writeFileSync(file, summary)
core.info(`Summary written to ${file}`)
core.setOutput('summary_file', file)
}
getParser(reporter: string, options: ParseOptions): TestParser { getParser(reporter: string, options: ParseOptions): TestParser {
switch (reporter) { switch (reporter) {
case 'dart-json': case 'dart-json':

View file

@ -12,7 +12,6 @@ export interface ReportOptions {
listSuites: 'all' | 'failed' | 'none' listSuites: 'all' | 'failed' | 'none'
listTests: 'all' | 'failed' | 'none' listTests: 'all' | 'failed' | 'none'
slugPrefix: string slugPrefix: string
listFiles: 'all' | 'failed' | 'none'
baseUrl: string baseUrl: string
onlySummary: boolean onlySummary: boolean
useActionsSummary: boolean useActionsSummary: boolean
@ -25,7 +24,6 @@ export const DEFAULT_OPTIONS: ReportOptions = {
listSuites: 'all', listSuites: 'all',
listTests: 'all', listTests: 'all',
slugPrefix: '', slugPrefix: '',
listFiles: 'all',
baseUrl: '', baseUrl: '',
onlySummary: false, onlySummary: false,
useActionsSummary: true, useActionsSummary: true,
@ -175,29 +173,21 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
sections.push(` `) sections.push(` `)
} }
// Filter test runs based on list-files option if (testRuns.length > 0 || options.onlySummary) {
const filteredTestRuns = const tableData = testRuns
options.listFiles === 'failed' .map((tr, originalIndex) => ({tr, originalIndex}))
? testRuns.filter(tr => tr.result === 'failed') .filter(({tr}) => tr.passed > 0 || tr.failed > 0 || tr.skipped > 0)
: options.listFiles === 'none' .map(({tr, originalIndex}) => {
? [] const time = formatTime(tr.time)
: testRuns const name = tr.path
const addr = options.baseUrl + makeRunSlug(originalIndex, options).link
const nameLink = link(name, addr)
const passed = tr.passed > 0 ? `${tr.passed} ${Icon.success}` : ''
const failed = tr.failed > 0 ? `${tr.failed} ${Icon.fail}` : ''
const skipped = tr.skipped > 0 ? `${tr.skipped} ${Icon.skip}` : ''
return [nameLink, passed, failed, skipped, time]
})
const tableData = filteredTestRuns
.map((tr, originalIndex) => ({tr, originalIndex}))
.filter(({tr}) => tr.passed > 0 || tr.failed > 0 || tr.skipped > 0)
.map(({tr, originalIndex}) => {
const time = formatTime(tr.time)
const name = tr.path
const addr = options.baseUrl + makeRunSlug(originalIndex, options).link
const nameLink = link(name, addr)
const passed = tr.passed > 0 ? `${tr.passed} ${Icon.success}` : ''
const failed = tr.failed > 0 ? `${tr.failed} ${Icon.fail}` : ''
const skipped = tr.skipped > 0 ? `${tr.skipped} ${Icon.skip}` : ''
return [nameLink, passed, failed, skipped, time]
})
if (tableData.length > 0) {
const resultsTable = table( const resultsTable = table(
['Report', 'Passed', 'Failed', 'Skipped', 'Time'], ['Report', 'Passed', 'Failed', 'Skipped', 'Time'],
[Align.Left, Align.Right, Align.Right, Align.Right, Align.Right], [Align.Left, Align.Right, Align.Right, Align.Right, Align.Right],
@ -207,7 +197,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
} }
if (options.onlySummary === false) { if (options.onlySummary === false) {
const suitesReports = filteredTestRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat() const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat()
sections.push(...suitesReports) sections.push(...suitesReports)
} }