Harness-engineering test-accessibility-testing

Test Accessibility Testing

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.md
source 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

  1. 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([]);
});
  1. 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([]);
});
  1. 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();
  1. 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();
});
  1. 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();
});
  1. 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');
});
  1. 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');
});
  1. 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

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. 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.