features/auth.feature
Gherkin · 7 escenarios
@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
@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
// 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
// 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 — 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
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
Exportar segmento a CSV
@wip · segmentos.feature:44
- Dadosegmento "Clientes activos Q4" creado
- Cuandohago click en "Exportar"
- Entoncesdescargo CSV con 1.243 filas
Saltado — @wip (en desarrollo)
Notificacion en tiempo real al pausar
@wip · campanas.feature:58
- Dadocampana activa con webhook
- Cuandopauso desde la API
- Entonceswebhook recibe evento PAUSED
Saltado — @wip (en desarrollo)
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