seed-skills/bdd-cucumber/SKILL.md
Behavior-Driven Development skill using Cucumber, covering feature files, step definitions, Gherkin best practices, data tables, scenario outlines, and hooks.
npx skillsauth add PramodDutta/qaskills BDD/Cucumber PatternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
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
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
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
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 |
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/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/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();
}
});
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")
);
}
}
}
// 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);
// 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}`);
}
});
# 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
@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"
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'],
},
};
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();
}
}
development
Generate test data from the schemas you already have. Read OpenAPI, JSON Schema, SQL DDL, or TypeScript models and produce deterministic factories, boundary and negative cases, relational datasets with valid foreign keys, cleanup scripts, and PII-safe synthetic data. Production records never leave the machine.
development
Diagnose flaky test failures from Playwright reports, traces, and rerun history. Classify each failure as product, test, environment, data, or unknown with cited evidence and a proposed fix. Never auto-modifies code without opt-in.
tools
Test LLM, RAG, MCP, and agentic systems end to end. Build golden datasets, run deterministic checks and LLM judges, score retrieval, probe prompt injection, verify tool use, and gate CI on thresholds. Orchestrates DeepEval, Ragas, promptfoo, and Langfuse.
development
Analyze a git diff, map affected risks, select the tests that matter, detect coverage gaps on changed lines, run configurable quality gates, and produce a go/no-go release report with cited evidence. Recommends only; never merges or deploys.