Marble Minotaur

Auditeur QA automatisé

Les tests générés par Claude étaient verts. L'appli ne marchait pas. 404 partout, écrans vides, boutons morts, trous de sécurité. Il fallait un minotaure qui se balade dans l'appli comme un vrai utilisateur, qui cartographie ce qu'il voit, et qui audite ce qu'il trouve.

Top Actions : les douze corrections à plus fort impact, ici sur tordu-jardin.fr
Les findings classés par sévérité et filtrables par auditor
Le graphe : zones chaudes, problèmes systémiques et pages isolées
L'analyse fonctionnelle : cohérence des gabarits de page, doublons, manques et incohérences

Le déclic

Granit Golem me servait de laboratoire pour évaluer Claude Code en conditions réelles. Un jour, le constat me saute aux yeux : les tests générés par l’agent étaient verts. Ils tournaient en CI, ils étaient cochés « OK ». Et pourtant, j’ouvrais l’appli dans un navigateur et je trouvais des 404 partout, des écrans vides, des boutons qui ne déclenchaient aucune requête, des trous de sécurité qui sautaient aux yeux.

Les tests verts mentaient. Ils vérifiaient ce que le code faisait, pas ce qu’il devait faire. Un test qui simule le bon retour d’une route cassée passe au vert tout en cachant le bug. Multipliez ça par cinquante routes nées dans la même rafale, et vous obtenez une appli truffée de mines.

Vous voyez le problème venir ? Comment poser un socle de confiance sur une appli dont une partie a été produite à la chaîne ? Comment savoir, sans relire chaque ligne, que les parcours critiques tiennent debout ? Chez siliceum, c’est notre métier, et on le fait à la main : prendre une appli, l’auditer, verrouiller ses invariants. Long, ingrat, jamais exhaustif, et même pas de quoi cartographier l’architecture au passage.

Et si je laissais une bestiole faire la première ronde à ma place ?

Le concept

Un agent autonome qui se déplace dans l’appli comme un minotaure dans son labyrinthe. Il part d’une URL, il explore, il cartographie, il audite. Pas un crawler bête : un auditeur qui comprend ce qu’il visite.

Le minotaure dans le labyrinthe
/ /blog /projets /a-propos /contact M
Il part de l'accueil, suit les liens de page en page, et note tout ce qu'il croise au passage.

L’outil tient en trois étages, montés dans cet ordre parce que chacun débloque le suivant.

Étage 1 : le déplacement

Avant de cartographier quoi que ce soit, le minotaure doit savoir entrer. Je lui donne des identifiants, il repère le formulaire de login, le remplit, valide, et détecte que l’état a changé (en clair : il sait dire « j’étais dehors, me voilà dedans »). C’est sa première heuristique embarquée.

Ensuite il déroule. Playwright en mode headless, navigation page par page, et l’enregistrement de toutes les requêtes réseau au passage. Chaque clic laisse une trace.

Étage 2 : la cartographie

C’est l’étage qui rend l’outil intelligent. Si l’appli liste cinquante projets, chacun avec sa propre URL, inutile de tous les auditer. Le minotaure applique une heuristique de similarité : il en visite quelques-uns, estime qu’il a vu le modèle, et passe à la suite. Imparfait, mais un échantillon suffit à dégager un gabarit de page.

Le résultat n’est pas une liste plate d’URLs. C’est une carte hiérarchisée : depuis la page A, je peux atteindre la page B ; voici la feature primaire, voici la secondaire. À cette carte s’ajoutent deux choses.

  • Un dictionnaire métier, l’ensemble des termes récurrents extraits du contenu. C’est ce qui permet à un agent réutilisant les données de parler la langue de l’appli, pas un anglais générique.
  • Un catalogue d’actions fonctionnelles : pour une ressource « projet », le minotaure repère qu’on peut la lister, la voir, l’éditer, la supprimer. Le CRUD émerge de l’observation, pas d’une lecture du code.

Étage 3 : l’audit

Une fois la carte construite, je superpose les audits. Pendant le parcours, le minotaure capture les traces HTTP, la complexité du DOM, le poids du CSS. Après coup, il rejoue tout ça : il challenge la solidité technique (perf, accessibilité, en-têtes de sécurité, poids des bundles) et traque les ratés fonctionnels, à commencer par le bouton qui ne déclenche rien.

Aujourd’hui, trente-sept auditors balaient l’appli sous tous ces angles. J’en ajoute un dès que je tombe sur un cas à vérifier.

Le bestiaire des auditors

Un auditor, c’est une petite unité qui sait répondre à une seule question : les en-têtes de sécurité sont-ils là ? Les images ont-elles un texte alternatif ? Le formulaire de login encaisse-t-il un mot de passe vide sans tout casser ? Trente-sept questions de ce genre, rangées par domaine, activables une à une ou en bloc.

Le bestiaire des auditors37 auditors

SEO & contenu5
  • seotitle, meta, H1 unique, canonical, Open Graph
  • structured-dataJSON-LD, Twitter Card, favicon, manifest
  • i18nclés de traduction oubliées, visibles dans le DOM
  • link-rotliens externes morts (4xx/5xx)
  • editorial-consistencynom de marque mentionné de façon cohérente
Accessibilité2
  • accessibilityalt, labels, landmarks + axe-core WCAG 2.1 AA
  • test-idscouverture data-testid sur les éléments interactifs
Sécurité & conformité7
  • security-headersHSTS, CSP, X-Frame-Options, Referrer-Policy
  • csp-evalCSP en profondeur : unsafe-inline, wildcard, frame-ancestors
  • tls-auditTLS 1.2+, HSTS, Certificate Transparency, ciphers
  • auth-securityJWT : algorithme faible, exp manquant, stockage
  • cookie-consentRGPD : cookies de tracking posés avant consentement
  • state-leakstokens et secrets qui s'accumulent dans le storage
  • exposure-probe/.git, /.env, source maps, bannières serveur
Performance & poids8
  • performanceTTFB, LCP, CLS, nombre et poids des ressources
  • dom-metricstaille et profondeur du DOM
  • render-blockingscripts bloquants, CSS critique au-delà de 14 ko
  • resource-prioritieslazy-loading, preload/prefetch, hints manquants
  • font-rendering@font-face bloquantes, font-display swap
  • image-optimizationformats anciens, images surdimensionnées, srcset
  • imagesimages cassées, requêtes 4xx/5xx
  • delivery-hygienecompression, cache, mixed content http/https
Résilience5
  • error-boundaryerreurs JS contenues ou crash global ? (test par injection)
  • dom-stabilityDOM déterministe d'une navigation à l'autre
  • page-lifecyclesurvie au refresh et au bouton retour
  • navigation-resiliencedeep-link et refresh sur pages internes
  • network-resiliencecomportement en réseau dégradé ou API coupée
Fonctionnel7
  • formschamps requis, état du bouton, soumission test
  • validation-qualityRFC 7807, pas de 5xx sur une entrée invalide
  • api-contractcohérence status / content-type / structure JSON
  • api-dependency-mapcartographie des dépendances entre API
  • dead-actionsboutons qui ne déclenchent rien (clic réel)
  • dead-uiéléments orphelins, href vides, inputs hors form
  • auth-flowparcours d'authentification, logout optionnel
Structure & UX3
  • responsive3 breakpoints : overflow, cibles sous 44px, viewport
  • bundle-healthpoids du bundle, duplication, tree-shaking
  • page-templatecohérence des gabarits entre routes

Sept domaines, trente-sept questions. Activables à la carte, par tag, ou en bloc via un preset.

Sur le papier, une liste de vérifications. À l’usage, ce que ça remonte est plus parlant. Trois prises réelles, du tonneau de ce que le minotaure trouve sur à peu près n’importe quel site.

La prochaine étape : les invariants fonctionnels

Le déplacement, la cartographie et l’audit technique tournent. Le palier suivant est plus ambitieux : interroger l’appli sur ses invariants fonctionnels.

Certains, le minotaure les vérifie déjà. Le bouton mort, par exemple (la sonde dead-actions clique vraiment les éléments et regarde si quelque chose se passe). D’autres sont en chantier.

  • Quand je m’authentifie, y a-t-il un rate limit, et au bout de combien d’essais ?
  • Un mot de passe oublié sur un email inexistant : l’appli avoue-t-elle que le compte n’existe pas (le grand classique de l’énumération) ?
  • Les messages d’erreur sont-ils tous rattrapés et renvoient-ils une notification propre, ou reste-t-il des trous qui crachent une stack trace ?

Au-delà de ces invariants techniques, je veux à terme remonter à l’utilisateur des invariants métier candidats. Le minotaure propose : « deux projets ne peuvent pas porter le même nom dans une même organisation, c’est bien ça ? ». Vous confirmez ou vous corrigez. Et la base d’invariants se constitue d’elle-même, passage après passage.

C’est cette base qui devient le socle de tests automatisés : une fois les invariants stabilisés, ils se rejouent sans intervention, et la régression se signale dès qu’elle les casse.

Où ça en est

Ça, c’est la cible. Le présent est plus modeste, et déjà bien suffisant.

C’est une application en ligne de commande. La CLI tourne, le crawl déroule, les audits sortent en JSON propre. Et depuis fin mai, le rapport a changé de standing. Fini la page HTML qui recrachait la donnée brute : le minotaure produit maintenant un tableau de bord autonome, en thème paper. Sept vues le découpent : les actions prioritaires, les pages, le graphe, les problèmes, les réussites, les gates, et l’analyse fonctionnelle. Un seul fichier, qu’on envoie par mail et qu’on ouvre sans rien installer.

Il sait aussi lire le fonctionnel, et c’est le chantier du moment. Le JourneyEngine fusionne un graphe d’entités heuristique avec une couche sémantique tirée d’un LLM : le minotaure ne se contente plus de lister des pages, il devine les parcours, relie les ressources à leurs API, et nomme ce qu’il voit.

De là, j’étudie la cohérence fonctionnelle de l’appli. Le minotaure compare les pages d’un même type (les listes entre elles, les formulaires entre eux) et remonte les doublons, les manques, les incohérences. Il vérifie au passage qu’un même gabarit garde la même grammaire visuelle d’une route à l’autre : quand deux pages « liste » n’ont ni la même taille de titre ni le même espacement, il le note. La question n’est plus « est-ce que ça marche », mais « est-ce que ça se tient ».

Marble produit du JSON, Granit Golem le consomme. Le minotaure crawle et audite, le golem analyse, garde la mémoire, et repère la dérive d’un audit à l’autre.

Je m’en sers en interne avant chaque livraison siliceum, pour ne pas laisser passer une régression SEO, d’accessibilité ou de sécurité. Onze auditors y sont d’ailleurs câblés sur nos volets d’analyse QA, du TLS au RGPD.

Ce qu’il reste à faire

Le feedback en temps réel

Le rapport est lisible, le plus dur est passé. Manque la dimension vivante : suivre le minotaure pendant qu’il explore, voir la carte se construire, les audits s’accrocher aux pages au fil du crawl. Aujourd’hui je lance, j’attends, je lis. J’aimerais regarder.

La parallélisation avec dépendances

L’outil est lent parce qu’il navigue pour de vrai sur la cible, page après page, en série. L’envie naïve, c’est de paralléliser. Sauf qu’il y a un arbre de dépendances entre les pages. Je crée un projet sur la page A, j’en récupère un identifiant ; la page B qui dépend de ce projet réclame cet identifiant. Il faut hydrater la donnée, récupérer le résultat d’une étape pour le réinjecter ailleurs. Je peux paralléliser les branches indépendantes de l’arbre, pas les chaînes de dépendance. C’est sans doute le chantier le plus intéressant techniquement.

Le socle de tests automatisés

Une fois les invariants stabilisés et le temps réel en place, l’outil devient producteur de tests, et plus seulement auditeur. C’est l’objectif lointain mais assumé : Marble pose le filet de sécurité que les générateurs de code n’ont pas su poser eux-mêmes.

Stack technique

Langage

TypeScript

Typage fort pour 37 auditors modulaires

Crawl & navigation

Playwright

Navigateur headless pour crawler et auditer chaque page

Tests

Vitest

Tests unitaires des auditors