diff --git a/CHANGELOG.md b/CHANGELOG.md index 7835fed..20a8c2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index f6624d8..38b2285 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/__tests__/report/get-report.test.ts b/__tests__/report/get-report.test.ts index 560f849..2f60d03 100644 --- a/__tests__/report/get-report.test.ts +++ b/__tests__/report/get-report.test.ts @@ -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)])]) +} diff --git a/action.yml b/action.yml index fb11227..7b7ddee 100644 --- a/action.yml +++ b/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: diff --git a/dist/index.js b/dist/index.js index 8f0b42f..4d3a526 100644 --- a/dist/index.js +++ b/dist/index.js @@ -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(`
Expand for details`); 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': diff --git a/package-lock.json b/package-lock.json index e579a35..726b89e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index c4e539f..3d02d9e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/main.ts b/src/main.ts index 62da4bc..330eaff 100644 --- a/src/main.ts +++ b/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': diff --git a/src/report/get-report.ts b/src/report/get-report.ts index cac5596..8fd30b5 100644 --- a/src/report/get-report.ts +++ b/src/report/get-report.ts @@ -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) }