De Gherkin al pipeline de despliegue
Conecta archivos .feature de Gherkin con pruebas automatizadas de Playwright, ejecútalas en CI, publica informes de éxito/fallo y bloquea despliegues, para que ningún código se envíe a menos que cada requisito pase.
El flujo completo
| Paso | Etapa | Detalle |
|---|---|---|
| 1 | Requisitos del producto | Define qué construir |
| 2 | Historia de usuario en GitHub Issues | Criterios de aceptación etiquetados con @REQ-xxx |
| 3 | Archivo .feature de Gherkin | Cucumber analiza los escenarios |
| 4 | Step definitions de Playwright + Cucumber | La automatización del navegador ejecuta las pruebas |
| 5 | Se ejecuta el pipeline de GitHub Actions | En cada PR y push a main |
| 6 | Publica informe de éxito/fallo | Por escenario, por requisito |
| 7 | Permite o bloquea el despliegue | Bloqueo deploy job needs: [test] |
Cada etapa es trazable. Un escenario fallido indica el requisito que cubre, la historia de usuario a la que pertenece y el comportamiento exacto que se rompió.
1. De requisitos del producto a historias de usuario
Los requisitos empiezan como GitHub Issues. Cada issue recibe un ID de requisito en el título y criterios de aceptación escritos como escenarios Gherkin.
Título del GitHub Issue: "REQ-101: Validación del formulario de perfil"
Etiquetas: requirement, priority: high
Como arquitecto cloud Quiero que el formulario de perfil valide mis entradas antes del envío Para recibir feedback inmediato y no enviar datos incompletos
Criterios de aceptación (Gherkin)
- Los campos obligatorios muestran errores cuando están vacíos
- El formato de email se valida
- La URL de LinkedIn debe apuntar a linkedin.com
- Los años de experiencia deben estar entre 0 y 50
- La fecha de fin del trabajo debe ser posterior a la de inicio
Ver:
features/profile-form/04-validation.feature
2. Archivos .feature de Gherkin con etiquetas de requisito
Cada escenario se vincula con su requisito mediante etiquetas @REQ-xxx. Cucumber recopila estas etiquetas para los informes.
# features/profile-form/04-validation.feature
@REQ-101
Feature: Profile Form Validation
As a Cloud Architect
I want the form to validate my inputs
So that I get immediate feedback on errors
Background:
Given the architect is logged in
And they navigate to "/profile/create"
@REQ-101 @critical
Scenario: Required fields show errors when empty
Given the architect is on step 1 "Personal Info"
And they have not filled in any fields
When they click "Next"
Then they should see "Full name is required"
And they should see "Email is required"
And the form should not advance to step 2
@REQ-101
Scenario: Email format is validated
Given the architect is on step 1 "Personal Info"
When they type "not-an-email" in the "Email" field
And they click "Next"
Then they should see "Please enter a valid email address"
@REQ-101
Scenario: LinkedIn URL must point to linkedin.com
Given the architect is on step 1 "Personal Info"
When they type "https://twitter.com/someone" in the "LinkedIn URL" field
And they click "Next"
Then they should see "Must be a valid LinkedIn profile URL"
@REQ-102
Scenario: Years of experience must be between 0 and 50
Given the architect is on step 2 "Experience"
When they type "-3" in the "Years of Experience" field
And they click "Next"
Then they should see "Must be between 0 and 50 years"
@REQ-102
Scenario: Job end date must be after start date
Given the architect is on step 3 "Job History"
When they set start date to "2025-06-01"
And they set end date to "2024-01-01"
And they click "Next"
Then they should see "End date must be after start date"# features/profile-form/08-file-uploads.feature
@REQ-201
Feature: File Upload Protection
As a Cloud Architect
I want the form to reject invalid files
So that only safe, correct images are uploaded
@REQ-201 @critical
Scenario: Profile photo rejects oversized file
Given the architect is on step 5 "Uploads"
When they select a 15MB file for "Profile Photo"
Then they should see "File must be under 5MB"
@REQ-201 @critical
Scenario: Profile photo rejects non-image files
Given the architect is on step 5 "Uploads"
When they select a .exe file for "Profile Photo"
Then they should see "Only JPEG, PNG, and WebP files are accepted"
@REQ-201
Scenario: Maximum file count enforced
Given the architect has uploaded 10 architecture diagrams
When they try to add another file
Then they should see "Maximum 10 files allowed"# features/profile-form/03-multi-step.feature
@REQ-301
Feature: Multi-Step Navigation
As a Cloud Architect
I want to navigate between form steps
So that I can fill out the form at my own pace
@REQ-301 @critical
Scenario: Completing step 1 unlocks step 2
Given the architect fills all required fields on step 1
When they click "Next"
Then step 2 "Experience" should be active
And step 1 should show a checkmark
@REQ-301
Scenario: Navigating back preserves data
Given the architect is on step 3 "Job History"
And they have entered "Acme Corp" as company name
When they click "Back"
And they click "Next"
Then the company name field should still contain "Acme Corp"
@REQ-301
Scenario: Cannot skip ahead past invalid steps
Given the architect is on step 1 "Personal Info"
And the "Full Name" field is empty
When they click step 3 in the progress indicator
Then step 1 should remain active3. Instalar Playwright + Cucumber
Configura la cadena de herramientas que conecta los archivos .feature con la automatización del navegador.
npm install -D @playwright/test playwright @cucumber/cucumber @badeball/cypress-cucumber-preprocessor
# Usamos @cucumber/cucumber como parser de Gherkin, no Cypress
# Para Cucumber nativo en Playwright, usa playwright-bdd:
npm install -D playwright-bddplaywright-bdd es el puente recomendado: analiza archivos .feature y genera archivos de prueba de Playwright automáticamente.
playwright.config.ts
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
import { defineBddConfig } from "playwright-bdd";
const testDir = defineBddConfig({
features: "features/**/*.feature",
steps: "features/step-definitions/**/*.ts",
});
export default defineConfig({
testDir,
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
["html", { open: "never" }],
["json", { outputFile: "test-results/results.json" }],
// Reporter JSON de Cucumber para seguimiento de requisitos
["junit", { outputFile: "test-results/junit.xml" }],
],
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "mobile", use: { ...devices["iPhone 14"] } },
],
webServer: {
command: "npm run dev",
port: 3000,
reuseExistingServer: !process.env.CI,
},
});4. Step definitions: conectar Gherkin con Playwright
Las step definitions son el pegamento. Cada Given/When/Then del archivo .feature coincide con una step definition que controla el navegador.
// features/step-definitions/navigation-steps.ts
import { createBdd } from "playwright-bdd";
import { expect } from "@playwright/test";
const { Given, When, Then } = createBdd();
// ── Pasos de Background ───────────────────────────────────
Given("the architect is logged in", async ({ page }) => {
// Establece cookie de auth o usa storageState
await page.goto("/api/test/login");
});
Given("they navigate to {string}", async ({ page }, url: string) => {
await page.goto(url);
});
// ── Navegación entre pasos ────────────────────────────────────
Given(
"the architect is on step {int} {string}",
async ({ page }, step: number, _name: string) => {
await page.goto("/profile/create");
for (let i = 1; i < step; i++) {
await fillStepWithValidData(page, i);
await page.getByTestId("btn-next").click();
}
await expect(
page.getByTestId(`step-${step}`)
).toHaveAttribute("data-status", "current");
}
);
Given(
"the architect fills all required fields on step {int}",
async ({ page }, step: number) => {
await fillStepWithValidData(page, step);
}
);
When("they click {string}", async ({ page }, buttonText: string) => {
await page.getByRole("button", { name: buttonText }).click();
});
When(
"they click step {int} in the progress indicator",
async ({ page }, step: number) => {
await page.getByTestId(`step-${step}`).click();
}
);
Then(
"step {int} {string} should be active",
async ({ page }, step: number, _name: string) => {
await expect(
page.getByTestId(`step-${step}`)
).toHaveAttribute("data-status", "current");
}
);
Then("step {int} should show a checkmark", async ({ page }, step: number) => {
await expect(
page.getByTestId(`step-${step}`)
).toHaveAttribute("data-status", "completed");
});
Then("step {int} should remain active", async ({ page }, step: number) => {
await expect(
page.getByTestId(`step-${step}`)
).toHaveAttribute("data-status", "current");
});
// ── Helper ─────────────────────────────────────────────
async function fillStepWithValidData(page: import("@playwright/test").Page, step: number) {
switch (step) {
case 1:
await page.getByTestId("field-fullName").fill("Jane Doe");
await page.getByTestId("field-email").fill("jane@example.com");
break;
case 2:
await page.getByTestId("field-yearsOfExperience").fill("8");
await page.getByTestId("field-currentRole").fill("Principal Architect");
await page.getByTestId("cert-aws-solutions-architect-professional").click();
break;
case 3:
await page.getByTestId("job-0-company").fill("Acme Corp");
await page.getByTestId("job-0-role").fill("Cloud Architect");
await page.getByTestId("job-0-start").fill("2020-01-01");
await page.getByTestId("job-0-current").click();
break;
case 4:
await page.getByTestId("platform-aws").click();
await page.getByTestId("specialty-serverless").click();
break;
case 5:
// Las subidas son opcionales -- omitir
break;
}
}// features/step-definitions/validation-steps.ts
import { createBdd } from "playwright-bdd";
import { expect } from "@playwright/test";
const { Given, When, Then } = createBdd();
When(
"they type {string} in the {string} field",
async ({ page }, value: string, fieldLabel: string) => {
const field = page.getByLabel(fieldLabel, { exact: false });
await field.clear();
await field.fill(value);
}
);
When(
"they set start date to {string}",
async ({ page }, date: string) => {
await page.getByTestId("job-0-start").fill(date);
}
);
When(
"they set end date to {string}",
async ({ page }, date: string) => {
await page.getByTestId("job-0-end").fill(date);
}
);
Given(
"they have not filled in any fields",
async () => {
// No-op -- los campos están vacíos por defecto
}
);
Given(
"the {string} field is empty",
async () => {
// No-op -- el campo está vacío por defecto
}
);
Then(
"they should see {string}",
async ({ page }, errorText: string) => {
await expect(page.getByText(errorText)).toBeVisible();
}
);
Then(
"the form should not advance to step {int}",
async ({ page }, step: number) => {
await expect(
page.getByTestId(`step-${step}`)
).not.toHaveAttribute("data-status", "current");
}
);// features/step-definitions/upload-steps.ts
import { createBdd } from "playwright-bdd";
import { expect } from "@playwright/test";
const { Given, When, Then } = createBdd();
When(
"they select a {int}MB file for {string}",
async ({ page }, sizeMB: number, fieldLabel: string) => {
const testId = fieldLabel.toLowerCase().replace(/\s+/g, "");
const input = page.getByTestId(`upload-${testId}`).locator("input[type=file]");
await input.setInputFiles({
name: "test-file.jpg",
mimeType: "image/jpeg",
buffer: Buffer.alloc(sizeMB * 1024 * 1024),
});
}
);
When(
"they select a .exe file for {string}",
async ({ page }, fieldLabel: string) => {
const testId = fieldLabel.toLowerCase().replace(/\s+/g, "");
const input = page.getByTestId(`upload-${testId}`).locator("input[type=file]");
await input.setInputFiles({
name: "malware.exe",
mimeType: "application/x-msdownload",
buffer: Buffer.alloc(1024),
});
}
);
Given(
"the architect has uploaded {int} architecture diagrams",
async ({ page }, count: number) => {
const input = page
.getByTestId("upload-architectureDiagrams")
.locator("input[type=file]");
for (let i = 0; i < count; i++) {
await input.setInputFiles({
name: `diagram-${i + 1}.png`,
mimeType: "image/png",
buffer: Buffer.alloc(1024),
});
}
}
);
When("they try to add another file", async ({ page }) => {
const input = page
.getByTestId("upload-architectureDiagrams")
.locator("input[type=file]");
await input.setInputFiles({
name: "one-more.png",
mimeType: "image/png",
buffer: Buffer.alloc(1024),
});
});// features/step-definitions/data-persistence-steps.ts
import { createBdd } from "playwright-bdd";
import { expect } from "@playwright/test";
const { Given, Then } = createBdd();
Given(
"they have entered {string} as company name",
async ({ page }, company: string) => {
await page.getByTestId("job-0-company").fill(company);
}
);
Then(
"the company name field should still contain {string}",
async ({ page }, expected: string) => {
await expect(page.getByTestId("job-0-company")).toHaveValue(expected);
}
);5. Generar pruebas a partir de los .feature
playwright-bdd lee tus archivos .feature y las step definitions, y luego genera archivos de prueba estándar de Playwright. Nunca escribes pruebas de Playwright manualmente: salen de Gherkin.
# Genera pruebas de Playwright a partir de archivos .feature
npx bddgen
# Ejecuta las pruebas generadas
npx playwright test
# Ejecuta solo escenarios etiquetados con @critical
npx playwright test --grep "@critical"
# Ejecuta solo escenarios de REQ-101
npx playwright test --grep "@REQ-101"Los archivos de prueba generados tienen este aspecto (no los edites):
// .features-gen/profile-form/04-validation.feature.spec.ts (auto-generado)
import { test } from "playwright-bdd";
test.describe("Profile Form Validation @REQ-101", () => {
test.beforeEach(async ({ Given, page }) => {
await Given("the architect is logged in");
await Given('they navigate to "/profile/create"');
});
test("Required fields show errors when empty @REQ-101 @critical", async ({
Given, When, Then
}) => {
await Given('the architect is on step 1 "Personal Info"');
await Given("they have not filled in any fields");
await When('they click "Next"');
await Then('they should see "El nombre completo es obligatorio"');
await Then('they should see "El email es obligatorio"');
await Then("the form should not advance to step 2");
});
// ... más escenarios auto-generados desde el archivo .feature
});6. Pipeline de GitHub Actions
El workflow de CI se ejecuta en cada PR y push a main. Genera pruebas a partir de los .feature, las ejecuta, publica informes y bloquea el despliegue.
# .github/workflows/gherkin-deploy.yml
name: Gherkin Test & Deploy Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
checks: write
pull-requests: write
jobs:
# ── Stage 1: Generate and run Cucumber/Playwright tests ──
test:
name: Run Gherkin Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Generate tests from .feature files
run: npx bddgen
- name: Build application
run: npm run build
- name: Run Gherkin Playwright tests
run: npx playwright test
env:
CI: true
# ── Publish test results ──
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
- name: Upload test results JSON
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: test-results/
retention-days: 30
# ── Post results to PR as comment ──
- name: Parse test results and post PR comment
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = JSON.parse(
fs.readFileSync('test-results/results.json', 'utf8')
);
// Count pass/fail per @REQ tag
const reqMap = new Map();
for (const suite of results.suites ?? []) {
for (const spec of suite.specs ?? []) {
const tags = spec.title.match(/@REQ-\d+/g) ?? [];
const passed = spec.tests.every(t => t.status === 'expected');
for (const tag of tags) {
if (!reqMap.has(tag)) reqMap.set(tag, { pass: 0, fail: 0, scenarios: [] });
const entry = reqMap.get(tag);
passed ? entry.pass++ : entry.fail++;
entry.scenarios.push({ title: spec.title, passed });
}
}
}
// Build markdown table
let body = '## Gherkin Test Results\n\n';
body += '| Requirement | Passed | Failed | Status |\n';
body += '|-------------|--------|--------|--------|\n';
for (const [req, data] of [...reqMap.entries()].sort()) {
const status = data.fail === 0 ? '✅ Pass' : '❌ Fail';
body += `| ${req} | ${data.pass} | ${data.fail} | ${status} |\n`;
}
const totalPass = [...reqMap.values()].reduce((s, d) => s + d.pass, 0);
const totalFail = [...reqMap.values()].reduce((s, d) => s + d.fail, 0);
body += `\n**Total: ${totalPass} passed, ${totalFail} failed**\n`;
body += '\n📊 [Full Report](../actions/runs/' + context.runId + ')\n';
// Post or update PR comment
const { data: comments } = await github.rest.issues.listComments({
...context.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c =>
c.body?.includes('## Gherkin Test Results')
);
if (existing) {
await github.rest.issues.updateComment({
...context.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body,
});
}
# ── Post JUnit results as GitHub Check ──
- name: Publish JUnit results
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: test-results/junit.xml
check_name: Gherkin BDD Results
detailed_summary: true
include_passed: true
# ── Stage 2: Deploy (gated by test job) ──
deploy:
name: Deploy to Production
needs: [test]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment:
name: production
url: https://your-app.vercel.app
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}Qué hace el pipeline
npm ci-- instalación limpia de dependenciasnpx bddgen-- lee archivos.feature+ step definitions y genera archivos de prueba de Playwrightnpm run build-- compila la app de Next.jsnpx playwright test-- ejecuta todas las pruebas generadas en Chromium sin interfaz- Sube artefactos -- informe HTML + resultados JSON conservados durante 30 días
- Publica comentario en la PR -- tabla markdown con éxito/fallo por etiqueta
@REQ-xxx - Publica check JUnit -- GitHub Check con resultados detallados de pruebas
- Job de despliegue -- solo se ejecuta si el job
testpasa (needs: [test])
7. Seguimiento de éxito/fallo por requisito
Las etiquetas @REQ-xxx en tus archivos .feature habilitan el seguimiento a nivel de requisito. El pipeline las analiza desde los resultados de pruebas y las reporta.
Salida del comentario en la PR
Cuando el pipeline se ejecuta en una PR, publica un comentario como este:
Resultados de pruebas Gherkin
Requisito Aprobados Fallidos Estado @REQ-101 3 0 Aprobado @REQ-102 2 0 Aprobado @REQ-201 2 1 Fallido @REQ-301 3 0 Aprobado Total: 10 aprobados, 1 fallido
Convenciones de etiquetas
| Etiqueta | Significado | Ejemplo |
|---|---|---|
@REQ-xxx | Vincula el escenario con un requisito/historia de usuario | @REQ-101 |
@critical | Debe pasar para desplegar | @critical @REQ-101 |
@smoke | Incluido en la suite de smoke tests | @smoke @REQ-301 |
@wip | En progreso, omitido en CI | @wip @REQ-401 |
Ejecución por etiqueta
# Todos los escenarios críticos
npx playwright test --grep "@critical"
# Todos los escenarios de REQ-101
npx playwright test --grep "@REQ-101"
# Omitir trabajo en progreso
npx playwright test --grep-invert "@wip"
# Solo smoke tests (comprobación rápida de bloqueo)
npx playwright test --grep "@smoke"8. Bloquear despliegues
El job de despliegue usa needs: [test] para bloquear el despliegue cuando falla cualquier escenario. Este es el mecanismo de aplicación.
Cómo funciona el bloqueo
deploy:
needs: [test] # ← Will not run if test job fails
if: github.ref == 'refs/heads/main'
environment: production # ← Optional: require manual approval tooBloqueo solo de críticos (opcional)
Si quieres permitir despliegues cuando fallan pruebas no críticas, añade un job separado solo para críticos:
jobs:
test-all:
name: Run All Gherkin Tests
runs-on: ubuntu-latest
steps:
# ... same setup ...
- name: Run all tests
run: npx playwright test
continue-on-error: true # Don't fail the job
# ... publish reports ...
test-critical:
name: Run Critical Tests (Deploy Gate)
runs-on: ubuntu-latest
steps:
# ... same setup ...
- name: Run critical tests only
run: npx playwright test --grep "@critical"
# This job WILL fail if any @critical scenario fails
deploy:
needs: [test-critical] # ← Only gated by critical tests
# test-all results are informational onlyReglas de protección de entorno
Para una capa adicional, configura la protección de entorno de GitHub:
- Ve a Settings > Environments > production
- Añade Required reviewers (aprobación manual opcional)
- Añade Deployment branch rules (solo
main) - El
environment: productionen el workflow aplica estas reglas
9. Vincular escenarios con historias de usuario
Cierra el bucle de trazabilidad vinculando las etiquetas Gherkin con GitHub Issues.
En el archivo .feature
# Links to GitHub Issue #42 and requirement REQ-101
@REQ-101 @issue-42
Scenario: Required fields show errors when empty
Given ...En el GitHub Issue
REQ-101: Validación del formulario de perfil
Pruebas vinculadas
- Escenarios 1-5 de
features/profile-form/04-validation.feature- Pipeline: enlace a la última ejecución
Estado: Todos los escenarios pasan
Actualización automática del estado del issue (opcional)
Añade este paso al pipeline para comentar en los issues vinculados cuando sus escenarios fallen:
- name: Notify linked issues on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = JSON.parse(
fs.readFileSync('test-results/results.json', 'utf8')
);
// Find failed @issue-xxx tags
const failedIssues = new Set();
for (const suite of results.suites ?? []) {
for (const spec of suite.specs ?? []) {
const hasFail = spec.tests.some(t => t.status !== 'expected');
if (!hasFail) continue;
const issueTags = spec.title.match(/@issue-(\d+)/g) ?? [];
for (const tag of issueTags) {
failedIssues.add(parseInt(tag.replace('@issue-', '')));
}
}
}
// Comment on each failed issue
for (const issueNum of failedIssues) {
await github.rest.issues.createComment({
...context.repo,
issue_number: issueNum,
body: `⚠️ **BDD test failure** in run [#${context.runNumber}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})\n\nOne or more Gherkin scenarios linked to this issue failed.`,
});
}Árbol de archivos completo
project/
├── features/
│ ├── profile-form/
│ │ ├── 01-purpose.feature ← @REQ-001
│ │ ├── 02-field-types.feature ← @REQ-002
│ │ ├── 03-multi-step.feature ← @REQ-301
│ │ ├── 04-validation.feature ← @REQ-101, @REQ-102
│ │ ├── 05-submission.feature ← @REQ-401
│ │ ├── 06-ui-ux.feature ← @REQ-501
│ │ ├── 07-accessibility.feature ← @REQ-601
│ │ ├── 08-file-uploads.feature ← @REQ-201
│ │ ├── 09-type-safety.feature ← @REQ-701
│ │ └── 10-tech-stack.feature ← @REQ-801
│ └── step-definitions/
│ ├── navigation-steps.ts
│ ├── validation-steps.ts
│ ├── upload-steps.ts
│ └── data-persistence-steps.ts
├── .features-gen/ ← Auto-generado por bddgen (gitignored)
├── playwright.config.ts ← Configuración de Cucumber + Playwright
├── .github/
│ └── workflows/
│ └── gherkin-deploy.yml ← Pipeline CI/CD
├── test-results/ ← Salida JSON + JUnit (gitignored)
└── playwright-report/ ← Informe HTML (gitignored)
Receta
Tarjeta de receta de referencia rápida — lista para copiar y pegar.
# 1. Escribe archivos .feature con etiquetas @REQ-xxx
# features/profile-form/04-validation.feature
# 2. Escribe step definitions
# features/step-definitions/validation-steps.ts
# 3. Genera pruebas de Playwright a partir de los .feature
npx bddgen
# 4. Ejecuta localmente
npx playwright test
# 5. Ejecuta un requisito específico
npx playwright test --grep "@REQ-101"
# 6. Ejecuta solo críticos (bloqueo de despliegue)
npx playwright test --grep "@critical"
# 7. Ver informe HTML
npx playwright show-reportCuándo usarlo: Cuando ya tienes archivos .feature y step definitions funcionando. Este documento muestra cómo integrarlos en CI/CD para que apliquen los requisitos en cada PR.
Errores comunes
-
npx bddgendebe ejecutarse antes denpx playwright test-- los archivos de prueba generados en.features-gen/no existen hasta que ejecutes el generador. Añádelo como paso de CI antes del paso de pruebas. -
.features-gen/debe estar en .gitignore -- son archivos generados. Hacer commit provoca conflictos de merge y pruebas obsoletas. -
Los tipos de parámetro en las step definitions importan --
{int}coincide con números,{string}con cadenas entre comillas. Tipos de parámetro incorrectos provocan errores de "step not found" difíciles de depurar. -
El filtrado por etiquetas usa
--grep, no--tags-- Playwright usa--greppara filtrar, no la sintaxis--tagsde Cucumber.@REQ-101se convierte en--grep "@REQ-101". -
continue-on-error: truevsif: always()-- usacontinue-on-erroren el paso de pruebas para permitir la publicación de informes. Usaif: always()en los pasos de subida de artefactos. No los confundas o perderás fallos. -
La ruta del reporter JSON debe coincidir con el script de análisis -- la ruta de
results.jsonenplaywright.config.tsdebe coincidir con lo que lee el script de GitHub Actions. Ambos usan por defectotest-results/results.json. -
La protección de entorno es independiente del
needsdel job --needs: [test]bloquea el job de despliegue. Las reglas de protección de entorno añaden aprobación manual. Usa ambos para despliegues a producción.
Alternativas
| Alternativa | Úsala cuando | No la uses cuando |
|---|---|---|
| Cucumber.js independiente (sin Playwright) | Pruebas solo de API sin navegador | Necesitas probar comportamiento real del navegador |
| Playwright sin Cucumber | El equipo prefiere escribir pruebas directamente | Los stakeholders necesitan leer y aprobar escenarios |
| Cypress + cypress-cucumber-preprocessor | El equipo ya usa Cypress | Empiezas de cero (Playwright tiene mejor rendimiento en CI) |
| SpecFlow (.NET) | El backend es .NET | Stack JavaScript/TypeScript |
| GitLab CI en lugar de GitHub Actions | Usas GitLab | Usas GitHub |
Relacionado
- Checklist de decisión de formularios con Gherkin -- escribe los escenarios Gherkin
- De Gherkin a código -- implementa el código del formulario
- Ejemplo de árbol de archivos -- cada archivo del formulario funcional
- Configuración de Playwright -- conceptos básicos de configuración de Playwright
- Patrones de Playwright -- patrones de localizadores y aserciones