BDD with Cucumber - Behavior Driven Development

Comprehensive Cucumber BDD examples including feature files, step definitions, data tables, hooks, and advanced BDD patterns for collaborative development

Key Facts

Category
Testing
Items
2
Format Families
text

Sample Overview

Comprehensive Cucumber BDD examples including feature files, step definitions, data tables, hooks, and advanced BDD patterns for collaborative development This sample set belongs to Testing and can be used to test related workflows inside Elysia Tools.

💻 Cucumber BDD Basic Setup and Configuration text

🟢 simple ⭐⭐

Complete Cucumber project setup with feature files, step definitions, configuration, and basic BDD patterns

⏱️ 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
});

💻 Advanced Cucumber Patterns and Best Practices text

🟡 intermediate ⭐⭐⭐

Complex BDD patterns including data tables, scenario outlines, hooks, tags, and enterprise-level Cucumber implementation

⏱️ 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);