SoftwareTestPilot
Automation TestingPublished: 14 min read

BDD with Cucumber — End-to-End Tutorial (2026)

Ship your first Cucumber BDD suite in a day. Feature files, step definitions, hooks, tags, and CI wiring for Java, JS, and Python. Includes when BDD hurts and how to avoid the Gherkin trap.

Avinash K
Founder & QA Engineer at SoftwareTestPilot
Share:XLinkedInWhatsApp
BDD with Cucumber end-to-end tutorial 2026 — Gherkin feature files and step definitions.
BDD with Cucumber end-to-end tutorial 2026 — Gherkin feature files and step definitions.

Last updated 2026-07-20 · 14 min read · By Avinash K

Cucumber done right closes the gap between product, dev, and QA. Cucumber done wrong is Selenium tests with extra YAML. This tutorial ships you a working BDD suite in Java, JS, or Python and calls out the four mistakes that make teams abandon Cucumber after six months.

Key takeaways

  • Working Cucumber setup in 15 minutes across 3 languages.
  • Gherkin best practices from real product-QA collaboration.
  • Tags, hooks, and parallel execution.
  • When BDD adds cost without value — how to spot it early.

1. When BDD is worth it (and when it isn't)

Worth it when product/BA/QA collaborate on scenarios before code exists. Not worth it when QA writes both the feature file and the step definitions with no product involvement — you added indirection for nothing.

2. Your first feature file

Feature: Checkout coupon
  As a shopper I want to apply coupons so I can save money

  Scenario: Expired coupon is rejected
    Given I have a cart with one item
    And the coupon "EXPIRED2025" has expired
    When I apply the coupon on checkout
    Then I see the message "This coupon has expired"
    And the cart total is unchanged

3. Step definitions in JavaScript (Cucumber-JS + Playwright)

import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';

Given('I have a cart with one item', async function () {
  await this.api.seedCart(this.user, [{ sku: 'BOOK-1', qty: 1 }]);
});

When('I apply the coupon on checkout', async function () {
  await this.page.goto('/checkout');
  await this.page.getByLabel('Coupon').fill(this.coupon);
  await this.page.getByRole('button', { name: 'Apply' }).click();
});

Then('I see the message {string}', async function (msg: string) {
  await expect(this.page.getByRole('alert')).toHaveText(msg);
});

4. Step definitions in Java (Cucumber-JVM + Selenium)

@Given("I have a cart with one item")
public void iHaveACart() { api.seedCart(user, "BOOK-1", 1); }

@When("I apply the coupon on checkout")
public void iApplyCoupon() {
  driver.get(BASE + "/checkout");
  driver.findElement(By.id("coupon")).sendKeys(coupon);
  driver.findElement(By.id("apply")).click();
}

@Then("I see the message {string}")
public void iSeeMessage(String msg) {
  assertEquals(msg, driver.findElement(By.cssSelector("[role=alert]")).getText());
}

5. Tags, hooks, and parallel execution

Tag scenarios (@smoke, @regression) and filter in CI: cucumber-js --tags "@smoke and not @flaky". Use Before / After hooks for browser context. Run in parallel with --parallel 4. See the Cucumber parallel execution docs.

6. Four Cucumber traps to avoid

  1. Imperative Gherkin ("I click the button") — write declarative business intent instead.
  2. Step explosion — dedupe and parameterize.
  3. Shared world state — leaks between scenarios. Reset in hooks.
  4. No product participation — if only QA reads .feature files, delete Cucumber.

Pair with the test automation complete guide and see our SpecFlow tutorial for C# for the .NET flavor.

Frequently asked questions

1.Cucumber vs Playwright Test — which for BDD?
Cucumber if product writes Gherkin. Otherwise Playwright Test with describe/it is simpler and faster.
2.Is Gherkin still readable at 500 scenarios?
Only with strict style rules and refactoring discipline. Otherwise no — it becomes worse than code.
3.Can I use Cucumber for API tests?
Yes, and it's often a better fit than UI — API contracts map cleanly to Given/When/Then.
4.How do I run Cucumber in GitHub Actions?
Cache node_modules or ~/.m2, run cucumber-js/mvn test with --tags @smoke on PRs, full suite on main.