• Skip to main content
  • Skip to navigation
  • Skip to footer
    AllyScan Logo
    AllyScan
    • Websites
    • Pricing
    Sign inFree Scan

    Search

    Categories

    Guidelines
    2
    Technology
    1
    Design
    1

    Recent Posts

    Top 5 Common WCAG Mistakes and How to Avoid Them

    Jan 20, 2024

    The Future of Web Accessibility: AI-Powered Compliance

    Jan 15, 2024

    WCAG 2.2 Guidelines: What's New and How to Implement

    Jan 10, 2024

    Building Inclusive User Experiences: A Designer's Guide

    Jan 5, 2024

    Tags

    WCAG (2)
    Accessibility (2)
    Compliance (2)
    Best Practices (1)
    AI (1)
    Guidelines (1)
    Implementation (1)
    UX (1)
    Design (1)
    Inclusion (1)

    Newsletter

    Get weekly tips

    Back to blog
    Development
    CI/CD
    Automation
    DevOps
    Testing

    Automated Accessibility Testing in CI/CD Pipelines

    By Mark de VriesJanuary 10, 20248 min read
    Share:

    Automated Accessibility Testing in CI/CD Pipelines

    Integrating accessibility testing into your development workflow is crucial for detecting and preventing accessibility issues early. This guide shows how to implement this effectively.

    Why Automate?

    The Cost of Late Detection

    • In development: €1 to fix
    • In QA: €10 to fix
    • In production: €100+ to fix
    • After complaint: €1000+ (including legal costs)

    Benefits of CI/CD Integration

    1. Early detection: Find issues before they reach production
    2. Consistent testing: Every commit is tested
    3. Developer education: Immediate feedback improves awareness
    4. Compliance tracking: Automatic documentation for audits

    Implementation Steps

    Step 1: Choose Your Tools

    AllyScan API

    // .github/workflows/accessibility.yml
    name: Accessibility Check
    on: [push, pull_request]
    
    jobs:
      a11y:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - name: Run AllyScan
            run: |
              curl -X POST https://api.allyscan.com/v1/scan \
                -H "Authorization: Bearer ${{ secrets.ALLYSCAN_API_KEY }}" \
                -d '{"url": "${{ env.PREVIEW_URL }}", "standard": "WCAG22AA"}'
    

    Pa11y in Pipeline

    # .gitlab-ci.yml
    accessibility_test:
      stage: test
      image: node:16
      before_script:
        - npm install -g pa11y
      script:
        - pa11y https://staging.example.com --standard WCAG2AA
      artifacts:
        reports:
          accessibility: pa11y-report.json
    

    Step 2: Configure Test Scenarios

    Basic Configuration

    // accessibility.config.js
    module.exports = {
      urls: [
        '/',
        '/products',
        '/contact',
        '/checkout'
      ],
      standard: 'WCAG2AA',
      timeout: 30000,
      wait: 1500,
      ignore: [
        'warning',
        'notice'
      ],
      threshold: {
        errors: 0,
        warnings: 5
      }
    };
    

    Advanced Scenarios

    // tests/a11y/checkout.test.js
    describe('Checkout Accessibility', () => {
      beforeEach(async () => {
        await page.goto('/checkout');
      });
    
      test('Form labels are properly associated', async () => {
        const violations = await page.accessibility.check();
        expect(violations.filter(v => v.id === 'label')).toHaveLength(0);
      });
    
      test('Focus management in multi-step form', async () => {
        await page.click('[data-step="2"]');
        const focusedElement = await page.evaluate(() => 
          document.activeElement.getAttribute('data-step')
        );
        expect(focusedElement).toBe('2');
      });
    
      test('Error messages are accessible', async () => {
        await page.click('[type="submit"]');
        const errorAnnouncement = await page.$eval(
          '[role="alert"]',
          el => el.textContent
        );
        expect(errorAnnouncement).toContain('Please fill in all required fields');
      });
    });
    

    Step 3: Integrate with Development Workflow

    Pre-commit Hooks

    // package.json
    {
      "husky": {
        "hooks": {
          "pre-commit": "npm run a11y:check"
        }
      },
      "scripts": {
        "a11y:check": "pa11y-ci --config .pa11yci.json"
      }
    }
    

    Pull Request Checks

    // .github/workflows/pr-check.yml
    name: PR Accessibility Check
    on:
      pull_request:
        types: [opened, synchronize]
    
    jobs:
      accessibility:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          
          - name: Setup Node
            uses: actions/setup-node@v2
            with:
              node-version: '16'
          
          - name: Install dependencies
            run: npm ci
          
          - name: Build application
            run: npm run build
          
          - name: Start preview server
            run: |
              npm run preview &
              npx wait-on http://localhost:3000
          
          - name: Run accessibility tests
            run: npm run test:a11y
          
          - name: Comment PR
            if: failure()
            uses: actions/github-script@v6
            with:
              script: |
                github.rest.issues.createComment({
                  issue_number: context.issue.number,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  body: '❌ Accessibility tests failed. Please check the logs.'
                })
    

    Best Practices

    1. Gradual Implementation

    Start with warnings, not hard failures:

    // Week 1-2: Report only
    if (violations.length > 0) {
      console.warn('Accessibility issues found:', violations.length);
    }
    
    // Week 3-4: Fail on critical issues
    if (violations.filter(v => v.impact === 'critical').length > 0) {
      process.exit(1);
    }
    
    // Month 2: Stricter rules
    if (violations.length > threshold) {
      process.exit(1);
    }
    

    2. Contextual Testing

    Test different user states:

    const scenarios = [
      { name: 'Logged out', setup: async () => { /* ... */ } },
      { name: 'Logged in', setup: async () => { await login(); } },
      { name: 'Admin view', setup: async () => { await loginAsAdmin(); } },
      { name: 'Mobile view', viewport: { width: 375, height: 667 } }
    ];
    
    for (const scenario of scenarios) {
      await scenario.setup?.();
      const results = await runAccessibilityTest();
      report(scenario.name, results);
    }
    

    3. Performance Optimization

    // Parallel testing for speed
    const testUrls = async (urls) => {
      const chunks = chunkArray(urls, 5); // 5 parallel
      
      for (const chunk of chunks) {
        await Promise.all(
          chunk.map(url => testAccessibility(url))
        );
      }
    };
    
    // Cache results for unchanged pages
    const cacheKey = generateHash(pageContent);
    if (cache.has(cacheKey)) {
      return cache.get(cacheKey);
    }
    

    Monitoring and Reporting

    Dashboard Setup

    // accessibility-reporter.js
    class AccessibilityReporter {
      constructor() {
        this.metrics = {
          totalTests: 0,
          passed: 0,
          failed: 0,
          violations: [],
          trends: []
        };
      }
    
      async generateReport() {
        return {
          summary: this.getSummary(),
          violations: this.getTopViolations(),
          trends: this.getTrends(),
          recommendations: this.getRecommendations()
        };
      }
    
      async sendToSlack() {
        const report = await this.generateReport();
        await slack.send({
          text: `A11y Report: ${report.summary.score}% compliant`,
          attachments: [{
            color: report.summary.score > 90 ? 'good' : 'warning',
            fields: [
              { title: 'Critical Issues', value: report.violations.critical },
              { title: 'Trend', value: report.trends.direction }
            ]
          }]
        });
      }
    }
    

    Trend Tracking

    -- Track accessibility scores over time
    CREATE TABLE accessibility_scores (
      id SERIAL PRIMARY KEY,
      commit_sha VARCHAR(40),
      branch VARCHAR(100),
      score DECIMAL(5,2),
      critical_issues INT,
      total_issues INT,
      tested_at TIMESTAMP DEFAULT NOW()
    );
    
    -- Query for trend analysis
    SELECT 
      DATE_TRUNC('week', tested_at) as week,
      AVG(score) as avg_score,
      SUM(critical_issues) as total_critical
    FROM accessibility_scores
    WHERE branch = 'main'
    GROUP BY week
    ORDER BY week DESC
    LIMIT 12;
    

    ROI Measurement

    Metrics to Track

    1. Production issues reduction: -75% in 3 months
    2. Development velocity: +20% from less rework
    3. Compliance score: From 65% to 95%
    4. User complaints: -90% accessibility related

    Cost-Benefit Analysis

    Investment (first year):
    - Tool licenses: €3,000
    - Setup time: 40 hours Γ— €100 = €4,000
    - Training: €2,000
    Total: €9,000
    
    Savings:
    - Prevented fixes: 50 issues Γ— €500 = €25,000
    - Avoided legal: €50,000 (potential)
    - Improved conversion: +2% = €30,000
    Total: €105,000
    
    ROI: 1,067%
    

    Conclusion

    Automated accessibility testing in CI/CD is no longer optional but essential for modern web development. Start small, measure results, and build toward comprehensive coverage.

    Ready to start? Try the AllyScan API for free and integrate accessibility testing into your pipeline today.

    Ready to make your website accessible?

    Start with a free scan and discover how AllyScan can help you

    Start free scan

    Related articles

    Compliance

    WCAG 2.2: The Complete Guide to Web Accessibility in 2024

    Everything you need to know about WCAG 2.2 guidelines, from new criteria to practical implementation tips for your website.

    12 min readRead more
    Business

    The Business Case for Web Accessibility: ROI and Benefits

    Discover the concrete business benefits of web accessibility, from increased conversion to legal compliance and brand value.

    10 min readRead more