🥒 BDD & Cucumber
Tests the whole team can read
Write behaviour in plain English with Gherkin, then automate it. The same two feature files run in three working projects — Java, TypeScript and Python — against this site. Download one, run it, and change it.
BDD in plain words
- Behaviour-Driven Development is a way of agreeing what software should do before building it, using concrete examples.
- Business, development and testing (the “three amigos”) write the examples together in Gherkin: Given / When / Then.
- Cucumber (Java, JavaScript) and Behave (Python) read those files and run matching code — the step definitions.
- The feature files become living documentation: always up to date, because they are the tests.
- Honest caveat: BDD pays off when business people read the scenarios. If only testers do, it's an extra layer — plain tests may be simpler.
Gherkin cheat sheet
- Feature
- — The capability being described, with a short “As a … I want … So that …” story.
- Background
- — Steps that run before every scenario in the file — shared setup.
- Scenario
- — One concrete example of the behaviour.
- Given / When / Then
- — Given the starting state, When the user acts, Then the outcome. And / But continue the previous kind.
- Scenario Outline + Examples
- — One scenario run once per row of a table — data-driven tests without copy-paste.
- Data table
- — A table passed into one step, e.g. a list of products to add.
- @tags
- — Labels for choosing what to run: @smoke, @regression, @wip.
Write behaviour, not clicks
Scenarios should say what the user does and expects. Selectors and clicks belong in the step definitions, so a UI change touches one step — not every feature file.
✗ Imperative — breaks when the UI changes
Scenario: Login
Given I open "https://…/login"
When I type "student" into the "#username" field
And I type "Password123" into the "#password" field
And I click the "Log in" button
Then the element ".success" is visible✓ Declarative — describes behaviour
Scenario: Successful login
Given I am on the login page
When I log in as "student" with password "Password123"
Then I am welcomed as "student"The practice project
Two features against this site: the login form and Nova Shop's cart and coupons — 10 scenarios, including a Scenario Outline and a data table. Every project below runs all 10 green; set HEADLESS=true to hide the browser.
1 · The feature files (shared by all three stacks)
Feature: Log in to the practice form
As a student
I want to log in with my username and password
So that I can reach my account
Background:
Given I am on the login page
@smoke
Scenario: Successful login
When I log in as "student" with password "Password123"
Then I am welcomed as "student"
Scenario Outline: Login is rejected
When I log in as "<username>" with password "<password>"
Then I see the error "<error>"
Examples:
| username | password | error |
| student | wrong | Invalid username or password. |
| | Password123 | Both username and password are required. |
| student | | Both username and password are required. |
Scenario: The account locks after three wrong passwords
When I enter a wrong password 3 times
Then I see the error "Too many failed attempts. Account locked — refresh to try again."
And the login button is disabled2 · The step definitions — pick your stack
mvn test<?xml version="1.0" encoding="UTF-8"?>
<!-- PracticeQAAutomation — BDD starter: Cucumber + Selenium + TestNG (Java 17+). MIT License. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.practiceqaautomation</groupId>
<artifactId>bdd-cucumber-selenium</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<cucumber.version>8.0.3</cucumber.version>
</properties>
<dependencies>
<dependency><groupId>io.cucumber</groupId><artifactId>cucumber-java</artifactId><version>${cucumber.version}</version><scope>test</scope></dependency>
<dependency><groupId>io.cucumber</groupId><artifactId>cucumber-testng</artifactId><version>${cucumber.version}</version><scope>test</scope></dependency>
<dependency><groupId>io.cucumber</groupId><artifactId>cucumber-picocontainer</artifactId><version>${cucumber.version}</version><scope>test</scope></dependency>
<!-- Cucumber 8 needs a JSON library to write its reports -->
<dependency><groupId>tools.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>3.2.3</version><scope>test</scope></dependency>
<dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-java</artifactId><version>4.49.0</version><scope>test</scope></dependency>
<dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>7.12.0</version><scope>test</scope></dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
</plugin>
</plugins>
</build>
</project>Real problems we hit building these — and the fixes
Chrome's password pop-ups (Selenium)
After a login with a simple password, Chrome may show “Save password?” or “password found in a data breach”. That pop-up takes the keyboard and mouse, so every later step silently fails. Turn it off with Chrome prefs — see Hooks.java / environment.py. Playwright does this for you.
Typing before the page is ready
React pages can discard what you type before they finish loading. Wait for a ready signal — this site sets <html data-app-ready="true"> — instead of sleeping.
Cucumber-JVM 8 needs a JSON library
Without one it stops with “Cucumber needs a JSON library to write reports”. The pom.xml adds Jackson for this reason.
Empty values in Behave examples
Behave's default matcher won't match "" — the login step uses a regular expression so an empty username still matches.
Sharing state between step classes
Don't use static fields. Cucumber-JVM injects one TestContext per scenario (PicoContainer); Cucumber.js uses a World; Behave uses context.
Preparing for interviews? See the BDD & Cucumber interview questions. The starter projects are free under the MIT licence.