From fb6e81e135e604c7e5f944c43ce9a8543ce9f7fa Mon Sep 17 00:00:00 2001 From: jedarden Date: Mon, 6 Jul 2026 00:25:39 -0400 Subject: [PATCH] feat(bf-21nd): add axe-core Playwright integration with shared helper - Add shared accessibility test helper (tests/accessibility/helper.js/ts) - scanAccessibility(): runs axe-core scans with WCAG 2.1 AA tags - formatViolations(): formats violation details for error reporting - expectNoAccessibilityViolations(): asserts no violations - Add smoke tests demonstrating helper usage (tests/accessibility/smoke.spec.js/ts) - Refactor existing accessibility tests to use shared helper - Simplified a11y.spec.js to use expectNoAccessibilityViolations() - Maintains all existing test coverage Co-Authored-By: Claude --- dashboard/tests/a11y.spec.js | 25 ++------ dashboard/tests/accessibility/helper.js | 69 +++++++++++++++++++++ dashboard/tests/accessibility/helper.ts | 68 ++++++++++++++++++++ dashboard/tests/accessibility/smoke.spec.js | 21 +++++++ dashboard/tests/accessibility/smoke.spec.ts | 21 +++++++ 5 files changed, 183 insertions(+), 21 deletions(-) create mode 100644 dashboard/tests/accessibility/helper.js create mode 100644 dashboard/tests/accessibility/helper.ts create mode 100644 dashboard/tests/accessibility/smoke.spec.js create mode 100644 dashboard/tests/accessibility/smoke.spec.ts diff --git a/dashboard/tests/a11y.spec.js b/dashboard/tests/a11y.spec.js index 4896bd4..948fe23 100644 --- a/dashboard/tests/a11y.spec.js +++ b/dashboard/tests/a11y.spec.js @@ -1,5 +1,5 @@ -const { test, expect } = require('@playwright/test'); -const AxeBuilder = require('@axe-core/playwright').default; +const { test } = require('@playwright/test'); +const { expectNoAccessibilityViolations } = require('./accessibility/helper'); const pages = [ { name: 'index', path: '/index.html' }, @@ -16,25 +16,8 @@ for (const page of pages) { const pg = await context.newPage(); await pg.goto(page.path, { waitUntil: 'load' }); - const results = await new AxeBuilder({ page: pg }) - .withTags(['wcag2a', 'wcag2aa']) - .analyze(); - - const violations = results.violations; - if (violations.length > 0) { - const details = violations - .map((v) => ` ${v.id}: ${v.nodes.length} node(s) — ${v.description}`) - .join('\n'); - // Attach full report for debugging - await test.info().attach('axe-violations', { - body: JSON.stringify(violations, null, 2), - contentType: 'application/json', - }); - } - - await expect(violations, `${violations.length} WCAG AA violation(s) on ${page.path}:\n${ - violations.map((v) => ` ${v.id}: ${v.description}`).join('\n') - }`).toEqual([]); + // Use the shared accessibility helper + await expectNoAccessibilityViolations(pg, page.path); await context.close(); }); diff --git a/dashboard/tests/accessibility/helper.js b/dashboard/tests/accessibility/helper.js new file mode 100644 index 0000000..100de71 --- /dev/null +++ b/dashboard/tests/accessibility/helper.js @@ -0,0 +1,69 @@ +const AxeBuilder = require('@axe-core/playwright').default; + +/** + * Run an axe-core accessibility scan with WCAG 2.1 AA tags + * + * @param {Page} page - Playwright Page object to scan + * @param {string} context - Optional context selector to limit the scan scope + * @returns {Promise} Axe scan results with violations, passes, and incomplete results + */ +async function scanAccessibility(page, context) { + const builder = new AxeBuilder({ page }); + + // Configure with WCAG 2.1 AA tags + builder.withTags(['wcag2a', 'wcag2aa']); + + // Limit scan scope if context is provided + if (context) { + builder.include(context); + } + + return await builder.analyze(); +} + +/** + * Format accessibility violations for error reporting + * + * @param {Array} violations - Array of axe violation results + * @returns {string} Formatted string with violation details + */ +function formatViolations(violations) { + return violations + .map((v) => ` ${v.id}: ${v.nodes.length} node(s) — ${v.description}`) + .join('\n'); +} + +/** + * Assert that a page has no accessibility violations + * + * @param {Page} page - Playwright Page object to scan + * @param {string} pageName - Name of the page for error reporting + * @param {string} context - Optional context selector to limit the scan scope + */ +async function expectNoAccessibilityViolations(page, pageName, context) { + const results = await scanAccessibility(page, context); + const violations = results.violations; + + if (violations.length > 0) { + // Attach full report for debugging + const { test } = require('@playwright/test'); + await test.info().attach('axe-violations', { + body: JSON.stringify(violations, null, 2), + contentType: 'application/json', + }); + } + + const { expect } = require('@playwright/test'); + await expect( + violations, + `${violations.length} WCAG AA violation(s) on ${pageName}:\n${ + violations.map((v) => ` ${v.id}: ${v.description}`).join('\n') + }` + ).toEqual([]); +} + +module.exports = { + scanAccessibility, + formatViolations, + expectNoAccessibilityViolations, +}; diff --git a/dashboard/tests/accessibility/helper.ts b/dashboard/tests/accessibility/helper.ts new file mode 100644 index 0000000..3737367 --- /dev/null +++ b/dashboard/tests/accessibility/helper.ts @@ -0,0 +1,68 @@ +import { Page } from '@playwright/test'; +import AxeBuilder from '@axe-core/playwright'; + +/** + * Run an axe-core accessibility scan with WCAG 2.1 AA tags + * + * @param page - Playwright Page object to scan + * @param context - Optional context selector to limit the scan scope + * @returns Axe scan results with violations, passes, and incomplete results + */ +export async function scanAccessibility(page: Page, context?: string) { + const builder = new AxeBuilder({ page }); + + // Configure with WCAG 2.1 AA tags + builder.withTags(['wcag2a', 'wcag2aa']); + + // Limit scan scope if context is provided + if (context) { + builder.include(context); + } + + return await builder.analyze(); +} + +/** + * Format accessibility violations for error reporting + * + * @param violations - Array of axe violation results + * @returns Formatted string with violation details + */ +export function formatViolations(violations: any[]): string { + return violations + .map((v) => ` ${v.id}: ${v.nodes.length} node(s) — ${v.description}`) + .join('\n'); +} + +/** + * Assert that a page has no accessibility violations + * + * @param page - Playwright Page object to scan + * @param pageName - Name of the page for error reporting + * @param context - Optional context selector to limit the scan scope + */ +export async function expectNoAccessibilityViolations( + page: Page, + pageName: string, + context?: string +): Promise { + const results = await scanAccessibility(page, context); + const violations = results.violations; + + if (violations.length > 0) { + // Attach full report for debugging + const { test } = require('@playwright/test'); + await test.info().attach('axe-violations', { + body: JSON.stringify(violations, null, 2), + contentType: 'application/json', + }); + } + + const { expect } = require('@playwright/test'); + await expect( + violations, + `${violations.length} WCAG AA violation(s) on ${pageName}:\n${ + violations.map((v) => ` ${v.id}: ${v.description}`).join('\n') + }` + ).toEqual([]); +} diff --git a/dashboard/tests/accessibility/smoke.spec.js b/dashboard/tests/accessibility/smoke.spec.js new file mode 100644 index 0000000..bc021ef --- /dev/null +++ b/dashboard/tests/accessibility/smoke.spec.js @@ -0,0 +1,21 @@ +const { test, expect } = require('@playwright/test'); +const { scanAccessibility, expectNoAccessibilityViolations } = require('./helper'); + +test.describe('Accessibility Helper Smoke Test', () => { + test('helper functions are available', async ({ page }) => { + // Basic smoke test to verify the helper is working + await page.goto('/index.html', { waitUntil: 'load' }); + + // Test the scanAccessibility function + const results = await scanAccessibility(page); + expect(results).toHaveProperty('violations'); + expect(results).toHaveProperty('passes'); + expect(Array.isArray(results.violations)).toBe(true); + expect(Array.isArray(results.passes)).toBe(true); + }); + + test('index page has no WCAG AA violations (using helper)', async ({ page }) => { + await page.goto('/index.html', { waitUntil: 'load' }); + await expectNoAccessibilityViolations(page, 'index page'); + }); +}); diff --git a/dashboard/tests/accessibility/smoke.spec.ts b/dashboard/tests/accessibility/smoke.spec.ts new file mode 100644 index 0000000..138921a --- /dev/null +++ b/dashboard/tests/accessibility/smoke.spec.ts @@ -0,0 +1,21 @@ +import { test, expect } from '@playwright/test'; +import { scanAccessibility, expectNoAccessibilityViolations } from './helper'; + +test.describe('Accessibility Helper Smoke Test', () => { + test('helper functions are available', async ({ page }) => { + // Basic smoke test to verify the helper is working + await page.goto('/index.html', { waitUntil: 'load' }); + + // Test the scanAccessibility function + const results = await scanAccessibility(page); + expect(results).toHaveProperty('violations'); + expect(results).toHaveProperty('passes'); + expect(Array.isArray(results.violations)).toBe(true); + expect(Array.isArray(results.passes)).toBe(true); + }); + + test('index page has no WCAG AA violations (using helper)', async ({ page }) => { + await page.goto('/index.html', { waitUntil: 'load' }); + await expectNoAccessibilityViolations(page, 'index page'); + }); +});