La moitié de vos scénarios Cucumber sont skipped. Pas failed. Skipped.
Ce statut jaune est pire qu’un build rouge. Il donne l’impression que votre suite est en bonne santé tout en cachant trois problèmes complètement différents sous une étiquette polie. Un scénario skipped peut signifier que votre tag filter l’a exclu, qu’une step definition a disparu, ou qu’un hook Before a levé une exception avant même que le premier step ne s’exécute. Votre travail consiste à déterminer quel parasite infeste votre suite.
Pourquoi « Skipped » est le statut le plus dangereux de Cucumber
Cucumber a trois états terminaux pour un scénario : passed, failed et skipped. Passed et failed sont honnêtes. Skipped est une poubelle.
Quand un scénario est skipped, Cucumber vous dit qu’un prérequis n’a pas été rempli. Le problème est que ce « prérequis » peut être n’importe quoi, d’un tag filter délibéré à un null pointer dans un setup hook. Une suite avec 50 % de scénarios skipped donne l’impression qu’elle a exécuté 500 tests. Ce n’est pas le cas. Elle en a exécuté 250 et a fait un signe de la main aux 250 autres sans les vérifier.
La plupart des tableaux de bord CI considèrent skipped comme neutre. Votre pipeline est verte. Mais la moitié de vos behavior specifications ne sont pas vérifiées. Si vous utilisez Cucumber comme living documentation, la moitié de votre documentation est un mensonge.
Les trois causes racines du mass skipping
Il existe exactement trois façons de générer un scénario skipped dans Cucumber. Deux d’entre elles sont des bugs. L’une est une mauvaise configuration qui ressemble à une fonctionnalité.
Des tag filters qui excluent plus que prévu
La cause la plus courante de 50 % de scénarios skipped est une tag expression dans votre runner config plus large que vous ne le pensez. Votre profil CI pourrait ressembler à ceci :
// cucumber.js
module.exports = {
default: [
'--format', 'progress',
'--tags', 'not @wip',
].join(' '),
ci: [
'--format', 'json:reports/cucumber.json',
'--tags', 'not @wip and not @slow',
].join(' '),
};
Les vrais dégâts surviennent quand les équipes utilisent des positive tag filters. Un filter comme --tags '@regression' n’exécute que les scénarios tagués @regression. Tous les autres sont skipped. C’est ainsi que vous passez de 100 % exécuté à 50 % exécuté sans changer un seul feature file.
Des step definitions qui ne correspondent plus
Cucumber skippe un scénario entier si un step n’a pas de step definition correspondante. Ce n’est pas un failure. C’est un skip. Votre CI reste verte. Cela arrive plus souvent que vous ne le pensez. Un product manager édite un Gherkin file pour corriger une faute de frappe dans un step :
# features/checkout.feature
Feature: Checkout
Scenario: Guest user completes purchase
Given a guest user with items in cart
When they proceed to checkout
Then the order total should include tax
Vos step definitions attendent ceci :
// features/step_definitions/checkout_steps.js
Given('a guest user with items in the cart', function () {
// setup
});
Remarquez le « the » manquant dans le feature file. La regex de Cucumber ne correspond plus. Le scénario entier est skipped. Multipliez cela dans une équipe qui refactorise des feature files sans exécuter la suite complète en local, et vous pouvez perdre la moitié de votre coverage à cause du drift.
Des hooks qui échouent et se déguisent en skips
Si un hook Before lève une exception, Cucumber skipe chaque scénario de ce feature file. Il ne les fait pas fail. Il les skippe.
// features/support/hooks.js
const { Before } = require('@cucumber/cucumber');
Before(async function () {
this.browser = await chromium.launch();
this.page = await this.browser.newPage();
// If this throws, every scenario in the file is skipped
await this.page.goto(process.env.TEST_URL);
});
Si TEST_URL est undefined en CI, le hook lève une exception et chaque scénario qui lui est rattaché est skipped. Votre build est verte. Vos tests ne valent rien.
Comment diagnostiquer quel parasite vous avez
Vous ne pouvez pas réparer 50 % de scénarios skipped sans savoir laquelle des trois causes est responsable. La sortie par défaut de Cucumber ne vous le dit pas. Vous devez l’interroger.
Exécutez votre suite avec --dry-run et sans tag filters :
npx cucumber-js --dry-run --tags ''
Un dry run parse chaque feature file et fait correspondre chaque step à une step definition sans rien exécuter. S’il affiche 100 % undefined, vous avez du step definition drift. S’il affiche 100 % passed, vos skips viennent de tag filters ou de runtime hooks.
Le JSON formatter inclut un champ status pour chaque step. Un scénario skippé par un tag filter n’aura aucun step du tout. Un scénario skippé par une step definition manquante affichera undefined sur le step qui ne correspond pas. Un scénario skippé par un hook qui échoue affichera skipped sur chaque step, et le tableau embeddings pourrait contenir l’erreur du hook.
npx cucumber-js --format json:report.json
Parsez le JSON et comptez combien de scénarios ont zéro step contre combien ont des steps avec le statut undefined ou skipped. Cela vous dit où chercher.
Le fix le plus sûr à long terme consiste à traiter tout skip inattendu comme un build failure. Un petit post-processor pour le JSON report fait l’affaire :
// scripts/fail-on-skips.js
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('report.json', 'utf8'));
let unexpectedSkips = 0;
for (const feature of report) {
for (const element of feature.elements) {
if (element.type !== 'scenario') continue;
const hasSkip = element.steps.some(s => s.result?.status === 'skipped');
const hasUndefined = element.steps.some(s => s.result?.status === 'undefined');
// Allow @wip to skip; everything else must run
const isWip = element.tags?.some(t => t.name === '@wip');
if ((hasSkip || hasUndefined) && !isWip) {
console.error(`Unexpected skip: ${feature.name} > ${element.name}`);
unexpectedSkips++;
}
}
}
if (unexpectedSkips > 0) {
console.error(`\n${unexpectedSkips} scenario(s) skipped unexpectedly. Failing build.`);
process.exit(1);
}
Exécutez ceci après chaque invocation de Cucumber en CI. Cela détectera les tag misconfigurations, les missing step definitions et les failing hooks avant qu’ils ne fassent pourrir silencieusement votre suite.
Le compromis : pourquoi un peu de skipping est acceptable
Le skipping délibéré est un outil valide. Le tag @wip existe pour une raison. Vous écrivez le scénario avant l’implémentation, le marquez @wip, et laissez le runner le skipper jusqu’à ce que le code soit prêt.
La différence entre un skipping sain et la pourriture de suite, c’est l’hygiène. @wip devrait être temporaire. Il devrait vivre sur une branche, pas sur main pendant six mois. Si 50 % de vos scénarios sont tagués @wip sur votre branche par défaut, vous n’avez pas de test suite. Vous avez une liste de souhaits.
Le tag-based filtering a aussi du sens pour les tests spécifiques à un environnement. Un scénario qui nécessite un terminal de paiement physique ne devrait pas s’exécuter en CI. Mais ce scénario devrait être tagué @hardware, pas @slow ou @manual. Soyez explicite sur la raison pour laquelle quelque chose est exclu, et auditez ces exclusions en code review de la même manière que vous auditez le code lui-même.
Comment arrêter la pourriture
Si vous êtes à 50 % de skipped aujourd’hui, voici le chemin le plus rapide pour retrouver l’honnêteté.
-
Faites un dry run sans tags. Comptez les steps
undefined. Corrigez chaque mismatch. C’est généralement un travail de cinq minutes de find-and-replace. -
Auditez vos runner profiles. Listez chaque tag expression dans votre
cucumber.js, votrepom.xmlMaven ou votre config Gradle. Confirmez que chacune est intentionnelle. Remplacez les positive filters (--tags '@regression') par des negative ones (--tags 'not @wip') pour que les nouveaux scénarios s’exécutent par défaut. -
Ajoutez le skip-check script à la CI. Faites-le faire échouer le build sur tout skip inattendu. C’est une configuration unique qui rapporte pour toujours.
-
Planifiez un tag audit mensuel. Cherchez
@wipdans vos feature files et comptez-les. Si le nombre augmente, vous avez un problème de processus, pas un problème d’outillage.
FAQ
Pourquoi Cucumber skippe-t-il les scénarios au lieu de les faire échouer quand une step definition est manquante ?
Cucumber traite une step definition manquante comme une spécification incomplète, pas comme un défaut de code. Le scénario est skipped parce que Cucumber ne peut pas exécuter ce qu’il ne comprend pas. C’est un comportement historique de l’implémentation Ruby originale, et il persiste à travers les ports. La seule façon de le faire échouer est d’ajouter un post-processor qui vérifie les statuts undefined.
Quelle est la différence entre un scénario skipped et un scénario pending ?
Dans les versions modernes de Cucumber, « pending » est un statut au niveau du step levé explicitement par une step definition (par exemple, en appelant pending() en Ruby ou en retournant 'pending' en JavaScript). « Skipped » est un statut au niveau du scénario appliqué quand un prérequis échoue, qu’un tag filter exclut le scénario, ou qu’un step précédent a échoué. En pratique, les deux apparaissent en jaune et les deux signifient « cela ne s’est pas exécuté jusqu’au bout ».
Puis-je faire échouer Cucumber sur des step definitions manquantes ? Il n’y a pas de CLI flag intégré pour cela dans la plupart des ports Cucumber. Vous devez parser la sortie JSON ou JUnit et faire échouer le build vous-même. Le script de cet article est un minimal working example.
Comment trouver quels tags sont réellement utilisés dans ma suite ?
Exécutez un grep sur vos feature files : grep -roh '@[a-zA-Z0-9_-]*' features/ | sort | uniq -c | sort -rn. Cela vous donne une table de fréquence de chaque tag. Croisez cela avec vos runner profiles pour trouver les tags qui filtrent sans documentation.