Skip to main content

Pipelines de test auto-réparables

Construisez des pipelines de test qui apprennent des échecs et s'adaptent automatiquement en utilisant la mémoire Synapse.


Pipelines de test auto-réparables

Les suites de test traditionnelles se cassent quand l'UI change. Les tests auto-réparables utilisent la mémoire Synapse pour apprendre des échecs passés et s'adapter — réduisant les tests instables et la charge de maintenance.

Concept

┌─────────┐  fails   ┌──────────┐  store   ┌──────────┐
│  Test   │ ───────▶ │  Synapse │ ───────▶ │ Memories │
│  Run    │          │  Memory  │          │ (failures)│
└─────────┘          └──────────┘          └──────────┘
                           ▲                     │
                           │   recall            │
                           │  before next run    │
                           └─────────────────────┘
  1. Le test s'exécute
  2. S'il échoue, stocker l'échec (ce qui n'a pas marché, pourquoi, comment corriger)
  3. Prochaine exécution : rappeler les échecs pertinents avant d'exécuter
  4. Appliquer automatiquement les corrections connues

Implémentation

Étape 1 : wrapper de test

Enveloppez chaque test avec rappel/stockage mémoire :

import requests
from datetime import datetime

URL = "https://synapse.schaefer.zone"
MIND_KEY = "mk_..."

def self_healing_test(test_name, test_fn):
    """Decorator: wrap a test with self-healing memory."""
    def wrapper():
        # 1. Rappeler les échecs passés pour ce test
        past_failures = requests.get(
            f"{URL}/memory/search?q={test_name}+failure",
            headers={"Authorization": f"Bearer {MIND_KEY}"}
        ).json()
        
        # 2. Exécuter le test avec le contexte d'échec
        try:
            test_fn(known_failures=past_failures)
        except Exception as e:
            # 3. Stocker l'échec
            store_failure(test_name, e, traceback.format_exc())
            raise
    
    return wrapper

def store_failure(test_name, error, traceback_str):
    requests.post(f"{URL}/memory",
        headers={"Authorization": f"Bearer {MIND_KEY}",
                 "Content-Type": "application/json"},
        json={
            "category": "mistake",
            "key": f"test_failure_{test_name}_{datetime.now().isoformat()}",
            "content": f"Test: {test_name}\nError: {error}\nTrace:\n{traceback_str}",
            "tags": ["test", "failure", test_name],
            "priority": "high"
        })

Étape 2 : logique de test adaptative

Dans le test, vérifiez les échecs connus et appliquez les corrections :

@self_healing_test
def test_login_page(browser, known_failures=None):
    browser.goto("https://app.com/login")
    
    # Vérifier si nous avons déjà vu ce changement de page
    if known_failures and known_failures.get("results"):
        for failure in known_failures["results"]:
            if "button moved" in failure["content"].lower():
                # Utiliser le label d'accessibilité au lieu des coordonnées
                browser.click(by_label="Login button")
                return
    
    # Par défaut : utiliser les coordonnées
    browser.click(x=150, y=400)

Étape 3 : stratégies de récupération

Stockez les stratégies de récupération en mémoire :

def store_recovery(failure_type, strategy):
    requests.post(f"{URL}/memory",
        headers={"Authorization": f"Bearer {MIND_KEY}",
                 "Content-Type": "application/json"},
        json={
            "category": "skill",
            "key": f"recovery_{failure_type}",
            "content": strategy,
            "tags": ["test", "recovery", failure_type],
            "priority": "high"
        })

# Stocker les récupérations pour les échecs courants
store_recovery("element_not_found",
    "When element not found by ID, try by CSS class, then by XPath, "
    "then by accessibility label. Take screenshot for debugging.")

store_recovery("timeout",
    "Increase timeout to 30s. If still fails, check if page is loading "
    "dynamically — wait for specific element instead of fixed time.")

store_recovery("stale_element",
    "Re-find element before each interaction. Don't cache element references "
    "across page transitions.")

Étape 4 : intégration CI

# .gitlab-ci.yml
test:self-healing:
  script:
    - export SYNAPSE_MIND_KEY=$SYNAPSE_TEST_MIND_KEY
    - pytest tests/ --self-healing
  after_script:
    # Résumer les nouveaux échecs
    - python scripts/synapse_failure_summary.py

Étape 5 : tableau de bord d'analyse des échecs

# Récupérer tous les échecs de test de la dernière semaine
r = requests.get(
    f"{URL}/memory/search?q=test+failure",
    headers={"Authorization": f"Bearer {MIND_KEY}"}
)

# Grouper par nom de test
failures = {}
for mem in r.json().get("results", []):
    test_name = extract_test_name(mem["content"])
    failures.setdefault(test_name, []).append(mem)

# Rapport
for test, fails in sorted(failures.items(), key=lambda x: -len(x[1])):
    print(f"{test}: {len(fails)} failures")

Bonnes pratiques

Schémas d'échec courants à stocker

Type d'échec Que stocker
Élément introuvable Sélecteur essayé, état de la page, capture d'écran
Timeout Temps d'attente, ce qui était attendu
Assertion échouée Valeur attendue vs réelle
Erreur réseau URL, code d'état, corps de réponse
Permission refusée Permission requise, rôle utilisateur actuel

Prochaines étapes