Skip to content

SFDAP A11y Audit (axe-core) #86

SFDAP A11y Audit (axe-core)

SFDAP A11y Audit (axe-core) #86

Workflow file for this run

# ============================================================
# SFDAP — Accessibility (axe-core) Audit
# ============================================================
# Static dashboard SPA'sına axe-core WCAG 2.0 + 2.1 A/AA tarama uygular.
# Strict mode: violation varsa job fail eder, gating görevi görür.
#
# Akış:
# 1) Python + Node kurulumu
# 2) FastAPI'yi arka planda başlat (port 8000)
# 3) /dashboard/ erişilebilir olduğunu doğrula
# 4) axe-core CLI ile WCAG 2.1 AA + WCAG 2.0 AA + WCAG 2.1 A tara
# 5) İhlal özetini CI log'una bas
# 6) Rapor artifact olarak yükle (30 gün retention)
#
# Tetikleyiciler:
# - Pull request (main)
# - Push (main)
# - Haftalık cron (yeni WCAG kuralları + dependency güncellemesi yakalamak için)
# - Manuel
# ============================================================
name: SFDAP A11y Audit (axe-core)
on:
pull_request:
branches: [main]
push:
branches: [main]
schedule:
- cron: "0 7 * * 1" # Mon 07:00 UTC
workflow_dispatch:
permissions:
contents: read
jobs:
axe:
name: ♿ axe-core WCAG 2.1 AA Tarama
runs-on: ubuntu-latest
steps:
- name: 📥 Kodu indir
uses: actions/checkout@v4
- name: 🐍 Python kur
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- name: 📦 Python bağımlılıklarını kur
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: 🟩 Node kur (axe-core/cli için)
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
# Lockfile commit'te; setup-node hash bazlı cache yapar.
# ---
# Lockfile is committed; setup-node uses it for hash-keyed caching.
cache-dependency-path: frontend/package-lock.json
- name: 📦 axe-core CLI kur
working-directory: frontend
# `npm ci` deterministik bağımlılık ağacı kurar (package-lock.json
# commit'te) — `npm install`'a göre daha hızlı ve hash-doğrulamalı.
# ---
# `npm ci` performs a deterministic install from the committed
# package-lock.json — faster than `npm install` and verifies hashes.
run: npm ci
- name: 🚀 FastAPI sunucusunu arka planda başlat
env:
# Test API key — axe taraması auth-required değil (sadece /dashboard/
# statik route'unu tarıyor) ama config validation için set ederiz.
API_KEY: "ci-test-key-not-real"
SECRET_KEY: "ci-test-secret-not-real"
ENVIRONMENT: "development"
# MQTT broker yok — listener no-op
MQTT_ENABLED: "false"
run: |
uvicorn app.main:app --host 127.0.0.1 --port 8000 &
echo "FastAPI PID: $!"
- name: ⏳ Sunucu hazır olana kadar bekle
run: |
for i in {1..30}; do
if curl -sf http://127.0.0.1:8000/api/health > /dev/null; then
echo "Server ready after ${i}s"
break
fi
sleep 1
done
# Dashboard erişilebilir mi?
curl -sfI http://127.0.0.1:8000/dashboard/ | head -3
- name: 🔧 ChromeDriver'ı Chrome ile senkronize et
# Runner imajındaki Chrome sürümü ile npx ile çekilen
# @axe-core/cli'nin ChromeDriver'ı arada uyumsuz çıkıyor
# ("This version of ChromeDriver only supports Chrome version N").
# browser-driver-manager runner'daki Chrome'a uyumlu driver yükler.
run: npx --yes browser-driver-manager install chrome
- name: ♿ axe-core taraması
working-directory: frontend
# `--exit` violation varsa exit 1 döner ve job fail eder.
# `--timeout 60` saniye headless Chrome timeout'u.
# WCAG tags: 2.0 A/AA + 2.1 A/AA — endüstri standardı gereksinim.
# Çıktı: ../axe-report.json (artifact'a yüklenir).
# ---
# `--exit` returns 1 on violations; the job fails accordingly.
# WCAG tags target both 2.0 and 2.1 A/AA — industry baseline.
run: |
npx --yes @axe-core/cli http://127.0.0.1:8000/dashboard/ \
--exit \
--timeout 60 \
--tags wcag2a,wcag2aa,wcag21a,wcag21aa \
--save ../axe-report.json
- name: 📋 İhlal özetini logla
# Strict mode'a geçtikten sonra başarısız run'larda hangi
# kuralın patladığını CI log'unda doğrudan görmek için.
# axe-report.json yüklendi mi diye `always()` ile koşar.
# ---
# Prints the violation summary directly into CI logs so a
# failing strict run is debuggable without downloading the
# JSON artifact.
if: always()
run: |
if [ ! -f axe-report.json ]; then
echo "axe-report.json yok — taramayı çalıştırma adımı başarısız olmuş olabilir."
exit 0
fi
python <<'PY'
import json
import sys
from pathlib import Path
data = json.loads(Path("axe-report.json").read_text())
# axe-cli sometimes emits a list (multi-URL) or a single dict.
pages = data if isinstance(data, list) else [data]
total = 0
for page in pages:
violations = page.get("violations", [])
if not violations:
continue
url = page.get("url", "(?)")
print(f"❌ {url} — {len(violations)} ihlal kuralı")
for v in violations:
total += 1
nodes = len(v.get("nodes", []))
print(f" [{v.get('impact','?')}] {v['id']} — {v.get('help','')} ({nodes} düğüm)")
for node in v.get("nodes", [])[:2]:
target = node.get("target", ["?"])
print(f" target: {target}")
if total == 0:
print("✅ axe-core: WCAG 2.0/2.1 A+AA için sıfır ihlal.")
else:
print(f"\nToplam {total} ihlal kuralı (strict mode → job fail).")
sys.exit(0) # actual fail comes from the previous axe step
PY
- name: 📊 axe-core rapor artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: axe-report
path: axe-report.json
retention-days: 30