La mitad de tus escenarios de Cucumber están skippeados. No fallados. Skippeados.
Ese estado amarillo es peor que un build rojo. Hace que tu suite parezca saludable mientras oculta tres problemas completamente distintos bajo una sola tag educada. Un escenario skippeado podría significar que tu filtro de tags lo excluyó, que una step definition desapareció, o que un hook Before lanzó una excepción antes de que el primer step siquiera se ejecutara. Tu trabajo es descubrir qué parásito vive en tu suite.
Por qué “Skipped” es el estado más peligroso de Cucumber
Cucumber tiene tres estados terminales para un escenario: passed, failed y skipped. Passed y failed son honestos. Skipped es un bote de basura.
Cuando un escenario es skippeado, Cucumber te está diciendo que algún prerequisito no se cumplió. El problema es que ese “prerequisito” puede ser cualquier cosa, desde un filtro de tags deliberado hasta un null pointer en un hook de setup. Una suite con un 50% de escenarios skippeados parece como si hubiera ejecutado 500 tests. No lo hizo. Ejecutó 250 tests y saludó a los otros 250 sin revisarlos.
La mayoría de los dashboards de CI tratan skipped como neutral. Tu pipeline está verde. Pero la mitad de tus especificaciones de comportamiento no están siendo verificadas. Si estás usando Cucumber como documentación viva, la mitad de tu documentación es una mentira.
Las tres causas raíz del skipping masivo
Hay exactamente tres formas de generar un escenario skippeado en Cucumber. Dos de ellas son bugs. Una es una misconfiguración que parece una feature.
Filtros de tags que excluyen más de lo que pretendías
La causa más común de un 50% de escenarios skippeados es una expresión de tags en tu config de runner que es más amplia de lo que crees. Tu perfil de CI podría verse así:
// cucumber.js
module.exports = {
default: [
'--format', 'progress',
'--tags', 'not @wip',
].join(' '),
ci: [
'--format', 'json:reports/cucumber.json',
'--tags', 'not @wip and not @slow',
].join(' '),
};
El daño real ocurre cuando los equipos usan filtros de tags positivos. Un filtro como --tags '@regression' ejecuta solo escenarios taggeados @regression. Todos los demás escenarios son skippeados. Así es como pasas de un 100% ejecutado a un 50% ejecutado sin cambiar ni un solo feature file.
Step definitions que ya no hacen match
Cucumber skipea un escenario completo si cualquier step carece de una step definition que haga match. Esto no es un failure. Es un skip. Tu CI se mantiene verde. Esto ocurre más a menudo de lo que esperarías. Un product manager edita un archivo Gherkin para corregir un typo en 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
Tus step definitions esperan esto:
// features/step_definitions/checkout_steps.js
Given('a guest user with items in the cart', function () {
// setup
});
Nota la falta del “the” en el feature file. La regex de Cucumber ya no hace match. El escenario completo es skippeado. Multiplica esto en un equipo que refactoriza feature files sin ejecutar la suite completa localmente, y puedes perder la mitad de tu coverage por drift.
Hooks fallidos que se disfrazan de skips
Si un hook Before lanza una excepción, Cucumber skipea todos los escenarios de ese feature file. No los falla. Los skipea.
// 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, el hook lanza una excepción y todos los escenarios vinculados a él son skippeados. Tu build está verde. Tus tests no valen nada.
Cómo diagnosticar qué parásito tienes
No puedes arreglar un 50% de escenarios skippeados sin saber cuál de las tres causas es la responsable. El output por defecto de Cucumber no te lo dice. Tienes que interrogarlo.
Ejecuta tu suite con --dry-run y sin filtros de tags:
npx cucumber-js --dry-run --tags ''
Un dry run parsea cada feature file y hace match de cada step con una step definition sin ejecutar nada. Si muestra 100% undefined, tienes drift en tus step definitions. Si muestra 100% passed, tus skips vienen de filtros de tags o hooks de runtime.
El JSON formatter incluye un campo status para cada step. Un escenario skippeado por un filtro de tags no tendrá steps en absoluto. Un escenario skippeado por una step definition faltante mostrará undefined en el step que no hace match. Un escenario skippeado por un hook fallido mostrará skipped en cada step, y el array embeddings podría contener el error del hook.
npx cucumber-js --format json:report.json
Parsea el JSON y cuenta cuántos escenarios tienen cero steps versus cuántos tienen steps con estado undefined o skipped. Eso te dice dónde mirar.
La solución más segura a largo plazo es tratar cualquier skip inesperado como un build failure. Un pequeño post-processor para el JSON report hace el trabajo:
// 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);
}
Ejecuta esto después de cada invocación de Cucumber en CI. Atrapará misconfiguraciones de tags, step definitions faltantes y hooks fallidos antes de que pudran silenciosamente tu suite.
El trade-off: por qué algunos skips están bien
El skipping deliberado es una herramienta válida. El tag @wip existe por una razón. Escribes el escenario antes de la implementación, lo marcas @wip, y dejas que el runner lo skipee hasta que el código esté listo.
La diferencia entre un skipping saludable y la podredumbre de la suite es la higiene. @wip debería ser temporal. Debería vivir en una branch, no en main durante seis meses. Si el 50% de tus escenarios están taggeados @wip en tu default branch, no tienes una test suite. Tienes una lista de deseos.
El filtrado basado en tags también tiene sentido para tests específicos de un entorno. Un escenario que requiere un terminal de pago físico no debería ejecutarse en CI. Pero ese escenario debería estar taggeado @hardware, no @slow o @manual. Sé explícito sobre por qué algo está excluido, y audita esas exclusiones en code review de la misma forma que auditas el código mismo.
Cómo detener la podredumbre
Si estás en un 50% de skippeados hoy, aquí está el camino más rápido de vuelta a la honestidad.
-
Ejecuta un dry run sin tags. Cuenta los steps
undefined. Arregla cada mismatch. Esto suele ser un trabajo de find-and-replace de cinco minutos. -
Audita tus perfiles de runner. Lista cada expresión de tags en tu
cucumber.js,pom.xmlde Maven, o config de Gradle. Confirma que cada una es intencional. Reemplaza filtros positivos (--tags '@regression') con negativos (--tags 'not @wip') para que los nuevos escenarios se ejecuten por defecto. -
Agrega el script de skip-check a CI. Haz que falle el build ante cualquier skip inesperado. Esta es una configuración única que rinde beneficios para siempre.
-
Programa una auditoría mensual de tags. Busca
@wipen tus feature files y cuéntalos. Si el número está creciendo, tienes un problema de proceso, no de tooling.
FAQ
¿Por qué Cucumber skipea escenarios en lugar de fallarlos cuando falta una step definition?
Cucumber trata una step definition faltante como una especificación incompleta, no como un defecto de código. El escenario es skippeado porque Cucumber no puede ejecutar lo que no entiende. Este es un comportamiento histórico de la implementación original en Ruby, y persiste entre ports. La única forma de hacer que falle es agregar un post-processor que revise estados undefined.
¿Cuál es la diferencia entre un escenario skipped y un escenario pending?
En versiones modernas de Cucumber, “pending” es un estado a nivel de step lanzado explícitamente por una step definition (por ejemplo, llamando pending() en Ruby o retornando 'pending' en JavaScript). “Skipped” es un estado a nivel de escenario aplicado cuando un prerequisito falla, un filtro de tags excluye el escenario, o un step previo falló. En la práctica, ambos aparecen en amarillo y ambos significan “esto no se ejecutó hasta completarse”.
¿Puedo hacer que Cucumber falle ante step definitions faltantes? No hay una CLI flag built-in para esto en la mayoría de los ports de Cucumber. Tienes que parsear el output JSON o JUnit y fallar el build tú mismo. El script en este post es un ejemplo mínimo funcional.
¿Cómo encuentro qué tags se están usando realmente en mi suite?
Ejecuta un grep en tus feature files: grep -roh '@[a-zA-Z0-9_-]*' features/ | sort | uniq -c | sort -rn. Esto te da una tabla de frecuencias de cada tag. Crúzala contra tus perfiles de runner para encontrar tags que filtran sin documentación.