Bdd cucumber
QA Skills Directory QA Skills is a curated directory of testing-specific skills for AI coding agents (Claude Code, Cursor, Copilot, etc.).
npx -y skills add PramodDutta/qaskills --skill bdd-cucumberAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Behavior-Driven Development skill using Cucumber, covering feature files, step definitions, Gherkin best practices, data tables, scenario outlines, and hooks.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
15.9 KB, as published. Nobody here has run it
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
- Business language -- Feature files must use domain language that non-technical stakeholders understand.
- Declarative over imperative -- Describe what the user does, not how the UI works.
- Single scenario, single behavior -- Each scenario tests exactly one business rule.
- Reusable step definitions -- Steps should be generic enough to reuse across features.
- 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
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)
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 |
| [email protected] | |
@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
Scenario: Create multiple users
Given the following users exist:
| email | name | role |
| [email protected] | Admin User | admin |
| [email protected] | User One | user |
| [email protected] | 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 "[email protected]"
When I view my profile
Then my profile should contain:
| Field | Value |
| Name | Admin User |
| Email | [email protected] |
| 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)
// 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('[email protected]', 'SecurePass123!');
});
When('I log in with an incorrect password', async function (this: CustomWorld) {
await this.loginPage.login('[email protected]', 'wrongpassword');
});
When('I log in with a non-registered email', async function (this: CustomWorld) {
await this.loginPage.login('[email protected]', '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('[email protected]', `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
// 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)
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("[email protected]", "SecurePass123!");
}
@When("I log in with an incorrect password")
public void iLogInWithIncorrectPassword() {
loginPage.loginAs("[email protected]", "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)
// 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
// 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('[email protected]', '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
# 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 "[email protected]" 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
@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:
# 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)
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)
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
- Write features before code -- BDD means features drive development.
- Use domain language -- Avoid technical terms in feature files.
- Keep scenarios short -- 3-8 steps per scenario is ideal.
- One behavior per scenario -- Do not test multiple things in one scenario.
- Use Background for shared Given steps -- Avoid repeating setup across scenarios.
- Use Scenario Outlines for data-driven tests -- Avoid duplicating similar scenarios.
- Tag strategically -- Use tags for test selection, not as test metadata.
- Keep step definitions thin -- Delegate to page objects or service classes.
- Use a consistent voice -- First person ("I") or third person ("the user"), not both.
- Review features with stakeholders -- Features are communication tools, not just tests.
Anti-Patterns to Avoid
- UI details in features -- "I click the blue button" is implementation detail.
- Technical jargon -- "The API returns a 200 status" is not business language.
- Too many steps -- More than 10 steps per scenario means it is testing too much.
- Coupled scenarios -- Each scenario must be independent.
- Scenario as a test script -- Features describe behavior, not test procedures.
- Unused step definitions -- Remove dead code; it confuses maintenance.
- Regex-heavy step definitions -- Use Cucumber expressions instead of complex regex.
- Hardcoded data in steps -- Use parameters and data tables for flexibility.
- No tags -- Without tags, you cannot run subsets of tests.
- Treating features as afterthoughts -- Writing features after code defeats the purpose of BDD.