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

@ -162,6 +162,10 @@ jobs:
# Allows you to generate reports for Actions Summary # Allows you to generate reports for Actions Summary
# https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/ # https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/
use-actions-summary: 'true' 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. # 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. # This is useful for separating your test report from other sections in the build summary.

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 https://github.com/orgs/github/teams/engineering/discussions/871
default: 'true' default: 'true'
required: false required: false
slug-prefix:
description: Prefix used when generating report anchor slugs
required: false
default: ''
badge-title: badge-title:
description: Customize badge title description: Customize badge title
required: false 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 ;// CONCATENATED MODULE: ./lib/utils/slugger.js
function slug(name, options) { function slug(name, options) {
const slugId = name const slugId = `${options.slugPrefix}${name}`
.trim() .trim()
.replace(/_/g, '') .replace(/_/g, '')
.replace(/[./\\]/g, '-') .replace(/[./\\]/g, '-')
@ -55891,6 +55891,7 @@ const MAX_ACTIONS_SUMMARY_LENGTH = 1048576;
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
listSuites: 'all', listSuites: 'all',
listTests: 'all', listTests: 'all',
slugPrefix: '',
baseUrl: '', baseUrl: '',
onlySummary: false, onlySummary: false,
useActionsSummary: true, useActionsSummary: true,
@ -57890,6 +57891,7 @@ class TestReporter {
workDirInput = getInput('working-directory', { required: false }); workDirInput = getInput('working-directory', { required: false });
onlySummary = getInput('only-summary', { required: false }) === 'true'; onlySummary = getInput('only-summary', { required: false }) === 'true';
useActionsSummary = getInput('use-actions-summary', { required: false }) === 'true'; useActionsSummary = getInput('use-actions-summary', { required: false }) === 'true';
slugPrefix = getInput('slug-prefix', { required: false });
badgeTitle = getInput('badge-title', { required: false }); badgeTitle = getInput('badge-title', { required: false });
reportTitle = getInput('report-title', { required: false }); reportTitle = getInput('report-title', { required: false });
collapsed = getInput('collapsed', { required: false }); collapsed = getInput('collapsed', { required: false });
@ -57989,7 +57991,7 @@ class TestReporter {
throw error; 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 passed = results.reduce((sum, tr) => sum + tr.passed, 0);
const failed = results.reduce((sum, tr) => sum + tr.failed, 0); const failed = results.reduce((sum, tr) => sum + tr.failed, 0);
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0); const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0);
@ -57999,6 +58001,7 @@ class TestReporter {
const summary = getReport(results, { const summary = getReport(results, {
listSuites, listSuites,
listTests, listTests,
slugPrefix,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
@ -58027,6 +58030,7 @@ class TestReporter {
const summary = getReport(results, { const summary = getReport(results, {
listSuites, listSuites,
listTests, listTests,
slugPrefix,
baseUrl, baseUrl,
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,

View file

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

View file

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

View file

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