09.03.07 — Skills Refactor#
Skills qui refactor la base de tests automatisés existante.
Listing (potentiellement non exhaustif)#
| Skill | Cible |
|---|---|
refactor-fragmentation | DRY selon préférence utilisateur |
introduce-pom-retries | Retries internes aux POMs, avec dédoublement (first-try + with-retries) |
refactor-fragmentation#
input : la base de tests
output : suggestions de refactor DRY :
- blocs répétés dans 3+ scénarios → extract en fragment
- connectors quasi-identiques → extract un connector paramétré
- séquences de log+screenshot répétées → extract un helper
+ recommandation au cas par casCLAUDE.md :
When to extract a fragment
- Don’t extract preemptively. Two scenarios sharing 3 acts is a coincidence. Wait for 3+ scenarios with the same block.
- The block must be a precondition/postcondition, not the focus of any test.
valid_login.pydoes not use the login fragment — login is the test there, and useslog_and_screenshotfor the meaningful page transition.- Unhappy-path login tests stay self-contained.
failed_loginsandunauthenticated_history_accessdon’t use the fragment — they must stay independent of demo-user state.
- Quantité : 3+ scénarios avec le même bloc.
- Rôle : pré/postcondition, pas le focus du test.
- Indépendance : tests unhappy-path doivent rester self-contained.
refactor-fragmentation applique ces critères et suggère (sans appliquer).
introduce-pom-retries#
input : un connector qui exerce une action flaky (par exemple click un bouton qui peut fail intermittemment)
output : refactor en :
1. méthode POM `<action>` sans retry (single try)
2. méthode POM `<action>_with_retries(retries: int, logger: ILogger)` qui retry intern
3. connectors duaux :
- `<action>` : appelle POM `<action>`
- `<action>_with_retries(retries, logger)` : appelle POM `<action>_with_retries`
4. scénarios mis à jour pour utiliser la variante appropriéeC’est ce pattern qu’on voit dans ocarina-example/lib/connectors/test_steps/actions/dashboard_login.py :
def login_without_otp(creds: ImmutableCredentials):
def unwrapped(p: DashboardLoginPage) -> DashboardLoginPage:
return p.login_without_otp(creds)
return unwrapped
def login_without_otp_and_with_retries(
creds: ImmutableCredentials, retries: int, *, logger: ILogger
):
def unwrapped(p: DashboardLoginPage) -> DashboardLoginPage:
return p.login_without_otp_and_with_retries(creds, retries, logger=logger)
return unwrappedDeux connectors. Le scénario choisit selon le besoin :
- Cas happy path :
login_without_otp_and_with_retries(leuseAuthà 10% de raté force le retry). - Cas unhappy path (« login avec mauvais mdp ») :
login_without_otp(on veut que l’échec se voie, sans retry).
Pourquoi dédoubler#
| Sans dédoublement | Avec dédoublement |
|---|---|
Une seule méthode login(...), toujours avec retry | Les tests qui veulent voir un fail immédiat sont brouillés par les retries |
| Pas de différenciation happy / unhappy | Différenciation claire |
| Tests fragiles si on enlève le retry | Tests robustes : la couverture de test est préservée, un test complémentaire visant à ne pas tolérer la flakiness permet de continuer de la tracer sur le côté |
« Retries au POM, pas au scénario »#
Holy Book (chapitre « Premiers obstacles du monde réel ») :
Aléas de pas de test#
Pensant avoir laissé derrière moi ce genre de désagréments, j’ai changé de crémerie… pour y découvrir des formulaires instables et des systèmes d’authentification qui fonctionnaient une fois sur deux.
Face à cela, la réponse d’Ocarina est différente : on délègue la responsabilité au POM.
| Variante | Avantage | Inconvénient |
|---|---|---|
Retry au framework (transient_errors) | Transparent, approprié en cas de flakiness impossible à vraiment isoler | Re-joue tout le test, pas juste l’action flaky |
| Retry au niveau du POM | Granulaire + placé dans une méthode bien nommée | — |
Discipline transversale#
- Analyser des patterns dans la base.
- Suggérer des refactors.
- Ne pas immédiatement appliquer sauf si instruction explicite.
- Respecter les règles d’extraction.