Integrations

Puppeteer + CaptchaAI pour les tests QA dans vos workflows de navigateur

Périmètre : ce guide couvre des tests basés sur navigateur dans vos propres environnements QA et staging, ou des environnements explicitement autorisés. Il décrit la détection des widgets, la vérification des chemins backend et la documentation des exécutions. Il ne décrit pas de connexions non autorisées, ni d'exécution dissimulée, ni d'optimisation pour des sites tiers.

Un test de formulaire qui s'arrête là où le widget CAPTCHA s'affiche ne prouve rien : ni que le sitekey déployé est le bon, ni que la vérification serveur accepte le token. Une suite Puppeteer branchée sur CaptchaAI referme cette boucle en quatre temps — détecter le widget en staging, envoyer la tâche, récupérer le token, le faire valider par votre propre endpoint — et journalise chaque étape. L'article suit cet enchaînement, code Node.js à l'appui.


Ce qu'une exécution QA doit prouver

Fixez le contrat du test avant d'écrire une ligne de code. Sur un environnement que vous contrôlez, une exécution utile démontre cinq choses :

  • le widget attendu est présent, au bon endroit du formulaire ;
  • le sitekey chargé est celui de la configuration déployée, pas celui d'un autre environnement ;
  • le token obtenu est accepté par la vérification côté serveur ;
  • le temps de l'étape reste dans votre budget interne ;
  • l'exécution est rejouable à l'identique après un déploiement.

Captures d'écran, logs console et métadonnées réseau servent ensuite à expliquer un échec : la valeur d'une suite QA vient de sa transparence.


Installer Puppeteer et ouvrir une page de staging

npm install puppeteer
import puppeteer from "puppeteer";

async function openQaPage(url) {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: "networkidle2" });
  return { browser, page };
}

Une configuration minimale suffit. Ce qui rend une suite fiable n'est pas une option de lancement exotique, c'est la répétabilité : même URL de staging, mêmes fixtures, mêmes assertions. Fixez la taille de fenêtre et le fuseau horaire si votre application en dépend, laissez le reste par défaut.


Identifier le widget CAPTCHA avant toute assertion

Chaque exécution commence par un diagnostic : quel widget est actif, quel sitekey est chargé, à quelle étape du formulaire.

async function detectCaptchaWidget(page) {
  return page.evaluate(() => {
    const recaptcha = document.querySelector(".g-recaptcha[data-sitekey]");
    if (recaptcha) {
      return {
        kind: "recaptcha_v2",
        sitekey: recaptcha.getAttribute("data-sitekey"),
        step: recaptcha.closest("form")?.getAttribute("data-step") || "unknown",
      };
    }

    const turnstile = document.querySelector(".cf-turnstile[data-sitekey]");
    if (turnstile) {
      return {
        kind: "turnstile",
        sitekey: turnstile.getAttribute("data-sitekey"),
        step: turnstile.closest("form")?.getAttribute("data-step") || "unknown",
      };
    }

    return null;
  });
}

Un retour null est déjà une information : soit le widget n'a pas été rendu, soit le sélecteur a changé. Traitez ce cas comme un échec explicite, jamais comme un « pas de CAPTCHA, on continue » — c'est ainsi qu'une dérive de configuration atteint la production.


Déléguer la résolution du défi CAPTCHA à CaptchaAI

L'état du widget connu, l'exécution crée une tâche CaptchaAI, interroge le résultat, puis journalise le tout pour la chaîne de test interne.

async function solveWithCaptchaAi(apiKey, widget, pageUrl) {
  const body = new URLSearchParams({
    key: apiKey,
    method: widget.kind === "turnstile" ? "turnstile" : "userrecaptcha",
    pageurl: pageUrl,
    json: "1",
  });

  if (widget.kind === "turnstile") {
    body.set("sitekey", widget.sitekey);
  } else {
    body.set("googlekey", widget.sitekey);
  }

  const submit = await fetch("https://ocr.captchaai.com/in.php", {
    method: "POST",
    body,
  });
  const submitJson = await submit.json();
  if (submitJson.status !== 1) {
    throw new Error(`CaptchaAI submit failed: ${submitJson.request}`);
  }

  for (let attempt = 0; attempt < 30; attempt += 1) {
    await new Promise((resolve) => setTimeout(resolve, 5000));
    const poll = await fetch(
      `https://ocr.captchaai.com/res.php?key=${apiKey}&action=get&id=${submitJson.request}&json=1`
    );
    const pollJson = await poll.json();
    if (pollJson.status === 1) {
      return pollJson.request;
    }
  }

  throw new Error("CaptchaAI polling timed out during QA test");
}

Deux points d'attention. La clé API ne se code jamais en dur : elle vient d'une variable d'environnement injectée par votre CI, au même titre que les identifiants de staging. Et le token obtenu reste un artefact de diagnostic, rattaché à une exécution précise : ni à conserver, ni à mutualiser entre tests. Demandez une résolution au moment où le test en a besoin.


Refermer la boucle sur votre endpoint de vérification

Une exécution propre ne s'arrête pas au token : elle se termine sur votre propre contrôle, via un endpoint dédié au staging.

async function verifyQaRun(token, widget, testRunId) {
  const response = await fetch("https://staging.example-app.test/qa/captcha/verify", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      token,
      widgetType: widget.kind,
      sitekey: widget.sitekey,
      expectedStep: widget.step,
      testRunId,
    }),
  });

  if (!response.ok) {
    throw new Error(`QA verification returned ${response.status}`);
  }

  return response.json();
}

Le testRunId transforme une suite bavarde en suite exploitable : il relie la capture d'écran, la ligne de log, la tâche envoyée et la réponse du backend. Quand une exécution nocturne échoue, vous remontez la chaîne en une requête.


Volumétrie et coût : dimensionner les threads

La capacité d'un plan CaptchaAI se mesure en threads, soit en résolutions simultanées. Une suite séquentielle n'en consomme qu'un ; un job GitHub Actions qui lance six navigateurs en parallèle en consomme six.

  • BASIC ($15/mois, 5 threads) couvre une suite de non-régression nocturne modérément parallélisée.
  • STANDARD ($30/mois, 15 threads) absorbe plusieurs pipelines simultanés, typiquement une équipe qui teste plusieurs environnements en même temps.

La facturation est en dollars US. Réglez le parallélisme du runner sur le nombre de threads du plan : au-delà, vos tâches attendent en file et vos délais d'expiration se déclenchent sans qu'aucune régression réelle soit en cause.


Dépannage : les pannes les plus fréquentes

Problème Cause Correctif
Le widget n'est pas détecté Sélecteur DOM obsolète Vérifier les sélecteurs sur la page de staging actuelle
Mauvais sitekey en QA Dérive de configuration entre environnements Comparer les variables d'environnement et la configuration front
La tâche CaptchaAI part en timeout Parallélisme supérieur aux threads du plan Aligner le parallélisme du runner, puis rallonger le délai d'expiration
L'endpoint QA renvoie 422 Champs attendus manquants dans le payload Aligner le schéma de requête avec l'équipe backend
Les résultats sont peu reproductibles Données de test changeantes Utiliser des comptes et des fixtures de staging figés

Intégrer la suite à votre CI sans la rendre instable

Faites tourner ces tests sur un runner proche de votre staging — une instance OVHcloud ou Scaleway pour une application hébergée en France, une région AWS eu-west-3 si votre pipeline y vit déjà. Sinon la latence réseau se lit dans les temps de résolution et fausse vos budgets.

Côté données, restez sur des fixtures : comptes de test dédiés, adresses e-mail internes, aucun jeu de données extrait de la production. C'est la façon la plus simple de tenir vos obligations RGPD hors production, et les exécutions y gagnent en déterminisme.

Isolez enfin ce scénario dans un job distinct de vos tests unitaires. Une résolution dure quelques secondes ; noyée dans une suite rapide, elle finit désactivée « le temps de débloquer la CI ».


FAQ

CaptchaAI prend-il en charge hCaptcha pour ce type de test ?

Non — hCaptcha n'est pas pris en charge, pas plus que FunCaptcha (Arkose Labs). Cette suite couvre reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile et GeeTest v3. Si votre staging affiche hCaptcha, prévoyez un mode de test qui neutralise le widget côté serveur.

Faut-il un environnement de staging, ou puis-je tester en production ?

Un environnement de staging, ou un environnement pour lequel vous détenez une autorisation explicite. Ces tests écrivent dans des formulaires : les lancer sur un site tiers sort du périmètre décrit ici.

Comment éviter que le test échoue au moment du polling ?

Comparez d'abord le parallélisme réel de votre CI aux threads de votre plan, puis observez la distribution des temps de résolution sur plusieurs nuits avant d'ajuster le délai d'expiration. Un timeout fixé à l'intuition produit des échecs aléatoires.

Puis-je réutiliser un token d'une exécution à l'autre pour accélérer la suite ?

Non. Les tokens ont une durée de vie courte et restent liés à la page qui les a demandés ; les stocker rend les tests faussement verts. Demandez une résolution à chaque exécution.


Guides connexes

Une suite qui prouve quelque chose vaut mieux qu'une suite qui passe : mesurez vos temps de résolution sur votre propre staging avec CaptchaAI.

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