TL;DR
- Cucumber enables BDD with tests written in plain English (Gherkin syntax)
- Feature files describe behavior: Given (preconditions), When (actions), Then (outcomes)
- Step definitions link Gherkin steps to actual test code
- Scenario Outlines enable data-driven testing with Examples tables
- Integrates with Selenium, TestNG, JUnit for complete test automation
Best for: Teams wanting business-readable tests, collaboration between QA and stakeholders Skip if: Small team where developers write all tests (traditional testing faster) Reading time: 15 minutes
Your test cases are in Jira. Test code is in the repository. Business requirements are in Confluence. Nobody knows if tests actually cover requirements. Every sprint, QA explains the same scenarios to stakeholders.
Cucumber bridges this gap. Tests are written in plain English. Business stakeholders can read them. Developers implement them. Everyone speaks the same language.
This tutorial covers Cucumber from Gherkin basics to CI/CD integration — everything for implementing BDD in your team.
What is BDD?
Behavior Driven Development (BDD) is a development approach where tests are written in natural language that describes business behavior. Tests become living documentation that everyone understands.
BDD benefits:
- Shared understanding — business, QA, and dev speak same language
- Living documentation — tests describe current system behavior
- Early defect detection — requirements are validated before coding
- Reduced rework — clear specifications prevent misunderstandings
What is Cucumber?
Cucumber is a tool that supports BDD by executing plain-text test specifications written in Gherkin. It connects human-readable scenarios to automated test code.
How Cucumber works:
- Write feature files in Gherkin (plain English)
- Create step definitions (code that implements each step)
- Run Cucumber — it matches steps to definitions and executes tests
- Get reports showing which scenarios passed/failed
Setup
Java (Maven)
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.17.0</version>
<scope>test</scope>
</dependency>
</dependencies>
JavaScript (npm)
npm install @cucumber/cucumber --save-dev
Project Structure
src/test/
├── java/
│ └── com/example/
│ ├── steps/
│ │ └── LoginSteps.java
│ ├── pages/
│ │ └── LoginPage.java
│ └── runners/
│ └── TestRunner.java
└── resources/
└── features/
└── login.feature
Gherkin Syntax
Feature File
# login.feature
Feature: User Login
As a registered user
I want to log into my account
So that I can access my dashboard
Background:
Given the user is on the login page
Scenario: Successful login with valid credentials
When the user enters email "user@example.com"
And the user enters password "password123"
And the user clicks the login button
Then the user should see the dashboard
And the welcome message should contain "Welcome"
Scenario: Failed login with invalid password
When the user enters email "user@example.com"
And the user enters password "wrongpassword"
And the user clicks the login button
Then the user should see an error message "Invalid credentials"
Keywords
| Keyword | Purpose | Example |
|---|---|---|
| Feature | Describes the feature being tested | Feature: User Login |
| Scenario | Single test case | Scenario: Successful login |
| Given | Preconditions (setup) | Given the user is on the login page |
| When | Actions performed | When the user clicks login |
| Then | Expected outcomes | Then the dashboard is displayed |
| And/But | Additional steps | And the user is logged in |
| Background | Common steps for all scenarios | Runs before each scenario |
Step Definitions
Basic Steps
// LoginSteps.java
package com.example.steps;
import io.cucumber.java.en.*;
import static org.testng.Assert.*;
public class LoginSteps {
private LoginPage loginPage;
private DashboardPage dashboardPage;
@Given("the user is on the login page")
public void userIsOnLoginPage() {
loginPage = new LoginPage(driver);
loginPage.navigateTo();
}
@When("the user enters email {string}")
public void userEntersEmail(String email) {
loginPage.enterEmail(email);
}
@When("the user enters password {string}")
public void userEntersPassword(String password) {
loginPage.enterPassword(password);
}
@When("the user clicks the login button")
public void userClicksLoginButton() {
dashboardPage = loginPage.clickLogin();
}
@Then("the user should see the dashboard")
public void userShouldSeeDashboard() {
assertTrue(dashboardPage.isDisplayed());
}
@Then("the welcome message should contain {string}")
public void welcomeMessageShouldContain(String expectedText) {
String actualMessage = dashboardPage.getWelcomeMessage();
assertTrue(actualMessage.contains(expectedText));
}
@Then("the user should see an error message {string}")
public void userShouldSeeErrorMessage(String expectedError) {
String actualError = loginPage.getErrorMessage();
assertEquals(actualError, expectedError);
}
}
Parameter Types
// Custom parameter types
@ParameterType("enabled|disabled")
public String status(String status) {
return status;
}
@Given("the feature is {status}")
public void featureIsStatus(String status) {
if (status.equals("enabled")) {
// enable feature
}
}
// Using regex
@When("the user waits for {int} seconds")
public void userWaitsForSeconds(int seconds) {
Thread.sleep(seconds * 1000);
}
// Doc strings for multi-line input
@When("the user enters the following address:")
public void userEntersAddress(String address) {
addressPage.enterAddress(address);
}
Scenario Outline (Data-Driven Testing)
Feature: Login Validation
Scenario Outline: Login with various credentials
Given the user is on the login page
When the user enters email "<email>"
And the user enters password "<password>"
And the user clicks the login button
Then the login result should be "<result>"
And the message should be "<message>"
Examples:
| email | password | result | message |
| user@example.com | password123 | success | Welcome back |
| admin@example.com | admin123 | success | Welcome admin |
| user@example.com | wrongpass | failure | Invalid credentials |
| | password123 | failure | Email is required |
| user@example.com | | failure | Password is required |
| invalid-email | password123 | failure | Invalid email format |
@Then("the login result should be {string}")
public void loginResultShouldBe(String expectedResult) {
if (expectedResult.equals("success")) {
assertTrue(dashboardPage.isDisplayed());
} else {
assertTrue(loginPage.hasError());
}
}
@Then("the message should be {string}")
public void messageShouldBe(String expectedMessage) {
String actual = getCurrentMessage();
assertEquals(actual, expectedMessage);
}
Hooks
// Hooks.java
package com.example.steps;
import io.cucumber.java.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Hooks {
private static WebDriver driver;
@BeforeAll
public static void beforeAll() {
// Run once before all scenarios
System.out.println("Starting test suite");
}
@Before
public void before(Scenario scenario) {
// Run before each scenario
driver = new ChromeDriver();
driver.manage().window().maximize();
System.out.println("Starting: " + scenario.getName());
}
@Before("@smoke")
public void beforeSmoke() {
// Run only for scenarios tagged @smoke
System.out.println("Running smoke test");
}
@After
public void after(Scenario scenario) {
// Run after each scenario
if (scenario.isFailed()) {
// Take screenshot on failure
byte[] screenshot = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BYTES);
scenario.attach(screenshot, "image/png", "failure-screenshot");
}
driver.quit();
}
@AfterAll
public static void afterAll() {
// Run once after all scenarios
System.out.println("Test suite completed");
}
public static WebDriver getDriver() {
return driver;
}
}
Tags
@regression
Feature: User Management
@smoke @critical
Scenario: Create new user
Given I am logged in as admin
When I create a new user
Then the user should be created
@slow
Scenario: Generate user report
Given I am on the reports page
When I generate a full user report
Then the report should be downloaded
Running Tagged Scenarios
# Run only smoke tests
mvn test -Dcucumber.filter.tags="@smoke"
# Run regression but not slow tests
mvn test -Dcucumber.filter.tags="@regression and not @slow"
# Run smoke OR critical
mvn test -Dcucumber.filter.tags="@smoke or @critical"
Page Object Integration
// LoginPage.java
public class LoginPage {
private WebDriver driver;
private WebDriverWait wait;
private By emailField = By.id("email");
private By passwordField = By.id("password");
private By loginButton = By.id("login-btn");
private By errorMessage = By.className("error");
public LoginPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
public void navigateTo() {
driver.get("https://example.com/login");
}
public void enterEmail(String email) {
wait.until(ExpectedConditions.visibilityOfElementLocated(emailField))
.sendKeys(email);
}
public void enterPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
}
public DashboardPage clickLogin() {
driver.findElement(loginButton).click();
return new DashboardPage(driver);
}
public String getErrorMessage() {
return wait.until(ExpectedConditions.visibilityOfElementLocated(errorMessage))
.getText();
}
public boolean hasError() {
return driver.findElements(errorMessage).size() > 0;
}
}
Test Runner
TestNG Runner
// 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",
plugin = {
"pretty",
"html:target/cucumber-reports/report.html",
"json:target/cucumber-reports/report.json"
},
tags = "@smoke or @regression"
)
public class TestRunner extends AbstractTestNGCucumberTests {
@Override
@DataProvider(parallel = true)
public Object[][] scenarios() {
return super.scenarios();
}
}
JUnit 5 Runner
import org.junit.platform.suite.api.*;
import static io.cucumber.junit.platform.engine.Constants.*;
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example.steps")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty, html:target/report.html")
public class RunCucumberTest {
}
CI/CD Integration
GitHub Actions
name: Cucumber Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Run Cucumber tests
run: mvn test -Dcucumber.filter.tags="@smoke"
- name: Publish report
uses: actions/upload-artifact@v4
if: always()
with:
name: cucumber-report
path: target/cucumber-reports/
Cucumber with AI Assistance
AI tools can help write and maintain Cucumber tests.
What AI does well:
- Generate Gherkin scenarios from user stories
- Create step definitions from feature files
- Suggest test data for scenario outlines
- Identify missing edge cases
What still needs humans:
- Defining business-relevant scenarios
- Balancing coverage vs maintainability
- Validating scenarios with stakeholders
- Refactoring duplicate steps
FAQ
What is Cucumber?
Cucumber is a BDD (Behavior Driven Development) testing tool that allows you to write tests in plain English using Gherkin syntax. Feature files describe application behavior that both technical and non-technical team members can understand. Step definitions connect these human-readable specifications to actual test automation code.
What is Gherkin?
Gherkin is Cucumber’s domain-specific language for writing test scenarios in plain text. It uses keywords like Feature, Scenario, Given, When, Then to structure tests in a readable format. The syntax is designed to be understood by everyone on the team — developers, QA engineers, product managers, and business stakeholders.
Is Cucumber free?
Yes, Cucumber is completely free and open-source. It’s available for multiple programming languages including Java, JavaScript, Ruby, Python, and Kotlin. There are no paid versions or enterprise features — all functionality is available at no cost.
Cucumber vs Selenium — what’s the difference?
Cucumber and Selenium serve different purposes and work together. Cucumber defines what to test using Gherkin scenarios (the specification). Selenium performs browser automation (the execution). In a typical setup, Cucumber scenarios describe the expected behavior, and step definitions use Selenium to interact with the browser and verify outcomes.
Official Resources
See Also
- Selenium Tutorial - Browser automation fundamentals
- TestNG Tutorial - Java testing framework
- API Testing Guide - REST API testing
- Test Documentation - Writing effective test specs
