El bug de coverage 0% en CI que me costó 3 horas: Vitest y los patrones de exclusión
Un pattern de exclude mal configurado en vitest.config.ts que hacía que el coverage reportara 0% solo en CI.
El síntoma
Local: coverage 87%. CI: coverage 0%. Mismo commit, misma versión de Node.
❯ vitest run --coverage
% Coverage report from v8
------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
------|---------|----------|---------|---------|
All | 0 | 0 | 0 | 0 |
------|---------|----------|---------|---------|
La investigación
Después de descartar versiones de Node, cachés y permisos, encontré el culpable en vitest.config.ts:
export default defineConfig({
test: {
coverage: {
exclude: [
'node_modules/**',
'dist/**',
'**/*.test.ts',
'src/generated/**',
],
},
},
});
¿Se ve bien, no? El problema era que en CI los archivos se compilaban en un paso previo y el directorio dist contenía los source maps que Vitest usaba para resolver los paths.
El fix
export default defineConfig({
test: {
coverage: {
include: ['src/**/*.ts'], // ser explícito con lo que SÍ queremos
exclude: [
'**/*.test.ts',
'**/*.spec.ts',
'src/generated/**',
],
},
},
});
La lección: prefiere include sobre exclude cuando defines coverage. Es más predecible y no depende de la estructura de directorios del entorno.