Metade dos seus Cucumber scenarios está skipped. Não failed. Skipped.
Aquele status amarelo é pior que um build red. Ele faz sua suite parecer saudável enquanto esconde três problemas completamente diferentes sob um único label educado. Um skipped scenario pode significar que seu tag filter o excluiu, uma step definition sumiu, ou um Before hook lançou uma exception antes mesmo do primeiro step rodar. Seu trabalho é descobrir qual parasita está morando na sua suite.
Por Que “Skipped” É o Status Mais Perigoso do Cucumber
O Cucumber tem três estados terminais para um scenario: passed, failed e skipped. Passed e failed são honestos. Skipped é uma lixeira.
Quando um scenario é skipped, o Cucumber está te dizendo que algum prerequisite não foi atendido. O problema é que esse “prerequisite” pode ser qualquer coisa, desde um tag filter deliberado até um null pointer em um setup hook. Uma suite com 50% de skipped scenarios parece que rodou 500 tests. Não rodou. Rodou 250 tests e acenou para os outros 250 sem checá-los.
A maioria dos CI dashboards trata skipped como neutral. Seu pipeline está green. Mas metade das suas behavior specifications não está sendo verificada. Se você está usando o Cucumber como living documentation, metade da sua documentation é uma mentira.
As Três Causas Raiz do Mass Skipping
Existem exatamente três formas de gerar um skipped scenario no Cucumber. Duas delas são bugs. Uma é uma misconfiguration que parece uma feature.
Tag filters que excluem mais do que você pretendia
A causa mais comum de 50% de skipped scenarios é uma tag expression na sua runner config que é mais abrangente do que você pensa. Seu CI profile pode parecer com isso:
// cucumber.js
module.exports = {
default: [
'--format', 'progress',
'--tags', 'not @wip',
].join(' '),
ci: [
'--format', 'json:reports/cucumber.json',
'--tags', 'not @wip and not @slow',
].join(' '),
};
O dano real acontece quando times usam positive tag filters. Um filter como --tags '@regression' roda apenas scenarios taggeados @regression. Todo outro scenario é skipped. É assim que você vai de 100% executed para 50% executed sem mudar um único feature file.
Step definitions que não combinam mais
O Cucumber skippa um scenario inteiro se qualquer step não tiver uma matching step definition. Isso não é um failure. É um skip. Seu CI continua green. Isso acontece mais do que você esperaria. Um product manager edita um Gherkin file para corrigir um typo em um 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
Suas step definitions esperam isso:
// features/step_definitions/checkout_steps.js
Given('a guest user with items in the cart', function () {
// setup
});
Note o “the” faltando no feature file. A regex do Cucumber não combina mais. O scenario inteiro é skipped. Multiplique isso por um time que refatora feature files sem rodar a suite completa localmente, e você pode perder metade da sua coverage por drift.
Failing hooks que se disfarçam de skips
Se um Before hook lançar uma exception, o Cucumber skippa todo scenario naquele feature file. Ele não os faz falhar. Ele os skippa.
// 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);
});
Se TEST_URL estiver undefined no CI, o hook lança uma exception e todo scenario anexado a ele é skipped. Seu build está green. Seus tests são inúteis.
Como Diagnosticar Qual Parasita Você Tem
Você não pode consertar 50% de skipped scenarios sem saber qual das três causas é responsável. O output padrão do Cucumber não te diz. Você tem que interrogá-lo.
Rode sua suite com --dry-run e sem tag filters:
npx cucumber-js --dry-run --tags ''
Um dry run parseia todo feature file e combina todo step com uma step definition sem executar nada. Se mostrar 100% undefined, você tem step definition drift. Se mostrar 100% passed, seus skips vêm de tag filters ou runtime hooks.
O JSON formatter inclui um campo status para cada step. Um scenario skipped por um tag filter não terá steps algum. Um scenario skipped por uma step definition faltando mostrará undefined no step que não combina. Um scenario skipped por um failing hook mostrará skipped em todo step, e o array embeddings pode conter o erro do hook.
npx cucumber-js --format json:report.json
Parseie o JSON e conte quantos scenarios têm zero steps versus quantos têm steps com status undefined ou skipped. Isso te diz onde olhar.
O fix mais seguro a longo prazo é tratar qualquer skip inesperado como um build failure. Um pequeno post-processor para o JSON report faz o trabalho:
// 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);
}
Rode isso após toda invocação do Cucumber no CI. Ele vai pegar tag misconfigurations, step definitions faltando e failing hooks antes que apodreçam silenciosamente sua suite.
O Trade-Off: Por Que Algum Skipping É Okay
O skipping deliberado é uma ferramenta válida. A tag @wip existe por um motivo. Você escreve o scenario antes da implementação, marca como @wip, e deixa o runner skipá-lo até que o código esteja pronto.
A diferença entre skipping saudável e suite rot é hygiene. @wip deve ser temporário. Deve viver em uma branch, não na main por seis meses. Se 50% dos seus scenarios estão taggeados @wip na sua default branch, você não tem uma test suite. Você tem uma wish list.
O tag-based filtering também faz sentido para tests específicos de ambiente. Um scenario que requer um physical payment terminal não deve rodar no CI. Mas esse scenario deve ser taggeado @hardware, não @slow ou @manual. Seja explícito sobre por que algo está excluído, e audite essas exclusions em code review da mesma forma que você audita o código em si.
Como Parar a Podridão
Se você está com 50% skipped hoje, aqui está o caminho mais rápido de volta à honestidade.
-
Rode um dry run sem tags. Conte os steps
undefined. Conserte todo mismatch. Isso geralmente é um trabalho de find-and-replace de cinco minutos. -
Audite seus runner profiles. Liste toda tag expression no seu
cucumber.js, Mavenpom.xml, ou Gradle config. Confirme que cada uma é intencional. Substitua positive filters (--tags '@regression') por negative ones (--tags 'not @wip') para que novos scenarios rodem por padrão. -
Adicione o skip-check script ao CI. Faça com que ele falhe o build em qualquer skip inesperado. Isso é uma configuração one-time que paga para sempre.
-
Agende uma tag audit mensal. Busque nos seus feature files por
@wipe os conte. Se o número está crescendo, você tem um process problem, não um tooling problem.
FAQ
Por que o Cucumber skippa scenarios ao invés de falhá-los quando uma step definition está faltando?
O Cucumber trata uma step definition faltando como uma specification incompleta, não um code defect. O scenario é skipped porque o Cucumber não pode executar o que ele não entende. Esse é um comportamento histórico da implementação original em Ruby, e persiste entre os ports. A única forma de fazê-lo falhar é adicionar um post-processor que cheque por statuses undefined.
Qual é a diferença entre um skipped scenario e um pending scenario?
Nas versões modernas do Cucumber, “pending” é um step-level status lançado explicitamente por uma step definition (por exemplo, chamando pending() em Ruby ou retornando 'pending' em JavaScript). “Skipped” é um scenario-level status aplicado quando um prerequisite falha, um tag filter exclui o scenario, ou um step anterior falhou. Na prática, ambos aparecem como yellow e ambos significam “isso não rodou até o fim”.
Posso fazer o Cucumber falhar em step definitions faltando? Não há uma built-in CLI flag para isso na maioria dos Cucumber ports. Você tem que parsear o output JSON ou JUnit e falhar o build você mesmo. O script neste post é um minimal working example.
Como eu encontro quais tags estão realmente sendo usadas na minha suite?
Rode um grep nos seus feature files: grep -roh '@[a-zA-Z0-9_-]*' features/ | sort | uniq -c | sort -rn. Isso te dá uma frequency table de toda tag. Cruzecione isso com seus runner profiles para encontrar tags que filtram sem documentation.