AI-powered diagnostic for Playwright test failures — ~600 lines replace 23K lines of MCP
Project description
🚀 QA Autopilot
Plugin pytest — Diagnostic IA des échecs Playwright en temps réel
Installation • Quick Start • Scorecard • Comment ça marche • Configuration
🎯 Le problème
Un test Playwright échoue. Le message d'erreur dit :
TimeoutError: Page.click: Timeout 5000ms exceeded.
waiting for locator("a[href='/international/']")
Le sélecteur est bon. L'élément existe. Alors pourquoi ça marche pas ?
Parce que le bandeau cookies recouvre tout. Ou parce que l'élément est dans un iframe. Ou parce que le DOM a été rechargé en AJAX. Ou parce que le bouton est disabled. Ou parce que tu fais un click() au lieu d'un dblclick().
QA Autopilot diagnostique la vraie cause en une seule commande.
📊 Scorecard
Résultats sur une suite de 7 tests pièges conçus pour piéger les outils de diagnostic :
| Test | Piège | Diagnostic IA | Catégorie | Confiance |
|---|---|---|---|---|
| 🫣 Overlay cookies | Élément recouvert par bandeau | ✅ Bandeau cookies bloque le click | element_obscured |
🟢 95% |
| 🖼️ Iframe invisible | Élément dans iframe, cherché dans main frame | ✅ Contexte iframe manquant | iframe_context |
🟢 95% |
| 👻 Stale AJAX | Locator capturé avant rechargement DOM | ✅ Référence obsolète après AJAX | stale_reference |
🟢 95% |
| ↪️ Redirect silencieux | URL redirigée 301/302 | ✅ Test PASSED (piège détecté) | — | ✅ |
| 🚫 Bouton disabled | Élément visible mais disabled | ✅ Attribut disabled détecté | element_disabled |
🟢 95% |
| 🔤 Regex Unicode | Zinedine vs Zinédine |
✅ Mismatch accent dans regex | encoding_mismatch |
🟢 95% |
| 🫣 Double-click | Consent manager intercepte le click | ✅ Overlay détecté | element_obscured |
🟢 95% |
6/6 diagnostics corrects à 95% de confiance — le 7ème test PASSED (pas de diagnostic nécessaire).
⚠️ Limitations
[!CAUTION] Tests de +200 lignes : le contexte envoyé à l'IA est volontairement tronqué. Un test E2E doit rester court — un scénario, une responsabilité, moins de 50 lignes. Au-delà, c'est un problème de conception, pas de diagnostic. Refactorisez vos tests avant de chercher la cause d'un échec.
📦 Installation
QA Autopilot est un module Python natif — disponible directement sur PyPI :
pip install qa-autopilot
C'est tout. Pas de config, pas de serveur, pas de compte. Une ligne.
Avec le chargement automatique du .env :
pip install qa-autopilot[dotenv]
Ou depuis les sources :
git clone https://github.com/julienmerconsulting/qa-autopilot.git
cd qa-autopilot
pip install -e .
Prérequis
playwright install chromium
Configuration .env
Crée un fichier .env à la racine de ton projet :
# OpenAI (défaut)
OPENAI_API_KEY=sk-...
# Ou DeepSeek
BASE_URL=https://api.deepseek.com
API_KEY=sk-...
QA_MODEL=deepseek-chat
# Ou Ollama local (zéro coût)
BASE_URL=http://localhost:11434/v1
API_KEY=ollama
QA_MODEL=llama3
⚡ Quick Start
Mode pytest (recommandé)
Ajoute un seul flag à ta commande pytest :
pytest tests/ --qa-autopilot -v
C'est tout. Chaque test en échec reçoit un diagnostic IA automatique.
Avec rapport HTML
pytest tests/ --qa-autopilot --html=qa-reports/rapport.html --self-contained-html -v
Mode standalone
python -m qa_autopilot tests/test_checkout.py
python -m qa_autopilot tests/test_login.py::test_auth
python -m qa_autopilot tests/ -k "checkout" --headed
Mode import direct
from qa_autopilot import QAInterceptor
# Dans ton test
interceptor = QAInterceptor(page)
interceptor.start()
# ... ton test ...
# En cas d'échec
diagnosis = interceptor.diagnose(error_message, "test_file.py")
print(diagnosis["root_cause"])
print(diagnosis["category"])
🔍 Comment ça marche
┌─────────────────────────────────────────────────────┐
│ TON TEST PLAYWRIGHT │
│ │
│ page.goto("https://example.com") │
│ page.click("#submit") ← FAIL │
│ expect(page).to_have_url(...) │
└──────────────────────┬──────────────────────────────┘
│
┌─────────────▼─────────────┐
│ QA AUTOPILOT HOOK │
│ (écoute en parallèle) │
└─────────────┬─────────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌───────────┐
│ DOM │ │ RÉSEAU │ │ CONSOLE │
│Listener│ │ Capture │ │ Capture │
│ (JS) │ │ req/res │ │ err/warn │
└───┬────┘ └────┬─────┘ └─────┬─────┘
│ │ │
└────────────────┼───────────────────┘
│
┌───────────▼───────────┐
│ BUNDLE CONTEXTE │
│ code + erreur + DOM │
│ + réseau + console │
│ + screenshot (opt) │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ UN PROMPT → IA │
│ (12 catégories) │
│ diagnostic + fix │
└───────────┬───────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────┐ ┌───────────┐ ┌──────────┐
│Terminal│ │ JSON │ │ Jira │
│ Output │ │ Report │ │ (si bug) │
└────────┘ └───────────┘ └──────────┘
Pipeline en 5 étapes
- Hook transparent — Se branche sur la page Playwright via les events natifs
- Capture en parallèle — DOM (listener JS injecté), réseau, console, screenshots
- Détection d'échec — Le hook pytest intercepte le
FAILED - Bundle + Prompt — Tout le contexte part en UN appel IA
- Diagnostic — Cause racine + catégorie + fix concret + rapport JSON
🏷️ Les 12 catégories de diagnostic
| Icône | Catégorie | Description |
|---|---|---|
| 🎯 | wrong_selector |
Sélecteur cassé, inexistant ou trop large |
| ⏭️ | missing_step |
Étape manquante (cookies, goto, dropdown) |
| ⏱️ | timing |
Race condition, élément pas encore prêt |
| 🫣 | element_obscured |
Élément recouvert par overlay/modal/bannière |
| 🚫 | element_disabled |
Élément trouvé mais désactivé |
| 🔀 | wrong_action |
Mauvaise méthode (click vs dblclick, fill vs type) |
| 🖼️ | iframe_context |
Élément cherché dans le mauvais frame |
| 🔤 | encoding_mismatch |
Problème Unicode/accents/regex |
| 👻 | stale_reference |
Locator obsolète après changement DOM |
| 📊 | test_data |
Assertion avec mauvaise valeur attendue |
| 🐛 | app_bug |
Bug applicatif (pas le test) → génère un ticket Jira |
| 🌐 | network |
Requêtes réseau en échec (4xx/5xx) |
⚙️ Configuration
Variables d'environnement
| Variable | Défaut | Description |
|---|---|---|
OPENAI_API_KEY |
(obligatoire si pas de API_KEY) | Clé API OpenAI |
API_KEY |
(optionnel) | Clé pour provider alternatif (DeepSeek, Ollama…) |
BASE_URL |
None (OpenAI natif) |
URL base du provider LLM |
QA_MODEL |
gpt-4.1-mini |
Modèle IA à utiliser |
QA_SCREENSHOT |
0 |
1 pour inclure les screenshots dans le prompt |
QA_REPORT_DIR |
qa-reports/ |
Dossier des rapports |
QA_REDACT_INPUTS |
1 |
Redaction auto des champs sensibles (password, CB, tokens, IBAN…). 0 pour désactiver (déconseillé). |
Arguments pytest
pytest tests/ --qa-autopilot # Active le diagnostic IA
pytest tests/ --qa-autopilot --headed # Avec navigateur visible
pytest tests/ --qa-autopilot -k "login" # Filtrer par keyword
📁 Structure des rapports
qa-reports/
├── summary_20260223_014751.json # Rapport consolidé du run
├── diag_test_casse_20260223_014713.json # Diagnostic individuel
├── diag_test_casse_20260223_014659.json
├── jira_test_casse_20260223_014659.md # Ticket Jira (si app_bug)
└── rapport.html # Rapport HTML pytest
Exemple de rapport consolidé
[
{
"test": "test_element_cache_par_overlay[chromium]",
"category": "element_obscured",
"confidence": 0.95,
"root_cause": "L'élément ciblé est recouvert par le bandeau cookies",
"suggested_fix": "Fermer le bandeau cookies avant de cliquer"
}
]
🔒 Sécurité & RGPD
QA Autopilot redacte automatiquement les données sensibles avant tout envoi au LLM. Cette protection est active par défaut (QA_REDACT_INPUTS=1) et opère sur deux fronts :
1. Redaction côté navigateur (DOM listener)
Les valeurs tapées dans les champs sensibles sont interceptées dans le navigateur, dans la fonction saveEntry() du listener JS, et remplacées par [REDACTED] avant tout stockage. La vraie valeur ne quitte jamais le périmètre du navigateur.
| Critère de détection | Exemples |
|---|---|
type HTML |
password, email, tel |
name/id contient |
password, passwd, pwd, secret, token, cvv, card, ssn, auth, pin, api_key, credit, iban, bic, swift, client_secret |
placeholder / aria-label contient |
mêmes patterns |
autocomplete |
current-password, new-password, cc-* (CB) |
2. Redaction du code source (avant envoi au LLM)
Le fichier .py du test est aussi scanné et les credentials hardcodés sont redactés :
page.fill("#password", "...")/.type()/.press_sequentially()/.input_value()sur des sélecteurs sensibles- Variables Python :
password = "...",token = "...",api_key = "...",client_secret = "...",access_token = "...", etc. os.environ["PASSWORD"] = "..."(assignation directe)
Si une redaction est appliquée, qa-autopilot affiche un warning :
⚠️ Credentials hardcodes detectes dans test_login.py, redactes avant envoi LLM.
Bonne pratique : utilise os.environ ou pytest fixtures pour les secrets.
Que voit le LLM ?
ACTIONS DOM CAPTUREES (3 total)
1. INPUT ✅ #username = 'john.doe@example.com'
2. INPUT ✅ #password = [REDACTED — champ sensible]
3. CLICK ✅ button[type="submit"] (texte: 'Login')
CODE DU TEST
def test_login(page):
page.fill("#username", "john.doe@example.com")
page.fill("#password", "[REDACTED]")
page.click("button[type='submit']")
Le LLM est explicitement informé dans le prompt que [REDACTED] ne signifie pas un champ vide ou cassé, mais une protection RGPD. Le diagnostic se fait sans connaître la valeur réelle.
Comment vérifier que la redaction fonctionne ?
# Lance un test qui tape un mot de passe
pytest tests/test_login.py --qa-autopilot
# Vérifie le rapport JSON — tu dois voir [REDACTED] partout
grep -i "password\|REDACTED" qa-reports/diag_*.json
Pour les ultra-paranoïaques : intercepte le trafic sortant avec mitmproxy et vérifie que les requêtes vers api.openai.com ne contiennent jamais ta vraie valeur sensible.
Données envoyées au LLM
| Donnée | Envoyée | Redactable |
|---|---|---|
| Code source du test (3000 chars max) | ✅ | ✅ par défaut (regex sur fill/assign/env) |
| Message d'erreur Playwright | ✅ | ❌ |
| Sélecteurs des éléments | ✅ | ❌ |
| Valeurs des inputs (DOM listener) | ✅ | ✅ par défaut (cascade 6 critères) |
| URL de la page | ✅ | ❌ |
| Erreurs console | ✅ | ❌ |
| Bodies des requêtes 4xx/5xx (tronqué) | ✅ | ❌ |
| Screenshots | uniquement si QA_SCREENSHOT=1 |
n/a |
Désactiver la redaction (déconseillé)
Pour les rares cas où le contenu d'un champ "sensible" est légitime à voir (faux positif sur un nom de champ qui contient auth mais qui n'est pas vraiment de l'auth) :
QA_REDACT_INPUTS=0 pytest tests/ --qa-autopilot
⚠️ À utiliser uniquement sur des données de test fictives. En cas de doute, garde la redaction active. La redaction n'est pas une excuse pour hardcoder des credentials : elle n'attrape pas tous les cas exotiques (variables aux noms inventés, valeurs concaténées, etc.). La règle d'or reste : jamais de secret en dur dans le code.
🏗️ Architecture
qa-autopilot/
├── qa_autopilot/
│ ├── __init__.py # Exports publics
│ ├── core.py # QAInterceptor + capture
│ ├── prompt.py # Prompt v2 (12 catégories)
│ ├── diagnose.py # Appel IA + retry + JSON mode
│ ├── reporter.py # Rapports JSON + Jira markdown
│ ├── listener.js # DOM listener (injection navigateur)
│ └── plugin.py # Hooks pytest
├── tests/
│ ├── test_casse.py # Suite de tests pièges
│ └── conftest.py
├── examples/
│ └── standalone.py # Exemple d'utilisation directe
├── pyproject.toml
├── LICENSE
└── README.md
Note : La version actuelle est un fichier monolithique
qa_autopilot.py(~600 lignes). Le découpage ci-dessus est la cible pour la v2.
🆚 Pourquoi pas les alternatives ?
| QA Autopilot | Playwright MCP (23K lignes) | SaaS (Testim, Mabl...) | |
|---|---|---|---|
| Lignes de code | ~600 | 23 000+ | Fermé |
| Installation | pip install |
MCP server + config | Compte + licence |
| Config | 1 flag | 32 outils MCP | Dashboard + intégration |
| Prix | Gratuit + clé OpenAI | Gratuit | 200-500€/mois/user |
| Diagnostic | 12 catégories, 95% | Basique | Variable |
| Vendor lock-in | Zéro | MCP protocol | Total |
🛠️ DOM Listener — Cascade 6 tiers
Le listener JavaScript injecté dans le navigateur utilise une cascade de sélecteurs en 6 niveaux, du plus stable au moins stable :
| Tier | Stratégie | Exemple |
|---|---|---|
| 1 | data-testid / id / name |
[data-testid="submit-btn"] |
| 2 | aria-label / placeholder / title |
[aria-label="Fermer"] |
| 3 | href (liens) |
a[href="/checkout"] |
| 4 | Parent avec attribut stable | [data-testid="form"] button |
| 5 | Label associé (inputs) | //label[contains(text(),"Email")]//input |
| 6 | CSS court + nth-of-type |
button.primary:nth-of-type(2) |
Chaque sélecteur est validé pour son unicité dans le DOM. Support Shadow DOM inclus.
🤝 Contributors
| Contributeur | Contribution |
|---|---|
| Julien Mer | Auteur original |
| @szwnba | Support multi-provider LLM (DeepSeek, Ollama) + traduction CN |
Les contributions sont bienvenues — issues, bug reports, pull requests.
📄 License
MIT — Fais-en ce que tu veux.
Créé par Julien Mer — JMer Consulting
QA Architect · 20+ ans d'expérience · Katalon Top Partner Europe
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qa_autopilot-1.2.2.tar.gz.
File metadata
- Download URL: qa_autopilot-1.2.2.tar.gz
- Upload date:
- Size: 30.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.10.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e0ba3454c7d57874a00f9b51fc8cb8b8de86c2f12f2c26f2875880bf18a45240
|
|
| MD5 |
15b649bc840402dff70be73ac17519c1
|
|
| BLAKE2b-256 |
d0ce8430cf395452ea93dc17f192c3c86c5c6d6465199bfc2504d03c58c2ecca
|
File details
Details for the file qa_autopilot-1.2.2-py3-none-any.whl.
File metadata
- Download URL: qa_autopilot-1.2.2-py3-none-any.whl
- Upload date:
- Size: 25.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.10.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a5601a6dc5601c2c8916a0153492e9fee50901b5da2c53f3f8ed7ed93aeb4d0
|
|
| MD5 |
f9e7336f3790ecf57f23f3c7d5a72dd6
|
|
| BLAKE2b-256 |
e554850a5628e7f9140fba98f29dc47115bc880dd3039d07a747c9c63580a456
|