Compare commits
2 commits
main
...
codex/sort
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1f5af1564 |
||
|
|
0e1fe1690f |
9 changed files with 105 additions and 278 deletions
|
|
@ -1,9 +1,5 @@
|
|||
# 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
|
||||
* Feature: Use NodeJS 24 LTS as default runtime https://github.com/dorny/test-reporter/pull/738
|
||||
|
||||
|
|
|
|||
|
|
@ -184,12 +184,6 @@ jobs:
|
|||
# none
|
||||
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.
|
||||
# Must be less or equal to 50.
|
||||
max-annotations: '10'
|
||||
|
|
@ -225,7 +219,6 @@ jobs:
|
|||
| time | Test execution time [ms] |
|
||||
| url | Check run URL |
|
||||
| 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 |
|
||||
|
||||
## Supported formats
|
||||
|
|
|
|||
|
|
@ -134,145 +134,26 @@ 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', () => {
|
||||
it('sorts suites by descending time when configured', () => {
|
||||
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)
|
||||
new TestRunResult('report.xml', [
|
||||
createSuite('unit', 10),
|
||||
createSuite('integration', 30),
|
||||
createSuite('smoke', 20)
|
||||
])
|
||||
]
|
||||
|
||||
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')
|
||||
const report = getReport(results, {
|
||||
...DEFAULT_OPTIONS,
|
||||
sortSuites: 'time-desc',
|
||||
listTests: 'none'
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
expect(report.indexOf('integration')).toBeLessThan(report.indexOf('smoke'))
|
||||
expect(report.indexOf('smoke')).toBeLessThan(report.indexOf('unit'))
|
||||
})
|
||||
})
|
||||
|
||||
function createSuite(name: string, time: number): TestSuiteResult {
|
||||
return new TestSuiteResult(name, [new TestGroupResult(name, [new TestCaseResult(`${name}-test`, 'success', time)])])
|
||||
}
|
||||
|
|
|
|||
17
action.yml
17
action.yml
|
|
@ -46,6 +46,13 @@ inputs:
|
|||
- none
|
||||
required: false
|
||||
default: 'all'
|
||||
sort-suites:
|
||||
description: |
|
||||
Sort order for test suites. Supported options:
|
||||
- name: Sort alphabetically by name (default)
|
||||
- time-desc: Sort by execution time, slowest first
|
||||
required: false
|
||||
default: 'name'
|
||||
list-tests:
|
||||
description: |
|
||||
Limits which test cases are listed. Supported options:
|
||||
|
|
@ -54,14 +61,6 @@ inputs:
|
|||
- none
|
||||
required: false
|
||||
default: 'all'
|
||||
list-files:
|
||||
description: |
|
||||
Limits which test result files are listed. Supported options:
|
||||
- all
|
||||
- failed
|
||||
- none
|
||||
required: false
|
||||
default: 'all'
|
||||
max-annotations:
|
||||
description: |
|
||||
Limits number of created annotations with error message and stack trace captured during test execution.
|
||||
|
|
@ -130,8 +129,6 @@ outputs:
|
|||
description: Check run URL
|
||||
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:
|
||||
description: Random prefix added to generated report anchor slugs for this action run
|
||||
runs:
|
||||
|
|
|
|||
95
dist/index.js
generated
vendored
95
dist/index.js
generated
vendored
|
|
@ -16015,7 +16015,7 @@ module.exports.fetch = async function fetch (init, options = undefined) {
|
|||
}
|
||||
module.exports.Headers = __nccwpck_require__(660).Headers
|
||||
module.exports.Response = __nccwpck_require__(9051).Response
|
||||
module.exports.Request = __nccwpck_require__(9967).Request
|
||||
module.exports.Request = __nccwpck_require__(2348).Request
|
||||
module.exports.FormData = __nccwpck_require__(5910).FormData
|
||||
module.exports.File = globalThis.File ?? (__nccwpck_require__(4573).File)
|
||||
module.exports.FileReader = __nccwpck_require__(8355).FileReader
|
||||
|
|
@ -27430,7 +27430,7 @@ const { urlEquals, getFieldValues } = __nccwpck_require__(6798)
|
|||
const { kEnumerableProperty, isDisturbed } = __nccwpck_require__(3440)
|
||||
const { webidl } = __nccwpck_require__(5893)
|
||||
const { Response, cloneResponse, fromInnerResponse } = __nccwpck_require__(9051)
|
||||
const { Request, fromInnerRequest } = __nccwpck_require__(9967)
|
||||
const { Request, fromInnerRequest } = __nccwpck_require__(2348)
|
||||
const { kState } = __nccwpck_require__(3627)
|
||||
const { fetching } = __nccwpck_require__(4398)
|
||||
const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = __nccwpck_require__(3168)
|
||||
|
|
@ -29744,7 +29744,7 @@ module.exports = {
|
|||
|
||||
const { pipeline } = __nccwpck_require__(7075)
|
||||
const { fetching } = __nccwpck_require__(4398)
|
||||
const { makeRequest } = __nccwpck_require__(9967)
|
||||
const { makeRequest } = __nccwpck_require__(2348)
|
||||
const { webidl } = __nccwpck_require__(5893)
|
||||
const { EventSourceStream } = __nccwpck_require__(4031)
|
||||
const { parseMIMEType } = __nccwpck_require__(1900)
|
||||
|
|
@ -33368,7 +33368,7 @@ const {
|
|||
fromInnerResponse
|
||||
} = __nccwpck_require__(9051)
|
||||
const { HeadersList } = __nccwpck_require__(660)
|
||||
const { Request, cloneRequest } = __nccwpck_require__(9967)
|
||||
const { Request, cloneRequest } = __nccwpck_require__(2348)
|
||||
const zlib = __nccwpck_require__(8522)
|
||||
const {
|
||||
bytesMatch,
|
||||
|
|
@ -35632,7 +35632,7 @@ module.exports = {
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ 9967:
|
||||
/***/ 2348:
|
||||
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
||||
|
||||
/* globals AbortController */
|
||||
|
|
@ -40814,7 +40814,7 @@ const {
|
|||
const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions } = __nccwpck_require__(8625)
|
||||
const { channels } = __nccwpck_require__(2414)
|
||||
const { CloseEvent } = __nccwpck_require__(5188)
|
||||
const { makeRequest } = __nccwpck_require__(9967)
|
||||
const { makeRequest } = __nccwpck_require__(2348)
|
||||
const { fetching } = __nccwpck_require__(4398)
|
||||
const { Headers, getHeadersList } = __nccwpck_require__(660)
|
||||
const { getDecodeSplit } = __nccwpck_require__(3168)
|
||||
|
|
@ -56445,12 +56445,6 @@ function getOctokit(token, options, ...additionalPlugins) {
|
|||
//# sourceMappingURL=github.js.map
|
||||
// EXTERNAL MODULE: external "node:crypto"
|
||||
var external_node_crypto_ = __nccwpck_require__(7598);
|
||||
;// CONCATENATED MODULE: external "node:fs"
|
||||
const external_node_fs_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:fs");
|
||||
;// CONCATENATED MODULE: external "node:os"
|
||||
const external_node_os_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:os");
|
||||
;// CONCATENATED MODULE: external "node:path"
|
||||
const external_node_path_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:path");
|
||||
// EXTERNAL MODULE: ./node_modules/adm-zip/adm-zip.js
|
||||
var adm_zip = __nccwpck_require__(1316);
|
||||
// EXTERNAL MODULE: ./node_modules/picomatch/index.js
|
||||
|
|
@ -56960,7 +56954,7 @@ const DEFAULT_OPTIONS = {
|
|||
listSuites: 'all',
|
||||
listTests: 'all',
|
||||
slugPrefix: '',
|
||||
listFiles: 'all',
|
||||
sortSuites: 'name',
|
||||
baseUrl: '',
|
||||
onlySummary: false,
|
||||
useActionsSummary: true,
|
||||
|
|
@ -56969,7 +56963,7 @@ const DEFAULT_OPTIONS = {
|
|||
collapsed: 'auto'
|
||||
};
|
||||
function getReport(results, options = DEFAULT_OPTIONS, shortSummary = '') {
|
||||
applySort(results);
|
||||
applySort(results, options);
|
||||
const opts = { ...options };
|
||||
let lines = renderReport(results, opts, shortSummary);
|
||||
let report = lines.join('\n');
|
||||
|
|
@ -57017,10 +57011,15 @@ function trimReport(lines, options) {
|
|||
reportLines.push(errorMsg);
|
||||
return reportLines.join('\n');
|
||||
}
|
||||
function applySort(results) {
|
||||
function applySort(results, options) {
|
||||
results.sort((a, b) => a.path.localeCompare(b.path, DEFAULT_LOCALE));
|
||||
for (const res of results) {
|
||||
res.suites.sort((a, b) => a.name.localeCompare(b.name, DEFAULT_LOCALE));
|
||||
if (options.sortSuites === 'time-desc') {
|
||||
res.suites.sort((a, b) => b.time - a.time);
|
||||
}
|
||||
else {
|
||||
res.suites.sort((a, b) => a.name.localeCompare(b.name, DEFAULT_LOCALE));
|
||||
}
|
||||
}
|
||||
}
|
||||
function getByteLength(text) {
|
||||
|
|
@ -57081,31 +57080,25 @@ function getTestRunsReport(testRuns, options) {
|
|||
sections.push(`<details><summary>Expand for details</summary>`);
|
||||
sections.push(` `);
|
||||
}
|
||||
// Filter test runs based on list-files option
|
||||
const filteredTestRuns = options.listFiles === 'failed'
|
||||
? testRuns.filter(tr => tr.result === 'failed')
|
||||
: options.listFiles === 'none'
|
||||
? []
|
||||
: testRuns;
|
||||
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 = markdown_utils_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) {
|
||||
if (testRuns.length > 0 || options.onlySummary) {
|
||||
const tableData = testRuns
|
||||
.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 = markdown_utils_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 resultsTable = table(['Report', 'Passed', 'Failed', 'Skipped', 'Time'], [Align.Left, Align.Right, Align.Right, Align.Right, Align.Right], ...tableData);
|
||||
sections.push(resultsTable);
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (shouldCollapse) {
|
||||
|
|
@ -58938,9 +58931,6 @@ class NetteTesterJunitParser {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -58964,8 +58954,8 @@ class TestReporter {
|
|||
pathReplaceBackslashes = getInput('path-replace-backslashes', { required: false }) === 'true';
|
||||
reporter = getInput('reporter', { required: true });
|
||||
listSuites = getInput('list-suites', { required: true });
|
||||
sortSuites = getInput('sort-suites', { required: false });
|
||||
listTests = getInput('list-tests', { required: true });
|
||||
listFiles = getInput('list-files', { required: true });
|
||||
maxAnnotations = parseInt(getInput('max-annotations', { required: true }));
|
||||
failOnError = getInput('fail-on-error', { required: true }) === 'true';
|
||||
failOnEmpty = getInput('fail-on-empty', { required: true }) === 'true';
|
||||
|
|
@ -58985,12 +58975,12 @@ class TestReporter {
|
|||
setFailed(`Input parameter 'list-suites' has invalid value`);
|
||||
return;
|
||||
}
|
||||
if (this.listTests !== 'all' && this.listTests !== 'failed' && this.listTests !== 'none') {
|
||||
setFailed(`Input parameter 'list-tests' has invalid value`);
|
||||
if (this.sortSuites !== 'name' && this.sortSuites !== 'time-desc') {
|
||||
setFailed(`Input parameter 'sort-suites' has invalid value`);
|
||||
return;
|
||||
}
|
||||
if (this.listFiles !== 'all' && this.listFiles !== 'failed' && this.listFiles !== 'none') {
|
||||
setFailed(`Input parameter 'list-files' has invalid value`);
|
||||
if (this.listTests !== 'all' && this.listTests !== 'failed' && this.listTests !== 'none') {
|
||||
setFailed(`Input parameter 'list-tests' has invalid value`);
|
||||
return;
|
||||
}
|
||||
if (this.collapsed !== 'auto' && this.collapsed !== 'always' && this.collapsed !== 'never') {
|
||||
|
|
@ -59077,7 +59067,7 @@ class TestReporter {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
const { listSuites, listTests, slugPrefix, listFiles, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed } = this;
|
||||
const { listSuites, sortSuites, listTests, slugPrefix, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed } = this;
|
||||
const passed = results.reduce((sum, tr) => sum + tr.passed, 0);
|
||||
const failed = results.reduce((sum, tr) => sum + tr.failed, 0);
|
||||
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0);
|
||||
|
|
@ -59086,9 +59076,9 @@ class TestReporter {
|
|||
if (this.useActionsSummary) {
|
||||
const summary = getReport(results, {
|
||||
listSuites,
|
||||
sortSuites,
|
||||
listTests,
|
||||
slugPrefix,
|
||||
listFiles,
|
||||
baseUrl,
|
||||
onlySummary,
|
||||
useActionsSummary,
|
||||
|
|
@ -59098,7 +59088,6 @@ class TestReporter {
|
|||
}, shortSummary);
|
||||
info('Summary content:');
|
||||
info(summary);
|
||||
this.writeSummaryFile(summary);
|
||||
await summary_summary.addRaw(summary).write();
|
||||
}
|
||||
else {
|
||||
|
|
@ -59117,9 +59106,9 @@ class TestReporter {
|
|||
baseUrl = createResp.data.html_url;
|
||||
const summary = getReport(results, {
|
||||
listSuites,
|
||||
sortSuites,
|
||||
listTests,
|
||||
slugPrefix,
|
||||
listFiles,
|
||||
baseUrl,
|
||||
onlySummary,
|
||||
useActionsSummary,
|
||||
|
|
@ -59129,7 +59118,6 @@ class TestReporter {
|
|||
});
|
||||
info('Creating annotations');
|
||||
const annotations = getAnnotations(results, this.maxAnnotations);
|
||||
this.writeSummaryFile(summary);
|
||||
const isFailed = this.failOnError && results.some(tr => tr.result === 'failed');
|
||||
const conclusion = isFailed ? 'failure' : 'success';
|
||||
info(`Updating check run conclusion (${conclusion}) and output`);
|
||||
|
|
@ -59152,13 +59140,6 @@ class TestReporter {
|
|||
}
|
||||
return results;
|
||||
}
|
||||
writeSummaryFile(summary) {
|
||||
const dir = process.env.RUNNER_TEMP || (0,external_node_os_namespaceObject.tmpdir)();
|
||||
const file = (0,external_node_path_namespaceObject.join)(dir, `test-reporter-summary-${(0,external_node_crypto_.randomBytes)(8).toString('hex')}.md`);
|
||||
(0,external_node_fs_namespaceObject.writeFileSync)(file, summary);
|
||||
info(`Summary written to ${file}`);
|
||||
setOutput('summary_file', file);
|
||||
}
|
||||
getParser(reporter, options) {
|
||||
switch (reporter) {
|
||||
case 'dart-json':
|
||||
|
|
|
|||
24
package-lock.json
generated
24
package-lock.json
generated
|
|
@ -35,7 +35,7 @@
|
|||
"eslint-plugin-jest": "^29.15.2",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"jest": "^30.3.0",
|
||||
"jest-junit": "^17.0.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"prettier": "^3.8.3",
|
||||
"ts-jest": "^29.4.9",
|
||||
|
|
@ -6110,19 +6110,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/jest-junit": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-17.0.0.tgz",
|
||||
"integrity": "sha512-RYWCkq4j59gUXj5DsgbIE7xFBZzu1gtibPhyjSjMmGaOTLnqlXhg7x9zuGCwgbCuMAyoyvk0Mi8wSrRR5uOeLA==",
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
|
||||
"integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"uuid": "^14.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-leak-detector": {
|
||||
|
|
@ -8785,17 +8785,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
|
||||
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist-node/bin/uuid"
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
"eslint-plugin-jest": "^29.15.2",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"jest": "^30.3.0",
|
||||
"jest-junit": "^17.0.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"prettier": "^3.8.3",
|
||||
"ts-jest": "^29.4.9",
|
||||
|
|
|
|||
29
src/main.ts
29
src/main.ts
|
|
@ -2,9 +2,6 @@ import * as core from '@actions/core'
|
|||
import * as github from '@actions/github'
|
||||
import {GitHub} from '@actions/github/lib/utils'
|
||||
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 {LocalFileProvider} from './input-providers/local-file-provider.js'
|
||||
|
|
@ -46,8 +43,8 @@ class TestReporter {
|
|||
readonly pathReplaceBackslashes = core.getInput('path-replace-backslashes', {required: false}) === 'true'
|
||||
readonly reporter = core.getInput('reporter', {required: true})
|
||||
readonly listSuites = core.getInput('list-suites', {required: true}) as 'all' | 'failed' | 'none'
|
||||
readonly sortSuites = core.getInput('sort-suites', {required: false}) as 'name' | 'time-desc'
|
||||
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 failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
||||
readonly failOnEmpty = core.getInput('fail-on-empty', {required: true}) === 'true'
|
||||
|
|
@ -70,13 +67,13 @@ class TestReporter {
|
|||
return
|
||||
}
|
||||
|
||||
if (this.listTests !== 'all' && this.listTests !== 'failed' && this.listTests !== 'none') {
|
||||
core.setFailed(`Input parameter 'list-tests' has invalid value`)
|
||||
if (this.sortSuites !== 'name' && this.sortSuites !== 'time-desc') {
|
||||
core.setFailed(`Input parameter 'sort-suites' has invalid value`)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.listFiles !== 'all' && this.listFiles !== 'failed' && this.listFiles !== 'none') {
|
||||
core.setFailed(`Input parameter 'list-files' has invalid value`)
|
||||
if (this.listTests !== 'all' && this.listTests !== 'failed' && this.listTests !== 'none') {
|
||||
core.setFailed(`Input parameter 'list-tests' has invalid value`)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -188,9 +185,9 @@ class TestReporter {
|
|||
|
||||
const {
|
||||
listSuites,
|
||||
sortSuites,
|
||||
listTests,
|
||||
slugPrefix,
|
||||
listFiles,
|
||||
onlySummary,
|
||||
useActionsSummary,
|
||||
badgeTitle,
|
||||
|
|
@ -209,9 +206,9 @@ class TestReporter {
|
|||
results,
|
||||
{
|
||||
listSuites,
|
||||
sortSuites,
|
||||
listTests,
|
||||
slugPrefix,
|
||||
listFiles,
|
||||
baseUrl,
|
||||
onlySummary,
|
||||
useActionsSummary,
|
||||
|
|
@ -224,7 +221,6 @@ class TestReporter {
|
|||
|
||||
core.info('Summary content:')
|
||||
core.info(summary)
|
||||
this.writeSummaryFile(summary)
|
||||
await core.summary.addRaw(summary).write()
|
||||
} else {
|
||||
core.info(`Creating check run ${name}`)
|
||||
|
|
@ -243,9 +239,9 @@ class TestReporter {
|
|||
baseUrl = createResp.data.html_url as string
|
||||
const summary = getReport(results, {
|
||||
listSuites,
|
||||
sortSuites,
|
||||
listTests,
|
||||
slugPrefix,
|
||||
listFiles,
|
||||
baseUrl,
|
||||
onlySummary,
|
||||
useActionsSummary,
|
||||
|
|
@ -256,7 +252,6 @@ class TestReporter {
|
|||
|
||||
core.info('Creating annotations')
|
||||
const annotations = getAnnotations(results, this.maxAnnotations)
|
||||
this.writeSummaryFile(summary)
|
||||
|
||||
const isFailed = this.failOnError && results.some(tr => tr.result === 'failed')
|
||||
const conclusion = isFailed ? 'failure' : 'success'
|
||||
|
|
@ -283,14 +278,6 @@ class TestReporter {
|
|||
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 {
|
||||
switch (reporter) {
|
||||
case 'dart-json':
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export interface ReportOptions {
|
|||
listSuites: 'all' | 'failed' | 'none'
|
||||
listTests: 'all' | 'failed' | 'none'
|
||||
slugPrefix: string
|
||||
listFiles: 'all' | 'failed' | 'none'
|
||||
sortSuites: 'name' | 'time-desc'
|
||||
baseUrl: string
|
||||
onlySummary: boolean
|
||||
useActionsSummary: boolean
|
||||
|
|
@ -25,7 +25,7 @@ export const DEFAULT_OPTIONS: ReportOptions = {
|
|||
listSuites: 'all',
|
||||
listTests: 'all',
|
||||
slugPrefix: '',
|
||||
listFiles: 'all',
|
||||
sortSuites: 'name',
|
||||
baseUrl: '',
|
||||
onlySummary: false,
|
||||
useActionsSummary: true,
|
||||
|
|
@ -39,7 +39,7 @@ export function getReport(
|
|||
options: ReportOptions = DEFAULT_OPTIONS,
|
||||
shortSummary = ''
|
||||
): string {
|
||||
applySort(results)
|
||||
applySort(results, options)
|
||||
|
||||
const opts = {...options}
|
||||
let lines = renderReport(results, opts, shortSummary)
|
||||
|
|
@ -98,10 +98,14 @@ function trimReport(lines: string[], options: ReportOptions): string {
|
|||
return reportLines.join('\n')
|
||||
}
|
||||
|
||||
function applySort(results: TestRunResult[]): void {
|
||||
function applySort(results: TestRunResult[], options: ReportOptions): void {
|
||||
results.sort((a, b) => a.path.localeCompare(b.path, DEFAULT_LOCALE))
|
||||
for (const res of results) {
|
||||
res.suites.sort((a, b) => a.name.localeCompare(b.name, DEFAULT_LOCALE))
|
||||
if (options.sortSuites === 'time-desc') {
|
||||
res.suites.sort((a, b) => b.time - a.time)
|
||||
} else {
|
||||
res.suites.sort((a, b) => a.name.localeCompare(b.name, DEFAULT_LOCALE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,29 +179,21 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
|
|||
sections.push(` `)
|
||||
}
|
||||
|
||||
// Filter test runs based on list-files option
|
||||
const filteredTestRuns =
|
||||
options.listFiles === 'failed'
|
||||
? testRuns.filter(tr => tr.result === 'failed')
|
||||
: options.listFiles === 'none'
|
||||
? []
|
||||
: testRuns
|
||||
if (testRuns.length > 0 || options.onlySummary) {
|
||||
const tableData = testRuns
|
||||
.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]
|
||||
})
|
||||
|
||||
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(
|
||||
['Report', 'Passed', 'Failed', 'Skipped', 'Time'],
|
||||
[Align.Left, Align.Right, Align.Right, Align.Right, Align.Right],
|
||||
|
|
@ -207,7 +203,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue