Cucumber BDD · GrowthlabSaaS

Suite de tests de comportamiento · Gherkin + TypeScript + GitHub Actions

✓ CI Passing Node 20 · TS @cucumber/cucumber
18 Escenarios
15 Pasando
2 @wip
1 Fallando
83% escenarios pasando · Duración total: 4.2s
📋
features/auth.feature Gherkin · 7 escenarios
🥒 auth.feature @auth @smoke
@auth @smoke
Feature: Autenticacion de usuarios
  Como agencia registrada en GrowthlabSaaS
  Quiero poder iniciar sesion de forma segura
  Para acceder al panel de campanas y metricas

  Background:
    Given la plataforma GrowthlabSaaS esta en funcionamiento
    And la base de datos tiene usuarios de prueba cargados

  @smoke
  Scenario: Login exitoso con credenciales de agencia
    Given estoy en la pagina de login
    When introduzco "agencia@mediawave.es" como email
    And introduzco "Media456!" como contrasena
    And hago click en el boton de acceder
    Then debo ser redirigido al dashboard principal
    And debo ver el mensaje "Bienvenida, MediaWave"
    And debo tener el rol "agencia" activo

  Scenario: Login fallido con contrasena incorrecta
    Given estoy en la pagina de login
    When introduzco "agencia@mediawave.es" como email
    And introduzco "wrongpass" como contrasena
    And hago click en el boton de acceder
    Then debo ver el error "Credenciales incorrectas"
    And debo permanecer en la pagina de login
    And el campo de contrasena debe estar vacio

  Scenario Outline: Validaciones del formulario de login
    Given estoy en la pagina de login
    When introduzco "<email>" como email
    And introduzco "<password>" como contrasena
    And hago click en el boton de acceder
    Then debo ver el error "<error>"

    Examples:
      | email                   | password   | error                       |
      |                         | Media456!  | El email es obligatorio     |
      | agencia@mediawave.es    |            | La contrasena es obligatoria|
      | no-es-un-email          | Media456!  | Formato de email invalido   |
      | admin@growthlabsaas.com | Admin123!  | Acceso admin no permitido   |

  @smoke
  Scenario: Logout cierra la sesion correctamente
    Given estoy autenticado como "agencia@mediawave.es"
    When hago click en el menu de usuario
    And selecciono "Cerrar sesion"
    Then debo ser redirigido a la pagina de login
    And el token de sesion debe estar invalidado
📋
features/campanas.feature Gherkin · 6 escenarios
🥒 campanas.feature @api @regression
@api @regression
Feature: Gestion de campanas de marketing
  Como gestor de campanas
  Quiero crear y gestionar campanas de email y social
  Para automatizar el alcance a mis segmentos de audiencia

  Background:
    Given estoy autenticado como "agencia@mediawave.es"
    And tengo al menos un segmento de audiencia creado

  @smoke
  Scenario: Crear una nueva campana de email
    Given estoy en la seccion de campanas
    When hago click en "Nueva Campana"
    And relleno el nombre con "Black Friday MediaWave 2026"
    And selecciono el tipo "Email"
    And asigno el segmento "Clientes activos Q4"
    And establezco la fecha de envio a "2026-11-28 09:00"
    And guardo la campana
    Then la campana "Black Friday MediaWave 2026" debe aparecer en el listado
    And el estado debe ser "Programada"
    And debo ver la confirmacion "Campana creada correctamente"

  Scenario: Pausar una campana activa
    Given existe la campana "Remarketing Agosto" con estado "Activa"
    When hago click en las opciones de "Remarketing Agosto"
    And selecciono "Pausar campana"
    And confirmo la accion en el dialogo
    Then el estado de "Remarketing Agosto" debe cambiar a "Pausada"
    And los envios pendientes deben quedar en cola

  Scenario: Duplicar campana existente con nuevo nombre
    Given existe la campana "Newsletter Septiembre"
    When selecciono "Duplicar" en el menu de acciones
    And cambio el nombre a "Newsletter Octubre"
    And confirmo la duplicacion
    Then debo ver "Newsletter Octubre" como borrador
    And la configuracion debe ser identica a la original
⚙️
features/step_definitions/auth.steps.ts TypeScript · 14 step definitions
🔷 auth.steps.ts
// Step definitions para el feature de autenticacion
// Mapea cada paso Gherkin a logica de prueba real
import { Given, When, Then, Before, After } from '@cucumber/cucumber';
import { expect } from 'chai';
import { CustomWorld } from '../support/world';

Before(async function(this: CustomWorld) {
  this.currentPage = '';
  this.lastResponse = null;
});

Given('la plataforma GrowthlabSaaS esta en funcionamiento', async function(this: CustomWorld) {
  const health = await this.api.get('/health');
  expect(health.status).to.equal(200);
  expect(health.data.version).to.match(/^\d+\.\d+\.\d+$/);
});

Given('la base de datos tiene usuarios de prueba cargados', async function(this: CustomWorld) {
  await this.api.post('/test/seed/users', {
    users: [
      { email: 'agencia@mediawave.es', password: 'Media456!', rol: 'agencia', nombre: 'MediaWave' },
      { email: 'admin@growthlabsaas.com', password: 'Admin123!', rol: 'admin' },
      { email: 'junior@growthlabsaas.com', password: 'Junior789!', rol: 'viewer' },
    ]
  });
});

Given('estoy en la pagina de login', async function(this: CustomWorld) {
  const res = await this.api.get('/login');
  expect(res.status).to.equal(200);
  this.currentPage = '/login';
});

When('introduzco {string} como email', function(this: CustomWorld, email: string) {
  this.formData.email = email;
});

When('introduzco {string} como contrasena', function(this: CustomWorld, password: string) {
  this.formData.password = password;
});

When('hago click en el boton de acceder', async function(this: CustomWorld) {
  this.lastResponse = await this.api.post('/auth/login', this.formData);
  if (this.lastResponse.status === 200) {
    this.authToken = this.lastResponse.data.token;
    this.currentPage = '/dashboard';
    this.currentUser = this.lastResponse.data.user;
  }
});

Then('debo ser redirigido al dashboard principal', function(this: CustomWorld) {
  expect(this.currentPage).to.equal('/dashboard');
  expect(this.lastResponse?.status).to.equal(200);
});

Then('debo ver el mensaje {string}', function(this: CustomWorld, texto: string) {
  expect(this.lastResponse?.data.message).to.include(texto);
});

Then('debo tener el rol {string} activo', function(this: CustomWorld, rol: string) {
  expect(this.currentUser?.rol).to.equal(rol);
});

Then('debo ver el error {string}', function(this: CustomWorld, errorMsg: string) {
  expect(this.lastResponse?.data.error).to.equal(errorMsg);
  expect(this.lastResponse?.status).to.be.oneOf([400, 401, 403]);
});
🌍
features/support/world.ts Custom World · Estado compartido entre steps
🔷 world.ts
// Custom World para GrowthlabSaaS — estado compartido entre step definitions
import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber';
import axios, { AxiosInstance } from 'axios';

export class CustomWorld extends World {
  api: AxiosInstance;
  authToken: string | null = null;
  currentPage: string = '';
  currentUser: any = null;
  lastResponse: any = null;
  formData: Record<string, string> = {};

  constructor(options: IWorldOptions) {
    super(options);
    this.api = axios.create({
      baseURL: process.env.TEST_API_URL || 'http://localhost:3000/api',
      timeout: 5000,
      validateStatus: () => true, // no lanzar excepciones en 4xx/5xx
    });
  }

  // Helper: autenticar y guardar token automaticamente
  async authenticate(email: string, password: string): Promise<void> {
    const res = await this.api.post('/auth/login', { email, password });
    expect(res.status).to.equal(200, `Login fallido para ${email}`);
    this.authToken = res.data.token;
    this.currentUser = res.data.user;
    this.api.defaults.headers.common['Authorization'] = `Bearer ${this.authToken}`;
  }

  // Helper: peticion autenticada con reintentos
  async apiGet(path: string, retries = 2) {
    for (let i = 0; i < retries; i++) {
      const res = await this.api.get(path);
      if (res.status !== 503) return res;
      await new Promise(r => setTimeout(r, 500));
    }
  }
}

setWorldConstructor(CustomWorld);
⚙️
cucumber.js · Configuracion de perfiles Perfiles: default · ci · smoke
⚙️ cucumber.js
// cucumber.js — Configuracion de Cucumber para GrowthlabSaaS
module.exports = {
  default: {
    requireModule: ['ts-node/register'],
    require: [
      'features/step_definitions/**/*.ts',
      'features/support/**/*.ts',
    ],
    format: [
      'progress-bar',
      'html:reports/cucumber.html',
      'json:reports/cucumber.json',
    ],
    publishQuiet: true,
    parallel: 2,   // ejecutar 2 workers en paralelo
  },

  ci: {
    requireModule: ['ts-node/register'],
    require: [
      'features/step_definitions/**/*.ts',
      'features/support/**/*.ts',
    ],
    format: ['json:reports/cucumber.json'],
    tags: 'not @wip',    // excluir tests en desarrollo
    publishQuiet: true,
    parallel: 4,
    retry: 1,           // un reintento automatico en CI
  },

  smoke: {
    requireModule: ['ts-node/register'],
    require: [
      'features/step_definitions/**/*.ts',
      'features/support/**/*.ts',
    ],
    format: ['progress-bar'],
    tags: '@smoke',    // solo tests criticos (pre-deploy)
    publishQuiet: true,
  },
};
🚀
.github/workflows/bdd.yml · Pipeline CI GitHub Actions · Ubuntu Latest
📦
Checkout
actions/checkout@v4
✓ 2s
🟢
Node 20
setup-node@v4
✓ 5s
⬇️
npm ci
Instalar deps
✓ 18s
🗄️
DB Seed
Test fixtures
✓ 3s
🥒
Cucumber CI
not @wip · x4
✓ 4.2s
📊
Reporte
HTML + JSON
✓ 1s
⚙️ .github/workflows/bdd.yml
name: BDD Tests — GrowthlabSaaS

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  cucumber:
    runs-on: ubuntu-latest
    env:
      TEST_API_URL: http://localhost:3000/api
      NODE_ENV: test

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: growthlabsaas_test
          POSTGRES_PASSWORD: testpass
        ports: ['5432:5432']

    steps:
      # 1. Codigo fuente
      - uses: actions/checkout@v4

      # 2. Node.js 20
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      # 3. Dependencias
      - run: npm ci

      # 4. Migraciones y seed de prueba
      - run: npm run db:migrate:test && npm run db:seed:test

      # 5. Arrancar API en background
      - run: npm run start:test &
      - run: npx wait-on http://localhost:3000/health --timeout 30000

      # 6. Ejecutar Cucumber (perfil CI, sin @wip)
      - run: npx cucumber-js --profile ci

      # 7. Publicar reporte HTML
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: bdd-report-${{ github.run_number }}
          path: reports/
          retention-days: 30
Resultados de ejecucion · Ultimo run 16 Jun 2026 · 09:41 UTC · 4.2s total
Login exitoso con credenciales de agencia
@auth · auth.feature:12
  • Dadola plataforma esta en funcionamiento
  • Yla base de datos tiene usuarios
  • Dadoestoy en la pagina de login
  • Cuandointroduzco email y contrasena
  • Yhago click en acceder
  • Entoncesredirijo al dashboard
0.38s
Login fallido con contrasena incorrecta
@auth · auth.feature:25
  • Dadoestoy en la pagina de login
  • Cuandointroduzco contrasena incorrecta
  • Yhago click en acceder
  • Entoncesveo error "Credenciales incorrectas"
  • Ypermanezco en login
0.22s
Crear campana de email programada
@api @smoke · campanas.feature:14
  • Dadoautenticado como agencia@mediawave.es
  • Ytengo al menos un segmento
  • Cuandocreo "Black Friday MediaWave 2026"
  • Yasigno segmento y fecha
  • Entoncesaparece con estado "Programada"
0.54s
Logout cierra la sesion correctamente
@auth @smoke · auth.feature:52
  • Dadoautenticado como agencia@mediawave.es
  • Cuandoclick en menu de usuario
  • Yselecciono "Cerrar sesion"
  • Entoncesredirijo a login
  • Ytoken invalidado
0.31s
Pausar campana activa
@api · campanas.feature:30
  • Dadocampana "Remarketing Agosto" activa
  • Cuandoselecciono "Pausar campana"
  • Yconfirmo en dialogo
  • Entoncesestado cambia a "Pausada"
  • Yenvios quedan en cola
0.47s
Ver ROAS en dashboard de metricas
@api @regression · metricas.feature:8
  • Dadoautenticado con campanas con datos
  • Yestoy en dashboard de metricas
  • Entoncesdebo ver ROAS > 0 — AssertionError: expected null to be greater than 0
1.02s — FAIL · metricas.steps.ts:47
Generado con Cucumber BDD · CULTIVA IA · GrowthlabSaaS v2.4.1 · Node 20 · @cucumber/cucumber 10.x
15 ✓ pasando 1 ✗ fallando 2 ○ @wip 4.2s total