software testing

Cucumber Testing: How to Write Cucumber Test Cases

Cucumber testing lets you write automated tests in plain English that both engineers and business stakeholders can read. Instead of tests buried in code only developers understand, a Cucumber scenario reads like a description of how the feature should behave. This guide covers what Cucumber testing is, how it works, real examples with working code, how to run and rerun tests at scale, and the best practices that separate a BDD suite people trust from one they quietly abandon.
Jul 15, 20268 min read
Get a Demo
blog_image

TABLE OF CONTENT

Start your AI testing pilotGenerate, run, and maintain tests across your CI/CD workflow with less manual effort

SHARE THIS ARTICLE

What Is Cucumber Testing?

Cucumber testing is a method of writing automated tests using Behavior-Driven Development (BDD), where test cases are expressed in plain, structured English rather than code. It uses a syntax called Gherkin, which describes how a feature should behave from the user’s point of view, so a product manager, a QA engineer, and a developer can all read the same test and agree on what it means.

The core idea is collaboration. In most automation, the test is code, which means only engineers can read or verify it. Cucumber splits the test in two: a human-readable specification that anyone can understand, and the underlying code (called step definitions) that connects that specification to the application. The business reads the first half; engineers own the second.

That split is Cucumber’s whole reason for existing. It turns “does everyone agree on what this feature should do?” from a meeting into an executable document.

Key Features of Cucumber

A few features define what Cucumber is and, just as importantly, what it is not:

  • Gherkin syntax. Tests are written in Given-When-Then structure that reads like natural language, making them accessible to non-technical stakeholders.
  • Language and framework support. Cucumber runs across Java (Cucumber-JVM), JavaScript, Ruby, and .NET, and pairs with automation libraries like Selenium, Playwright, and Appium.
  • Living documentation. Because the feature files describe behavior in plain English and are executable, they double as always-current documentation of how the system works.
  • Reusable step definitions. One step definition (“I am logged in”) can back many scenarios, so common actions are written once and reused everywhere.
  • Tags and hooks. Tags (@smoke, @payments) let you group and selectively run scenarios; hooks run setup and teardown code before and after scenarios.

One feature Cucumber deliberately does not have: it cannot drive a browser or call an API on its own. Cucumber is the collaboration and structure layer. The actual interaction with your application comes from a tool underneath it, which is the single most common point of confusion for newcomers.

Getting Started with Cucumber Testing

A working Cucumber setup has three moving parts, and understanding them upfront saves hours of confusion later:

  1. The feature file (.feature) holds your scenarios in Gherkin. This is the plain-English half.
  2. The step definitions are code files that map each Gherkin step to an action. “When I click login” maps to code that actually clicks the login button.
  3. The runner and automation library execute it all. A runner (JUnit, TestNG, or the Cucumber CLI) launches the tests, and an automation library (Selenium, Playwright) performs the browser or API actions.

In a Java project, you would add the cucumber-java, cucumber-junit-platform-engine, and your automation library dependencies, create a src/test/resources folder for feature files, and a src/test/java package for step definitions. Run the suite and Cucumber matches each Gherkin step to its definition and executes it.

The mental model to hold: Gherkin says what to test, step definitions say how, and the automation library does the actual work.

See AI generate and maintain your test cases, validated by human experts

Why Should You Use Cucumber?

Cucumber earns its place when communication, not just execution, is the problem you are solving.

It closes the gap between requirements and tests

When a product owner writes acceptance criteria and an engineer writes tests separately, the two drift apart. In Cucumber, the acceptance criteria are the test, so they cannot disagree.

It makes tests survive team turnover

A code-only test suite becomes unreadable when the engineer who wrote it leaves. Gherkin scenarios stay readable to anyone, so knowledge does not walk out the door.

It documents the system as a side effect

The feature files are always-current, executable documentation, unlike a wiki that goes stale the week after it is written.

It brings non-engineers into quality

QA analysts and product managers can write and review scenarios without coding, widening who can contribute to test coverage.

Be honest about the flip side, though: if your team is all engineers and no non-technical stakeholder will ever read a test, Cucumber’s plain-English layer adds overhead without adding value. The abstraction is worth it only when someone actually benefits from reading it. That trade-off matters, and skipping it is why some teams adopt Cucumber, feel the friction, and abandon it.

How Does Cucumber Work?

Cucumber works by matching plain-English steps to code and executing them in sequence. Here is the flow, end to end:

  1. You write a scenario in Gherkin inside a .feature file, using Given (setup), When (action), and Then (expected result).
  2. Cucumber parses the feature file and reads each step as a string.
  3. It matches each step to a step definition by pattern. The step “Given I am on the login page” matches a step definition annotated with that same text.
  4. The step definition executes, running whatever code it contains, typically driving the application through Selenium, Playwright, or an API call.
  5. Cucumber records the result of each step and reports pass or fail, producing output and reports at the end of the run.

The elegance is in step 3: the plain-English sentence and the code are linked by matching text, which is why one readable scenario can trigger complex automation underneath without exposing any of it to the reader.

Examples of Cucumber Tests

Here is a complete Cucumber test for a login feature, both halves.

The feature file (login.feature), written in Gherkin:

Feature: User login

  As a registered user

  I want to log into my account

  So that I can access my dashboard

  Scenario: Successful login with valid credentials

    Given I am on the login page

    When I enter a valid email and password

    And I click the login button

    Then I should see my dashboard

  Scenario: Failed login with wrong password

    Given I am on the login page

    When I enter a valid email and an incorrect password

    And I click the login button

    Then I should see an “Invalid credentials” error

The step definitions (in Java, using Selenium), which make those steps real:

@Given("I am on the login page")

public void iAmOnTheLoginPage() {

    driver.get("https://example.com/login");

}

@When("I enter a valid email and password")

public void iEnterValidCredentials() {

    driver.findElement(By.id("email")).sendKeys("user@example.com");

    driver.findElement(By.id("password")).sendKeys("correct-password");

}

@When("I click the login button")

public void iClickLogin() {

    driver.findElement(By.id("login-btn")).click();

}

@Then("I should see my dashboard")

public void iShouldSeeDashboard() {

    assertTrue(driver.findElement(By.id("dashboard")).isDisplayed());

}

Notice how the two scenarios reuse the same “Given I am on the login page” step. Write that definition once, use it everywhere, that reuse is what keeps a growing suite maintainable.

For data-driven testing, a Scenario Outline runs the same steps against multiple inputs:

  Scenario Outline: Login validation across inputs

    Given I am on the login page

    When I enter “<email>” and “<password>”

    Then I should see “<result>”

    Examples:

      | email             | password        | result             |

      | user@example.com  | correct-password| dashboard          |

      | user@example.com  | wrong-password  | Invalid credentials|

      | not-an-email      | anything        | Invalid email      |

One scenario, three test cases. This is the cucumber test cases example pattern that scales coverage without duplicating steps.

Benefits of Using Cucumber for Testing

The benefits of Cucumber automation testing show up in both quality and collaboration:

  • Shared understanding. One artifact that business and engineering both read eliminates the “that’s not what I meant” gap between requirements and tests.
  • Readable coverage. Anyone can audit what is and is not tested by reading the feature files, no code review required.
  • Reusable steps. Common actions are written once and reused, so the suite grows in coverage faster than it grows in code.
  • Living documentation. The tests describe current behavior in plain English and never go stale, because a wrong description fails the build.
  • Earlier defect detection. Writing scenarios during requirements (rather than after coding) surfaces ambiguity before a line of code is written, when fixing it is nearly free.

Cucumber Testing Best Practices

These practices separate teams whose Cucumber suite stays trusted from teams who quietly stop running it.

Keep each scenario focused on one behavior

A scenario that tests five things fails ambiguously. One behavior per scenario means a failure points to exactly one cause.

Write declarative steps, not imperative ones

‘When I log in’ is declarative and readable; “When I type my email, then type my password, then click the button” is imperative and brittle. Push the mechanical detail down into step definitions and keep the feature file at the level of intent.

Reuse step definitions ruthlessly

Every duplicated step is future maintenance. Shared steps like “Given I am logged in” should exist once.

Use tags to organize execution

Tag scenarios by risk and speed (@smoke, @payments, @slow) so you can run the right subset at the right time. Tags are also how you drive parallel execution and CI scheduling.

Avoid async assertions that assume instant results

In modern apps and microservices, an action triggers work that finishes later. Asserting the outcome immediately causes flaky failures. Use condition-based waits (wait for the element or state), never fixed sleeps.

Isolate test data

Scenarios that share data or state leak into each other and fail unpredictably under parallel execution. Each scenario should set up and clean up its own data.

Treat maintenance as ongoing work

Every UI or API change can break step definitions. Budget for upkeep, or the suite decays into false failures until nobody trusts a red build.

Cucumber Testing Frameworks

The cucumber testing framework you choose depends on your language and the automation library you pair it with:

  • Cucumber-JVM (Java) is the most widely used, typically paired with Selenium or Playwright and run through JUnit 5 or TestNG.
  • Cucumber.js (JavaScript/TypeScript) suits Node teams and pairs with Playwright or WebDriverIO.
  • Behave (Python) and pytest-bdd bring Gherkin to Python stacks.
  • SpecFlow / Reqnroll (.NET) is the Cucumber-family framework for C# teams.
  • Cucumber-Ruby, the original implementation, remains common in Ruby projects.

A critical 2026 note for Java teams: Cucumber now runs on the JUnit 5 platform engine (cucumber-junit-platform-engine), which handles test execution and rerunning differently from the older JUnit 4 @RunWith(Cucumber.class) approach. Many older tutorials still show the JUnit 4 setup, and copying it into a JUnit 5 project is a frequent cause of reruns silently not working. If your reruns are misbehaving, this mismatch is the first thing to check.

How to Write Cucumber Test Cases

Writing effective Cucumber test cases follows a repeatable sequence:

  1. Identify the feature. Pick one piece of functionality: login, checkout, search.
  2. Define the scenarios. List the behaviors worth verifying, including the failure paths, not just the happy path. A login feature needs valid login, wrong password, locked account, and empty fields.
  3. Write the Gherkin. Use Given (context) When (action) Then (result), keeping each step declarative.
  4. Implement step definitions. Write the code behind each step in your chosen language, driving the app through your automation library.
  5. Run and refine. Execute, confirm the steps match, and tighten anything flaky.

For how to write BDD test cases in Cucumber well, the discipline is to write scenarios from the user’s perspective, describing what they experience, not how the code works internally. A scenario that mentions database tables or API endpoints has leaked implementation detail into what should be a behavior description.

How to Run, Rerun, and Scale Cucumber Tests

This is the section most beginner guides skip, and it is exactly what teams search for once they are past the basics.

How to run specific test cases in Cucumber

Use tags. Annotate scenarios with a tag like @smoke, then run only those:

mvn test -Dcucumber.filter.tags=”@smoke”

How to run multiple test cases in parallel

Large suites run slowly one at a time. The JUnit 5 platform engine supports parallel execution through a configuration property:

cucumber.execution.parallel.enabled=true

cucumber.execution.parallel.config.strategy=dynamic

With this set, scenarios distribute across threads automatically. Parallel execution is the single biggest lever for keeping a growing suite fast, but it makes test-data isolation non-negotiable, since scenarios now run at the same time and will collide if they share state.

How to execute and rerun failed test cases in Cucumber

Cucumber’s rerun plugin writes every failed scenario to a file during the run:

cucumber.plugin=rerun:target/rerun.txt

That rerun.txt file lists the exact failed scenarios (by file and line). You then point a second run at that file to re-execute only the failures:

cucumber.features=@target/rerun.txt

This two-step pattern, run everything, then rerun only what failed, is how teams handle flaky failures without re-running the entire suite. One caution rooted in the JUnit 5 change above: the rerun mechanism behaves differently on the platform engine than it did under JUnit 4, so verify your rerun actually re-executes the failures rather than silently running the whole suite again, a well-known gotcha.

How to prioritize test cases in Cucumber

Prioritize by business risk using tags. Run @critical and @payments scenarios first and on every commit; run slower, lower-risk tags less often. Prioritization is a tagging discipline, not a Cucumber feature, which means it is entirely on you to design.

How to Automate Cucumber Testing

Automating Cucumber testing end to end means wiring the suite into CI/CD so it runs on every change, not on demand. A typical pipeline: a commit triggers the build, the runner executes tagged scenarios in parallel across a browser grid, failures are written to the rerun file and re-executed once to filter out flakiness, and the results gate the merge.

The honest limitation of traditional Cucumber automation is the same one every scripted framework hits: someone has to write and maintain every step definition. When the UI changes, step definitions break, and a team spends more time repairing tests than writing new coverage. This maintenance burden, not the initial authoring, is what stalls most BDD programs.

This is where AI-driven approaches change the economics. Instead of hand-writing feature files and step definitions, autonomous testing tools generate test scenarios from product requirements and self-heal them when the application changes, so a renamed button does not break the suite overnight.

Move from hand-maintained BDD suites to autonomous coverage in 48 hours

BotGauge takes exactly this approach. You describe behavior in plain language, its AI agents generate the executable test cases across UI and API workflows, a dedicated FDE pod validates them, and self-healing keeps them current as your product changes, all running in your CI/CD pipeline with unlimited parallel runs. For teams drawn to Cucumber for its readability but worried about the maintenance tax, it delivers the plain-English intent without the upkeep. The same agentic AI testing approach extends across your whole stack, and it is built on the QA best practices that keep any suite trustworthy.

Conclusion

Cucumber testing solves a problem that pure code-based testing cannot: it gives everyone, engineer or not, one readable specification of how the software should behave. Get the fundamentals right, one behavior per scenario, declarative steps, reused definitions, isolated data, and a Cucumber suite becomes living documentation that also protects every release.

The harder truth is that Cucumber’s readability does not remove automation’s oldest cost: maintenance. Feature files are only as valuable as the step definitions behind them stay current, and keeping them current as the product changes is where most BDD programs quietly break down. Whether you solve that with disciplined upkeep or by letting AI generate and heal the tests, the principle is the same: a test suite is only worth keeping if the team still trusts it on a red build.

Frequently Asked Questions

How to prioritize test cases in Cucumber?
Prioritize by business risk using tags. Annotate scenarios with tags like @critical, @payments, or @smoke, then configure your pipeline to run the highest-risk tags first and on every commit, while lower-risk tags run less frequently. Cucumber does not prioritize automatically, so this is a tagging and scheduling discipline you design deliberately, weighting coverage toward the flows where a failure costs the most.
How to run test cases in parallel using Cucumber?
On the JUnit 5 platform engine, enable parallel execution by setting cucumber.execution.parallel.enabled=true and choosing a strategy such as dynamic in your junit-platform.properties file. Scenarios then distribute across available threads automatically. Parallel execution dramatically cuts run time for large suites, but it requires strict test-data isolation, since scenarios now run simultaneously and will interfere with each other if they share state or records.
How to run multiple test cases in Cucumber?
Multiple scenarios in a feature file run automatically when you execute the runner. To run multiple specific scenarios across files, use tags: annotate the scenarios you want and run with -Dcucumber.filter.tags=”@yourtag”. For data-driven cases, a Scenario Outline with an Examples table runs the same steps against many input rows in a single scenario definition.
How to write BDD test cases in Cucumber?
Write from the user’s perspective using Given-When-Then. Given establishes context, When performs the action, Then asserts the expected outcome. Keep steps declarative (describe intent, “When I log in,” not mechanics, “When I type into field X”), cover failure paths as well as the happy path, and write the scenarios during requirements gathering so they double as acceptance criteria the whole team agrees on before coding starts.
How to run specific test cases in Cucumber?
Use tags to target specific scenarios. Add a tag such as @login above the scenarios you want, then run only those with mvn test -Dcucumber.filter.tags=”@login”. You can combine tags with logical operators, for example “@smoke and not @slow”, to run precise subsets without touching the rest of the suite.
How to rerun failed test cases in Cucumber?
Add the rerun plugin (cucumber.plugin=rerun:target/rerun.txt), which writes each failed scenario to a file during the run. Then execute a second run pointed at that file using cucumber.features=@target/rerun.txt to re-execute only the failures. Note that on the JUnit 5 platform engine this behaves differently from the older JUnit 4 setup, so confirm the rerun actually re-runs only the failed scenarios rather than the entire suite, a common misconfiguration.
Aparna Jayan

About the Author

Aparna Jayan

An SEO and growth strategist with over four years of experience in SaaS content. With hands-on experience creating in-depth, user-focused content for QA testing, AI testing tools, and automation technologies, I'm passionate about simplifying complex technical topics and making them accessible to everyone

More from our Blog

blog_image

10 Best Website Testing Tools To Look For In 2026

Website testing tools have evolved from script-heavy automation frameworks to AI-powered platforms that generate, execute, and maintain tests automatically. Explore the top website testing tools and discover how they help teams improve coverage, reduce maintenance, and release software with confidence.

Read article
blog_image

11 Best Web Application Testing Tools Every QA Team Needs

Discover the top 11 web application testing tools for 2025. Boost QA efficiency with AI-driven, codeless, and cross-browser automation platforms.

Read article
blog_image

Best AI Testing Tools in 2025 (Updated Nov) – Top AI Automation Solutions

Explore the best AI testing tools of 2025 — including Testim, Applitools, Mabl, Functionize, and BotGauge’s Autonomous QA as a Solution (AQAAS).

Read article
Autonomous Testing for Modern Engineering Teams