BDD with Cucumber - Pratiques de Développement Orienté Comportement
Exemples complets de Cucumber BDD incluant fichiers de fonctionnalités, définitions de steps, tables de données, hooks et patterns BDD avancés pour développement collaboratif
Key Facts
- Category
- Testing
- Items
- 2
- Format Families
- text
Sample Overview
Exemples complets de Cucumber BDD incluant fichiers de fonctionnalités, définitions de steps, tables de données, hooks et patterns BDD avancés pour développement collaboratif This sample set belongs to Testing and can be used to test related workflows inside Elysia Tools.
💻 Configuration de Base Cucumber BDD text
Configuration complète de projet Cucumber avec fichiers de fonctionnalités, définitions de steps, configuration et patterns BDD de base
// 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
});
💻 Patterns Avancés et Meilleures Pratiques Cucumber text
Patterns BDD complexes incluant tables de données, schémas de scénario, hooks, tags et implémentation Cucumber au niveau entreprise
// 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);