1
0
Fork 0
mirror of https://github.com/dorny/test-reporter.git synced 2026-03-21 23:52:12 +01:00

feat: added a slug-prefix parameter for link anchors

Motivation: when using a matrix job, or more than one kind of tests in a same workflow, we can end up with multiple summaries at the same time. This will lead to multiple anchors and html id that will no longer be unique.
This prefix option allow to disambiguate those anchors ; and keep them functional.
This commit is contained in:
Thomas Durand 2026-03-05 01:52:45 +01:00
parent 3d76b34a45
commit 7f0723a953
No known key found for this signature in database
GPG key ID: 68F13AD8CF49EEEB
7 changed files with 49 additions and 4 deletions

View file

@ -163,6 +163,10 @@ jobs:
# https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/
use-actions-summary: 'true'
# Prefix used when generating report anchor slugs.
# Useful to avoid collisions when multiple reports are rendered together.
slug-prefix: ''
# Optionally specify a title (Heading level 1) for the report. Leading and trailing whitespace are ignored.
# This is useful for separating your test report from other sections in the build summary.
# If omitted or set to whitespace/empty, no title will be printed.

View file

@ -0,0 +1,28 @@
import {DEFAULT_OPTIONS} from '../../src/report/get-report.js'
import {slug} from '../../src/utils/slugger.js'
describe('slugger', () => {
it('adds prefix from report options to generated slug', () => {
const result = slug('r0s1', {
...DEFAULT_OPTIONS,
slugPrefix: 'prefix-'
})
expect(result).toEqual({
id: 'user-content-prefix-r0s1',
link: '#user-content-prefix-r0s1'
})
})
it('sanitizes custom prefix using existing slug normalization', () => {
const result = slug('r0', {
...DEFAULT_OPTIONS,
slugPrefix: ' my /custom_prefix?.'
})
expect(result).toEqual({
id: 'user-content-my-customprefix-r0',
link: '#user-content-my-customprefix-r0'
})
})
})

View file

@ -88,6 +88,10 @@ inputs:
https://github.com/orgs/github/teams/engineering/discussions/871
default: 'true'
required: false
slug-prefix:
description: Prefix used when generating report anchor slugs
required: false
default: ''
badge-title:
description: Customize badge title
required: false

8
dist/index.js generated vendored
View file

@ -55869,7 +55869,7 @@ function getExceptionSource(stackTrace, trackedFiles, getRelativePath) {
;// CONCATENATED MODULE: ./lib/utils/slugger.js
function slug(name, options) {
const slugId = name
const slugId = `${options.slugPrefix}${name}`
.trim()
.replace(/_/g, '')
.replace(/[./\\]/g, '-')
@ -55891,6 +55891,7 @@ const MAX_ACTIONS_SUMMARY_LENGTH = 1048576;
const DEFAULT_OPTIONS = {
listSuites: 'all',
listTests: 'all',
slugPrefix: '',
baseUrl: '',
onlySummary: false,
useActionsSummary: true,
@ -57890,6 +57891,7 @@ class TestReporter {
workDirInput = getInput('working-directory', { required: false });
onlySummary = getInput('only-summary', { required: false }) === 'true';
useActionsSummary = getInput('use-actions-summary', { required: false }) === 'true';
slugPrefix = getInput('slug-prefix', { required: false });
badgeTitle = getInput('badge-title', { required: false });
reportTitle = getInput('report-title', { required: false });
collapsed = getInput('collapsed', { required: false });
@ -57989,7 +57991,7 @@ class TestReporter {
throw error;
}
}
const { listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed } = this;
const { listSuites, 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);
@ -57999,6 +58001,7 @@ class TestReporter {
const summary = getReport(results, {
listSuites,
listTests,
slugPrefix,
baseUrl,
onlySummary,
useActionsSummary,
@ -58027,6 +58030,7 @@ class TestReporter {
const summary = getReport(results, {
listSuites,
listTests,
slugPrefix,
baseUrl,
onlySummary,
useActionsSummary,

View file

@ -49,6 +49,7 @@ class TestReporter {
readonly workDirInput = core.getInput('working-directory', {required: false})
readonly onlySummary = core.getInput('only-summary', {required: false}) === 'true'
readonly useActionsSummary = core.getInput('use-actions-summary', {required: false}) === 'true'
readonly slugPrefix = core.getInput('slug-prefix', {required: false})
readonly badgeTitle = core.getInput('badge-title', {required: false})
readonly reportTitle = core.getInput('report-title', {required: false})
readonly collapsed = core.getInput('collapsed', {required: false}) as 'auto' | 'always' | 'never'
@ -174,7 +175,7 @@ class TestReporter {
}
}
const {listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed} = this
const {listSuites, 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)
@ -188,6 +189,7 @@ class TestReporter {
{
listSuites,
listTests,
slugPrefix,
baseUrl,
onlySummary,
useActionsSummary,
@ -219,6 +221,7 @@ class TestReporter {
const summary = getReport(results, {
listSuites,
listTests,
slugPrefix,
baseUrl,
onlySummary,
useActionsSummary,

View file

@ -11,6 +11,7 @@ const MAX_ACTIONS_SUMMARY_LENGTH = 1048576
export interface ReportOptions {
listSuites: 'all' | 'failed' | 'none'
listTests: 'all' | 'failed' | 'none'
slugPrefix: string
baseUrl: string
onlySummary: boolean
useActionsSummary: boolean
@ -22,6 +23,7 @@ export interface ReportOptions {
export const DEFAULT_OPTIONS: ReportOptions = {
listSuites: 'all',
listTests: 'all',
slugPrefix: '',
baseUrl: '',
onlySummary: false,
useActionsSummary: true,

View file

@ -4,7 +4,7 @@
import {ReportOptions} from '../report/get-report.js'
export function slug(name: string, options: ReportOptions): {id: string; link: string} {
const slugId = name
const slugId = `${options.slugPrefix}${name}`
.trim()
.replace(/_/g, '')
.replace(/[./\\]/g, '-')