Gherkin Keywords ⭐
| Keyword | Purpose |
|---|---|
| Feature | The feature being described (one per file) |
| Scenario | A single test case |
| Scenario Outline | A data-driven test (runs per Examples row) |
| Examples | The data table for a Scenario Outline |
| Given | Precondition / context |
| When | The action |
| Then | The expected outcome |
| And / But | Continue the previous step type |
| Background | Steps run before every scenario in the file |
| Rule | Groups related scenarios (Gherkin 6+) |
# |
Comment |
Feature File Structure
@login @smoke
Feature: User Login
As a registered user
I want to log in
So that I can access my dashboard
Background:
Given the user is on the login page
@positive
Scenario: Login with valid credentials
When the user enters username "admin" and password "admin123"
And clicks the login button
Then the dashboard should be displayed
@negative
Scenario Outline: Login with invalid credentials
When the user enters username "<username>" and password "<password>"
And clicks the login button
Then the error message "<message>" should be displayed
Examples:
| username | password | message |
| admin | wrong | Invalid password |
| unknown | admin123 | User not found |
| | admin123 | Username is required |
Scenario vs Scenario Outline ⭐ — a Scenario runs once; a Scenario Outline runs once per Examples row.
Data Tables
Scenario: Create multiple users
Given the following users exist:
| name | email | role |
| Nav | nav@test.com | admin |
| Alex | alex@test.com | user |
@Given("the following users exist:")
public void createUsers(DataTable table) {
List<Map<String, String>> rows = table.asMaps(String.class, String.class);
for (Map<String, String> row : rows) {
System.out.println(row.get("name") + " – " + row.get("role"));
}
}
Doc strings for multi-line text:
And the request body is:
"""
{ "id": 1, "name": "Nav" }
"""
Step Definitions ⭐
@Given("the user is on the login page")
public void userOnLoginPage() {
driver.get("https://example.com/login");
}
// Cucumber Expressions — {string} {int} {float} {word}
@When("the user enters username {string} and password {string}")
public void enterCredentials(String user, String pass) {
loginPage.login(user, pass); // ⭐ call the Page Object
}
@Then("the dashboard should be displayed")
public void dashboardDisplayed() {
Assert.assertTrue(dashboardPage.isDisplayed());
}
// Regex alternative
@When("^the user clicks the \"([^\"]*)\" button$")
public void clickButton(String button) { }
| Expression | Matches |
|---|---|
{string} |
A quoted string |
{int} |
An integer |
{float} |
A decimal |
{word} |
A single word |
{} |
Anonymous — any type |
⚠️ The rule: step definitions call Page Object methods — no Selenium code in step defs.
Hooks
@Before // before every scenario
public void setUp() { driver = new ChromeDriver(); }
@After // after every scenario
public void tearDown(Scenario scenario) {
if (scenario.isFailed()) {
byte[] shot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
scenario.attach(shot, "image/png", "failure");
}
driver.quit();
}
@Before("@smoke") // tagged hook — only for @smoke
public void smokeSetup() { }
@BeforeStep / @AfterStep // around each step
Order: @Before → Background → Given/When/Then → @After
Tags ⭐
@smoke @regression
Scenario: ...
# Run by tag
--tags "@smoke"
--tags "@smoke and @regression" # both
--tags "@smoke or @regression" # either
--tags "not @wip" # exclude
--tags "@regression and not @slow" # combined
Tags can sit on a Feature (applies to all its scenarios) or a single Scenario/Scenario Outline.
The Runner
@RunWith(Cucumber.class) // JUnit
// or: extends AbstractTestNGCucumberTests ← for TestNG + parallel
@CucumberOptions(
features = "src/test/resources/features",
glue = "stepdefinitions",
tags = "@smoke",
plugin = { "pretty",
"html:target/cucumber-report.html",
"json:target/cucumber.json" },
monochrome = true,
dryRun = false // true = check for missing step defs only
)
public class TestRunner { }
| Option | Purpose |
|---|---|
features |
Path to .feature files |
glue |
Package holding step definitions |
tags |
Which scenarios to run |
plugin |
Reporters |
monochrome |
Readable console output |
dryRun |
Validate step defs without executing |
Parallel: extend AbstractTestNGCucumberTests and override scenarios() with @DataProvider(parallel = true).
Project Structure
src/test/java
├── runners/ TestRunner.java
├── stepdefinitions/ LoginSteps.java
├── pages/ LoginPage.java ← Selenium lives here
└── utils/ Hooks.java · DriverFactory
src/test/resources
└── features/ login.feature
Common Errors
| Error | Cause |
|---|---|
| Undefined step | No matching step def — check glue path |
| Ambiguous step | Two step defs match the same text |
| Duplicate step definition | Same annotation text used twice |
| NullPointerException on driver | Missing @Before hook / wrong sharing |
Go Deeper
- Complete BDD & Cucumber Guide — the full path
- Top 15 BDD Questions — interview prep
- BDD vs TDD — the comparison
- Hybrid Framework With POM — the framework