Skip to content

Commit a00be0c

Browse files
committed
Add comprehensive axe accessibility test coverage
Fills test coverage gaps identified in PR #14125 review: - Dashboard console/json output modes (3 test cases) - Dashboard dark theme CSS custom property handling - HTML hover interaction and element highlighting - Negative tests for revealjs/dashboard (4 smoke-all tests) Updates hover interaction tests to use Playwright best practices: - Use .toHaveClass() instead of string interpolation - Use .first() to handle non-unique selectors (e.g., "span") All tests passing (75 Playwright, 9 smoke-all) across all browsers.
1 parent 0282b84 commit a00be0c

8 files changed

Lines changed: 275 additions & 3 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: "Sales Dashboard"
3+
format:
4+
dashboard:
5+
theme: darkly
6+
axe:
7+
output: document
8+
---
9+
10+
## Row {height=30%}
11+
12+
### Column
13+
14+
::: {.valuebox title="Total Revenue" color="primary"}
15+
$1.2M
16+
:::
17+
18+
### Column
19+
20+
::: {.valuebox title="Active Users" color="success"}
21+
8,432
22+
:::
23+
24+
### Column
25+
26+
::: {.valuebox title="Conversion Rate" color="warning"}
27+
3.2%
28+
:::
29+
30+
## Row {height=70%}
31+
32+
### Column {width=60%}
33+
34+
::: {.card title="Monthly Revenue"}
35+
This card shows [monthly revenue trends]{style="color: #333; background: #222;"} across all regions.
36+
37+
Additional analysis text to fill the card with content. The dashboard layout should fill the viewport.
38+
:::
39+
40+
### Column {width=40%}
41+
42+
::: {.card title="Top Products"}
43+
| Product | Sales |
44+
|---------|-------|
45+
| Widget A | $450K |
46+
| Widget B | $320K |
47+
| Widget C | $280K |
48+
| Widget D | $150K |
49+
:::
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: "Sales Dashboard"
3+
format: dashboard
4+
axe:
5+
output: console
6+
---
7+
8+
## Row {height=30%}
9+
10+
### Column
11+
12+
::: {.valuebox title="Total Revenue" color="primary"}
13+
$1.2M
14+
:::
15+
16+
### Column
17+
18+
::: {.valuebox title="Active Users" color="success"}
19+
8,432
20+
:::
21+
22+
### Column
23+
24+
::: {.valuebox title="Conversion Rate" color="warning"}
25+
3.2%
26+
:::
27+
28+
## Row {height=70%}
29+
30+
### Column {width=60%}
31+
32+
::: {.card title="Monthly Revenue"}
33+
This card shows [monthly revenue trends]{style="color: #eee; background: #fff;"} across all regions.
34+
35+
Additional analysis text to fill the card with content. The dashboard layout should fill the viewport.
36+
:::
37+
38+
### Column {width=40%}
39+
40+
::: {.card title="Top Products"}
41+
| Product | Sales |
42+
|---------|-------|
43+
| Widget A | $450K |
44+
| Widget B | $320K |
45+
| Widget C | $280K |
46+
| Widget D | $150K |
47+
:::
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: "Sales Dashboard"
3+
format: dashboard
4+
axe:
5+
output: json
6+
---
7+
8+
## Row {height=30%}
9+
10+
### Column
11+
12+
::: {.valuebox title="Total Revenue" color="primary"}
13+
$1.2M
14+
:::
15+
16+
### Column
17+
18+
::: {.valuebox title="Active Users" color="success"}
19+
8,432
20+
:::
21+
22+
### Column
23+
24+
::: {.valuebox title="Conversion Rate" color="warning"}
25+
3.2%
26+
:::
27+
28+
## Row {height=70%}
29+
30+
### Column {width=60%}
31+
32+
::: {.card title="Monthly Revenue"}
33+
This card shows [monthly revenue trends]{style="color: #eee; background: #fff;"} across all regions.
34+
35+
Additional analysis text to fill the card with content. The dashboard layout should fill the viewport.
36+
:::
37+
38+
### Column {width=40%}
39+
40+
::: {.card title="Top Products"}
41+
| Product | Sales |
42+
|---------|-------|
43+
| Widget A | $450K |
44+
| Widget B | $320K |
45+
| Widget C | $280K |
46+
| Widget D | $150K |
47+
:::
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
title: "No Axe - Dashboard"
3+
format: dashboard
4+
_quarto:
5+
tests:
6+
dashboard:
7+
ensureHtmlElements:
8+
- []
9+
- ['script[src*="axe-check"]', '#quarto-axe-checker-options']
10+
ensureFileRegexMatches:
11+
- []
12+
- ['axe-check\.js', 'quarto-axe-checker-options']
13+
---
14+
15+
## Row
16+
17+
### Column
18+
19+
::: {.card title="Test Card"}
20+
Render without axe config — no axe dependencies should be present.
21+
22+
This document verifies that axe is disabled by default for Dashboard format.
23+
:::
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: "No Axe - RevealJS"
3+
format: revealjs
4+
_quarto:
5+
tests:
6+
revealjs:
7+
ensureHtmlElements:
8+
- []
9+
- ['script[src*="axe-check"]', '#quarto-axe-checker-options']
10+
ensureFileRegexMatches:
11+
- []
12+
- ['axe-check\.js', 'quarto-axe-checker-options']
13+
---
14+
15+
## Slide 1
16+
17+
Render without axe config — no axe dependencies should be present.
18+
19+
## Slide 2
20+
21+
This document verifies that axe is disabled by default for RevealJS format.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title: "Axe False - Dashboard"
3+
format:
4+
dashboard:
5+
axe: false
6+
_quarto:
7+
tests:
8+
dashboard:
9+
ensureHtmlElements:
10+
- []
11+
- ['script[src*="axe-check"]', '#quarto-axe-checker-options']
12+
ensureFileRegexMatches:
13+
- []
14+
- ['axe-check\.js', 'quarto-axe-checker-options']
15+
---
16+
17+
## Row
18+
19+
### Column
20+
21+
::: {.card title="Test Card"}
22+
Render with axe: false — no axe dependencies should be present.
23+
24+
This document verifies that axe can be explicitly disabled for Dashboard format.
25+
:::
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
title: "Axe False - RevealJS"
3+
format:
4+
revealjs:
5+
axe: false
6+
_quarto:
7+
tests:
8+
revealjs:
9+
ensureHtmlElements:
10+
- []
11+
- ['script[src*="axe-check"]', '#quarto-axe-checker-options']
12+
ensureFileRegexMatches:
13+
- []
14+
- ['axe-check\.js', 'quarto-axe-checker-options']
15+
---
16+
17+
## Slide 1
18+
19+
Render with axe: false — no axe dependencies should be present.
20+
21+
## Slide 2
22+
23+
This document verifies that axe can be explicitly disabled for RevealJS format.

tests/integration/playwright/tests/axe-accessibility.spec.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ const testCases: AxeTestCase[] = [
6666
// Dashboard — axe-check.js loads as standalone module, falls back to document.body (#13781)
6767
{ format: 'dashboard', outputMode: 'document', url: '/dashboard/axe-accessibility.html',
6868
expectedViolation: 'color-contrast' },
69+
{ format: 'dashboard', outputMode: 'console', url: '/dashboard/axe-console.html',
70+
expectedViolation: 'color-contrast' },
71+
{ format: 'dashboard', outputMode: 'json', url: '/dashboard/axe-json.html',
72+
expectedViolation: 'color-contrast' },
73+
74+
// Dashboard dark theme — verifies CSS custom property bridge for theming
75+
{ format: 'dashboard-dark', outputMode: 'document', url: '/dashboard/axe-accessibility-dark.html',
76+
expectedViolation: 'color-contrast' },
6977

7078
// Dashboard with pages — multi-page dashboard with global sidebar
7179
{ format: 'dashboard-pages', outputMode: 'document', url: '/dashboard/axe-accessibility-pages.html',
@@ -272,12 +280,41 @@ test.describe('Dashboard axe — offcanvas interaction and highlight', () => {
272280
await target.hover();
273281

274282
// The corresponding element in the dashboard should get highlight class
275-
const highlighted = page.locator(`${selector}.quarto-axe-hover-highlight`);
276-
await expect(highlighted).toBeAttached({ timeout: 3000 });
283+
// Use .first() since selector may match multiple elements
284+
const element = page.locator(selector).first();
285+
await expect(element).toHaveClass(/quarto-axe-hover-highlight/, { timeout: 3000 });
286+
287+
// Move mouse away — highlight should be removed
288+
await page.mouse.move(0, 0);
289+
await expect(element).not.toHaveClass(/quarto-axe-hover-highlight/);
290+
});
291+
});
292+
293+
test.describe('HTML axe — hover interaction and highlight', () => {
294+
const htmlUrl = '/html/axe-accessibility.html';
295+
296+
test('hover highlights the corresponding page element', async ({ page }) => {
297+
await page.goto(htmlUrl, { waitUntil: 'networkidle' });
298+
299+
// Wait for axe to complete
300+
const axeReport = page.locator('.quarto-axe-report');
301+
await expect(axeReport).toBeVisible({ timeout: 10000 });
302+
303+
// Find the first violation target and get its CSS selector text
304+
const target = axeReport.locator('.quarto-axe-violation-target').first();
305+
const selector = await target.textContent();
306+
307+
// Hover the target (event bubbles to parent with mouseenter listener)
308+
await target.hover();
309+
310+
// The corresponding element on the page should get highlight class
311+
// Use .first() since selector may match multiple elements (e.g., "span")
312+
const element = page.locator(selector).first();
313+
await expect(element).toHaveClass(/quarto-axe-hover-highlight/, { timeout: 3000 });
277314

278315
// Move mouse away — highlight should be removed
279316
await page.mouse.move(0, 0);
280-
await expect(highlighted).not.toBeAttached();
317+
await expect(element).not.toHaveClass(/quarto-axe-hover-highlight/);
281318
});
282319
});
283320

0 commit comments

Comments
 (0)