DevOps & Scaling

GitHub Actions et CaptchaAI : tester les CAPTCHA en CI/CD

Un test d'intégration qui passe sur votre machine mais casse dès qu'un formulaire de connexion affiche un reCAPTCHA : voilà le scénario qui fait rougir une chaîne CI/CD sans prévenir. La réponse tient en une seule brique — déléguer la résolution du défi à l'API CaptchaAI directement depuis votre workflow GitHub Actions, pour que la suite de tests reste verte sans manipulation manuelle. Ce guide câble le mécanisme de bout en bout : déclencheurs, secrets, fichier de test pytest, matrice multi-types, mise en cache et alertes.


Pourquoi un CAPTCHA fait échouer vos tests automatisés

Sur un runner CI, vos tests s'exécutent en mode headless et finissent par tomber sur un reCAPTCHA : page de connexion, inscription, formulaire de contact. Le navigateur automatisé se bloque, le test dépasse son délai, et la chaîne vire au rouge.

Deux réflexes classiques aggravent le problème. Désactiver le CAPTCHA sur l'environnement de staging éloigne vos tests de la réalité de production. Ignorer le test concerné supprime précisément la couverture qui protège un parcours sensible. La troisième voie consiste à résoudre le défi pendant l'exécution, comme le ferait un utilisateur réel, en passant par CaptchaAI.

Le cadre reste celui du test QA : vous validez vos propres parcours protégés, sur vos propres environnements. Pensez aussi RGPD — gardez vos jeux de données de test exempts de données personnelles réelles, surtout sur des formulaires de connexion ou d'inscription.


Assembler le workflow GitHub Actions

Le workflow réagit à trois événements : chaque push sur main, chaque pull request, et une exécution planifiée une fois par semaine (cron le lundi à 6 h). Le timeout-minutes coupe court à un job qui resterait bloqué, et la clé API n'apparaît jamais dans le dépôt : elle est injectée depuis un secret GitHub via une variable d'environnement.

# .github/workflows/captcha-tests.yml
name: CAPTCHA Integration Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:

    - cron: "0 6 * * 1"  # Weekly Monday 6 AM

jobs:
  captcha-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:

      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install requests pytest

      - name: Run CAPTCHA integration tests
        env:
          CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
        run: pytest tests/test_captcha.py -v --tb=short

Le runner ubuntu-latest installe Python, ajoute requests et pytest, puis lance la suite. Tant que CAPTCHAAI_KEY est présente, l'étape de test dispose de tout ce qu'il faut pour appeler l'API.


Déclarer la clé API dans les secrets du dépôt

Le workflow lit secrets.CAPTCHAAI_KEY. Créez ce secret une seule fois :

  1. Ouvrez Paramètres → Secrets et variables → Actions
  2. Cliquez sur Nouveau secret de dépôt
  3. Nom : CAPTCHAAI_KEY
  4. Valeur : votre clé API CaptchaAI
  5. Cliquez sur Ajouter un secret

GitHub chiffre la valeur et la masque dans les journaux d'exécution : elle n'est jamais affichée en clair. Ne collez jamais la clé dans un fichier versionné, et pensez à la faire tourner régulièrement.


Rédiger le fichier de test pytest

Le module de test enveloppe un helper solve_recaptcha. Il envoie la tâche à in.php avec method=userrecaptcha et le sitekey, puis interroge res.php toutes les 5 secondes jusqu'à obtenir le token ou atteindre le timeout. Trois tests suivent : résoudre un reCAPTCHA v2 sur la page de démonstration Google, vérifier que le solde est suffisant, et valider que la clé est acceptée.

# tests/test_captcha.py
import os
import time
import pytest
import requests


API_KEY = os.environ.get("CAPTCHAAI_KEY")
BASE_URL = "https://ocr.captchaai.com"


def solve_recaptcha(site_key, page_url, timeout=90):
    """Solve reCAPTCHA v2 via CaptchaAI."""
    resp = requests.post(f"{BASE_URL}/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1,
    }, timeout=30)
    result = resp.json()
    assert result.get("status") == 1, f"Submit failed: {result}"

    task_id = result["request"]
    start = time.time()

    while time.time() - start < timeout:
        time.sleep(5)
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)
        data = resp.json()
        if data["request"] != "CAPCHA_NOT_READY":
            assert data.get("status") == 1, f"Solve failed: {data}"
            return data["request"]

    pytest.fail("CAPTCHA solve timed out")


@pytest.mark.skipif(not API_KEY, reason="CAPTCHAAI_KEY not set")
class TestCaptchaIntegration:
    """Integration tests for CAPTCHA-protected flows."""

    def test_recaptcha_v2_solve(self):
        """Verify CaptchaAI can solve reCAPTCHA v2."""
        token = solve_recaptcha(
            site_key="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
            page_url="https://www.google.com/recaptcha/api2/demo",
        )
        assert len(token) > 100
        assert token.isascii()

    def test_balance_sufficient(self):
        """Ensure account balance is enough for test suite."""
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY,
            "action": "getbalance",
            "json": 1,
        })
        balance = float(resp.json()["request"])
        assert balance > 0.50, f"Low balance: ${balance}"

    def test_api_key_valid(self):
        """Verify API key is accepted."""
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY,
            "action": "getbalance",
            "json": 1,
        })
        result = resp.json()
        assert result.get("status") == 1, f"Invalid key: {result}"

Le décorateur skipif fait dégrader la suite proprement quand le secret est absent — typiquement sur les pull requests venues de forks, où GitHub ne transmet pas les secrets. Le test de solde sert de garde-fou peu coûteux : il échoue tôt et clairement si le compte est vide, avant même de lancer une résolution.


Couvrir plusieurs types de CAPTCHA

Une matrice exécute le même job en parallèle sur plusieurs types : recaptcha-v2, turnstile et image (OCR). CaptchaAI prend en charge reCAPTCHA v2 et v3, Cloudflare Turnstile et Challenge, GeeTest v3, ainsi que les CAPTCHA image/OCR et les grilles d'images — de quoi couvrir la plupart des parcours protégés. En revanche, hCaptcha et FunCaptcha ne sont pas pris en charge : n'ajoutez pas ces types à votre matrice. Le fail-fast: false laisse tourner toutes les branches même si l'une échoue.

jobs:
  captcha-matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        captcha-type: [recaptcha-v2, turnstile, image]
      fail-fast: false

    steps:

      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install requests pytest

      - name: Run ${{ matrix.captcha-type }} tests
        env:
          CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
          CAPTCHA_TYPE: ${{ matrix.captcha-type }}
        run: pytest tests/test_${{ matrix.captcha-type }}.py -v

Mettre en cache pour éviter les résolutions inutiles

Chaque résolution mobilise un thread. Inutile de relancer une résolution complète quand le code de test n'a pas bougé. Le cache est indexé sur l'empreinte du répertoire tests/ : si un marqueur de réussite existe déjà, l'étape de résolution est sautée.


      - name: Cache test results
        uses: actions/cache@v4
        with:
          path: .test-cache
          key: captcha-tests-${{ hashFiles('tests/**') }}

      - name: Skip if cached
        id: check-cache
        run: |
          if [ -f .test-cache/passed ]; then
            echo "skip=true" >> $GITHUB_OUTPUT
          fi

      - name: Run tests
        if: steps.check-cache.outputs.skip != 'true'
        env:
          CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
        run: |
          pytest tests/test_captcha.py -v
          mkdir -p .test-cache && touch .test-cache/passed

Alerter l'équipe en cas d'échec

Un test CAPTCHA qui casse doit remonter vite. L'étape Slack ne se déclenche que sur failure() et pointe directement vers l'exécution concernée, sans polluer les runs qui passent.


      - name: Notify on failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "CAPTCHA tests failed on ${{ github.ref }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "CAPTCHA tests *failed* on `${{ github.ref }}`\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Dépannage

Problème Cause Correctif
Les tests sont ignorés avec « CAPTCHAAI_KEY non défini » Secret non configuré Ajoutez le secret dans les paramètres du dépôt
Timeout en CI alors que ça passe en local Latence réseau du runner Portez le délai d'expiration à 120 s
La vérification du solde échoue Format de clé incorrect Vérifiez que la valeur du secret ne contient pas d'espace
Le workflow ne se déclenche jamais Mauvaise configuration de branche ou de déclencheur Contrôlez le bloc on: dans le YAML

Maîtriser le coût de la résolution en CI

CaptchaAI facture au thread simultané, pas à la résolution : chaque plan inclut un nombre de résolutions illimité par thread, sans frais par CAPTCHA ni plafond quotidien. Une suite de tests CI lance rarement plus de quelques résolutions en parallèle, donc le plan BASIC ($15/mois, 5 threads) couvre la plupart des pipelines. Si vous parallélisez une grosse matrice ou plusieurs dépôts, passez à STANDARD ($30/mois, 15 threads). La facturation est en dollars US, et votre coût dépend de la concurrence choisie, pas du nombre de fois où la chaîne s'exécute.


FAQ

Combien de threads une suite de tests CI consomme-t-elle ?

En général très peu. Un job résout les CAPTCHA les uns après les autres et n'occupe qu'un ou deux threads à la fois ; le plan BASIC ($15/mois, 5 threads) suffit tant que vous ne lancez pas plusieurs matrices en parallèle. La facturation étant au thread, ce n'est pas la fréquence des exécutions qui pèse, mais la concurrence.

Comment gérer les pull requests de forks où le secret n'est pas disponible ?

GitHub ne transmet pas les secrets aux workflows déclenchés par un fork. Le décorateur skipif détecte l'absence de CAPTCHAAI_KEY et ignore proprement les tests de résolution, sans faire échouer la chaîne. Réservez la résolution complète aux push sur main et à l'exécution planifiée.

Vaut-il mieux désactiver le CAPTCHA en staging plutôt que le résoudre ?

Non, sauf cas très particulier. Désactiver le CAPTCHA en staging crée un écart avec la production : vous ne testez plus le parcours réel. Le résoudre en CI garde le test représentatif de ce que vit l'utilisateur.

CaptchaAI prend-il en charge hCaptcha dans ma matrice de tests ?

Non — pas encore pris en charge, tout comme FunCaptcha (Arkose Labs). Limitez votre matrice aux types couverts : reCAPTCHA v2/v3, Cloudflare Turnstile et Challenge, GeeTest v3, image/OCR et grilles d'images.


Guides connexes


Gardez votre pipeline au vert : passez à CaptchaAI pour vos tests CI/CD.

Les commentaires sont désactivés pour cet article.