Skip to content

Commit 6ef447b

Browse files
authored
ci: pool a11y tests until failure is detected (#833)
1 parent 3114183 commit 6ef447b

File tree

1 file changed

+66
-16
lines changed

1 file changed

+66
-16
lines changed

test/nuxt/a11y.spec.ts

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ declare const axe: {
1313
// Track mounted containers for cleanup
1414
const mountedContainers: HTMLElement[] = []
1515

16+
const axeRunOptions: RunOptions = {
17+
// Only compute violations to reduce work per run
18+
resultTypes: ['violations'],
19+
// Disable rules that don't apply to isolated component testing
20+
rules: {
21+
// These rules check page-level concerns that don't apply to isolated components
22+
'landmark-one-main': { enabled: false },
23+
'region': { enabled: false },
24+
'page-has-heading-one': { enabled: false },
25+
// Duplicate landmarks are expected when testing multiple header/footer components
26+
'landmark-no-duplicate-banner': { enabled: false },
27+
'landmark-no-duplicate-contentinfo': { enabled: false },
28+
'landmark-no-duplicate-main': { enabled: false },
29+
},
30+
}
31+
1632
/**
1733
* Run axe accessibility audit on a mounted component.
1834
* Mounts the component in an isolated container to avoid cross-test pollution.
@@ -29,19 +45,7 @@ async function runAxe(wrapper: VueWrapper): Promise<AxeResults> {
2945
container.appendChild(el)
3046

3147
// Run axe only on the isolated container
32-
return axe.run(container, {
33-
// Disable rules that don't apply to isolated component testing
34-
rules: {
35-
// These rules check page-level concerns that don't apply to isolated components
36-
'landmark-one-main': { enabled: false },
37-
'region': { enabled: false },
38-
'page-has-heading-one': { enabled: false },
39-
// Duplicate landmarks are expected when testing multiple header/footer components
40-
'landmark-no-duplicate-banner': { enabled: false },
41-
'landmark-no-duplicate-contentinfo': { enabled: false },
42-
'landmark-no-duplicate-main': { enabled: false },
43-
},
44-
})
48+
return axe.run(container, axeRunOptions)
4549
}
4650

4751
// Clean up mounted containers after each test
@@ -2117,14 +2121,60 @@ describe('background theme accessibility', () => {
21172121
},
21182122
]
21192123

2124+
/**
2125+
* For performance, we pool axe runs for each theme combination, optimistically assuming no
2126+
* violations will occur. If violations are found in the pooled run, we re-run axe on individual
2127+
* components for precise results.
2128+
*/
2129+
const pooledResults = new Map<string, Promise<AxeResults>>()
2130+
2131+
function getPooledResults(colorMode: string, bgTheme: string) {
2132+
const key = `${colorMode}:${bgTheme}`
2133+
const cached = pooledResults.get(key)
2134+
if (cached) return cached
2135+
2136+
const promise = (async () => {
2137+
const wrappers = await Promise.all(components.map(({ mount }) => mount()))
2138+
const poolContainer = document.createElement('div')
2139+
poolContainer.id = `a11y-theme-pool-${colorMode}-${bgTheme}`
2140+
document.body.appendChild(poolContainer)
2141+
mountedContainers.push(poolContainer)
2142+
2143+
try {
2144+
for (const wrapper of wrappers) {
2145+
const el = wrapper.element.cloneNode(true) as HTMLElement
2146+
poolContainer.appendChild(el)
2147+
}
2148+
2149+
await nextTick()
2150+
return await axe.run(poolContainer, axeRunOptions)
2151+
} finally {
2152+
for (const wrapper of wrappers) {
2153+
wrapper.unmount()
2154+
}
2155+
}
2156+
})()
2157+
2158+
pooledResults.set(key, promise)
2159+
return promise
2160+
}
2161+
21202162
for (const { name, mount } of components) {
21212163
describe(`${name} colors`, () => {
21222164
for (const [colorMode, bgTheme] of pairs) {
21232165
it(`${colorMode}/${bgTheme}`, async () => {
21242166
applyTheme(colorMode, bgTheme)
2125-
const results = await runAxe(await mount())
2126-
await nextTick()
2127-
expect(results.violations).toEqual([])
2167+
2168+
const pooled = await getPooledResults(colorMode, bgTheme)
2169+
if (pooled.violations.length === 0) return
2170+
2171+
const wrapper = await mount()
2172+
try {
2173+
const results = await runAxe(wrapper)
2174+
expect(results.violations).toEqual([])
2175+
} finally {
2176+
wrapper.unmount()
2177+
}
21282178
})
21292179
}
21302180
})

0 commit comments

Comments
 (0)