Accessibility Testing #1276
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Accessibility Testing | |
| on: | |
| pull_request: | |
| branches: [main, develop] | |
| push: | |
| branches: [main] | |
| schedule: | |
| - cron: '0 6 * * 1' # Weekly on Monday at 6 AM UTC | |
| workflow_dispatch: | |
| # pull-requests: write is required for the "Comment PR with accessibility | |
| # results" step. Without it the comment step throws "Resource not | |
| # accessible by integration" and turns the whole job red even when the | |
| # tests passed. Issues: write is for label additions on follow-ups. | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| jobs: | |
| accessibility-audit: | |
| runs-on: ubuntu-latest | |
| # Workflow-wide opt-out for the strict env check in | |
| # scripts/check-env.js — `npm run build` (prebuild) AND `npm start` | |
| # (prestart) both invoke it, so a per-step env was insufficient. | |
| # Vercel builds still validate strictly because VERCEL_ENV=production | |
| # short-circuits this opt-out. | |
| env: | |
| CHECK_ENV_SKIP_IN_CI: '1' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps | |
| - name: Build application | |
| run: npm run build | |
| env: | |
| # Same opt-out pattern as formaos-quality-gates.yml: this CI | |
| # workflow does not carry production Supabase secrets, so the | |
| # strict env check in scripts/check-env.js (CHECK_ENV_STRICT=1 | |
| # in prebuild) needs to skip. Vercel builds still validate | |
| # strictly because VERCEL_ENV=production short-circuits this. | |
| CHECK_ENV_SKIP_IN_CI: '1' | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ vars.NEXT_PUBLIC_SUPABASE_URL }} | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ vars.NEXT_PUBLIC_SUPABASE_ANON_KEY }} | |
| - name: Start application | |
| run: | | |
| npm start & | |
| echo $! > app.pid | |
| env: | |
| NODE_ENV: production | |
| PORT: 3000 | |
| - name: Wait for application | |
| run: npx wait-on http://localhost:3000 --timeout 60000 | |
| - name: Create reports directory | |
| run: mkdir -p tests/accessibility/reports | |
| - name: Run Pa11y accessibility tests | |
| run: | | |
| npx pa11y-ci \ | |
| --config tests/accessibility/pa11y.config.json \ | |
| --reporter html > tests/accessibility/reports/pa11y-report.html || true | |
| npx pa11y-ci \ | |
| --config tests/accessibility/pa11y.config.json \ | |
| --reporter json > tests/accessibility/reports/pa11y-report.json || true | |
| - name: Run comprehensive accessibility audit | |
| run: node tests/accessibility/a11y-audit.js | |
| - name: Stop application | |
| if: always() | |
| run: | | |
| if [ -f app.pid ]; then | |
| kill $(cat app.pid) || true | |
| rm app.pid | |
| fi | |
| - name: Check accessibility results | |
| id: check_results | |
| run: | | |
| if [ -f tests/accessibility/reports/a11y-summary-report.json ]; then | |
| CRITICAL_ISSUES=$(node -e "console.log(JSON.parse(require('fs').readFileSync('tests/accessibility/reports/a11y-summary-report.json', 'utf8')).summary.criticalIssues)") | |
| echo "critical_issues=$CRITICAL_ISSUES" >> $GITHUB_OUTPUT | |
| if [ "$CRITICAL_ISSUES" -gt "0" ]; then | |
| echo "❌ $CRITICAL_ISSUES critical accessibility issues found" | |
| echo "status=failed" >> $GITHUB_OUTPUT | |
| else | |
| echo "✅ No critical accessibility issues found" | |
| echo "status=passed" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "No results file found" | |
| echo "status=error" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Upload accessibility reports | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: accessibility-test-results | |
| path: | | |
| tests/accessibility/reports/ | |
| retention-days: 30 | |
| - name: Comment PR with accessibility results | |
| if: github.event_name == 'pull_request' && always() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| let comment = '## ♿ Accessibility Test Results\n\n'; | |
| if ('${{ steps.check_results.outputs.status }}' === 'passed') { | |
| comment += '✅ **PASSED** - No critical accessibility issues found\n\n'; | |
| } else if ('${{ steps.check_results.outputs.status }}' === 'failed') { | |
| comment += `❌ **FAILED** - ${{ steps.check_results.outputs.critical_issues }} critical accessibility issues found\n\n`; | |
| comment += '**Critical issues must be fixed before merging.**\n\n'; | |
| } else { | |
| comment += '⚠️ **ERROR** - Unable to complete accessibility testing\n\n'; | |
| } | |
| comment += '**Tests Performed:**\n'; | |
| comment += '- WCAG 2.1 AA compliance validation\n'; | |
| comment += '- Cross-browser accessibility testing\n'; | |
| comment += '- Keyboard navigation testing\n'; | |
| comment += '- Screen reader compatibility\n'; | |
| comment += '- Color contrast validation\n\n'; | |
| comment += '**Artifacts:** Download the accessibility reports from the "Artifacts" section for detailed results.\n'; | |
| if ('${{ steps.check_results.outputs.status }}' === 'failed') { | |
| comment += '\n**Next Steps:**\n'; | |
| comment += '1. Review the detailed accessibility report\n'; | |
| comment += '2. Fix critical accessibility violations\n'; | |
| comment += '3. Re-run tests to verify fixes\n'; | |
| } | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| - name: Fail if critical issues found | |
| if: steps.check_results.outputs.status == 'failed' | |
| run: | | |
| echo "Critical accessibility issues found. Failing the build." | |
| exit 1 | |
| lighthouse-accessibility: | |
| runs-on: ubuntu-latest | |
| env: | |
| CHECK_ENV_SKIP_IN_CI: '1' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build application | |
| run: npm run build | |
| env: | |
| # Same opt-out pattern as formaos-quality-gates.yml: this CI | |
| # workflow does not carry production Supabase secrets, so the | |
| # strict env check in scripts/check-env.js (CHECK_ENV_STRICT=1 | |
| # in prebuild) needs to skip. Vercel builds still validate | |
| # strictly because VERCEL_ENV=production short-circuits this. | |
| CHECK_ENV_SKIP_IN_CI: '1' | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ vars.NEXT_PUBLIC_SUPABASE_URL }} | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ vars.NEXT_PUBLIC_SUPABASE_ANON_KEY }} | |
| - name: Start application | |
| run: | | |
| npm start & | |
| echo $! > app.pid | |
| env: | |
| NODE_ENV: production | |
| PORT: 3000 | |
| - name: Wait for application | |
| run: npx wait-on http://localhost:3000 --timeout 60000 | |
| - name: Run Lighthouse accessibility audit | |
| run: | | |
| mkdir -p tests/accessibility/reports/lighthouse | |
| # Test key pages with Lighthouse | |
| npx lighthouse http://localhost:3000 \ | |
| --only-categories=accessibility \ | |
| --output=html \ | |
| --output-path=tests/accessibility/reports/lighthouse/homepage.html \ | |
| --max-wait-for-load=120000 \ | |
| --chrome-flags="--headless --no-sandbox --disable-gpu --disable-dev-shm-usage --no-zygote" | |
| npx lighthouse http://localhost:3000/pricing \ | |
| --only-categories=accessibility \ | |
| --output=html \ | |
| --output-path=tests/accessibility/reports/lighthouse/pricing.html \ | |
| --max-wait-for-load=120000 \ | |
| --chrome-flags="--headless --no-sandbox --disable-gpu --disable-dev-shm-usage --no-zygote" | |
| - name: Stop application | |
| if: always() | |
| run: | | |
| if [ -f app.pid ]; then | |
| kill $(cat app.pid) || true | |
| rm app.pid | |
| fi | |
| - name: Upload Lighthouse reports | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: lighthouse-accessibility-results | |
| path: | | |
| tests/accessibility/reports/lighthouse/ | |
| retention-days: 30 |