Intégration CI/CD

Ajoutez des vérifications d'accessibilité à votre chaîne de déploiement. AllyProof peut bloquer ou signaler les demandes de fusion qui introduisent des non-conformités WCAG, et détecter ainsi les régressions avant la mise en production.

Business inclut les clés d'API, la CLI, les outils MCP authentifiés et l'intégration CI/CD. Les propriétaires et administrateurs de l'organisation peuvent créer des clés. Free n'inclut pas les clés d'API ; l'outil MCP public scan_url reste disponible sans clé.

Générer une clé d'API

Rendez-vous dans Réglages > Clés API et cliquez sur Créer une clé. Donnez-lui un nom explicite (par exemple github-actions-ci) et copiez la clé immédiatement : elle n'est affichée qu'une seule fois.

Conservez la clé comme secret dans votre environnement d'intégration continue :

  • GitHub Actions : Settings > Secrets > ALLYPROOF_API_KEY
  • GitLab CI : Settings > CI/CD > Variables > ALLYPROOF_API_KEY

Le point d'accès d'analyse

Déclenchez une analyse en envoyant une requête POST à l'API. Le site doit déjà être ajouté et vérifié dans votre tableau de bord AllyProof.

POST https://allyproof.com/api/v1/scan
x-api-key: <API_KEY>
Content-Type: application/json

{
  "url": "https://example.com",
  "threshold": 85,
  "wait": true,
  "max_pages": 20,
  "source": "github_actions"
}

Paramètres de la requête

ChampTypeObligatoireDescription
urlchaîne (URL)OuiL'URL du site vérifié à analyser.
thresholdnombre (0-100)NonScore minimal pour réussir. Valeur par défaut : 0 (réussite systématique).
waitbooléenNonSi true, maintient la connexion jusqu'à la fin de l'analyse (jusqu'à 270 s, puis renvoie une poll_url). Convient seulement aux petits sites : préférez le mode asynchrone avec interrogation. Valeur par défaut : false.
max_pagesnombre (1-100)NonNombre maximal de pages à analyser. S'il est omis, toutes les pages découvertes sont analysées, dans la limite configurée pour le site.
sourcechaîneNonLe système qui a déclenché l'analyse. Affiché dans la colonne Source de l'historique du site. Valeur par défaut : api.

Nommer la source

L'historique d'un site liste toutes ses analyses, quelle qu'en soit l'origine : le tableau de bord, les exécutions planifiées, l'interface en ligne de commande, les outils MCP et vos chaînes d'intégration. Le champ source est ce qui distingue un contrôle de mise en production d'un clic sur « Scan Now » trois semaines plus tard.

Renseignez-le avec l'une des valeurs github_actions, gitlab_ci, jenkins, circleci, bitbucket, azure_pipelines, other_ci. Les graphies courantes comme github ou gitlab sont acceptées et normalisées ; toute valeur non reconnue retombe sur api au lieu de faire échouer la requête, afin qu'une coquille dans un fichier de flux ne puisse jamais casser une construction.

Pourquoi vous devez l'indiquer. Les exemples ci-dessous exécutent curl dans votre exécuteur, et curl s'identifie comme curl : l'identité de l'exécuteur ne nous parvient jamais. Nous préférons demander plutôt que deviner et étiqueter votre chaîne GitLab comme GitHub. L'interface en ligne de commande fait exception : elle lit GITHUB_ACTIONS, GITLAB_CI, JENKINS_URL et leurs équivalents dans l'environnement et déclare d'elle-même la bonne source ; allyproof scan dans une chaîne ne demande donc aucune configuration.

Remarque : la clé d'API se transmet par l'en-tête x-api-key, et non par l'en-tête Authorization.

Formats de réponse

Synchrone (wait: true)

Lorsque wait vaut true, l'API maintient la connexion ouverte jusqu'à la fin de l'analyse et renvoie directement les résultats. Recommandé uniquement pour les petits sites : une connexion HTTP maintenue longtemps peut être coupée par un mandataire ou une passerelle d'entreprise avant la fin de l'analyse. Pour les chaînes d'intégration, utilisez le mode asynchrone ci-dessous : déclenchez, puis interrogez par de courtes requêtes.

{
  "pass": true,
  "score": 92,
  "threshold": 85,
  "scan_id": "scan-uuid",
  "site": { "name": "My Site", "url": "https://example.com" },
  "summary": {
    "pages_scanned": 12,
    "pages_failed": 0,
    "total_violations": 3,
    "critical": 0,
    "serious": 1,
    "moderate": 2,
    "minor": 0
  },
  "duration_ms": 14200,
  "top_violations": [
    {
      "rule_id": "color-contrast",
      "impact": "serious",
      "description": "Elements must meet minimum color contrast ratio thresholds",
      "pages_affected": 3
    }
  ],
  "pages": [
    { "url": "example.com/", "violations": 1, "critical": 0 },
    { "url": "example.com/about", "violations": 2, "critical": 0 }
  ],
  "dashboard_url": "https://allyproof.com/sites/site-uuid"
}

Asynchrone (par défaut)

Sans wait: true, l'API répond immédiatement avec un identifiant d'analyse et une URL d'interrogation. La réponse porte le code HTTP 202 Accepted.

{
  "scan_id": "scan-uuid",
  "status": "pending",
  "poll_url": "/api/v1/scan?id=scan-uuid",
  "message": "Scan started. Poll the poll_url for results, or use wait=true to block."
}

Pour obtenir les résultats, interrogez l'URL fournie dans la réponse avec la même clé d'API :

GET https://allyproof.com/api/v1/scan?id=scan-uuid&threshold=85
x-api-key: <API_KEY>

Répétez le threshold dans l'URL d'interrogation. La réponse calcule pass à partir du seuil présent dans sa propre URL ; sans lui, toute analyse terminée réussit.

Configurer le seuil

Le paramètre threshold fixe le score d'accessibilité minimal acceptable. À la fin de l'analyse :

  • si le score atteint ou dépasse le seuil, la réponse contient "pass": true ;
  • s'il est inférieur au seuil, la réponse contient "pass": false.

Utilisez cette valeur dans votre script pour décider de faire réussir ou échouer l'étape. Un seuil de 85 constitue un point de départ raisonnable pour la plupart des sites. Relevez-le progressivement à mesure que vous corrigez les non-conformités existantes.

Exemple GitHub Actions

À enregistrer sous .github/workflows/accessibility.yml :

name: Accessibility Check

on:
  pull_request:
    branches: [main]

jobs:
  a11y-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Run AllyProof scan
        id: scan
        env:
          ALLYPROOF_API_KEY: ${{ secrets.ALLYPROOF_API_KEY }}
          SITE_URL: ${{ vars.SITE_URL }}
        run: |
          set -euo pipefail
          API=https://allyproof.com/api/v1/scan
          THRESHOLD=85
          START=$(curl -sS -X POST "$API" \
            -H "x-api-key: $ALLYPROOF_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"url\": \"$SITE_URL\", \"threshold\": $THRESHOLD, \"source\": \"github_actions\"}")
          SCAN_ID=$(echo "$START" | jq -r '.scan_id // empty')
          if [ -z "$SCAN_ID" ]; then
            echo "Could not start the scan: $START"
            exit 1
          fi
          # Poll for up to 20 minutes. Repeat the threshold: the poll
          # response computes pass against the threshold in its own URL.
          RESULT=""
          for _ in $(seq 1 80); do
            RESULT=$(curl -sS "$API?id=$SCAN_ID&threshold=$THRESHOLD" -H "x-api-key: $ALLYPROOF_API_KEY")
            case "$(echo "$RESULT" | jq -r '.status // "error"')" in
              completed|failed|error) break ;;
            esac
            sleep 15
          done
          echo "$RESULT" | jq '{status, score, threshold, pass, summary, dashboard_url, error}'
          {
            echo "score=$(echo "$RESULT" | jq -r '.score // empty')"
            echo "violations=$(echo "$RESULT" | jq -r '.summary.total_violations // empty')"
            echo "url=$(echo "$RESULT" | jq -r '.dashboard_url // empty')"
            echo "pass=$(echo "$RESULT" | jq -r '.pass == true')"
          } >> "$GITHUB_OUTPUT"

      - name: Comment on PR
        if: always() && steps.scan.outputs.score != ''
        uses: actions/github-script@v7
        with:
          script: |
            const score = '${{ steps.scan.outputs.score }}';
            const violations = '${{ steps.scan.outputs.violations }}';
            const url = '${{ steps.scan.outputs.url }}';
            const passed = '${{ steps.scan.outputs.pass }}' === 'true';
            const emoji = passed ? '✅' : '❌';

            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `${emoji} **AllyProof Scan**\n\nScore: **${score}/100** | Violations: **${violations}**\n\n[View full report](${url})`
            });

      - name: Fail unless the scan passed
        if: steps.scan.outputs.pass != 'true'
        run: |
          echo "Accessibility gate did not pass (score: ${{ steps.scan.outputs.score }})"
          exit 1

Les deux exemples échouent par défaut : le build ne réussit que si l'analyse se termine avec un score égal ou supérieur au seuil. Une clé d'API refusée, une analyse en échec ou une analyse toujours en cours après 20 minutes fait échouer l'étape.

Exemple GitLab CI

À enregistrer sous .gitlab-ci.yml :

accessibility:
  stage: test
  image: alpine:latest
  before_script:
    - apk add --no-cache curl jq
  script:
    - |
      set -eu
      API=https://allyproof.com/api/v1/scan
      THRESHOLD=85
      START=$(curl -sS -X POST "$API" \
        -H "x-api-key: $ALLYPROOF_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"url\": \"$SITE_URL\", \"threshold\": $THRESHOLD, \"source\": \"gitlab_ci\"}")
      SCAN_ID=$(echo "$START" | jq -r '.scan_id // empty')
      if [ -z "$SCAN_ID" ]; then echo "Could not start the scan: $START"; exit 1; fi
      RESULT=""
      for _ in $(seq 1 80); do
        RESULT=$(curl -sS "$API?id=$SCAN_ID&threshold=$THRESHOLD" -H "x-api-key: $ALLYPROOF_API_KEY")
        case "$(echo "$RESULT" | jq -r '.status // "error"')" in
          completed|failed|error) break ;;
        esac
        sleep 15
      done
      echo "$RESULT" | jq '{status, score, threshold, pass, error}'
      if [ "$(echo "$RESULT" | jq -r '.pass == true')" != "true" ]; then
        echo "Accessibility gate did not pass - failing pipeline"
        exit 1
      fi
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Bonnes pratiques

  • Lancez les analyses sur les demandes de fusion, et pas seulement à la fusion dans la branche principale : vous détecterez les régressions plus tôt.
  • Commencez avec un seuil bas (par exemple 70) et relevez-le progressivement à mesure que vous corrigez les non-conformités existantes.
  • Utilisez max_pages pour restreindre l'étendue de l'analyse et accélérer les exécutions.
  • Configurez les notifications par courriel en complément, afin que les parties prenantes voient les résultats même sans consulter la chaîne d'intégration.
  • Utilisez dashboard_url dans les commentaires de demande de fusion, pour que les personnes développeuses accèdent directement au rapport complet.
  • Renseignez source, afin que l'historique indique quelle chaîne a lancé chaque analyse : sans cela, toute analyse automatisée apparaît comme un appel d'API générique.

Limites

  • L'analyse automatisée détecte des schémas d'échec courants au regard des WCAG 2.2 niveau AA, non l'ensemble des défaillances, et une construction au vert n'est pas un résultat de conformité. Les critères qui exigent un jugement humain - pertinence d'une alternative textuelle, ordre de lecture logique - ne sont pas testés du tout.
  • Les analyses lancées avec wait: true renvoient une poll_url au bout de 270 secondes si l'analyse est toujours en cours. Pour tout site qui n'est pas de petite taille, utilisez le mode asynchrone avec interrogation.
  • Le site doit être vérifié dans votre tableau de bord AllyProof avant que l'API puisse l'analyser.