diff --git a/__tests__/report/get-report.test.ts b/__tests__/report/get-report.test.ts index 30e1e89..2f60d03 100644 --- a/__tests__/report/get-report.test.ts +++ b/__tests__/report/get-report.test.ts @@ -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('URI encoding with special characters', () => { @@ -131,3 +132,28 @@ describe('getBadge', () => { }) }) }) + +describe('getReport', () => { + it('sorts suites by descending time when configured', () => { + const results = [ + new TestRunResult('report.xml', [ + createSuite('unit', 10), + createSuite('integration', 30), + createSuite('smoke', 20) + ]) + ] + + const report = getReport(results, { + ...DEFAULT_OPTIONS, + sortSuites: 'time-desc', + listTests: 'none' + }) + + 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 be606d4..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: diff --git a/dist/index.js b/dist/index.js index c8865b0..4d3a526 100644 --- a/dist/index.js +++ b/dist/index.js @@ -56954,6 +56954,7 @@ const DEFAULT_OPTIONS = { listSuites: 'all', listTests: 'all', slugPrefix: '', + sortSuites: 'name', baseUrl: '', onlySummary: false, useActionsSummary: true, @@ -56962,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'); @@ -57010,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) { @@ -58948,6 +58954,7 @@ 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 }); maxAnnotations = parseInt(getInput('max-annotations', { required: true })); failOnError = getInput('fail-on-error', { required: true }) === 'true'; @@ -58968,6 +58975,10 @@ class TestReporter { setFailed(`Input parameter 'list-suites' has invalid value`); return; } + if (this.sortSuites !== 'name' && this.sortSuites !== 'time-desc') { + setFailed(`Input parameter 'sort-suites' has invalid value`); + return; + } if (this.listTests !== 'all' && this.listTests !== 'failed' && this.listTests !== 'none') { setFailed(`Input parameter 'list-tests' has invalid value`); return; @@ -59056,7 +59067,7 @@ class TestReporter { throw error; } } - const { listSuites, listTests, slugPrefix, 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); @@ -59065,6 +59076,7 @@ class TestReporter { if (this.useActionsSummary) { const summary = getReport(results, { listSuites, + sortSuites, listTests, slugPrefix, baseUrl, @@ -59094,6 +59106,7 @@ class TestReporter { baseUrl = createResp.data.html_url; const summary = getReport(results, { listSuites, + sortSuites, listTests, slugPrefix, baseUrl, diff --git a/src/main.ts b/src/main.ts index fc6ea16..330eaff 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,6 +43,7 @@ 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 maxAnnotations = parseInt(core.getInput('max-annotations', {required: true})) readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true' @@ -66,6 +67,11 @@ class TestReporter { return } + if (this.sortSuites !== 'name' && this.sortSuites !== 'time-desc') { + core.setFailed(`Input parameter 'sort-suites' has invalid value`) + return + } + if (this.listTests !== 'all' && this.listTests !== 'failed' && this.listTests !== 'none') { core.setFailed(`Input parameter 'list-tests' has invalid value`) return @@ -177,7 +183,17 @@ class TestReporter { } } - const {listSuites, listTests, slugPrefix, 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) @@ -190,6 +206,7 @@ class TestReporter { results, { listSuites, + sortSuites, listTests, slugPrefix, baseUrl, @@ -222,6 +239,7 @@ class TestReporter { baseUrl = createResp.data.html_url as string const summary = getReport(results, { listSuites, + sortSuites, listTests, slugPrefix, baseUrl, diff --git a/src/report/get-report.ts b/src/report/get-report.ts index 325e28c..8fd30b5 100644 --- a/src/report/get-report.ts +++ b/src/report/get-report.ts @@ -12,6 +12,7 @@ export interface ReportOptions { listSuites: 'all' | 'failed' | 'none' listTests: 'all' | 'failed' | 'none' slugPrefix: string + sortSuites: 'name' | 'time-desc' baseUrl: string onlySummary: boolean useActionsSummary: boolean @@ -24,6 +25,7 @@ export const DEFAULT_OPTIONS: ReportOptions = { listSuites: 'all', listTests: 'all', slugPrefix: '', + sortSuites: 'name', baseUrl: '', onlySummary: false, useActionsSummary: true, @@ -37,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) @@ -96,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)) + } } }