Cucumber BDD Tutorial
Behaviour-Driven Development (BDD) describes how software should behave in plain language that business people, developers and testers all understand. Cucumber turns those descriptions — written in Gherkin — into automated tests by matching each line to Java code. This tutorial covers the complete path: the BDD mindset, a working Cucumber project, and the framework patterns real teams use, with every in-depth tutorial linked in order.
What Is BDD?
BDD starts with a conversation, not code. Before a story is built, the "three amigos" — a business representative, a developer and a tester — agree on concrete examples of how it should behave. Those examples become scenarios:
Scenario: Successful login
Given the user is on the login page
When the user logs in with valid credentials
Then the dashboard should be displayed
The same scenario is a requirement, an acceptance criterion and an automated test. That shared understanding — not the tool — is the real benefit of BDD.
More: BDD Fundamentals & Gherkin and BDD vs TDD.
How Cucumber Works
- You write feature files (
.feature) in Gherkin. - You write step definitions — Java methods annotated with
@Given,@When,@Then. - A runner tells Cucumber where the features and step definitions (the glue) are.
- Cucumber matches each step's text to exactly one step definition and runs it — usually driving Selenium or an API client.
See it step by step in the Cucumber BDD Visualizer.
Set Up a Cucumber Project (Java + TestNG)
Add these to your pom.xml (keep all Cucumber artifacts on the same version):
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.20.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.20.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<!-- shares state between step classes -->
<version>7.20.1</version>
<scope>test</scope>
</dependency>
Plus selenium-java for UI tests. A typical layout:
src/test/java/com/example/
├── runners/ → TestRunner
├── steps/ → step definition classes + Hooks
├── pages/ → Page Objects
└── context/ → TestContext (shared state)
src/test/resources/
└── features/ → login.feature, checkout.feature …
Step 1 — A Feature File
@login
Feature: Login
Background:
Given the user is on the login page
@smoke
Scenario: Successful login
When the user logs in with "asha@example.com" and "Secret@123"
Then the dashboard should be displayed
Scenario Outline: Invalid login shows an error
When the user logs in with "<email>" and "<password>"
Then the error "<message>" should be displayed
Examples:
| email | password | message |
| asha@example.com | wrong | Invalid email or password |
| | x | Email is required |
Everything about Scenario, Scenario Outline, Examples, Background and Data Tables: Feature Files Explained.
Step 2 — Step Definitions
public class LoginSteps {
private final TestContext context;
private final LoginPage loginPage;
public LoginSteps(TestContext context) { // injected by PicoContainer
this.context = context;
this.loginPage = new LoginPage(context.getDriver());
}
@Given("the user is on the login page")
public void onLoginPage() {
loginPage.open();
}
@When("the user logs in with {string} and {string}")
public void logsIn(String email, String password) {
loginPage.login(email, password);
}
@Then("the error {string} should be displayed")
public void errorDisplayed(String expected) {
Assert.assertEquals(loginPage.errorText(), expected);
}
}
{string} is a Cucumber Expression that captures quoted text. Step definitions stay thin: they call page methods and assert — no locators here.
More: Step Definitions & Cucumber Expressions.
Step 3 — Share State Between Step Classes
Steps for one scenario often live in several classes (login steps, cart steps) that need the same browser. Don't use static fields — let PicoContainer inject one shared object per scenario:
public class TestContext {
private WebDriver driver;
public WebDriver getDriver() {
if (driver == null) driver = new ChromeDriver();
return driver;
}
public void quitDriver() {
if (driver != null) {
driver.quit();
driver = null;
}
}
}
Every step class that declares TestContext in its constructor receives the same instance for the current scenario, and a fresh one for the next — which also makes parallel runs safe.
Step 4 — Hooks
public class Hooks {
private final TestContext context;
public Hooks(TestContext context) {
this.context = context;
}
@After
public void tearDown(Scenario scenario) {
if (scenario.isFailed()) {
byte[] png = ((TakesScreenshot) context.getDriver())
.getScreenshotAs(OutputType.BYTES);
scenario.attach(png, "image/png", "failure");
// appears in the report
}
context.quitDriver();
}
}
Hooks hold technical setup and teardown; business preconditions belong in a feature's Background. Use tagged hooks such as @Before("@needsCart") for setup that only some scenarios need.
Step 5 — The Runner and Tags
@CucumberOptions(
features = "src/test/resources/features",
glue = "com.example.steps", // a package name, not a folder path
tags = "@smoke",
plugin = {
"pretty",
"html:target/cucumber-report.html",
"json:target/cucumber.json"
}
)
public class TestRunner extends AbstractTestNGCucumberTests {
@Override
@DataProvider(parallel = true) // run scenarios in parallel
public Object[][] scenarios() {
return super.scenarios();
}
}
Override tags from the command line without touching code:
mvn test -Dcucumber.filter.tags="@smoke and not @wip"
More: Tags & Test Execution.
If steps show up as undefined or ambiguous, see Ambiguous & Undefined Steps.
Step 6 — The Hybrid Framework
Real projects combine Cucumber with the Page Object Model, configuration, test data, reporting and CI:
Feature files → step definitions → page objects → Selenium → browser, with shared context, hooks, config files, and reports published by Jenkins.
Full architecture: BDD Hybrid Framework With POM and The Complete Automation Framework Guide.
When BDD Works — and When It Doesn't
- Works well when business people actually read or help write scenarios, and features have clear business rules.
- Struggles when feature files are written by testers alone after development — you get the cost of Gherkin without the collaboration.
- Common problems: imperative, UI-heavy steps; hundreds of near-duplicate steps; very long scenarios. See BDD Challenges in Real Projects.
The Learning Path
- BDD Fundamentals & Gherkin — what BDD is, BDD vs TDD, Gherkin keywords.
- Feature Files — Scenario vs Scenario Outline, Examples, Background, Data Tables.
- Step Definitions — annotations, parameters, Cucumber Expressions, hooks.
- Tags & Execution — tagging levels, the runner, smoke and regression runs.
- Hybrid Framework With POM — architecture, data, config and parallel runs.
- Real-Project Challenges — step duplication, Gherkin quality and fixes.
- Troubleshooting Ambiguous & Undefined Steps
- Top 15 BDD & Cucumber Interview Questions
Keep the Cucumber & Gherkin Cheat Sheet handy, and pair this with The Complete Selenium Guide, since Cucumber usually drives Selenium underneath.
How to Use This Guide
- Beginners: steps 1–5 above, then the learning path in order.
- Framework builders: shared state, hooks, the runner and the hybrid framework tutorial.
- Interview prep: Scenario vs Scenario Outline, Background vs hooks, sharing state, tags, and the interview questions.
From Real Projects
BDD and TDD are part of the frameworks I've worked with. What makes BDD valuable is the conversation: scenarios written in plain language that testers, developers and product people all understand, which is also the idea behind Testsigma's plain-English test creation. Keep each scenario focused on one behaviour and let the step definitions call your existing page and business-library code. Write the feature file with the product owner before writing any step definitions.
📚 Official documentation: Cucumber docs: Gherkin reference · Cucumber docs: Step definitions
Cucumber BDD Framework Folder Structure
A Cucumber framework with Java, TestNG and the Page Object Model usually looks like this:
src/test/java/
runners/TestRunner.java extends AbstractTestNGCucumberTests
stepdefinitions/LoginSteps.java maps Gherkin steps to Java
hooks/Hooks.java @Before / @After: start and quit the browser
pages/LoginPage.java page objects: locators + actions
context/TestContext.java shares WebDriver between step classes
utils/ConfigReader.java reads config.properties
src/test/resources/
features/login.feature Gherkin scenarios
config.properties base URL, browser, timeouts
pom.xml
The Maven dependencies you need are cucumber-java, cucumber-testng (or cucumber-junit-platform-engine for JUnit 5), cucumber-picocontainer for sharing state, and selenium-java. Keep every io.cucumber artifact on the same version, because mixed versions are the most common cause of "no backend found" and missing-step errors.
The runner ties it together. features points to the feature files and glue lists the packages that hold step definitions and hooks:
@CucumberOptions(
features = "src/test/resources/features",
glue = {"stepdefinitions", "hooks"},
tags = "@smoke",
plugin = {"pretty", "html:target/cucumber-report.html"}
)
public class TestRunner extends AbstractTestNGCucumberTests { }
Frequently Asked Questions
What is BDD?
Behaviour-Driven Development is a collaborative approach where business, development and QA agree on concrete examples of behaviour, written in Gherkin, which then become automated acceptance tests.
What is Cucumber?
A tool that runs Gherkin feature files by matching each step to a step definition in code. It supports Java, JavaScript, Ruby and other languages.
What is the difference between Scenario and Scenario Outline?
A Scenario runs once with one set of data; a Scenario Outline runs once per row of its Examples table.
What are step definitions?
Java methods annotated with @Given, @When or @Then whose text matches Gherkin steps. They perform the actions and assertions.
How do you share the WebDriver between step definition classes?
With dependency injection — typically PicoContainer — injecting a shared context object into each step class's constructor, rather than using static fields.
How do you run only selected scenarios?
Tag them (@smoke, @regression) and filter with tags in the runner or -Dcucumber.filter.tags on the command line.
How does Cucumber work with the Page Object Model?
Step definitions call page object methods, and page objects contain the locators and Selenium actions — keeping steps readable and changes in one place.
Is Cucumber a testing tool?
It's a tool for executable specifications. It runs tests, but its main value is shared understanding; without collaboration, a plain TestNG framework is often simpler.
Which dependencies does a Cucumber framework need?
For Java with TestNG: cucumber-java, cucumber-testng, cucumber-picocontainer and selenium-java, all managed with Maven. Keep every Cucumber artifact on the same version.
What is the glue option in Cucumber?
glue tells Cucumber which packages contain the step definitions and hooks. If a step shows as undefined even though you wrote it, the package is usually missing from glue.
Can Cucumber run scenarios in parallel?
Yes. With TestNG, override scenarios() in the runner and annotate it with @DataProvider(parallel = true). Each scenario then needs its own WebDriver, which is why a dependency-injection container like PicoContainer is used for shared state.