install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/test-accessibility-testing" ~/.claude/skills/intense-visions-harness-engineering-test-accessibility-testing && rm -rf "$T"
manifest:
agents/skills/claude-code/test-accessibility-testing/SKILL.mdsource content
Test Accessibility Testing
Automate WCAG accessibility checks using axe-core with Playwright and jest-axe
When to Use
- Catching accessibility violations automatically in CI
- Testing keyboard navigation and screen reader compatibility
- Verifying WCAG 2.1 AA compliance in web applications
- Integrating accessibility checks into existing test suites
Instructions
- Playwright + axe-core for E2E accessibility testing:
npm install -D @axe-core/playwright
import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; test('homepage has no accessibility violations', async ({ page }) => { await page.goto('/'); const results = await new AxeBuilder({ page }).analyze(); expect(results.violations).toEqual([]); });
- Scan specific sections:
test('login form is accessible', async ({ page }) => { await page.goto('/login'); const results = await new AxeBuilder({ page }).include('#login-form').analyze(); expect(results.violations).toEqual([]); });
- Exclude known issues while working on fixes:
const results = await new AxeBuilder({ page }) .exclude('#legacy-widget') // Known issue, tracked in JIRA-123 .disableRules(['color-contrast']) // Temporarily disable specific rules .analyze();
- jest-axe for component tests:
npm install -D jest-axe @types/jest-axe
import { render } from '@testing-library/react'; import { axe, toHaveNoViolations } from 'jest-axe'; expect.extend(toHaveNoViolations); it('Button component has no a11y violations', async () => { const { container } = render( <Button onClick={() => {}}>Click me</Button> ); const results = await axe(container); expect(results).toHaveNoViolations(); });
- Test keyboard navigation:
test('tab order follows visual layout', async ({ page }) => { await page.goto('/form'); await page.keyboard.press('Tab'); await expect(page.getByLabel('Name')).toBeFocused(); await page.keyboard.press('Tab'); await expect(page.getByLabel('Email')).toBeFocused(); await page.keyboard.press('Tab'); await expect(page.getByRole('button', { name: 'Submit' })).toBeFocused(); });
- Test keyboard interaction patterns:
test('dropdown opens with Enter and navigates with arrow keys', async ({ page }) => { await page.goto('/select'); const select = page.getByRole('combobox', { name: 'Country' }); await select.focus(); await page.keyboard.press('Enter'); await expect(page.getByRole('listbox')).toBeVisible(); await page.keyboard.press('ArrowDown'); await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); await expect(select).toHaveValue('Canada'); });
- Test ARIA attributes:
test('error messages are associated with inputs', async ({ page }) => { await page.goto('/form'); // Submit empty form to trigger validation await page.getByRole('button', { name: 'Submit' }).click(); const emailInput = page.getByLabel('Email'); const errorId = await emailInput.getAttribute('aria-describedby'); expect(errorId).toBeTruthy(); const errorMessage = page.locator(`#${errorId}`); await expect(errorMessage).toHaveText('Email is required'); await expect(emailInput).toHaveAttribute('aria-invalid', 'true'); });
- Run accessibility checks on all pages:
const pages = ['/', '/about', '/contact', '/products', '/login']; for (const path of pages) { test(`${path} has no a11y violations`, async ({ page }) => { await page.goto(path); const results = await new AxeBuilder({ page }).analyze(); expect(results.violations).toEqual([]); }); }
Details
Automated accessibility testing catches approximately 30-50% of WCAG violations. The remaining violations require manual testing (screen reader behavior, cognitive load, content clarity).
What axe-core catches:
- Missing alt text on images
- Insufficient color contrast
- Missing form labels
- Invalid ARIA attributes
- Heading level order violations
- Missing lang attribute
- Duplicate IDs
- Keyboard traps
What axe-core cannot catch:
- Meaningful alt text (it checks existence, not quality)
- Logical tab order (it checks focusability, not order)
- Screen reader experience (announcement quality, context)
- Cognitive accessibility (plain language, predictable behavior)
WCAG conformance levels:
- A — minimum accessibility. Most axe rules target this level
- AA — standard target for most organizations and legal requirements
- AAA — highest level. Rarely required but good to aim for in new projects
axe-core rule tags: Filter by conformance level with
.withTags(['wcag2a', 'wcag2aa', 'best-practice']).
Trade-offs:
- Automated a11y tests catch regressions early — but miss many real-world accessibility issues
- Axe-core has very low false positive rates — but high false negative rates (misses ~50% of issues)
- Component-level testing catches issues early — but some violations only appear in full-page context
- Excluding rules/elements is sometimes necessary — but creates a backlog of known violations that can grow
Source
https://playwright.dev/docs/accessibility-testing
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.