name: Frontend a11y regression gate # Reusable workflow called from build.yml when frontend sources change. # # Scans the stories this branch touches in real Chromium and runs axe against # each; the check fails on any axe violation, or on a story that fails to render # at all. # # Only changed stories, because a full sweep is ~30 minutes: far too slow to sit # in front of every merge. The whole suite is scanned nightly instead # (nightly.yml), which catches anything a branch didn't touch. on: workflow_call: permissions: contents: read jobs: frontend-a11y: runs-on: ubuntu-latest timeout-minutes: 25 steps: - name: Harden Runner uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Need the base branch too, to diff against it. fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: "22" cache: "npm" cache-dependency-path: frontend/package-lock.json - name: Install Task uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0 - name: a11y gate (changed stories) run: task frontend:storybook:a11y:changed -- origin/${{ github.base_ref || 'main' }} - name: Upload scan reports # The reports carry the offending selector and help text for each # violation; without them a red run can only be understood by # reproducing the whole scan locally. if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: a11y-scan-${{ github.run_id }} path: frontend/.a11y-scan/ retention-days: 7 if-no-files-found: ignore # The reports live in a dot-directory, which upload-artifact treats as # hidden and silently skips by default. include-hidden-files: true