Testland
Browse all skills & agents

cucumber-testing

Configures Cucumber for BDD scenarios - Cucumber-JVM (Java/Kotlin via JUnit 5), Cucumber-JS (Node), Cucumber-Ruby. Authors `.feature` files in Gherkin, writes step definitions in the host language, runs via the framework's runner, integrates with JUnit XML reporting. Use when the user mentions Cucumber, Gherkin, `.feature` files, or behavior-driven (BDD) tests in Java, Kotlin, JavaScript, or Ruby, as the canonical wrapper for any of the three official implementations.

Install with skills.sh (any agent)

npx skills add testland/qa --skill cucumber-testing
View source

cucumber-testing

Overview

Per cucumber-install (opens in new window):

"Cucumber is available for most mainstream programming languages."

Cucumber implementations split into tiers (cucumber-install (opens in new window)):

  • Official: Hosted under the main Cucumber organization (JavaScript, Java, Ruby, Android, Kotlin, Scala, C++)
  • Semi-official: Developed elsewhere but use Cucumber components (PHP's Behat, Python's Behave, .NET's Reqnroll)

This skill covers the three most-used official implementations: Cucumber-JVM (Java + Kotlin), Cucumber-JS (Node), and Cucumber-Ruby. For Python, see behave-testing. For .NET, see reqnroll-testing.

When to use

  • The team uses BDD with Gherkin features.
  • Cross-stakeholder collaboration is the value (Gherkin scenarios read by non-engineers).
  • Acceptance criteria authored as Gherkin scenarios per gherkin-from-stories.

If only engineers will read the tests, BDD's collaboration value is wasted - plain xUnit-style tests are simpler. Not sure which runner fits the repo, or whether BDD is worth adopting at all? See references/runner-selection.md - the runner decision table, per-runner first-run commands, the SpecFlow end-of-life note, and the "when BDD is not worth it" test.

Step 1 - Install (Cucumber-JVM)

Maven:

<dependency>
  <groupId>io.cucumber</groupId>
  <artifactId>cucumber-java</artifactId>
  <version>7.20.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>io.cucumber</groupId>
  <artifactId>cucumber-junit-platform-engine</artifactId>
  <version>7.20.0</version>
  <scope>test</scope>
</dependency>

For Kotlin: replace cucumber-java with cucumber-kotlin.

Step 2 - Install (Cucumber-JS)

npm install --save-dev @cucumber/cucumber

Step 3 - Install (Cucumber-Ruby)

gem install cucumber
# Or in Gemfile:
group :test do
  gem 'cucumber-ruby'
end

Step 4 - Author a Feature

# features/cart.feature
Feature: Apply promo code at checkout

  Background:
    Given a logged-in user with email confirmed
    And the cart contains 1 of "BOOK-001" at $24.99

  Scenario: Apply valid promo
    When I enter "WELCOME10" in the promo input
    And I click "Apply"
    Then the subtotal updates to $22.49
    And a confirmation toast appears

  Scenario Outline: Promo validation rejects bad codes
    When I enter "<code>" in the promo input
    And I click "Apply"
    Then an error appears: "<error>"

    Examples:
      | code         | error                    |
      | EXPIRED50    | This code has expired    |
      | NOTREAL      | Code not found           |
      | "" (empty)   | Please enter a code      |

Step 5 - Write step definitions

Bind each Gherkin line to a method with a {cucumber-expression} capture. Minimal JS example:

const { Given, When, Then } = require('@cucumber/cucumber');

Given('a logged-in user with email confirmed', function() {
  this.page = new CheckoutPage(createLoggedInUser());
});

When('I enter {string} in the promo input', function(code) {
  this.page.enterPromo(code);
});

Then('the subtotal updates to ${float}', function(expected) {
  assert.equal(this.page.getSubtotal(), expected);
});

Full Java (Cucumber-JVM) and JS step definitions, plus the JUnit XML reporting config, are in references/step-definitions-and-reporting.md.

Step 6 - Run

JVM (via Maven):

mvn test
# Or specific feature:
mvn test -Dcucumber.features=features/cart.feature

JS:

npx cucumber-js features/

Ruby:

cucumber features/

Step 7 - Reporting

Cucumber outputs multiple formats; JUnit XML is the CI-canonical one and feeds junit-xml-analysis (in the qa-test-reporting plugin). The JVM (cucumber.properties) and JS (CLI) reporting config is in references/step-definitions-and-reporting.md.

Step 8 - Tags + filtering

@regression @critical
Scenario: Apply valid promo
  ...
# Run only critical tests
npx cucumber-js features/ --tags '@critical'

# Skip @wip
npx cucumber-js features/ --tags 'not @wip'

Anti-patterns

Anti-patternWhy it failsFix
Imperative steps ("I click button id=#submit")Couples to implementation; scenarios become fragile.Declarative steps ("I submit the form").
100 unique step definitionsDrift; inconsistency.Step library curation (see bdd-step-library-curator).
BDD without business stakeholder involvementDefeats the point; expensive xUnit tests.If non-engineers don't read the features, switch to plain unit tests.
Mixing Cucumber + plain JUnit assertionsTwo test runners; double maintenance.Cucumber's runner only; plain JUnit for non-BDD tests in separate suite.
Skipping Background for shared setupRepeated Given lines clutter scenarios.Background block (Step 4 example).

Limitations

  • Step ambiguity. Two step definitions matching the same Gherkin line cause runtime errors; the framework reports ambiguous steps but only at runtime.
  • Per-language quirks. Cucumber-JVM's regex syntax differs from Cucumber-JS's; copy-paste between languages doesn't work.
  • Performance. BDD adds runner overhead vs plain xUnit; acceptable for <500-test suites, slow beyond.
  • Step-definition discoverability. Without IDE plugin, finding the implementation behind a Gherkin line is manual grep.

References

  • ci (opens in new window) - Cucumber installation: official + semi-official + unofficial implementation tiers; recommendation to match production language.
  • behave-testing - Python sibling.
  • reqnroll-testing - .NET sibling.
  • bdd-step-library-curator - addresses step proliferation.
  • gherkin-from-stories - upstream skill that generates Gherkin from stories, AC lists, manual steps, and raw specs.
  • references/runner-selection.md - runner decision table + first-run commands.

Picking a BDD runner - decision table and first-run commands

View source (opens in new window)

Picking a BDD runner - decision table and first-run commands

Deep reference for cucumber-testing. Runner choice in BDD is almost entirely determined by your language and build system, because step definitions are written in that language. Read the repo, match a row, stop deliberating.

What you find in the repoRunnerPackage to add
pom.xml or build.gradle (Java, Kotlin, Scala, Groovy)Cucumber-JVMio.cucumber:cucumber-java plus cucumber-junit-platform-engine (cucumber.io/docs/installation/java (opens in new window), cucumber-jvm README (opens in new window))
package.json (JavaScript or TypeScript on Node)Cucumber-JS@cucumber/cucumber (cucumber.io/docs/installation/javascript (opens in new window))
Gemfile or *.gemspec (Ruby, Rails)Cucumber-Rubygem 'cucumber', or cucumber-rails for Rails (cucumber.io/docs/installation/ruby (opens in new window))
requirements.txt or pyproject.toml (Python)Behave (behave-testing)behave (behave.readthedocs.io/install (opens in new window))
*.csproj or *.sln (.NET, any test framework)Reqnroll (reqnroll-testing)Reqnroll.NUnit, Reqnroll.MsTest, Reqnroll.xUnit or Reqnroll.TUnit (docs.reqnroll.net setup (opens in new window))
A .NET repo that already references SpecFlow.* packagesMigrate to Reqnrollsee the SpecFlow note below
No stakeholder outside engineering will ever read the feature filesNone. Write tests directly in your test frameworksee "When BDD is not worth it" below

The .NET trap: do not start on SpecFlow

SpecFlow was the standard .NET BDD runner for a decade, so most tutorials still point at it. It is dead: Tricentis states "SpecFlow has been retired" (shiftsync.tricentis.com (opens in new window)), specflow.org redirects there, and it "reached its end-of-life on December 31, 2024" with the GitHub projects deleted as of 1 January (reqnroll.net (opens in new window)). The packages still install only because nuget.org will not delete existing ones (reqnroll.net (opens in new window)) - exactly how newcomers land on an unsupported dependency. Use Reqnroll, the maintained successor: "a reboot of the SpecFlow project" (reqnroll.net (opens in new window)); migrating is mostly package and namespace renames, not a rewrite (reqnroll.net (opens in new window)). See reqnroll-testing (and its references/specflow-legacy.md for not-yet-migrated projects).

First runnable path per runner

Run the block that matches your row. Success looks the same everywhere: the runner reports undefined steps and prints copy-pasteable step-definition snippets. That is the correct first result, not a failure.

# Node (Cucumber-JS), per cucumber.io/docs/installation/javascript
npm install --save-dev @cucumber/cucumber
mkdir -p features/step_definitions && npx cucumber-js

# Python (Behave), per behave.readthedocs.io/en/latest/install
pip install behave
mkdir -p features/steps && behave

# Java (Cucumber-JVM), the maintained Maven starter project
git clone https://github.com/cucumber/cucumber-jvm-starter-maven-java
cd cucumber-jvm-starter-maven-java && ./mvnw test

# Ruby (Cucumber-Ruby), per cucumber.io/docs/installation/ruby
gem install cucumber
cucumber --init && cucumber

# .NET (Reqnroll), per docs.reqnroll.net/latest/installation/setup-project.html
dotnet new install Reqnroll.Templates.DotNet
dotnet new reqnroll-project -t nunit -f net8.0 -o CheckoutSpecs
cd CheckoutSpecs && dotnet test

Command sources: npm install --save-dev @cucumber/cucumber (install/javascript (opens in new window)) with npx cucumber-js (cucumber-js CLI (opens in new window)); pip install behave (behave install (opens in new window)); ./mvnw test on the starter repo, which runs features through Cucumber's JUnit Platform Engine (jvm starter (opens in new window)); gem install cucumber and cucumber --init (install/ruby (opens in new window)); the Reqnroll template install and -t / -f flags per docs.reqnroll.net setup (opens in new window).

When BDD is not worth it

BDD's payoff is the shared understanding produced by the conversation. The Cucumber project is explicit that documentation and automated tests "are produced by a BDD team, you can think of them as nice side-effects. The real goal is valuable, working software, and the fastest way to get there is through conversations between the people who are involved in imagining and delivering that software." (cucumber.io/docs/bdd (opens in new window))

Read that as a cost test (practitioner judgment, not a documented rule): if no non-technical stakeholder ever reads, reviews or writes a feature file, the translation layer is pure overhead. Write the tests directly in JUnit, pytest, NUnit or Mocha instead. Usually skip BDD for: internal libraries, SDKs, CLIs and infrastructure with no business-facing behaviour; teams where only engineers have ever opened a feature file; retrofitting Gherkin onto an existing UI automation suite; and spike work whose specification is not stable enough to formulate.

The honest signal that BDD is working: someone who does not write code has edited a .feature file in the last month.

Cucumber step definitions and reporting

View source (opens in new window)

Cucumber step definitions and reporting

Full step-definition and JUnit XML reporting examples for cucumber-testing (opens in new window) (Steps 5 and 7). The Gherkin feature they bind to is in Step 4 of the SKILL.md.

Step definitions - JVM (Java)

import io.cucumber.java.en.*;

public class CheckoutSteps {

    private Cart cart;
    private CheckoutPage page;

    @Given("a logged-in user with email confirmed")
    public void a_logged_in_user() {
        TestUser user = TestUsers.loggedInWithEmailConfirmed();
        page = new CheckoutPage().loginAs(user);
    }

    @Given("the cart contains {int} of {string} at ${double}")
    public void the_cart_contains(int qty, String sku, double price) {
        cart = new Cart();
        cart.addItem(new Item(sku, qty, price));
        page.setCart(cart);
    }

    @When("I enter {string} in the promo input")
    public void i_enter(String code) {
        page.enterPromo(code);
    }

    @When("I click {string}")
    public void i_click(String label) {
        page.click(label);
    }

    @Then("the subtotal updates to ${double}")
    public void the_subtotal_updates(double expected) {
        assertEquals(expected, page.getSubtotal(), 0.01);
    }
}

Step definitions - JS

const { Given, When, Then } = require('@cucumber/cucumber');

Given('a logged-in user with email confirmed', function() {
  this.user = createLoggedInUser();
  this.page = new CheckoutPage(this.user);
});

When('I enter {string} in the promo input', function(code) {
  this.page.enterPromo(code);
});

Then('the subtotal updates to ${float}', function(expected) {
  assert.equal(this.page.getSubtotal(), expected);
});

Reporting

Cucumber outputs to multiple formats; JUnit XML is the CI-canonical one.

JVM (in cucumber.properties):

cucumber.plugin=pretty,html:target/cucumber-report.html,junit:target/cucumber-report.xml

JS (CLI):

npx cucumber-js features/ \
  --format html:reports/cucumber.html \
  --format junit:reports/cucumber.xml

The JUnit XML feeds junit-xml-analysis (in the qa-test-reporting plugin).

Related skills

bdd-step-library-curator

Keeps a BDD step-definition library DRY across a Cucumber / Behave / Reqnroll project - inventories every step definition, detects duplicates (different patterns matching the same intent), recommends canonical consolidations, reorganizes steps by domain, publishes a step-library README the team greps for "is there already a step for X?" before authoring new ones, and builds a scenario coverage map that fingerprints new Gherkin scenarios against the live suite to classify each as duplicate, partial overlap, or genuine gap before any test is authored. Use when a BDD project's step count grows past ~50, on a quarterly step-library review, when a new engineer is about to write a duplicate step, or when fresh .feature files need a covered-already check.

behave-testing

Configures Behave for Python BDD scenarios - `pip install behave`, authors `.feature` files in Gherkin, writes step implementations in `features/steps/*.py`, configures via `environment.py` for setup/teardown hooks, organizes via tags, runs via `behave`. Use for Python codebases that want Cucumber-family BDD without Cucumber-Ruby / Cucumber-JS.

gherkin-from-stories

Converts requirements in any input shape into Gherkin scenarios - a user story ("As a … I want … so that …"), a signed-off acceptance-criteria list (ATDD: @AC-N-tagged scenarios, NotImplementedError step stubs, AC-to-test traceability table), existing manual test steps (declarative rewrite that strips UI mechanics), or a raw spec / PRD section (acceptance-criteria extraction with Gherkin or plain-list output). Maps criteria to Scenario blocks, detects Scenario Outline opportunities, factors shared Background, reuses the curated step library, and flags implicit preconditions instead of fabricating them. Emits Gherkin (plus stubs in ATDD mode): runner detection and full step wiring belong to bdd-scenario-author. Use whenever requirements text of any shape needs to become a .feature file.

living-documentation-publisher

Converts passing Cucumber JSON output into stakeholder-facing living documentation: generates HTML reports via multiple-cucumber-html-reporter (Node) or Serenity BDD aggregate (JVM), applies Gherkin tags to drive report sections, and publishes to GitHub/GitLab Pages in CI. Use when BDD scenarios are in use and the team needs an always-current, non-test-engineer-readable document showing which acceptance criteria pass.

reqnroll-testing

Configures Reqnroll (the canonical .NET BDD framework) - install via `dotnet add package Reqnroll`, author `.feature` files in Gherkin, write step bindings as `[Given/When/Then]`-decorated methods in any C# class, runs via `dotnet test`. Reqnroll is the SpecFlow successor (SpecFlow reached end-of-life 2024-12-31); covers the SpecFlow-to-Reqnroll migration path, and references/specflow-legacy.md maintains not-yet-migrated SpecFlow projects. Use for .NET projects starting BDD, migrating from SpecFlow, or maintaining legacy SpecFlow suites.