BDD Testing with Cucumber: Beginner Guide (2026)
The 2026 BDD testing with Cucumber beginner's guide — Gherkin syntax, step definitions in Java/JS/Python, scenario outlines, data tables, tags, hooks, and Cucumber + Selenium / Playwright integration.

What you'll master: BDD philosophy, production-quality Gherkin feature files, step definitions in Java/JS/Python, Cucumber + Selenium and Cucumber + Playwright integration, CI execution, and the common mistakes that turn BDD projects into maintenance nightmares.
1. What Is BDD in 2026?
Behavior-Driven Development (BDD) is a software development methodology that emerged from Test-Driven Development in the mid-2000s. Dan North coined the term in 2006 to address a critical gap: TDD was excellent at the unit level but failed to align developers, testers and business stakeholders on what to build.
BDD's core idea: write executable specifications in plain language that everyone — developer, QA, PM, business analyst — can read, write and review. The format: Given (preconditions) When (action) Then (expected outcome). This pattern, called Gherkin, is the lingua franca of BDD.
BDD has matured from a niche Agile practice into a mainstream methodology, especially in:
- Banking and fintech — where regulatory compliance demands documented acceptance criteria
- Healthcare and life sciences — where traceability between requirements and tests is mandatory
- E-commerce — where business-critical flows need shared understanding across teams
- Government and defense — where stakeholder signoff requires executable specifications
The default tool remains Cucumber. Alternatives: SpecFlow (.NET), Behave (Python), JBehave (Java), CodeceptJS (JS), Gauge (multi-language).
🚀 Rehearse BDD interview rounds live with our AI mock interview and pair this guide with SpecFlow interview questions.
2. Why Cucumber?
| Dimension | Cucumber | In-house framework |
|---|---|---|
| Stakeholder readability | Excellent (Gherkin) | Poor (code) |
| Tooling ecosystem | Mature (plugins, reporters) | Custom |
| Multi-language | Java, JS, Ruby, Python | Single |
| Maintenance cost | Medium | High (custom) |
| Onboarding speed | Fast — Gherkin learnable in a day | Slow |
| Reports | HTML, JSON, JUnit, Allure | Custom |
| Community | Large | None |
For most teams starting BDD, Cucumber is the right choice. Pick SpecFlow if you're in a .NET-only shop; pick Behave if Python is your primary language. For code-first comparison, see our Selenium WebDriver guide.
3. The BDD Workflow (the Three Amigos)
BDD works because it requires three roles to collaborate on every feature.
The Three Amigos
| Role | Brings |
|---|---|
| Business / PM | What the feature should do, what value it creates |
| Developer | How to build it, technical constraints, refactoring approach |
| Tester / QA | Edge cases, validation, exploratory scenarios |
The discovery workshop (the most under-used step)
Before writing any Gherkin, the Three Amigos hold a discovery workshop (30–60 min per story). They discuss the user story, identify concrete examples of behavior, surface edge cases early, and agree on scope. The output: a set of Gherkin scenarios the team has collectively agreed on. By the time code is written, there is no ambiguity.
BDD in the sprint
Day 1: Three Amigos discovery workshop → Gherkin scenarios agreed
Day 2: QA writes step definitions (or pairs with dev)
Day 3: Developer implements feature, runs scenarios
Day 4: Scenarios pass → mark story done
Day 5: Regression run → verify no breakageFor broader Agile QA context, see our manual testing guide.
4. Gherkin Syntax Deep Dive
Feature file structure
Feature: User login
As a registered user
I want to log in to the application
So that I can access my account
Background:
Given the application is running at "https://staging.example.com"
Scenario: Successful login
Given I am on the login page
When I enter "admin@example.com" as the email
And I enter "Sup3rSecret!" as the password
And I click the "Sign in" button
Then I should be on the dashboard page
And I should see "Welcome, admin"
Scenario: Invalid password
Given I am on the login page
When I enter "admin@example.com" as the email
And I enter "wrong-password" as the password
And I click the "Sign in" button
Then I should see an error "Invalid credentials"Keywords
| Keyword | Purpose |
|---|---|
Feature | Top-level description of the feature |
Rule | Groups scenarios under a business rule (Gherkin 6+) |
Background | Steps that run before every scenario in the feature |
Scenario | A single concrete example of behavior |
Scenario Outline | A template scenario with multiple data sets |
Given | Preconditions (state before the action) |
When | The action (the event being tested) |
Then | The expected outcome (the verification) |
And / But | Continuation of Given/When/Then |
@tag | Marker for filtering or grouping scenarios |
The Given-When-Then rule
- Given — state before the test (past tense: "I am logged in")
- When — the action (present tense: "I click submit")
- Then — the outcome (assertions: "I should see…")
Common mistake: writing the assertion in When. Move it to Then.
5. Install Cucumber Step by Step
Java + Maven
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.3</version>
<scope>test</scope>
</dependency>
</dependencies>Create src/test/resources/features/ for .feature files and src/test/java/com/qa/cucumber/ for step definitions.
JavaScript + npm
npm install --save-dev @cucumber/cucumberCreate features/ for .feature files and features/step-definitions/ for JS steps.
Python + pip
pip install behaveCreate features/ for .feature files and features/steps/ for Python step files.
6. Your First Feature File and Step Definitions
Java
Feature file: src/test/resources/features/login.feature
Feature: User login
Scenario: Successful login
Given I am on the login page
When I enter "admin@example.com" as the email
And I enter "Sup3rSecret!" as the password
And I click the "Sign in" button
Then I should be on the dashboard page
And I should see "Welcome, admin"Step definitions: LoginSteps.java
package com.qa.cucumber;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import static org.junit.jupiter.api.Assertions.*;
public class LoginSteps {
@Given("I am on the login page")
public void iAmOnTheLoginPage() {
DriverFactory.getDriver().get("https://example.com/login");
}
@When("I enter {string} as the email")
public void iEnterEmail(String email) {
DriverFactory.getDriver().findElement(org.openqa.selenium.By.id("email")).sendKeys(email);
}
@When("I enter {string} as the password")
public void iEnterPassword(String password) {
DriverFactory.getDriver().findElement(org.openqa.selenium.By.id("password")).sendKeys(password);
}
@When("I click the {string} button")
public void iClickTheButton(String label) {
DriverFactory.getDriver().findElement(org.openqa.selenium.By.cssSelector("button")).click();
}
@Then("I should be on the dashboard page")
public void iShouldBeOnTheDashboardPage() {
String url = DriverFactory.getDriver().getCurrentUrl();
assertTrue(url.contains("/dashboard"), "Expected /dashboard, got " + url);
}
@Then("I should see {string}")
public void iShouldSee(String text) {
assertTrue(DriverFactory.getDriver().getPageSource().contains(text));
}
}Test runner: RunCucumberTest.java
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = "com.qa.cucumber",
plugin = {"pretty", "html:target/cucumber-report.html"}
)
public class RunCucumberTest {}Run with mvn test.
JavaScript (with Playwright)
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('@playwright/test');
Given('I am on the login page', async function () {
await this.page.goto('https://example.com/login');
});
When('I enter {string} as the email', async function (email) {
await this.page.getByTestId('email').fill(email);
});
When('I enter {string} as the password', async function (password) {
await this.page.getByTestId('password').fill(password);
});
When('I click the {string} button', async function () {
await this.page.getByTestId('submit').click();
});
Then('I should be on the dashboard page', async function () {
await expect(this.page).toHaveURL(/\/dashboard$/);
});
Then('I should see {string}', async function (text) {
await expect(this.page.getByRole('heading')).toContainText(text);
});7. Scenario Outlines and Data Tables
Scenario Outline (the killer feature)
Run the same scenario against many data sets:
Scenario Outline: Login with multiple users
Given I am on the login page
When I enter "<email>" as the email
And I enter "<password>" as the password
And I click the "Sign in" button
Then I should see "<greeting>"
Examples:
| email | password | greeting |
| admin@example.com | Sup3rSecret! | Welcome, admin |
| viewer@example.com | ViewerPass1! | Welcome, viewer |
| guest@example.com | GuestPass1! | Welcome, guest |Cucumber runs this scenario once per row — 3 rows above = 3 test runs.
Data Tables
Scenario: Apply multiple promo codes
Given I am on the cart page
When I apply the following promo codes:
| code | expected_discount |
| WELCOME10 | 10% |
| SAVE20 | 20% |
| VIP30 | 30% |
Then I should see the total discount applied@When("I apply the following promo codes:")
public void iApplyTheFollowingPromoCodes(DataTable dataTable) {
List<Map<String,String>> codes = dataTable.asMaps(String.class, String.class);
for (Map<String,String> code : codes) {
// Apply code.get("code") and assert code.get("expected_discount")
}
}9. Cucumber + Selenium Integration
The most common stack: Cucumber + Selenium 4 + Java + JUnit 5 + Maven.
pom.xml
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.25.0</version>
</dependency>Driver factory
public class DriverFactory {
private static WebDriver driver;
public static WebDriver getDriver() {
if (driver == null) {
driver = new ChromeDriver();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
return driver;
}
public static void quitDriver() {
if (driver != null) { driver.quit(); driver = null; }
}
}Page object
public class LoginPage {
private WebDriver driver;
public LoginPage(WebDriver driver) { this.driver = driver; }
public void navigate() { driver.get("https://example.com/login"); }
public LoginPage enterEmail(String e) { driver.findElement(By.id("email")).sendKeys(e); return this; }
public LoginPage enterPassword(String p) { driver.findElement(By.id("password")).sendKeys(p); return this; }
public DashboardPage clickSignIn() { driver.findElement(By.id("submit")).click(); return new DashboardPage(driver); }
}For the full Selenium POM pattern, see our Selenium WebDriver guide and Java for Selenium deep-dive.
10. Cucumber + Playwright Integration
For JS/TS teams, Cucumber + Playwright is a faster, more modern combination than Cucumber + Selenium.
npm install --save-dev @cucumber/cucumber @playwright/testconst { Before, BeforeAll, After, AfterAll } = require('@cucumber/cucumber');
const { chromium } = require('@playwright/test');
let browser;
let context;
BeforeAll(async function () {
browser = await chromium.launch({ headless: true });
});
Before(async function () {
context = await browser.newContext();
this.page = await context.newPage();
});
After(async function () { await context.close(); });
AfterAll(async function () { await browser.close(); });For the full Playwright setup, see our Playwright complete guide.
11. Reporting with Cucumber
Built-in HTML report
@CucumberOptions(plugin = {"pretty", "html:target/cucumber-report.html"})Open target/cucumber-report.html after a run.
JSON report for CI
@CucumberOptions(plugin = {"json:target/cucumber.json"})Cucumber Reports service
The Cucumber Reports service provides cross-browser trend analysis, flaky test detection, scenario search and historical reporting.
Allure + Cucumber
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-cucumber7-jvm</artifactId>
<version>2.27.0</version>
</dependency>mvn test
allure serve target/allure-resultsAllure's HTML report shows step-by-step screenshots, logs and timing for every scenario.
12. BDD Best Practices for 2026
Do
- Hold a Three Amigos discovery workshop before writing any Gherkin
- Write declarative scenarios, not imperative scripts
- Keep scenarios short (3–7 steps each)
- Use Scenario Outlines for data-driven scenarios
- Use tags to organize by feature area, priority and test type
- Use Background for shared preconditions
- Generate living documentation from Gherkin (publish the HTML report)
- Pair BDD with POM for clean code architecture
- Use Allure + Cucumber for the richest reports
Don't
- Don't write scenarios as imperative test scripts — use declarative language
- Don't write too many scenarios — 10–30 per feature is the sweet spot
- Don't put business logic in step definitions — they should be thin glue
- Don't write technical jargon in Gherkin (locators, API endpoints)
- Don't skip the discovery workshop — it's the most valuable part of BDD
- Don't use BDD for unit tests — it's an acceptance testing methodology
- Don't maintain BDD suites that no one reads