cypress-flake-fixer

Par github · awesome-copilot

Trouvez et corrigez les anti-patterns sources de tests instables dans une suite Cypress existante, ainsi que les API supprimées dans Cypress 12 à 16. Intègre un scanner sans dépendance pour les délais fixes `cy.wait()`, les valeurs de retour de commandes assignées, `async`/`await`, `force: true`, le chaînage après des actions, les `expect()` à usage unique dans `.then()`, les tests conditionnels, et les API supprimées telles que `Cypress.env()`, `cy.exec()`, `cy.server()`/`cy.route()`. À utiliser lorsqu'on vous demande de corriger des tests Cypress instables, de supprimer les `cy.wait`, de stabiliser ou de renforcer une suite Cypress, d'auditer des specs Cypress, de déboguer des erreurs « detached from the DOM », ou de préparer une migration vers Cypress 15 ou 16.

npx skills add https://github.com/github/awesome-copilot --skill cypress-flake-fixer

Cypress Flake Fixer

Audit une suite Cypress existante pour identifier les patterns de code qui causent des échecs intermittents, puis les corriger sans masquer le problème par des retries, des timeouts plus longs ou force: true.

Quand utiliser cette compétence

  • Une spec Cypress passe localement et échoue en CI, ou échoue une fois tous les quelques essais
  • La suite contient cy.wait(<number>), { force: true }, ou des tests qui ne passent que dans l'ordre
  • Les erreurs mentionnent « detached from the DOM », « timed out retrying » ou un subject qui « is no longer attached »
  • Le projet met à niveau Cypress sur une version majeure (12 à 16)
  • Un audit du code des tests Cypress est demandé

Pour écrire des tests Cypress à partir de zéro, les standards de codage suffisent. Cette compétence est destinée au diagnostic et à la réparation d'une suite existante.

Prérequis

  • Node.js 18 ou ultérieur pour exécuter le scanner (il n'a pas de dépendances et n'a pas besoin que Cypress soit installé)
  • La version de Cypress, lue depuis package.json. Les correctifs diffèrent selon la version majeure

Workflow

1. Scanner

node ./scripts/scan-cypress.mjs <project-root>
node ./scripts/scan-cypress.mjs <project-root> --json          # format lisible par machine
node ./scripts/scan-cypress.mjs <project-root> --strict        # ajouter les règles de faible confiance
node ./scripts/scan-cypress.mjs cypress/e2e/checkout.cy.ts     # un seul fichier

Le scanner lit la version de Cypress depuis le package.json le plus proche et classe les APIs supprimées en error quand la version majeure installée les rejette déjà, et en warn quand elles échouent lors de la mise à niveau. Lancez --list-rules pour le catalogue.

Les résultats sont heuristiques. Ouvrez chaque localisation et confirmez avant d'éditer.

2. Mesurer avant de changer quoi que ce soit

Quand l'objectif est de corriger une spec flaky spécifique, enregistrez d'abord la fréquence des échecs. Sans ligne de base, il n'y a aucun moyen de distinguer un correctif de la chance.

for i in $(seq 1 20); do npx cypress run --spec "cypress/e2e/checkout.cy.ts" --quiet || echo "FAIL $i"; done

Une spec qui échoue 1 fois sur 10 a besoin d'environ 30 exécutions réussies après le correctif avant que le correctif soit crédible.

3. Corriger dans cet ordre

Ordre Règles Pourquoi en premier
1 CY002, CY003, CY007 classées error Le test est incorrect ou déjà cassé ; les autres correctifs s'appuient sur un flux de commande correct
2 CY001, intercept ordering Les attentes basées sur le temps sont la plus grande source unique de flake
3 CY005, CY006 L'absence de retry-ability cause les erreurs detached-DOM et les assertions one-shot
4 CY004 force: true masque un vrai chevauchement, une animation ou un état désactivé
5 CY008, CY010, CY012 Fuites d'état et dépendance d'ordre
6 CY013, CY101, CY102 Hygiène des sélecteurs et des assertions

Le code avant/après pour chaque règle est dans references/anti-patterns.md. Les remplaçants pour les APIs supprimées sont dans references/removed-apis.md. Lisez la section pour la règle en cours de correction ; ne chargez pas les deux fichiers d'avance.

4. Remplacer chaque attente corrigée par la condition qu'elle couvrait

Ne supprimez pas un cy.wait(n) ou ne le remplacez pas par une hypothèse. Établissez ce que le délai couvrait :

L'attente couvrait Remplacer par
Une requête réseau cy.intercept(...).as('x') enregistrée avant le déclenchement, puis cy.wait('@x')
Un rendu qui suit une requête ou un changement d'état Un .should() sur le DOM qui en résulte
Un debounce, throttle, polling interval ou toast timeout cy.clock() avant cy.visit(), puis cy.tick(ms)
Une animation ou transition CSS Une assertion sur l'état final ; Cypress attend déjà que l'élément cesse de bouger avant d'agir
Hydratation client du HTML rendu côté serveur Un signal que l'app définit quand l'hydratation se termine (voir Gotchas)
Inconnue Lancez le test dans npx cypress open, sélectionnez l'attente dans le Command Log et lisez les requêtes et changements DOM qui se sont produits durant celle-ci

5. Vérifier

  1. Lancez la spec modifiée seule, puis répétez-la au moins autant de fois que l'exécution de base.
  2. Relancez le scanner et comparez les comptages par règle.
  3. Lancez la suite complète une fois pour détecter la dépendance d'ordre exposée par le changement.

Règles pour le correctif lui-même

  • Modifiez uniquement le code de test. Si le test expose un bug applicatif (double submit, état de chargement manquant, race dans l'UI), signalez-le plutôt que de le contourner.
  • Ne relevez pas defaultCommandTimeout, n'ajoutez pas de retries ou { force: true } pour faire passer un test. Chacun convertit un échec visible en un plus lent ou masqué.
  • Ne remplacez pas cy.wait(2000) par cy.wait(5000), ou par .should('exist') sur un élément déjà présent.
  • Gardez une catégorie de règle par commit pour qu'une régression puisse être bisectée.
  • Corrigez d'abord les custom commands partagées dans cypress/support. Une attente corrigée à l'intérieur d'une command s'exécute dans chaque test qui l'appelle.

Gotchas

  • Enregistrez cy.intercept() avant que la requête puisse partir. Pour les requêtes faites au chargement de la page, cela signifie avant cy.visit(). Un intercept enregistré après le départ de la requête ne correspond jamais, et cy.wait('@alias') expire intermittent selon la vitesse du réseau.

  • Chaque cy.wait('@alias') consomme une requête. Si l'app appelle l'endpoint deux fois, la première attente résout au premier appel et l'assertion s'exécute sur les données obsolètes. Attendez une fois par appel attendu, ou assertez sur le DOM.

  • GraphQL utilise une URL pour chaque opération. Aliasez dans le handler : cy.intercept('POST', '/graphql', (req) => { if (req.body.operationName === 'GetOrders') req.alias = 'getOrders' }).

  • .should('not.exist') passe avant que l'élément n'ait rendu. L'utiliser pour attendre qu'un spinner disparaisse réussit instantanément sur une page lente. Assertez l'état final positif.

  • Les apps rendues côté serveur acceptent les clics avant que les handlers ne soient attachés. Dans Next.js, Nuxt, SvelteKit et Remix, le bouton est visible et activé dans le HTML serveur, donc Cypress le clique lors de l'hydratation et rien ne se passe. Faites en sorte que l'app définisse un marqueur quand l'hydratation se termine (par exemple un attribut data-hydrated sur <body> depuis un mount effect) et assertez-le après cy.visit().

  • Un .should() au milieu d'une chaîne épingle le subject. Les queries après ne peuvent pas re-requérir passé l'assertion, donc un re-render produit une erreur detached-DOM. Divisez la chaîne à l'assertion.

  • .then() casse la chaîne de retry même sans assertion à l'intérieur. Tout avant s'exécute une seule fois.

  • Cypress 16 tape sans délai entre les touches (le défaut de keystrokeDelay est passé de 10 à 0). Les inputs qui re-rendent par frappe ou debounce sur input peuvent maintenant perdre des caractères ou se déclencher une fois. Assertez la valeur du champ après la frappe au lieu de restaurer le délai.

  • Cypress 16 a transformé les getters de cookie et de stockage en queries. cy.getCookie(), cy.getCookies(), cy.getAllCookies(), cy.getAllLocalStorage() et cy.getAllSessionStorage() réessaient maintenant les assertions .should() chaînées, ce qui élimine le besoin d'attentes autour des cookies d'auth.

  • La page est vierge après cy.session(). Une cy.visit() manquante après ressemble à une échec de sélecteur flaky.

  • testIsolation: false partage l'état entre les tests. Si la config le définit, chaque échec dépendant de l'ordre est un comportement attendu, pas une flake. Vérifiez la config avant de diagnostiquer.

  • Les retries masquent la flake sauf s'ils sont configurés pour la signaler. Pour trouver les tests flaky au lieu de les masquer, utilisez la stratégie expérimentale qui réessaie et échoue toujours :

    retries: {
      experimentalStrategy: 'detect-flake-but-always-fail',
      experimentalOptions: { maxRetries: 2, stopIfAnyPassed: true },
      openMode: true,
      runMode: true,
    }
  • CY005 est un appel au jugement. cy.get(input).clear().type('x') sur un input qui ne re-render jamais est correct. Divisez la chaîne quand l'élément appartient à une liste, une form library qui remonte les champs, ou n'importe quoi piloté par l'état async.

Format du rapport

## Audit flake Cypress : <scope>

- Version Cypress : <range>, majeure <n>
- Fichiers scannés : <n>
- Ligne de base : <spec> a échoué <f> sur <n> exécutions

| Règle | Avant | Après | Notes |
|---|---|---|---|
| CY001 fixed-wait | 14 | 0 | 9 remplacées par intercept aliases, 5 par assertions |

### Non corrigées
- <location> : <raison, par exemple nécessite un changement applicatif>

### Problèmes applicatifs trouvés
- <comportement que les tests ont exposé>

### Vérification
- <spec> : <n> sur <n> exécutions réussies après le changement

Dépannage

Problème Solution
Le scanner signale « Aucun fichier Cypress trouvé » Passez le dossier qui contient cypress/ ou les fichiers *.cy.*. Les specs en dehors de ces conventions peuvent être passées comme chemins de fichier explicites
Un résultat est un faux positif Ajoutez // cy-scan-ignore CY0xx sur la même ligne ou la ligne précédente, avec la raison
La version affiche « not detected » Passez --cypress-major <n> ; dans une monorepo, lancez le scanner depuis le package qui dépend de Cypress
Le test échoue uniquement en CI après les corrections Comparez viewport, browser et CPU. Lancez localement avec le browser CI et --browser chrome --headless, et limitez le CPU pour exposer les brèches de timing
cy.wait('@alias') expire après le changement L'intercept est enregistré après la requête, le pattern URL manque une query string (ajoutez *), ou la requête est servie du cache et ne atteint jamais le réseau

Références

Skills similaires