Testing and Validation Tools

1. Automated Testing Integration

Tool Type Best Use Case Coverage
axe-core JavaScript library Comprehensive automated testing (unit, integration, E2E) ~57% WCAG issues detectable
@axe-core/react React DevTools Development-time warnings in console Real-time feedback during development
jest-axe Jest matcher Unit tests for React/Vue components Automated a11y assertions in test suite
@axe-core/playwright E2E testing Full page accessibility scans in Playwright Integration with Playwright test runner
cypress-axe E2E testing Accessibility checks in Cypress tests Per-page and per-component scanning
pa11y CLI/Node.js Command-line testing, CI/CD integration HTML CodeSniffer + custom rules
Lighthouse CI CI/CD Performance + accessibility scoring Google Lighthouse accessibility audit
eslint-plugin-jsx-a11y Linter Static analysis of JSX code Catches common React a11y mistakes

Example: Automated testing setup

// Install dependencies
// npm install --save-dev jest-axe @testing-library/react @testing-library/jest-dom

// Jest setup (setupTests.js)
import { toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);

// Component test with jest-axe
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';
import Button from './Button';

describe('Button accessibility', () => {
  it('should not have accessibility violations', async () => {
    const { container } = render(
      <Button onClick={() => {}}>Click me</Button>
    );
    
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
  
  it('should have accessible name', () => {
    const { getByRole } = render(<Button>Submit</Button>);
    expect(getByRole('button', { name: 'Submit' })).toBeInTheDocument();
  });
});

// Playwright with axe-core
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage should not have accessibility violations', async ({ page }) => {
  await page.goto('http://localhost:3000');
  
  const accessibilityScanResults = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
    .analyze();
  
  expect(accessibilityScanResults.violations).toEqual([]);
});

// Cypress with cypress-axe
import 'cypress-axe';

describe('Accessibility tests', () => {
  beforeEach(() => {
    cy.visit('/');
    cy.injectAxe();
  });
  
  it('should have no accessibility violations', () => {
    cy.checkA11y();
  });
  
  it('should check specific component', () => {
    cy.checkA11y('.modal-dialog');
  });
  
  it('should exclude specific elements', () => {
    cy.checkA11y(null, {
      exclude: ['.third-party-widget']
    });
  });
});

// ESLint configuration (.eslintrc.js)
module.exports = {
  extends: [
    'plugin:jsx-a11y/recommended'
  ],
  plugins: ['jsx-a11y'],
  rules: {
    'jsx-a11y/anchor-is-valid': 'error',
    'jsx-a11y/img-redundant-alt': 'error',
    'jsx-a11y/no-autofocus': 'warn',
    'jsx-a11y/label-has-associated-control': 'error'
  }
};
Automated Testing Limitations: Automated tools catch ~30-50% of accessibility issues. They can't detect: poor visual design, logical reading order, meaningful alt text quality, keyboard usability, screen reader UX. Always combine with manual testing.

2. Screen Reader Testing Procedures

Platform Screen Reader Key Commands Testing Focus
Windows NVDA (free) Ctrl: Stop speech
Insert+Down: Read all
H/Shift+H: Headings
K/Shift+K: Links
D/Shift+D: Landmarks
B/Shift+B: Buttons
F/Shift+F: Form fields
Heading structure, landmarks, form labels, ARIA announcements
Windows JAWS (commercial) Insert+F5: Form fields list
Insert+F6: Headings list
Insert+F7: Links list
R: Regions/landmarks
Insert+F3: Elements list
Complex ARIA widgets, forms, tables, dynamic content
macOS VoiceOver Cmd+F5: Toggle VO
VO+A: Read all
VO+Right/Left: Navigate
VO+U: Rotor menu
VO+Space: Activate
VO+Shift+Down: Into group
Safari/Chrome compatibility, mobile web, native app integration
iOS VoiceOver Swipe right/left: Navigate
Double-tap: Activate
Two-finger Z: Back
Rotor: Quick nav
Touch gestures, mobile-specific patterns, responsive design
Android TalkBack Swipe right/left: Navigate
Double-tap: Activate
Swipe down-then-up: Reading controls
Local context menu: Actions
Android web, PWA, custom actions, material design

Example: Screen reader testing checklist

// Screen Reader Test Script

1. Page Structure Test
   □ Navigate by headings (H key) - logical hierarchy?
   □ Navigate by landmarks (D/R key) - proper regions?
   □ Page title announced correctly?
   □ Main content easily accessible?

2. Interactive Elements Test
   □ Navigate by buttons (B key) - all announced?
   □ Button states announced (pressed, expanded)?
   □ Links (K key) - descriptive text, not "click here"?
   □ Form fields (F key) - labels associated?

3. Dynamic Content Test
   □ Form validation errors announced?
   □ Live region updates announced?
   □ Loading states announced?
   □ Route changes announced (SPA)?

4. Complex Widgets Test
   □ Modal dialog focus trapped?
   □ Accordion expand/collapse announced?
   □ Tab panels keyboard navigable?
   □ Combobox autocomplete working?

5. Table Test
   □ Table headers announced with cells?
   □ Navigate by cell (Ctrl+Alt+Arrow)?
   □ Row/column headers associated?

// Screen Reader Testing Script Example
describe('Screen reader announcements', () => {
  it('should announce form errors', () => {
    // Simulate screen reader
    const form = document.querySelector('form');
    const errorMessage = document.querySelector('[role="alert"]');
    
    // Verify error in accessibility tree
    expect(errorMessage).toHaveAttribute('role', 'alert');
    expect(errorMessage).toHaveTextContent('Email is required');
  });
  
  it('should announce dynamic updates', () => {
    const liveRegion = document.querySelector('[aria-live="polite"]');
    expect(liveRegion).toBeInTheDocument();
    
    // Update content
    liveRegion.textContent = '3 items added to cart';
    
    // Verify content updated
    expect(liveRegion).toHaveTextContent('3 items added to cart');
  });
});
Screen Reader Testing Gotchas: Different screen readers behave differently (test with at least 2). Browser choice matters (NVDA+Firefox vs Chrome). Mobile screen readers have different navigation. Virtual cursor vs focus mode impacts testing. Always test in actual screen readers, not simulators.

3. Keyboard Navigation Testing

Key/Combination Expected Behavior Test Scenario Common Issues
Tab Move focus forward through interactive elements All buttons, links, inputs reachable in logical order Skip important elements, illogical order, focus traps
Shift+Tab Move focus backward Reverse navigation works correctly Order different from forward tab
Enter Activate buttons, links, submit forms All interactive elements respond to Enter Custom buttons missing Enter handler
Space Activate buttons, checkboxes, toggle switches Buttons work with Space key Custom buttons missing Space handler
Escape Close modals, cancel actions, clear autocomplete Modals/dialogs close, focus restored No Escape handler, focus not restored
Arrow keys Navigate within composite widgets (tabs, menus, lists) Tab panels, radio groups, dropdown menus Arrows move page instead of focus
Home/End Jump to first/last item in lists, first/last character in inputs Long lists, combobox options Missing Home/End support in custom widgets
Page Up/Down Scroll content or navigate by page in lists Scroll containers, data grids Focus lost when scrolling

Example: Keyboard testing automation

// Keyboard navigation test utilities
class KeyboardTester {
  // Simulate Tab key
  static tab(element, shift = false) {
    const event = new KeyboardEvent('keydown', {
      key: 'Tab',
      code: 'Tab',
      keyCode: 9,
      shiftKey: shift,
      bubbles: true
    });
    element.dispatchEvent(event);
  }
  
  // Get all focusable elements
  static getFocusableElements(container = document) {
    const selector = [
      'a[href]',
      'button:not([disabled])',
      'textarea:not([disabled])',
      'input:not([disabled]):not([type="hidden"])',
      'select:not([disabled])',
      '[tabindex]:not([tabindex="-1"])'
    ].join(', ');
    
    return Array.from(container.querySelectorAll(selector));
  }
  
  // Test tab order
  static testTabOrder(expectedOrder) {
    const focusable = this.getFocusableElements();
    const actualOrder = focusable.map(el => 
      el.getAttribute('data-testid') || el.textContent.trim()
    );
    
    console.log('Expected:', expectedOrder);
    console.log('Actual:', actualOrder);
    
    return JSON.stringify(expectedOrder) === JSON.stringify(actualOrder);
  }
}

// Playwright keyboard testing
test('keyboard navigation works correctly', async ({ page }) => {
  await page.goto('/');
  
  // Tab through all interactive elements
  await page.keyboard.press('Tab');
  await expect(page.locator('button').first()).toBeFocused();
  
  await page.keyboard.press('Tab');
  await expect(page.locator('a').first()).toBeFocused();
  
  // Test Shift+Tab (reverse)
  await page.keyboard.press('Shift+Tab');
  await expect(page.locator('button').first()).toBeFocused();
  
  // Test Enter activation
  await page.keyboard.press('Enter');
  // Verify button action occurred
});

// Testing-Library keyboard test
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('modal closes with Escape key', async () => {
  const user = userEvent.setup();
  const onClose = jest.fn();
  
  const { getByRole } = render(
    <Modal isOpen onClose={onClose}>
      <h2>Modal Title</h2>
    </Modal>
  );
  
  // Modal is open
  expect(getByRole('dialog')).toBeInTheDocument();
  
  // Press Escape
  await user.keyboard('{Escape}');
  
  // Modal should close
  expect(onClose).toHaveBeenCalled();
});

// Manual test checklist
/*
Keyboard Navigation Test Checklist:

□ Tab reaches all interactive elements
□ Tab order follows visual/logical order
□ Shift+Tab works in reverse
□ No keyboard traps (can always Tab out)
□ Enter activates buttons and links
□ Space activates buttons and checkboxes
□ Escape closes modals and cancels actions
□ Focus indicators clearly visible (3:1 contrast)
□ Arrow keys work in composite widgets
□ Home/End jump to first/last items
□ No auto-focus unless necessary
□ Focus restored after modal/dropdown closes
*/
Keyboard Testing Tips: Unplug your mouse and navigate entire site with keyboard only. Test focus visibility (3:1 contrast minimum). Verify no focus traps (can always Tab out). Check custom widgets follow ARIA keyboard patterns. Test with browser zoom at 200%.

4. Color Contrast Validation

Tool Type Features Best For
WebAIM Contrast Checker Web tool Foreground/background ratio, WCAG level compliance Quick manual checks, design validation
Chrome DevTools Browser tool Color picker with contrast ratio, suggestions Real-time adjustments during development
Colour Contrast Analyser (CCA) Desktop app Eyedropper, pass/fail indicators, simulations Pixel-level accuracy, design review
axe DevTools Browser extension Automated contrast checking, element highlighting Full page audits, QA testing
Stark (Figma plugin) Design tool Contrast check in Figma, suggestions, color blindness sim Design phase validation
APCA Calculator Web tool Advanced Perceptual Contrast Algorithm (future WCAG 3) Forward-looking contrast validation
Polypane Browser Built-in contrast checking, visual impairment simulators Professional accessibility testing

Example: Contrast ratio requirements and calculations

// WCAG Contrast Requirements
/*
Text Size              | WCAG AA  | WCAG AAA
--------------------- | -------- | --------
Normal text (<18px)   | 4.5:1    | 7:1
Large text (≥18px)    | 3:1      | 4.5:1
Large bold (≥14px)    | 3:1      | 4.5:1
UI components         | 3:1      | N/A
Graphics/icons        | 3:1      | N/A
Focus indicators      | 3:1      | N/A (WCAG 2.4.13)
*/

// Contrast ratio calculation (relative luminance)
function getLuminance(r, g, b) {
  const [R, G, B] = [r, g, b].map(val => {
    val = val / 255;
    return val <= 0.03928 
      ? val / 12.92 
      : Math.pow((val + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}

function getContrastRatio(rgb1, rgb2) {
  const lum1 = getLuminance(...rgb1);
  const lum2 = getLuminance(...rgb2);
  const lighter = Math.max(lum1, lum2);
  const darker = Math.min(lum1, lum2);
  return (lighter + 0.05) / (darker + 0.05);
}

// Usage
const textColor = [0, 0, 0];      // Black
const bgColor = [255, 255, 255];  // White
const ratio = getContrastRatio(textColor, bgColor);
console.log(`Contrast ratio: ${ratio.toFixed(2)}:1`); // 21:1

// Validation function
function meetsWCAG(ratio, level = 'AA', isLargeText = false) {
  if (level === 'AAA') {
    return isLargeText ? ratio >= 4.5 : ratio >= 7;
  }
  // AA
  return isLargeText ? ratio >= 3 : ratio >= 4.5;
}

// Automated contrast checking in tests
import { toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';

test('button has sufficient contrast', async () => {
  const { container } = render(
    <button style={{ background: '#0066cc', color: '#ffffff' }}>
      Click me
    </button>
  );
  
  const results = await axe(container, {
    rules: {
      'color-contrast': { enabled: true }
    }
  });
  
  expect(results).toHaveNoViolations();
});

// CSS color contrast validation
/*
Good contrast examples:
- #000000 on #FFFFFF = 21:1 (AAA)
- #0066CC on #FFFFFF = 8.59:1 (AAA)
- #666666 on #FFFFFF = 5.74:1 (AA)
- #FFFFFF on #0066CC = 8.59:1 (AAA)

Poor contrast examples (avoid):
- #777777 on #FFFFFF = 4.47:1 (fails AA for normal text)
- #0066CC on #000000 = 2.44:1 (fails AA)
- #CCCCCC on #FFFFFF = 1.61:1 (fails all)
*/
Contrast Testing Gotchas: Test with actual rendered colors, not design specs (gradients, opacity, overlays affect contrast). Images with text need manual checking. Focus indicators need 3:1 against both adjacent colors. Disabled elements don't need to meet contrast (but consider UX).

5. Manual Testing Checklists

Test Category Key Checkpoints Tools Needed WCAG Coverage
Keyboard Navigation ✓ All interactive elements reachable
✓ Logical tab order
✓ Visible focus indicators (3:1)
✓ No keyboard traps
✓ Escape closes modals
Keyboard only, DevTools 2.1.1, 2.1.2, 2.4.3, 2.4.7, 2.4.13
Screen Reader ✓ Headings in logical order
✓ Landmarks identify regions
✓ Images have alt text
✓ Form labels associated
✓ Errors announced
NVDA/JAWS/VoiceOver 1.1.1, 1.3.1, 2.4.6, 3.3.1, 4.1.2
Zoom & Reflow ✓ Text to 200% without horizontal scroll
✓ Content reflows at 320px
✓ No loss of content/functionality
✓ Touch targets at least 24×24px
Browser zoom, mobile view 1.4.4, 1.4.10, 2.5.8
Color & Contrast ✓ Text contrast 4.5:1 (AA)
✓ UI components 3:1
✓ Info not conveyed by color alone
✓ Color blindness friendly
Contrast checker, color filters 1.4.1, 1.4.3, 1.4.11
Forms ✓ All inputs have labels
✓ Required fields indicated
✓ Error messages clear & helpful
✓ Success confirmation
✓ Keyboard accessible
Screen reader, keyboard 1.3.1, 3.3.1, 3.3.2, 3.3.3, 4.1.2
Dynamic Content ✓ Updates announced (live regions)
✓ Focus managed on changes
✓ Loading states announced
✓ Time limits adjustable
Screen reader, DevTools 2.2.1, 4.1.2, 4.1.3
Mobile/Touch ✓ Touch targets 44×44px minimum
✓ Works in portrait & landscape
✓ Zoom not disabled
✓ Gestures have alternatives
Real device, mobile SR 1.3.4, 1.4.4, 2.5.1, 2.5.8

Example: Comprehensive testing checklist

// Accessibility Testing Checklist Template

=== KEYBOARD NAVIGATION ===
□ Tab through entire page (top to bottom)
□ Verify all interactive elements reachable
□ Check tab order matches visual order
□ Shift+Tab works in reverse
□ Focus visible on all elements (3:1 contrast)
□ No keyboard traps detected
□ Enter activates buttons/links
□ Space activates buttons/checkboxes
□ Escape closes modals/dropdowns
□ Arrow keys work in custom widgets

=== SCREEN READER ===
□ Use heading navigation (H key) - logical structure
□ Navigate by landmarks (D/R key) - proper regions
□ Check all images have alt text
□ Verify form labels read correctly
□ Test form validation announcements
□ Check ARIA states announced (expanded, pressed, etc.)
□ Verify live region updates announced
□ Test table navigation (headers announced)
□ Check custom widget announcements

=== VISUAL & CONTRAST ===
□ Text contrast meets 4.5:1 (normal) or 3:1 (large)
□ UI component contrast meets 3:1
□ Focus indicators visible (3:1 against adjacent)
□ Information not conveyed by color alone
□ Test with color blindness simulator
□ Check in high contrast mode (Windows)

=== ZOOM & REFLOW ===
□ Zoom to 200% - no horizontal scroll
□ Zoom to 400% for text - content visible
□ Resize to 320px width - content reflows
□ No loss of content at any zoom level
□ Touch targets remain 24×24px minimum
□ Test responsive design breakpoints

=== FORMS ===
□ All inputs have visible labels
□ Labels programmatically associated
□ Required fields clearly indicated
□ Error messages clear and specific
□ Errors announced by screen reader
□ Success confirmation provided
□ Can submit with keyboard

=== MEDIA ===
□ Videos have captions
□ Audio has transcripts
□ Auto-play videos can be paused
□ Media controls keyboard accessible
□ Transcripts provided for audio-only

=== MOBILE ===
□ Test with VoiceOver (iOS)
□ Test with TalkBack (Android)
□ Touch targets 44×44px minimum
□ Works in both orientations
□ Zoom not disabled (no user-scalable=no)
□ Gestures have button alternatives

=== DYNAMICS ===
□ Route changes announced (SPA)
□ Loading states announced
□ Form submission feedback
□ Modal opens - focus trapped
□ Modal closes - focus restored
□ Live regions working (cart updates, etc.)

=== DOCUMENTATION ===
□ Document any known issues
□ Note browser/AT combinations tested
□ Include WCAG conformance level
□ List exemptions (if any)
□ Provide remediation timeline
Manual Testing Best Practices: Test with real users with disabilities when possible. Use multiple screen readers (NVDA, JAWS, VoiceOver). Test on actual mobile devices, not just emulators. Document test results with screenshots/videos. Retest after fixes to verify remediation.

6. Accessibility Linting and CI/CD

Tool/Approach Integration Point Configuration Benefits
ESLint jsx-a11y Pre-commit, IDE, CI Rules for React/JSX accessibility Catches issues during coding; instant feedback
Prettier (formatting) Pre-commit, CI Consistent ARIA attribute formatting Code consistency, readability
Stylelint a11y Pre-commit, CI CSS accessibility rules (contrast, outline, etc.) Prevents CSS-based a11y issues
Pa11y CI GitHub Actions, GitLab CI Automated page scans on deployment Catches regressions before production
Lighthouse CI GitHub Actions, GitLab CI Performance + accessibility scoring Metrics tracking, budget enforcement
axe-core in tests Unit tests, E2E tests Jest/Playwright/Cypress integration Automated testing in CI pipeline
Git pre-commit hooks Local git hooks (Husky) Run linters before commit Prevents committing a11y violations
Storybook a11y addon Component library axe checks in Storybook Component-level validation during development

Example: CI/CD accessibility pipeline

// package.json scripts
{
  "scripts": {
    "lint:a11y": "eslint --ext .js,.jsx,.ts,.tsx src/",
    "test:a11y": "jest --testMatch '**/*.a11y.test.js'",
    "audit:a11y": "pa11y-ci --config .pa11yci.json",
    "lighthouse": "lighthouse-ci --config=.lighthouserc.json"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "jest --findRelatedTests --passWithNoTests"
    ]
  }
}

// .eslintrc.js
module.exports = {
  extends: [
    'plugin:jsx-a11y/recommended'
  ],
  plugins: ['jsx-a11y'],
  rules: {
    'jsx-a11y/alt-text': 'error',
    'jsx-a11y/anchor-has-content': 'error',
    'jsx-a11y/anchor-is-valid': 'error',
    'jsx-a11y/aria-props': 'error',
    'jsx-a11y/aria-role': 'error',
    'jsx-a11y/aria-unsupported-elements': 'error',
    'jsx-a11y/label-has-associated-control': 'error',
    'jsx-a11y/no-autofocus': 'warn'
  }
};

// .pa11yci.json (Pa11y CI configuration)
{
  "defaults": {
    "standard": "WCAG2AA",
    "runners": ["axe", "htmlcs"],
    "timeout": 10000,
    "wait": 1000,
    "chromeLaunchConfig": {
      "args": ["--no-sandbox"]
    }
  },
  "urls": [
    "http://localhost:3000",
    "http://localhost:3000/about",
    "http://localhost:3000/products"
  ]
}

// .lighthouserc.json (Lighthouse CI)
{
  "ci": {
    "collect": {
      "url": ["http://localhost:3000"],
      "numberOfRuns": 3
    },
    "assert": {
      "assertions": {
        "categories:accessibility": ["error", {"minScore": 0.9}],
        "aria-allowed-attr": "error",
        "aria-required-attr": "error",
        "button-name": "error",
        "color-contrast": "error",
        "duplicate-id-aria": "error",
        "html-has-lang": "error",
        "image-alt": "error",
        "label": "error",
        "link-name": "error",
        "list": "error"
      }
    }
  }
}

// GitHub Actions workflow (.github/workflows/a11y.yml)
name: Accessibility Tests

on: [push, pull_request]

jobs:
  accessibility:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Lint accessibility
        run: npm run lint:a11y
      
      - name: Build app
        run: npm run build
      
      - name: Start server
        run: npm start &
        
      - name: Wait for server
        run: npx wait-on http://localhost:3000
      
      - name: Run Pa11y CI
        run: npm run audit:a11y
      
      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli
          lhci autorun
      
      - name: Upload results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: accessibility-reports
          path: |
            pa11y-results/
            .lighthouseci/

// Storybook a11y addon (.storybook/preview.js)
import { withA11y } from '@storybook/addon-a11y';

export const decorators = [withA11y];

export const parameters = {
  a11y: {
    config: {
      rules: [
        {
          id: 'color-contrast',
          enabled: true
        }
      ]
    }
  }
};
CI/CD Gotchas: Automated tools catch only 30-50% of issues - still need manual testing. Don't block deployments for minor warnings. Set appropriate thresholds (e.g., 90% Lighthouse score). Test with production-like data. Run tests on every PR, not just main branch.

Testing and Validation Quick Reference

  • Automated Tools: Use axe-core (jest-axe, @axe-core/playwright, cypress-axe) for ~30-50% coverage; combine with manual testing
  • Screen Readers: Test with NVDA (Windows free), JAWS (commercial), VoiceOver (macOS/iOS), TalkBack (Android)
  • Keyboard: Tab through entire page, verify focus visibility (3:1 contrast), test Enter/Space/Escape, check no keyboard traps
  • Contrast: Use WebAIM checker, Chrome DevTools, or CCA; verify 4.5:1 for text, 3:1 for UI components
  • Manual Checklist: Keyboard navigation, screen reader, zoom/reflow, color/contrast, forms, media, mobile, dynamics
  • Linting: eslint-plugin-jsx-a11y for React, stylelint-a11y for CSS, run in pre-commit hooks and CI
  • CI/CD: Pa11y CI for automated scans, Lighthouse CI for metrics, axe in test suites, Storybook addon for components
  • Testing Strategy: Shift-left (test early), automate what you can, manual test critical flows, retest after fixes
  • Browser DevTools: Chrome Accessibility tree, Firefox Accessibility Inspector, Edge Accessibility Insights
  • Best Practice: Test with real users with disabilities when possible; document all test results and remediation plans