1
0
Fork 0
mirror of https://github.com/dorny/test-reporter.git synced 2026-05-06 10:37:36 +02:00

Merge pull request #773 from dorny/codex/list-files-input

This commit is contained in:
Jozef Izso 2026-04-25 13:34:59 +02:00 committed by GitHub
commit 1ab1766274
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 237 additions and 33 deletions

View file

@ -1,5 +1,8 @@
# Changelog # Changelog
## 3.1.0
* Feature: Add `list-files` input to control test report file listing https://github.com/dorny/test-reporter/pull/773
## 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

@ -184,6 +184,12 @@ 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'

View file

@ -1,4 +1,5 @@
import {getBadge, DEFAULT_OPTIONS, ReportOptions} from '../../src/report/get-report.js' import {DEFAULT_OPTIONS, getBadge, getReport, 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', () => {
@ -131,3 +132,147 @@ 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,6 +54,14 @@ 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.

22
dist/index.js generated vendored
View file

@ -56954,6 +56954,7 @@ const DEFAULT_OPTIONS = {
listSuites: 'all', listSuites: 'all',
listTests: 'all', listTests: 'all',
slugPrefix: '', slugPrefix: '',
listFiles: 'all',
baseUrl: '', baseUrl: '',
onlySummary: false, onlySummary: false,
useActionsSummary: true, useActionsSummary: true,
@ -57074,8 +57075,13 @@ function getTestRunsReport(testRuns, options) {
sections.push(`<details><summary>Expand for details</summary>`); sections.push(`<details><summary>Expand for details</summary>`);
sections.push(` `); sections.push(` `);
} }
if (testRuns.length > 0 || options.onlySummary) { // Filter test runs based on list-files option
const tableData = testRuns const filteredTestRuns = options.listFiles === 'failed'
? testRuns.filter(tr => tr.result === 'failed')
: options.listFiles === 'none'
? []
: testRuns;
const tableData = filteredTestRuns
.map((tr, originalIndex) => ({ tr, originalIndex })) .map((tr, originalIndex) => ({ tr, originalIndex }))
.filter(({ tr }) => tr.passed > 0 || tr.failed > 0 || tr.skipped > 0) .filter(({ tr }) => tr.passed > 0 || tr.failed > 0 || tr.skipped > 0)
.map(({ tr, originalIndex }) => { .map(({ tr, originalIndex }) => {
@ -57088,11 +57094,12 @@ function getTestRunsReport(testRuns, options) {
const skipped = tr.skipped > 0 ? `${tr.skipped} ${Icon.skip}` : ''; const skipped = tr.skipped > 0 ? `${tr.skipped} ${Icon.skip}` : '';
return [nameLink, passed, failed, skipped, time]; return [nameLink, passed, failed, skipped, time];
}); });
if (tableData.length > 0) {
const resultsTable = table(['Report', 'Passed', 'Failed', 'Skipped', 'Time'], [Align.Left, Align.Right, Align.Right, Align.Right, Align.Right], ...tableData); const resultsTable = table(['Report', 'Passed', 'Failed', 'Skipped', 'Time'], [Align.Left, Align.Right, Align.Right, Align.Right, Align.Right], ...tableData);
sections.push(resultsTable); sections.push(resultsTable);
} }
if (options.onlySummary === false) { if (options.onlySummary === false) {
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat(); const suitesReports = filteredTestRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat();
sections.push(...suitesReports); sections.push(...suitesReports);
} }
if (shouldCollapse) { if (shouldCollapse) {
@ -58949,6 +58956,7 @@ class TestReporter {
reporter = getInput('reporter', { required: true }); reporter = getInput('reporter', { required: true });
listSuites = getInput('list-suites', { required: true }); listSuites = getInput('list-suites', { required: true });
listTests = getInput('list-tests', { required: true }); listTests = getInput('list-tests', { required: true });
listFiles = getInput('list-files', { required: true });
maxAnnotations = parseInt(getInput('max-annotations', { required: true })); maxAnnotations = parseInt(getInput('max-annotations', { required: true }));
failOnError = getInput('fail-on-error', { required: true }) === 'true'; failOnError = getInput('fail-on-error', { required: true }) === 'true';
failOnEmpty = getInput('fail-on-empty', { required: true }) === 'true'; failOnEmpty = getInput('fail-on-empty', { required: true }) === 'true';
@ -58972,6 +58980,10 @@ class TestReporter {
setFailed(`Input parameter 'list-tests' has invalid value`); setFailed(`Input parameter 'list-tests' has invalid value`);
return; return;
} }
if (this.listFiles !== 'all' && this.listFiles !== 'failed' && this.listFiles !== 'none') {
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') {
setFailed(`Input parameter 'collapsed' has invalid value`); setFailed(`Input parameter 'collapsed' has invalid value`);
return; return;
@ -59056,7 +59068,7 @@ class TestReporter {
throw error; throw error;
} }
} }
const { listSuites, listTests, slugPrefix, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed } = this; const { 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);
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0); const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0);
@ -59067,6 +59079,7 @@ class TestReporter {
listSuites, listSuites,
listTests, listTests,
slugPrefix, slugPrefix,
listFiles,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
@ -59096,6 +59109,7 @@ class TestReporter {
listSuites, listSuites,
listTests, listTests,
slugPrefix, slugPrefix,
listFiles,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,

View file

@ -44,6 +44,7 @@ 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'
@ -71,6 +72,11 @@ 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
@ -177,7 +183,17 @@ class TestReporter {
} }
} }
const {listSuites, listTests, slugPrefix, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed} = this const {
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)
@ -192,6 +208,7 @@ class TestReporter {
listSuites, listSuites,
listTests, listTests,
slugPrefix, slugPrefix,
listFiles,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
@ -224,6 +241,7 @@ class TestReporter {
listSuites, listSuites,
listTests, listTests,
slugPrefix, slugPrefix,
listFiles,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,

View file

@ -12,6 +12,7 @@ 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
@ -24,6 +25,7 @@ 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,
@ -173,8 +175,15 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
sections.push(` `) sections.push(` `)
} }
if (testRuns.length > 0 || options.onlySummary) { // Filter test runs based on list-files option
const tableData = testRuns const filteredTestRuns =
options.listFiles === 'failed'
? testRuns.filter(tr => tr.result === 'failed')
: options.listFiles === 'none'
? []
: testRuns
const tableData = filteredTestRuns
.map((tr, originalIndex) => ({tr, originalIndex})) .map((tr, originalIndex) => ({tr, originalIndex}))
.filter(({tr}) => tr.passed > 0 || tr.failed > 0 || tr.skipped > 0) .filter(({tr}) => tr.passed > 0 || tr.failed > 0 || tr.skipped > 0)
.map(({tr, originalIndex}) => { .map(({tr, originalIndex}) => {
@ -188,6 +197,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
return [nameLink, passed, failed, skipped, time] 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],
@ -197,7 +207,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
} }
if (options.onlySummary === false) { if (options.onlySummary === false) {
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat() const suitesReports = filteredTestRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat()
sections.push(...suitesReports) sections.push(...suitesReports)
} }