Skip to content

Commit d830830

Browse files
committed
Classify Axe findings by conformance tier
1 parent dba0765 commit d830830

3 files changed

Lines changed: 49 additions & 1 deletion

File tree

.github/actions/find/src/findForUrl.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {ColorSchemePreference, Finding, ReducedMotionPreference, UrlConfig} from './types.d.js'
1+
import type {ColorSchemePreference, Finding, FindingCategory, ReducedMotionPreference, UrlConfig} from './types.d.js'
22
import {AxeBuilder} from '@axe-core/playwright'
33
import playwright from 'playwright'
44
import {AuthContext} from './AuthContext.js'
@@ -87,6 +87,7 @@ async function runAxeScan({
8787
for (const violation of rawFindings.violations) {
8888
await addFinding({
8989
scannerType: 'axe',
90+
category: categorizeAxeViolation(violation.tags),
9091
url,
9192
html: violation.nodes[0].html.replace(/'/g, '''),
9293
problemShort: violation.help.toLowerCase().replace(/'/g, '''),
@@ -98,3 +99,11 @@ async function runAxeScan({
9899
}
99100
}
100101
}
102+
103+
// Maps an Axe violation's tags to a conformance tier. Experimental is checked
104+
// first because some experimental rules also carry a wcag* tag.
105+
function categorizeAxeViolation(tags: string[]): FindingCategory {
106+
if (tags.includes('experimental')) return 'experimental'
107+
if (tags.includes('best-practice')) return 'best-practice'
108+
return 'wcag'
109+
}

.github/actions/find/src/types.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
export type FindingCategory = 'wcag' | 'best-practice' | 'experimental'
2+
13
export type Finding = {
24
scannerType: string
5+
category?: FindingCategory
36
url: string
47
html?: string
58
problemShort: string

.github/actions/find/tests/findForUrl.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,40 @@ describe('findForUrl', () => {
117117
expect(loadedPlugins[1].default).toHaveBeenCalledTimes(0)
118118
})
119119
})
120+
121+
describe('axe finding categorization', () => {
122+
function axeViolation(tags: string[]) {
123+
return {
124+
id: 'some-rule',
125+
help: 'Help',
126+
helpUrl: 'https://example.com',
127+
description: 'Description',
128+
tags,
129+
nodes: [{html: '<div></div>', failureSummary: 'summary'}],
130+
}
131+
}
132+
133+
async function categoryFor(tags: string[]) {
134+
clearAll()
135+
actionInput = JSON.stringify(['axe'])
136+
vi.mocked(AxeBuilder.prototype.analyze).mockResolvedValueOnce({
137+
violations: [axeViolation(tags)],
138+
} as unknown as axe.AxeResults)
139+
140+
const findings = await findForUrl('test.com')
141+
return findings[0].category
142+
}
143+
144+
it('categorizes a violation with only wcag tags as wcag', async () => {
145+
expect(await categoryFor(['wcag2a', 'wcag111'])).toBe('wcag')
146+
})
147+
148+
it('categorizes a violation with a best-practice tag as best-practice', async () => {
149+
expect(await categoryFor(['cat.semantics', 'best-practice'])).toBe('best-practice')
150+
})
151+
152+
it('categorizes a violation with an experimental tag as experimental, even alongside wcag tags', async () => {
153+
expect(await categoryFor(['wcag2a', 'experimental'])).toBe('experimental')
154+
})
155+
})
120156
})

0 commit comments

Comments
 (0)