From 0e1fe1690fc8277c287147114c8cfed4a5c6f4d4 Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Mon, 23 Feb 2026 16:12:01 +0100 Subject: [PATCH] feat: add sort-suites input to order suites by time descending Add a new 'sort-suites' input parameter that accepts 'name' (default, alphabetical) or 'time-desc' (slowest first). This allows consumers to surface the slowest test suites at the top of the report. Co-authored-by: Cursor --- action.yml | 7 +++++++ dist/index.js | 21 +++++++++++++++++---- src/main.ts | 20 +++++++++++++++++++- src/report/get-report.ts | 12 +++++++++--- 4 files changed, 52 insertions(+), 8 deletions(-) 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)) + } } }