🎯 Ejemplos recomendados
Balanced sample collections from various categories for you to explore
BDD with Cucumber - Prácticas de Desarrollo Orientado a Comportamiento
Ejemplos completos de Cucumber BDD incluyendo archivos de características, definiciones de pasos, tablas de datos, hooks y patrones BDD avanzados para desarrollo colaborativo
💻 Configuración Básica de Cucumber BDD javascript
🟢 simple
⭐⭐
Configuración completa de proyecto Cucumber con archivos de características, definiciones de pasos, configuración y patrones BDD básicos
⏱️ 45 min
🏷️ cucumber, bdd, setup, configuration
Prerequisites:
JavaScript basics, Testing concepts, Gherkin syntax
// Cucumber BDD - Basic Setup and Configuration
// 1. package.json - Dependencies and Scripts
{
"name": "cucumber-bdd-example",
"version": "1.0.0",
"description": "Behavior Driven Development with Cucumber",
"main": "index.js",
"scripts": {
"test": "cucumber-js",
"test:parallel": "cucumber-js --parallel 4",
"test:wip": "cucumber-js --tags @wip",
"test:smoke": "cucumber-js --tags @smoke",
"test:regression": "cucumber-js --tags @regression",
"report:html": "cucumber-js --format html:reports/cucumber-report.html",
"report:json": "cucumber-js --format json:reports/cucumber-report.json"
},
"dependencies": {
"@cucumber/cucumber": "^10.3.1",
"chai": "^4.3.10",
"axios": "^1.6.2"
},
"devDependencies": {
"@cucumber/cucumber": "^10.3.1",
"@cucumber/pretty-formatter": "^1.0.0",
"multiple-cucumber-html-reporter": "^3.6.0"
}
}
// 2. cucumber.js - Configuration
module.exports = {
default: {
requireModule: ['@babel/register'],
require: ['step-definitions/**/*.js', 'support/**/*.js'],
format: [
'progress-bar',
'html:reports/cucumber-report.html',
'json:reports/cucumber-report.json'
],
formatOptions: {
snippetInterface: 'async-await',
snippetSyntax: 'typescript',
},
publishQuiet: true,
dryRun: false,
failFast: false,
strict: true,
worldParameters: {
apiUrl: 'https://api.example.com',
timeout: 10000
}
}
};
// 3. features/user-registration.feature - Gherkin Feature File
Feature: User Registration
As a new user
I want to register for an account
So that I can access the application features
Scenario: Successful user registration
Given I am on the registration page
When I enter valid registration details:
| firstName | John |
| lastName | Doe |
| email | [email protected] |
| password | Secret123! |
And I submit the registration form
Then I should see a success message
And I should receive a confirmation email
And my account should be created in the system
Scenario: Registration with invalid email
Given I am on the registration page
When I enter registration details with invalid email:
| firstName | Jane |
| lastName | Smith |
| email | invalid-email |
| password | Secret123! |
And I submit the registration form
Then I should see an email validation error
And my account should not be created
@smoke
Scenario Outline: Registration validation
Given I am on the registration page
When I enter registration details with "field" value "value"
And I submit the registration form
Then I should see "errorType" error message
Examples:
| field | value | errorType |
| email | invalid-email | email validation |
| password | 123 | password strength |
| firstName | | required field |
| lastName | | required field |
// 4. step-definitions/user-registration-steps.js - Step Definitions
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('chai');
const RegistrationPage = require('../pages/registration-page');
const UserApiClient = require('../support/api-client');
let registrationPage;
let apiClient;
let userData;
// Before and After hooks
Before(async function () {
registrationPage = new RegistrationPage();
apiClient = new UserApiClient(this.parameters.apiUrl);
userData = {};
});
After(async function () {
// Cleanup: Delete test users
if (userData.email) {
await apiClient.deleteUser(userData.email);
}
});
Given('I am on the registration page', async function () {
await registrationPage.navigateTo();
expect(await registrationPage.isLoaded()).to.be.true;
});
When('I enter valid registration details:', async function (dataTable) {
userData = dataTable.rowsHash();
await registrationPage.fillForm(userData);
});
When('I enter registration details with invalid email:', async function (dataTable) {
userData = dataTable.rowsHash();
await registrationPage.fillForm(userData);
});
When('I enter registration details with {string} value {string}', async function (field, value) {
userData[field] = value;
await registrationPage.fillField(field, value);
});
When('I submit the registration form', async function () {
await registrationPage.submitForm();
});
Then('I should see a success message', async function () {
const successMessage = await registrationPage.getSuccessMessage();
expect(successMessage).to.include('Registration successful');
});
Then('I should receive a confirmation email', async function () {
// This would integrate with email service
const emailReceived = await apiClient.checkConfirmationEmail(userData.email);
expect(emailReceived).to.be.true;
});
Then('my account should be created in the system', async function () {
const userExists = await apiClient.userExists(userData.email);
expect(userExists).to.be.true;
});
Then('I should see an email validation error', async function () {
const errorMessage = await registrationPage.getErrorMessage();
expect(errorMessage).to.include('Invalid email format');
});
Then('my account should not be created', async function () {
const userExists = await apiClient.userExists(userData.email);
expect(userExists).to.be.false;
});
Then('I should see {string} error message', async function (errorType) {
const errorMessage = await registrationPage.getErrorMessage();
const errorMessages = {
'email validation': 'Invalid email format',
'password strength': 'Password must be at least 8 characters',
'required field': 'This field is required'
};
expect(errorMessage).to.include(errorMessages[errorType]);
});
// 5. pages/registration-page.js - Page Object Model
class RegistrationPage {
constructor() {
this.url = '/register';
this.firstNameInput = '#firstName';
this.lastNameInput = '#lastName';
this.emailInput = '#email';
this.passwordInput = '#password';
this.submitButton = '#registerButton';
this.successMessage = '.success-message';
this.errorMessage = '.error-message';
}
async navigateTo() {
await page.goto(this.url);
}
async isLoaded() {
await page.waitForSelector(this.firstNameInput);
await page.waitForSelector(this.lastNameInput);
await page.waitForSelector(this.emailInput);
await page.waitForSelector(this.passwordInput);
await page.waitForSelector(this.submitButton);
return true;
}
async fillForm(userData) {
await page.fill(this.firstNameInput, userData.firstName || '');
await page.fill(this.lastNameInput, userData.lastName || '');
await page.fill(this.emailInput, userData.email || '');
await page.fill(this.passwordInput, userData.password || '');
}
async fillField(field, value) {
const fieldSelectors = {
firstName: this.firstNameInput,
lastName: this.lastNameInput,
email: this.emailInput,
password: this.passwordInput
};
await page.fill(fieldSelectors[field], value);
}
async submitForm() {
await page.click(this.submitButton);
}
async getSuccessMessage() {
await page.waitForSelector(this.successMessage);
return await page.textContent(this.successMessage);
}
async getErrorMessage() {
await page.waitForSelector(this.errorMessage);
return await page.textContent(this.errorMessage);
}
}
module.exports = RegistrationPage;
// 6. support/api-client.js - API Support
const axios = require('axios');
class UserApiClient {
constructor(baseUrl) {
this.client = axios.create({
baseURL: baseUrl,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
}
async userExists(email) {
try {
const response = await this.client.get(`/users?email=${email}`);
return response.data.length > 0;
} catch (error) {
return false;
}
}
async deleteUser(email) {
try {
await this.client.delete(`/users/${email}`);
return true;
} catch (error) {
return false;
}
}
async checkConfirmationEmail(email) {
// Mock implementation - would integrate with email service
return true;
}
}
module.exports = UserApiClient;
// 7. support/hooks.js - Custom Hooks
const { Before, After, BeforeAll, AfterAll } = require('@cucumber/cucumber');
const { chromium } = require('playwright');
let browser;
let page;
BeforeAll(async function () {
browser = await chromium.launch({ headless: true });
});
AfterAll(async function () {
await browser.close();
});
Before(async function () {
page = await browser.newPage();
global.page = page;
});
After(async function () {
await page.close();
});
// Tag-based hooks
Before({ tags: '@smoke' }, async function () {
console.log('Running smoke test...');
});
Before({ tags: '@api' }, async function () {
// Setup API test environment
});
After({ tags: '@cleanup' }, async function () {
// Perform cleanup operations
});
💻 Patrones Avanzados y Mejores Prácticas de Cucumber javascript
🟡 intermediate
⭐⭐⭐
Patrones BDD complejos incluyendo tablas de datos, esquemas de escenario, hooks, etiquetas e implementación Cucumber a nivel empresarial
⏱️ 75 min
🏷️ cucumber, bdd, advanced, patterns
Prerequisites:
Cucumber basics, BDD concepts, JavaScript advanced, Page Object Model
// Cucumber Advanced Patterns and Best Practices
// 1. features/e-commerce-shopping.feature - Complex Feature File
Feature: E-commerce Shopping Cart
As a customer
I want to add products to my cart and complete purchases
So that I can buy products online
Background:
Given I am logged in as a customer
And the following products exist in the catalog:
| id | name | price | category | stock |
| 1 | Laptop Pro | 999.99 | Electronics | 10 |
| 2 | Wireless Mouse | 29.99 | Electronics | 50 |
| 3 | Coffee Maker | 79.99 | Home | 25 |
| 4 | Bluetooth Headphones | 149.99 | Electronics | 30 |
@shopping @regression
Scenario: Add single product to cart
When I add product "Laptop Pro" to cart
Then my cart should contain 1 item
And the cart total should be $999.99
And the product stock should be updated to 9
@shopping @regression
Scenario: Add multiple products to cart
When I add the following products to cart:
| product name | quantity |
| Wireless Mouse | 2 |
| Bluetooth Headphones | 1 |
| Coffee Maker | 1 |
Then my cart should contain 4 items
And the cart total should be $289.96
And each product stock should be updated accordingly
@checkout @critical
Scenario Outline: Complete purchase with different payment methods
Given I have the following products in my cart:
| product name | quantity |
| Laptop Pro | 1 |
When I proceed to checkout
And I select payment method
And I enter payment details
Then I should see order confirmation
And I should receive order confirmation email
And the order should be created in the system
And my cart should be empty
Examples:
| payment method |
| Credit Card |
| PayPal |
| Bank Transfer |
@inventory @edge-case
Scenario: Attempt to add out-of-stock product
When the product "Laptop Pro" stock is 0
And I attempt to add "Laptop Pro" to cart
Then I should see "out of stock" error message
And the product should not be added to my cart
@discount @promotion
Scenario: Apply discount code to cart
Given I have the following products in my cart:
| product name | quantity |
| Laptop Pro | 1 |
| Wireless Mouse | 1 |
When I apply discount code "SAVE10"
Then a 10% discount should be applied
And the cart total should be $923.98
// 2. step-definitions/shopping-cart-steps.js - Advanced Step Definitions
const { Given, When, Then, defineParameterType } = require('@cucumber/cucumber');
const { expect } = require('chai');
const ShoppingCart = require('../support/shopping-cart');
const ProductCatalog = require('../support/product-catalog');
const PaymentProcessor = require('../support/payment-processor');
// Custom parameter type for product identification
defineParameterType({
name: 'product',
regexp: /"([^"]+)"/,
transformer(name) {
return ProductCatalog.findByName(name);
}
});
// Custom parameter type for currency amounts
defineParameterType({
name: 'amount',
regexp: /$([0-9.]+)/,
transformer(amount) {
return parseFloat(amount);
}
});
let shoppingCart;
let productCatalog;
let paymentProcessor;
let currentUser;
Before(async function () {
shoppingCart = new ShoppingCart();
productCatalog = new ProductCatalog();
paymentProcessor = new PaymentProcessor();
currentUser = null;
});
Given('I am logged in as a customer', async function () {
currentUser = await this.createUser({
email: '[email protected]',
role: 'customer'
});
});
Given('the following products exist in the catalog:', async function (dataTable) {
for (const row of dataTable.hashes()) {
await productCatalog.addProduct({
id: parseInt(row.id),
name: row.name,
price: parseFloat(row.price),
category: row.category,
stock: parseInt(row.stock)
});
}
});
When('I add product {product} to cart', async function (product) {
await shoppingCart.addItem({
productId: product.id,
quantity: 1,
userId: currentUser.id
});
});
When('I add the following products to cart:', async function (dataTable) {
for (const row of dataTable.hashes()) {
const product = await productCatalog.findByName(row['product name']);
await shoppingCart.addItem({
productId: product.id,
quantity: parseInt(row.quantity),
userId: currentUser.id
});
}
});
Then('my cart should contain {int} item(s)', async function (expectedCount) {
const cart = await shoppingCart.getCart(currentUser.id);
expect(cart.items.length).to.equal(expectedCount);
});
Then('the cart total should be {amount}', async function (expectedTotal) {
const cart = await shoppingCart.getCart(currentUser.id);
expect(cart.total).to.equal(expectedTotal);
});
Then('the product stock should be updated to {int}', async function (expectedStock) {
const product = await productCatalog.findByName('Laptop Pro');
expect(product.stock).to.equal(expectedStock);
});
Then('each product stock should be updated accordingly', async function () {
const cart = await shoppingCart.getCart(currentUser.id);
for (const item of cart.items) {
const product = await productCatalog.findById(item.productId);
const originalStock = 10; // From background setup
expect(product.stock).to.equal(originalStock - item.quantity);
}
});
// 3. support/data-tables.js - Data Table Utilities
class DataTableHelper {
static toMap(dataTable, keyColumn, valueColumn) {
const result = {};
for (const row of dataTable.hashes()) {
result[row[keyColumn]] = row[valueColumn];
}
return result;
}
static toArrayOfMaps(dataTable) {
return dataTable.rows().map(row => {
const headers = dataTable.raw()[0];
return headers.reduce((obj, header, index) => {
obj[header] = row[index];
return obj;
}, {});
});
}
static toList(dataTable, column) {
return dataTable.rows().map(row => row[column]);
}
static validateRequiredColumns(dataTable, requiredColumns) {
const headers = dataTable.raw()[0];
for (const column of requiredColumns) {
if (!headers.includes(column)) {
throw new Error(`Required column '${column}' not found in data table`);
}
}
}
}
module.exports = DataTableHelper;
// 4. support/world-parameters.js - Custom World
const { setWorldConstructor } = require('@cucumber/cucumber');
class CustomWorld {
constructor({ attach, parameters }) {
this.attach = attach;
this.parameters = parameters;
this.testData = new Map();
this.apiClient = null;
this.database = null;
}
// Test data management
setTestData(key, value) {
this.testData.set(key, value);
}
getTestData(key) {
return this.testData.get(key);
}
// API client management
async createApiClient(options = {}) {
this.apiClient = new ApiClient({
baseURL: this.parameters.apiUrl,
...options
});
return this.apiClient;
}
// Database helpers
async createDatabaseConnection() {
this.database = new DatabaseClient(this.parameters.databaseUrl);
await this.database.connect();
return this.database;
}
// Screenshot attachment
async attachScreenshot(name) {
if (this.page) {
const screenshot = await this.page.screenshot({
encoding: 'base64',
fullPage: true
});
await this.attach(screenshot, `image/png`);
await this.attach(`Screenshot: ${name}`, 'text/plain');
}
}
// Response attachment
attachApiResponse(response) {
const responseBody = JSON.stringify(response.data, null, 2);
this.attach(`API Response (${response.status}):`, 'text/plain');
this.attach(responseBody, 'application/json');
}
}
setWorldConstructor(CustomWorld);
// 5. support/hooks.js - Advanced Hook Management
const { Before, After, BeforeAll, AfterAll } = require('@cucumber/cucumber');
const DatabaseClient = require('./database-client');
// Global hooks
BeforeAll(async function () {
console.log('Setting up test environment...');
await setupTestEnvironment();
});
AfterAll(async function () {
console.log('Cleaning up test environment...');
await cleanupTestEnvironment();
});
// Tag-based hooks
Before({ tags: '@api' }, async function (world) {
await world.createApiClient({
timeout: 30000,
retries: 3
});
});
Before({ tags: '@database' }, async function (world) {
await world.createDatabaseConnection();
});
After({ tags: '@database' }, async function (world) {
if (world.database) {
await world.database.close();
}
});
// Conditional hooks based on scenario name
Before(async function (scenario) {
if (scenario.pickle.name.includes('performance')) {
this.testType = 'performance';
this.startTime = Date.now();
}
});
After(async function (scenario) {
// Attach screenshot if scenario failed
if (scenario.result.status === 'FAILED') {
await this.attachScreenshot(`failed-${scenario.pickle.name}`);
}
// Log performance metrics
if (this.testType === 'performance') {
const duration = Date.now() - this.startTime;
console.log(`Scenario '${scenario.pickle.name}' completed in ${duration}ms`);
}
});
// 6. support/reporting.js - Custom Reporting
const { Before, After } = require('@cucumber/cucumber');
Before(async function (scenario) {
this.scenarioData = {
name: scenario.pickle.name,
tags: scenario.pickle.tags.map(tag => tag.name),
startTime: new Date()
};
});
After(async function (scenario) {
const endTime = new Date();
const duration = endTime - this.scenarioData.startTime;
// Generate custom report data
const reportData = {
scenario: this.scenarioData.name,
status: scenario.result.status,
duration: duration,
tags: this.scenarioData.tags,
timestamp: endTime.toISOString()
};
// Store for custom reporting
if (!global.customReports) {
global.customReports = [];
}
global.customReports.push(reportData);
});
// 7. support/custom-matchers.js - Custom Chai Matchers
const { expect } = require('chai');
// Custom matcher for price validation
expect.addMethod('toCost', function (expectedAmount) {
const actual = this._obj;
const tolerance = 0.01; // Allow for floating point precision
this.assert(
Math.abs(actual - expectedAmount) <= tolerance,
`expected #{this} to cost ${expectedAmount}, but it costs ${actual}`,
`expected #{this} to not cost ${expectedAmount}, but it does`
);
});
// Custom matcher for product stock validation
expect.addMethod('toHaveStock', function (expectedStock) {
const product = this._obj;
this.assert(
product.stock === expectedStock,
`expected product #{product.name} to have stock ${expectedStock}, but has ${product.stock}`,
`expected product #{product.name} to not have stock ${expectedStock}, but it does`
);
});
// Usage examples:
// expect(cart.total).toCost(99.99);
// expect(product).toHaveStock(10);