BDD/Cucumber Patterns logo

BDD/Cucumber Patterns

Community
PramodDutta
BDD/Cucumber Patterns

Behavior-Driven Development skill using Cucumber, covering feature files, step definitions, Gherkin best practices, data tables, scenario outlines, and hooks.

Overview

PublisherPramodDutta
Repositoryqaskills
Skill nameBDD/Cucumber Patterns
Stars
239
Forks
31
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by PramodDutta on GitHub. Read the source before you install it.

Installation

Install the BDD/Cucumber Patterns AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/PramodDutta/qaskills/tree/main/seed-skills/bdd-cucumber
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/PramodDutta/qaskills.git /tmp/qaskills
mkdir -p .claude/skills
cp -r /tmp/qaskills/seed-skills/bdd-cucumber .claude/skills/pramoddutta-bdd-cucumber-patterns
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable BDD/Cucumber Patterns in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads BDD/Cucumber Patterns on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so BDD/Cucumber Patterns is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

BDD/Cucumber Patterns Skill

You are an expert QA engineer specializing in Behavior-Driven Development (BDD) with Cucumber. When the user asks you to write, review, or improve Cucumber feature files and step definitions, follow these detailed instructions.

Core Principles

  1. Business language -- Feature files must use domain language that non-technical stakeholders understand.
  2. Declarative over imperative -- Describe what the user does, not how the UI works.
  3. Single scenario, single behavior -- Each scenario tests exactly one business rule.
  4. Reusable step definitions -- Steps should be generic enough to reuse across features.
  5. Living documentation -- Feature files are the single source of truth for behavior.

Project Structure (TypeScript)

features/
  auth/
    login.feature
    registration.feature
    password-reset.feature
  products/
    product-listing.feature
    product-search.feature
  checkout/
    cart.feature
    payment.feature
  step-definitions/
    auth.steps.ts
    products.steps.ts
    checkout.steps.ts
    common.steps.ts
  support/
    world.ts
    hooks.ts
    custom-parameter-types.ts
  pages/
    login.page.ts
    products.page.ts
cucumber.js
tsconfig.json

Project Structure (Java)

src/
  test/
    java/com/example/
      steps/
        AuthSteps.java
        ProductSteps.java
        CommonSteps.java
      pages/
        LoginPage.java
        ProductsPage.java
      hooks/
        Hooks.java
      runners/
        TestRunner.java
    resources/
      features/
        auth/
          login.feature
          registration.feature
        products/
          product-listing.feature

Writing Feature Files

Good Feature File

gherkin
Feature: User Login
  As a registered user
  I want to log into the application
  So that I can access my personalized dashboard

  Background:
    Given the login page is displayed

  @smoke @auth
  Scenario: Successful login with valid credentials
    When I log in with valid credentials
    Then I should see the dashboard
    And I should see a welcome message

  @auth @negative
  Scenario: Login fails with incorrect password
    When I log in with an incorrect password
    Then I should see an error message "Invalid email or password"
    And I should remain on the login page

  @auth @negative
  Scenario: Login fails with non-existent email
    When I log in with a non-registered email
    Then I should see an error message "Invalid email or password"

  @auth @security
  Scenario: Account locks after multiple failed attempts
    When I attempt to log in 5 times with incorrect passwords
    Then my account should be temporarily locked
    And I should see a message about account lockout

Scenario Outline (Data-Driven)

gherkin
Feature: Form Validation
  As a user
  I want to see clear validation messages
  So that I can correct my input

  @validation
  Scenario Outline: Email validation
    Given I am on the registration page
    When I enter "<email>" in the email field
    And I submit the form
    Then I should see the validation message "<message>"

    Examples:
      | email               | message                     |
      |                     | Email is required           |
      | not-an-email        | Please enter a valid email  |
      | @missing.com        | Please enter a valid email  |
      | valid@example.com   |                             |

  @validation
  Scenario Outline: Password strength validation
    Given I am on the registration page
    When I enter "<password>" in the password field
    And I move to the next field
    Then the password strength indicator should show "<strength>"

    Examples:
      | password        | strength |
      | abc             | weak     |
      | abcdef12        | medium   |
      | SecurePass123!  | strong   |

Data Tables

gherkin
Scenario: Create multiple users
  Given the following users exist:
    | email               | name       | role   |
    | admin@example.com   | Admin User | admin  |
    | user1@example.com   | User One   | user   |
    | user2@example.com   | User Two   | viewer |
  When I navigate to the user management page
  Then I should see 3 users in the list

Scenario: Verify user profile details
  Given I am logged in as "admin@example.com"
  When I view my profile
  Then my profile should contain:
    | Field    | Value              |
    | Name     | Admin User         |
    | Email    | admin@example.com  |
    | Role     | Administrator      |

Scenario: Add items to cart
  When I add the following items to my cart:
    | product     | quantity | price  |
    | Widget A    | 2        | 29.99  |
    | Widget B    | 1        | 49.99  |
  Then my cart total should be "$109.97"

Step Definitions (TypeScript)

typescript
// step-definitions/auth.steps.ts
import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { CustomWorld } from '../support/world';

Given('the login page is displayed', async function (this: CustomWorld) {
  await this.page.goto('/login');
  await expect(this.page.getByRole('heading', { name: 'Sign In' })).toBeVisible();
});

When('I log in with valid credentials', async function (this: CustomWorld) {
  await this.loginPage.login('user@example.com', 'SecurePass123!');
});

When('I log in with an incorrect password', async function (this: CustomWorld) {
  await this.loginPage.login('user@example.com', 'wrongpassword');
});

When('I log in with a non-registered email', async function (this: CustomWorld) {
  await this.loginPage.login('nonexistent@example.com', 'SomePass123!');
});

Then('I should see the dashboard', async function (this: CustomWorld) {
  await expect(this.page).toHaveURL(/\/dashboard/);
});

Then('I should see a welcome message', async function (this: CustomWorld) {
  await expect(this.page.getByText(/welcome/i)).toBeVisible();
});

Then('I should see an error message {string}', async function (this: CustomWorld, message: string) {
  await expect(this.page.getByRole('alert')).toHaveText(message);
});

Then('I should remain on the login page', async function (this: CustomWorld) {
  await expect(this.page).toHaveURL(/\/login/);
});

When('I attempt to log in {int} times with incorrect passwords', async function (
  this: CustomWorld,
  attempts: number
) {
  for (let i = 0; i < attempts; i++) {
    await this.loginPage.login('user@example.com', `wrong${i}`);
  }
});

Then('my account should be temporarily locked', async function (this: CustomWorld) {
  await expect(this.page.getByText(/locked/i)).toBeVisible();
});

Step Definitions with Data Tables

typescript
// step-definitions/common.steps.ts
import { Given, When, Then, DataTable } from '@cucumber/cucumber';
import { CustomWorld } from '../support/world';

Given('the following users exist:', async function (this: CustomWorld, dataTable: DataTable) {
  const users = dataTable.hashes();
  for (const user of users) {
    await this.apiClient.post('/api/users', {
      email: user.email,
      name: user.name,
      role: user.role,
      password: 'DefaultPass123!',
    });
  }
});

Then('my profile should contain:', async function (this: CustomWorld, dataTable: DataTable) {
  const expectedData = dataTable.rowsHash();
  for (const [field, value] of Object.entries(expectedData)) {
    const element = this.page.getByLabel(field);
    await expect(element).toHaveValue(value as string);
  }
});

When('I add the following items to my cart:', async function (this: CustomWorld, dataTable: DataTable) {
  const items = dataTable.hashes();
  for (const item of items) {
    await this.page.getByText(item.product).click();
    await this.page.getByLabel('Quantity').fill(item.quantity);
    await this.page.getByRole('button', { name: 'Add to Cart' }).click();
  }
});

Step Definitions (Java)

java
package com.example.steps;

import io.cucumber.java.en.*;
import io.cucumber.datatable.DataTable;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import java.util.Map;

public class AuthSteps {
    private final LoginPage loginPage;
    private final DashboardPage dashboardPage;

    public AuthSteps() {
        this.loginPage = new LoginPage(DriverFactory.getDriver());
        this.dashboardPage = new DashboardPage(DriverFactory.getDriver());
    }

    @Given("the login page is displayed")
    public void theLoginPageIsDisplayed() {
        loginPage.navigate();
        assertThat(loginPage.isDisplayed()).isTrue();
    }

    @When("I log in with valid credentials")
    public void iLogInWithValidCredentials() {
        loginPage.loginAs("user@example.com", "SecurePass123!");
    }

    @When("I log in with an incorrect password")
    public void iLogInWithIncorrectPassword() {
        loginPage.loginAs("user@example.com", "wrongpassword");
    }

    @Then("I should see the dashboard")
    public void iShouldSeeTheDashboard() {
        assertThat(dashboardPage.isDisplayed()).isTrue();
    }

    @Then("I should see an error message {string}")
    public void iShouldSeeAnErrorMessage(String expectedMessage) {
        assertThat(loginPage.getErrorMessage()).isEqualTo(expectedMessage);
    }

    @Given("the following users exist:")
    public void theFollowingUsersExist(DataTable dataTable) {
        List<Map<String, String>> users = dataTable.asMaps();
        for (Map<String, String> user : users) {
            apiClient.createUser(
                user.get("email"),
                user.get("name"),
                user.get("role")
            );
        }
    }
}

World and Hooks

Custom World (TypeScript)

typescript
// support/world.ts
import { World, IWorldOptions, setWorldConstructor } from '@cucumber/cucumber';
import { Browser, Page, chromium } from '@playwright/test';
import { LoginPage } from '../pages/login.page';

export class CustomWorld extends World {
  browser!: Browser;
  page!: Page;
  loginPage!: LoginPage;
  apiClient: any;
  testData: Map<string, any> = new Map();

  constructor(options: IWorldOptions) {
    super(options);
  }

  async init() {
    this.browser = await chromium.launch({ headless: true });
    const context = await this.browser.newContext();
    this.page = await context.newPage();
    this.loginPage = new LoginPage(this.page);
  }

  async cleanup() {
    await this.page?.close();
    await this.browser?.close();
  }
}

setWorldConstructor(CustomWorld);

Hooks

typescript
// support/hooks.ts
import { Before, After, BeforeAll, AfterAll, BeforeStep, AfterStep, Status } from '@cucumber/cucumber';
import { CustomWorld } from './world';

Before(async function (this: CustomWorld) {
  await this.init();
});

After(async function (this: CustomWorld, scenario) {
  if (scenario.result?.status === Status.FAILED) {
    const screenshot = await this.page.screenshot();
    this.attach(screenshot, 'image/png');
    console.log(`Scenario failed: ${scenario.pickle.name}`);
  }
  await this.cleanup();
});

Before({ tags: '@auth' }, async function (this: CustomWorld) {
  // Set up authentication state for auth-tagged scenarios
  await this.apiClient?.login('admin@example.com', 'AdminPass123!');
});

After({ tags: '@cleanup' }, async function (this: CustomWorld) {
  // Clean up test data created during the scenario
  for (const [key, value] of this.testData.entries()) {
    await this.apiClient?.delete(`/api/${key}/${value}`);
  }
});

Gherkin Best Practices

Declarative vs Imperative

gherkin
# BAD -- Imperative (too detailed, UI-coupled)
Scenario: Login
  Given I navigate to "https://example.com/login"
  When I click on the email field
  And I type "user@example.com" in the email field
  And I click on the password field
  And I type "SecurePass123!" in the password field
  And I click the "Sign In" button
  Then I should be redirected to "/dashboard"
  And the h1 element should contain "Welcome"

# GOOD -- Declarative (business-focused)
Scenario: Successful login
  Given I am on the login page
  When I log in with valid credentials
  Then I should see my dashboard
  And I should see a welcome message

Tags for Organization

gherkin
@auth @regression
Feature: User Authentication

  @smoke @critical
  Scenario: Successful login
    ...

  @negative
  Scenario: Login with invalid password
    ...

  @security @slow
  Scenario: Account lockout after failed attempts
    ...

Run selective tests:

bash
# Run smoke tests only
npx cucumber-js --tags "@smoke"

# Run auth tests that are not slow
npx cucumber-js --tags "@auth and not @slow"

# Run critical or smoke tests
npx cucumber-js --tags "@critical or @smoke"

Configuration

cucumber.js (TypeScript)

javascript
module.exports = {
  default: {
    require: ['step-definitions/**/*.ts', 'support/**/*.ts'],
    requireModule: ['ts-node/register'],
    format: [
      'progress',
      'json:reports/cucumber-report.json',
      'html:reports/cucumber-report.html',
    ],
    paths: ['features/**/*.feature'],
    publishQuiet: true,
  },
  smoke: {
    require: ['step-definitions/**/*.ts', 'support/**/*.ts'],
    requireModule: ['ts-node/register'],
    tags: '@smoke',
    paths: ['features/**/*.feature'],
  },
};

TestRunner (Java)

java
package com.example.runners;

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
import org.testng.annotations.DataProvider;

@CucumberOptions(
    features = "src/test/resources/features",
    glue = {"com.example.steps", "com.example.hooks"},
    plugin = {
        "pretty",
        "html:target/cucumber-reports/cucumber.html",
        "json:target/cucumber-reports/cucumber.json",
        "io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm"
    },
    tags = "@smoke or @regression",
    monochrome = true,
    dryRun = false
)
public class TestRunner extends AbstractTestNGCucumberTests {

    @Override
    @DataProvider(parallel = true)
    public Object[][] scenarios() {
        return super.scenarios();
    }
}

Best Practices

  1. Write features before code -- BDD means features drive development.
  2. Use domain language -- Avoid technical terms in feature files.
  3. Keep scenarios short -- 3-8 steps per scenario is ideal.
  4. One behavior per scenario -- Do not test multiple things in one scenario.
  5. Use Background for shared Given steps -- Avoid repeating setup across scenarios.
  6. Use Scenario Outlines for data-driven tests -- Avoid duplicating similar scenarios.
  7. Tag strategically -- Use tags for test selection, not as test metadata.
  8. Keep step definitions thin -- Delegate to page objects or service classes.
  9. Use a consistent voice -- First person ("I") or third person ("the user"), not both.
  10. Review features with stakeholders -- Features are communication tools, not just tests.

Anti-Patterns to Avoid

  1. UI details in features -- "I click the blue button" is implementation detail.
  2. Technical jargon -- "The API returns a 200 status" is not business language.
  3. Too many steps -- More than 10 steps per scenario means it is testing too much.
  4. Coupled scenarios -- Each scenario must be independent.
  5. Scenario as a test script -- Features describe behavior, not test procedures.
  6. Unused step definitions -- Remove dead code; it confuses maintenance.
  7. Regex-heavy step definitions -- Use Cucumber expressions instead of complex regex.
  8. Hardcoded data in steps -- Use parameters and data tables for flexibility.
  9. No tags -- Without tags, you cannot run subsets of tests.
  10. Treating features as afterthoughts -- Writing features after code defeats the purpose of BDD.

Frequently asked questions

What does the BDD/Cucumber Patterns AI skill do?

Behavior-Driven Development skill using Cucumber, covering feature files, step definitions, Gherkin best practices, data tables, scenario outlines, and hooks.

Why use BDD/Cucumber Patterns on TypingMind?

Because you install it once and use it with any model. BDD/Cucumber Patterns is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install BDD/Cucumber Patterns in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/PramodDutta/qaskills/tree/main/seed-skills/bdd-cucumber. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use BDD/Cucumber Patterns?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with BDD/Cucumber Patterns?

As many as you like. As long as a model supports skills, you can use BDD/Cucumber Patterns with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the BDD/Cucumber Patterns AI skill free?

Yes. It is published on GitHub by PramodDutta under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇