Cucumber Runner Class in Cucumber JVM

What Is a Cucumber Runner Class?

A Cucumber Runner Class is the entry point that starts the execution of Cucumber feature files in a Java automation project. It connects feature files, step definitions, hooks, plugins, reporting configuration, tag filters, and the selected test framework. In simple terms, the runner class tells Cucumber which features to run, where the step definitions are located, and how the test execution should be managed.

A Cucumber project contains many moving parts. Feature files describe behavior in Gherkin. Step definitions connect Gherkin steps to Java code. Hooks manage setup and teardown. Page objects, utilities, API clients, drivers, and configuration classes support execution. The runner class acts as the coordinator that starts Cucumber and points it toward these pieces.

Without a runner class, or without an equivalent JUnit Platform configuration in modern projects, Cucumber does not know how to start the test run. It may not know where feature files exist, where glue code is located, which tags should be selected, or which reports should be generated. The runner class supplies that execution instruction.

In older Cucumber JVM projects, the runner class was usually built with JUnit 4 or TestNG. In modern Cucumber projects, JUnit 5 through the JUnit Platform Engine is also common. The syntax differs by test framework, but the purpose remains the same: provide a bridge between the test framework and the Cucumber engine.

Why Is a Runner Class Needed?

A Cucumber framework is not only a collection of feature files. It is an execution system. Something must read the features, connect them to Java step definitions, apply hooks, filter scenarios by tags, generate reports, and integrate with build tools such as Maven or Gradle. The runner class performs this coordination role.

Feature Files
   |
   v
Step Definitions
   |
   v
Hooks
   |
   v
Page Objects
   |
   v
Utilities

The runner class sits above this structure and starts execution. It tells the test framework, such as JUnit or TestNG, that Cucumber should be used. It tells Cucumber where to find the feature files. It tells Cucumber where to find glue code. It can also tell Cucumber which scenarios to run through tag expressions and which reporting plugins to enable.

In real projects, runner configuration is important because it affects every execution. A wrong feature path can cause no scenarios to run. A wrong glue package can produce undefined steps. A wrong tag expression can skip important tests. A wrong plugin configuration can produce missing reports. For that reason, the runner class should be simple, readable, and reviewed carefully.

Runner Class Responsibilities

A runner class is responsible for starting Cucumber execution and passing important execution configuration. Its responsibilities usually include locating feature files, locating glue packages, applying tag filters, enabling plugins, configuring report outputs, choosing the test engine, and integrating with build tools or CI pipelines.

The runner is not responsible for business logic. It should not log in to the application, create orders, click buttons, or validate outcomes. Those responsibilities belong to step definitions, hooks, page objects, services, and assertions. The runner should remain focused on execution configuration.

A clean runner class is usually short. It contains annotations and configuration, not complex Java methods. If the runner class becomes large, it may be trying to do work that belongs elsewhere. The best runner classes are boring, predictable, and easy to understand.

Execution Flow

The runner starts the overall execution flow. When the runner is launched, the test framework loads the runner class, Cucumber reads configuration, locates feature files, locates glue code, matches Gherkin steps to step definitions, executes hooks, runs scenarios, and produces reports.

Runner Class
   |
   v
Read Configuration
   |
   v
Locate Feature Files
   |
   v
Locate Step Definitions
   |
   v
Match Steps
   |
   v
Execute Hooks
   |
   v
Execute Scenarios
   |
   v
Generate Reports

This flow explains why runner configuration matters. If feature location is wrong, Cucumber cannot find scenarios. If glue location is wrong, steps remain undefined. If tags are too restrictive, no matching scenarios run. If plugins are missing, reports may not be generated. The runner class is small, but it influences the entire test lifecycle.

Runner Class in Cucumber JVM

Historically, Cucumber JVM projects commonly used JUnit 4 runners or TestNG runners. A JUnit 4 runner uses @RunWith(Cucumber.class) with @CucumberOptions. A TestNG runner extends AbstractTestNGCucumberTests and also uses @CucumberOptions. Modern Cucumber versions also support JUnit 5 through the Cucumber JUnit Platform Engine.

The choice depends on the project. Some teams prefer JUnit 4 because many older tutorials and frameworks use it. Some prefer TestNG because of its parallel execution controls, test groups, and reporting familiarity. Many newer Java projects prefer JUnit 5 because it integrates cleanly with the JUnit Platform and modern build tooling.

The syntax changes, but the concept does not. Every approach must tell Cucumber where features and glue exist and how execution should be filtered and reported.

JUnit 4 Runner Example

A JUnit 4 runner is one of the classic Cucumber JVM patterns. It uses @RunWith(Cucumber.class) to tell JUnit to run Cucumber instead of normal JUnit test methods. The @CucumberOptions annotation supplies Cucumber configuration.

import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    glue = "stepdefinitions"
)
public class TestRunner {
}

When this runner is executed, JUnit delegates to Cucumber. Cucumber reads feature files from src/test/resources/features and looks for step definitions and hooks inside the stepdefinitions package. If the feature files and glue code are correctly placed, scenarios execute.

JUnit 4 runner classes are usually empty because the annotations contain the configuration. The class exists to provide an entry point for the test framework. Adding business logic inside the runner would be a design mistake.

TestNG Runner Example

A TestNG runner usually extends AbstractTestNGCucumberTests. TestNG manages execution, while Cucumber handles feature parsing and step execution. This style is common in Selenium automation frameworks that already use TestNG.

import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;

@CucumberOptions(
    features = "src/test/resources/features",
    glue = "stepdefinitions"
)
public class TestRunner extends AbstractTestNGCucumberTests {
}

This runner executes Cucumber scenarios through TestNG. Teams using TestNG may also configure parallel execution, listeners, reports, and suite XML files. The runner still has the same core responsibilities: feature location, glue location, tags, and plugins.

TestNG can be useful when a project has existing TestNG infrastructure. However, teams should avoid mixing too many execution concepts. Cucumber tags and TestNG groups should not compete without a clear strategy. In Cucumber projects, tags are usually the preferred way to select scenarios.

JUnit 5 Runner and JUnit Platform

Modern Cucumber JVM supports execution through the JUnit Platform Engine. Instead of the older JUnit 4 @RunWith approach, a project can use a suite class with JUnit Platform annotations and Cucumber configuration parameters.

import static io.cucumber.junit.platform.engine.Constants.*;

import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
@ConfigurationParameter(
    key = GLUE_PROPERTY_NAME,
    value = "stepdefinitions"
)
public class RunCucumberTest {
}

This setup uses the Cucumber JUnit Platform Engine. The @IncludeEngines("cucumber") annotation selects the Cucumber engine. The @SelectClasspathResource("features") annotation tells the platform where feature files are located on the classpath. The GLUE_PROPERTY_NAME configuration parameter defines where Cucumber should look for step definitions and hooks.

JUnit 5 style is common in newer projects because it aligns with modern Java testing. It also supports configuration through properties and build tools. The runner may look different from JUnit 4, but the same concepts still apply.

Main Components of a Runner

A Cucumber runner typically includes feature location, glue package, tags, plugins, reporting configuration, and execution settings. These are the core pieces that tell Cucumber what to execute and how to report it.

Runner Class
|
|-- Feature Location
|-- Glue Package
|-- Tags
|-- Plugins
|-- Reporting
|-- Execution Settings

Each component should be configured deliberately. Feature location controls which Gherkin files are discovered. Glue controls where step definitions, hooks, and parameter types are discovered. Tags control selective execution. Plugins control output and reporting. Execution settings control behavior such as publish mode, snippets, monochrome output, and parallel strategy depending on framework and version.

Feature Location

Feature location tells Cucumber where feature files are stored. In many Maven projects, feature files are placed under src/test/resources/features. The runner points Cucumber to that folder.

features = "src/test/resources/features"

Cucumber searches this location for .feature files. If the path is wrong, no scenarios may run. If the path is too broad, the runner may pick up more features than intended. If the path is too narrow, some scenarios may be missed.

In JUnit Platform style, classpath resource selection is common:

@SelectClasspathResource("features")

This assumes the features folder is available on the test classpath, usually under src/test/resources. Understanding the difference between file-system paths and classpath resources helps avoid runner configuration mistakes.

Glue Package

The glue package tells Cucumber where to search for step definitions, hooks, parameter types, data table transformers, and related glue code. Without correct glue, Cucumber may read feature files but fail to match steps.

glue = "stepdefinitions"

If the step definition class is inside com.company.project.steps, the glue should usually point to that package or a parent package that includes it. For example:

glue = "com.company.project"

Using a parent package can be convenient because it includes steps, hooks, and support classes under one root. However, glue should not be too broad if it causes unrelated classes to be scanned. A clean package structure makes glue configuration easier.

Tag Filtering

Tag filtering allows the runner to execute selected scenarios. For example, a runner can execute only smoke scenarios:

tags = "@Smoke"

Modern tag expressions support logical operators:

tags = "@Smoke and not @API"

This runs smoke scenarios while excluding API scenarios. Tag filtering is one of the most common runner responsibilities because real projects rarely run every scenario all the time. Smoke, regression, module, environment, priority, and status tags all support selective execution.

In JUnit Platform style, tags can be configured with FILTER_TAGS_PROPERTY_NAME:

@ConfigurationParameter(
    key = FILTER_TAGS_PROPERTY_NAME,
    value = "@Smoke"
)

Multiple Tags

Multiple tags can be combined with tag expressions. A runner may run UI smoke tests, payment regression, or staging critical checks.

@Smoke and @UI
@Regression and @Payment
(@Smoke or @Critical) and @Staging
@Regression and not @Quarantined

The expression should match a real execution need. Use and to narrow, or to expand, and not to exclude. Use parentheses when mixing operators so the expression is clear to future maintainers.

Hardcoding tags in a runner is simple, but many teams pass tags from Maven, Gradle, or CI so the same runner can support different suites. This is usually more flexible for mature frameworks.

Plugins and Reports

Plugins configure Cucumber output and reports. Common plugins include pretty, html, json, junit, and rerun. These plugins help teams read console output, generate HTML reports, produce machine-readable JSON, integrate with CI, and capture failed scenarios for reruns.

plugin = {
    "pretty",
    "html:target/cucumber-report.html",
    "json:target/cucumber.json",
    "junit:target/cucumber.xml",
    "rerun:target/failed_scenarios.txt"
}

The pretty plugin improves console output. The HTML plugin creates a human-readable report. The JSON plugin is often used by external report tools. The JUnit XML plugin helps CI systems display results. The rerun plugin writes failed scenarios to a file that can be used for retry execution.

Report paths should be stable and CI-friendly. Reports generated inside target are common in Maven projects. CI pipelines should archive the report files as build artifacts so failures can be reviewed after execution.

Monochrome and Output Settings

Some runner configurations include output settings such as monochrome. In older runner styles, monochrome = true makes console output cleaner by removing unnecessary escape characters.

@CucumberOptions(
    monochrome = true
)

Although this setting is small, clean console output matters in CI logs. Test failures are easier to read when logs are not cluttered. Output settings should support debugging without overwhelming the team.

Dry Run Configuration

Dry run is a configuration mode that checks whether every Gherkin step has a matching step definition without actually executing the step logic. It is useful when building or refactoring feature files.

@CucumberOptions(
    dryRun = true
)

When dry run is enabled, Cucumber validates step mappings. It does not launch browsers, call APIs, or execute business actions. This helps detect undefined steps quickly. However, dry run should not be left enabled in normal execution because scenarios will not actually test application behavior.

Dry run is especially useful in CI as a separate validation job for feature syntax and step coverage, but the main test pipeline should execute real scenarios.

Runner Class and Hooks

The runner class does not directly execute hook logic. It points Cucumber to the glue package where hooks are located. Cucumber then discovers hooks and executes them according to lifecycle rules and tag filters.

This means hook discovery depends on glue configuration. If hooks are in com.company.framework.hooks but the glue points only to com.company.steps, hooks may not run. This can cause missing browser setup, missing cleanup, missing screenshots, or missing report handling.

A common best practice is to keep step definitions, hooks, and support glue under a shared parent package so the runner can scan them together. For example, glue = "com.company.automation" can include steps, hooks, and transformers.

Runner Class and Step Definitions

Step definitions are discovered through the glue package. When Cucumber reads a feature file, it tries to match each Gherkin step to a Java method annotated with @Given, @When, @Then, @And, or @But. If glue is wrong, matching fails.

Undefined steps are one of the most common signs of runner misconfiguration. The step definition may exist, but Cucumber cannot see it. Before creating duplicate step definitions, check the runner glue path. Many step duplication problems begin when teams misunderstand glue scanning.

Runner configuration should be kept aligned with package structure. If packages are renamed, the runner must be updated. If new hook or transformer packages are added outside the glue root, they may not be discovered.

Runner Class in Maven Projects

In Maven projects, runner classes usually live under src/test/java, while feature files live under src/test/resources. The build tool compiles test Java classes and puts resources on the test classpath. Cucumber then uses the runner configuration to find features and glue.

src/test/java
  runners/TestRunner.java
  stepdefinitions/LoginSteps.java
  hooks/Hooks.java

src/test/resources
  features/login.feature

This structure is common because it separates executable Java test code from Gherkin feature files. The runner acts as the connection point between them. If Maven does not include resources correctly or the runner points to the wrong location, Cucumber execution may fail.

Runner Class and CI/CD

CI/CD pipelines often execute Cucumber through runner classes. The runner may be triggered by Maven Surefire, Maven Failsafe, Gradle test tasks, TestNG XML, or JUnit Platform discovery. The runner configuration determines which scenarios run and which reports are produced.

For CI, it is often better to pass tag expressions through command-line properties rather than hardcoding them in the runner. This allows the same runner to execute smoke, regression, API, UI, module, and environment-specific suites.

mvn test -Dcucumber.filter.tags="@Smoke and @QA" -Denv=qa

The runner provides the general Cucumber entry point, while the command supplies environment-specific or pipeline-specific selection. This keeps the framework flexible.

Single Runner vs Multiple Runners

Some projects use one generic runner and pass execution options dynamically. Other projects create separate runners such as SmokeRunner, RegressionRunner, ApiRunner, and UiRunner. Both approaches can work, but each has tradeoffs.

A single runner is flexible and reduces duplication. It works well when tags, features, and plugins are passed through build configuration. Multiple runners are easy for beginners to understand and can make common suites obvious. However, too many runners can become hard to maintain because the same feature path, glue path, and plugin configuration may be repeated in many places.

For larger frameworks, prefer a small number of runners and dynamic configuration where possible. If multiple runners are used, keep shared settings consistent and avoid copying outdated configuration between runner classes.

Common Runner Class Mistakes

Wrong Feature Path

If the feature path is incorrect, Cucumber may run no scenarios or miss important feature files. Always verify whether the path is file-system based or classpath-resource based depending on the runner style.

Wrong Glue Package

If glue is wrong, steps and hooks may not be discovered. This causes undefined steps or missing setup and teardown. Keep glue aligned with package structure.

Hardcoded Tags in Too Many Runners

Hardcoding tag expressions in many runner classes creates maintenance overhead. A CI-friendly framework often passes tags dynamically.

Missing Report Plugins

If report plugins are not configured, CI may not have useful execution evidence. Configure HTML, JSON, JUnit XML, or other reporting outputs as needed.

Leaving Dry Run Enabled

Dry run is useful for validation, but it should not remain enabled during real execution. It prevents actual scenario steps from running.

Best Practices

Keep runner classes simple and configuration-focused. Do not put business logic, browser actions, API calls, or assertions in runner classes. Use the runner to configure feature locations, glue packages, tags, plugins, reports, and execution settings.

Use meaningful package structures so glue configuration is easy. Keep step definitions, hooks, and transformers under a predictable root package. Use tag expressions for selective execution and prefer dynamic tag configuration in CI when the same runner must support multiple suites.

Configure reports clearly. Use stable output paths under target or another build artifact folder. Archive reports in CI. Review runner configuration whenever feature folders, glue packages, Cucumber versions, or build tools change.

Real-Time Framework Example

A real Cucumber JVM Selenium framework may use one runner for local and CI execution, with tags passed dynamically through Maven. The runner defines the feature location, glue root, and plugins, while the pipeline provides the tag expression and environment.

@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    glue = "com.softwaretips4u.automation",
    plugin = {
        "pretty",
        "html:target/cucumber-report.html",
        "json:target/cucumber.json",
        "junit:target/cucumber.xml"
    },
    monochrome = true
)
public class TestRunner {
}

The CI command can then choose what to run:

mvn test -Dcucumber.filter.tags="@Smoke and @UI" -Denv=qa

This design keeps the runner stable and lets the pipeline control execution. It avoids creating a separate runner for every tag combination.

Runner Class Governance in Large Frameworks

In a small learning project, a runner class may look like a simple technical file that only one tester touches. In a large automation framework, however, runner classes become part of execution governance. They decide what runs in local machines, pull requests, nightly builds, release pipelines, and production verification suites. Because of that, runner classes should be reviewed with the same seriousness as page objects, utilities, and test data strategy.

Governance starts with naming. A runner named TestRunner does not explain its responsibility. A runner named SmokeTestRunner, RegressionTestRunner, ApiCucumberRunner, or UiCucumberRunner tells the team what it is meant to execute. Clear names reduce confusion when multiple runners exist in the same project. They also help new team members understand which runner to use for a particular purpose.

The next governance point is ownership of tag expressions. If every tester edits runner classes whenever they want to run a custom subset, the framework quickly becomes inconsistent. One branch may change @Smoke to @Regression. Another branch may add a temporary tag and forget to remove it. A better approach is to keep permanent runner files stable and pass temporary tag filters from Maven, Gradle, IDE run configuration, or CI variables. This keeps the committed runner class meaningful while still allowing flexible execution.

Runner classes should also avoid hidden environment assumptions. A runner should not hardcode a QA URL, staging username, browser name, download folder, or grid endpoint. Those values belong in configuration files, system properties, environment variables, or pipeline settings. The runner should select scenarios and reporting outputs; the runtime environment should be configured separately. This separation makes the same runner usable on local machines and CI servers.

Documentation is another practical part of runner governance. A short comment above a complex tag expression can be useful when the expression controls an important CI suite. For example, a release runner may intentionally execute @Smoke or @Critical while excluding @Wip. If that decision is business-driven, the reason should be clear somewhere in the project documentation. The goal is not to fill runner classes with comments, but to prevent execution rules from becoming tribal knowledge.

In mature teams, runner changes are often reviewed carefully because they can silently change test coverage. A small edit to a tag expression can remove dozens of scenarios from a nightly suite. A wrong feature path can make a build pass even though no feature files actually ran. A changed report plugin path can break pipeline artifacts. For this reason, runner class changes should be treated as behavior-affecting changes, not simple configuration edits.

Migrating Runner Classes Across Cucumber Versions

Cucumber JVM has evolved over time, and runner class style can differ depending on whether a project uses JUnit 4, TestNG, or JUnit 5. Many real projects still contain older JUnit 4 runners that use @RunWith(Cucumber.class) and @CucumberOptions. Newer projects may prefer the JUnit Platform engine, especially when the organization standardizes on JUnit 5. Understanding this migration path helps testers explain runner classes beyond one fixed syntax.

When migrating from JUnit 4 to JUnit 5, the important thing is not just replacing annotations. The team must verify that feature discovery, glue discovery, tag expressions, plugin outputs, and naming strategies still behave the same way. A migration is successful only when the same intended scenarios run and the same reporting artifacts are produced. It is common for teams to migrate the runner syntax and then discover that hooks are not firing because the glue package was configured differently.

TestNG migration has its own considerations. TestNG is often used in Selenium frameworks because teams rely on TestNG XML suites, groups, parallel execution, and reporting integrations. A TestNG Cucumber runner commonly extends AbstractTestNGCucumberTests. If the project uses parallel execution, the runner may override data provider behavior. This makes the runner more involved than a basic JUnit 4 class, so changes must be made carefully.

Version migration should always include a scenario count comparison. Before the change, note how many scenarios execute for the smoke suite, regression suite, and any module suite. After the change, compare the counts and investigate differences. A passing build does not prove the migration is correct if only half the intended scenarios ran. Scenario count, tag coverage, plugin output, and hook execution should all be checked.

Another migration concern is plugin syntax. Cucumber reporting options have changed across versions, and teams sometimes carry old plugin names that no longer behave as expected. When updating Cucumber dependencies, the runner class should be checked along with the build file. The HTML report, JSON report, JUnit XML output, and rerun file should all be generated in expected locations. CI jobs often depend on those files.

A clean migration keeps the runner simple after the change. If the old runner had accumulated temporary settings, commented-out plugins, outdated tags, and unused paths, migration is a good time to remove those leftovers. The end result should be a runner that expresses current execution needs clearly. A migrated runner that keeps years of unused options remains difficult to trust.

Debugging Runner Class Problems

Many Cucumber execution problems appear to be step definition or Selenium problems, but the root cause is often runner configuration. When no scenarios run, the first thing to check is the feature path. The path must point to the correct feature file folder, and the files must be available on the test runtime classpath. A typo in the feature path can lead to an empty execution or a runtime error depending on the setup.

When scenarios run but steps appear as undefined, the glue configuration is the next place to look. The runner may be pointing to the wrong package, or the step definition class may have been moved without updating glue. In Cucumber JVM, glue is not a general project search. Cucumber only scans configured glue packages. If step definitions are outside those packages, Cucumber will not bind them to feature steps.

When hooks do not execute, the same glue rule applies. Hook classes must be inside the glue path or explicitly included through configuration. Teams sometimes place hooks in a separate package such as hooks while the runner only points to steps. The scenarios may execute, but setup and teardown logic will be skipped. This can produce confusing failures such as browser not initialized, test data not created, or screenshots not captured.

Tag expression issues are another common debugging area. A tag expression such as @Smoke and @Regression runs only scenarios that have both tags. If the team expected it to run either smoke or regression scenarios, the expression should use or. Similarly, adding not @Wip may exclude more scenarios than expected if tags are inherited from the feature level. When a suite runs fewer scenarios than expected, tag logic should be checked before assuming scenarios are missing.

Report problems often trace back to plugin configuration. If an HTML report is missing, check the plugin entry and output folder. If CI cannot publish results, check whether the runner writes reports to the location expected by the pipeline. If rerun files are empty or missing, check whether the rerun plugin is configured and whether the target folder exists. Runner classes and CI jobs must agree on report paths.

Dry run can also create confusion. When dryRun is enabled, Cucumber checks step mappings without executing step code. This is useful during development, but if it remains enabled accidentally, testers may think automation is running when it is only validating definitions. A runner used for real execution should not leave dry run enabled. If a suite completes suspiciously fast, dry run is one of the first settings to inspect.

A disciplined debugging approach reads the build log carefully. Cucumber output usually tells you which features were discovered, which scenarios were selected, which steps were undefined, and where reports were generated. Instead of changing many things at once, adjust one runner setting at a time and rerun a small feature. Runner problems are usually simple, but they become hard when several paths, tags, and plugins are changed together.

Runner Class Review Checklist

When reviewing a runner class, check whether feature location is correct, glue package includes steps and hooks, tag filters match the intended suite, plugins generate useful reports, and output paths are CI-friendly. Also check whether dry run is disabled for real execution and whether runner settings duplicate configuration from other runners unnecessarily.

Review whether the runner is tied too tightly to one environment. Environment details such as base URL, credentials, browser name, and grid endpoint usually belong in configuration, not hardcoded in the runner. The runner can select tests, but runtime values should come from configuration files or pipeline variables.

Finally, run a small smoke suite after runner changes. Runner changes can affect discovery and execution broadly, so quick validation prevents confusing failures later.

Interview-Ready Explanation

A Cucumber Runner Class is the entry point that starts Cucumber execution. It connects the test framework with the Cucumber engine and configures feature file location, glue package, tag filters, plugins, reports, and execution settings. Without a runner class or equivalent JUnit Platform configuration, Cucumber does not know what to execute or where to find step definitions.

In Cucumber JVM, runner classes can be created using JUnit 4, TestNG, or JUnit 5 through the JUnit Platform Engine. JUnit 4 uses @RunWith(Cucumber.class) and @CucumberOptions. TestNG extends AbstractTestNGCucumberTests. JUnit 5 uses suite annotations and configuration parameters. A good runner class is simple, readable, and focused only on execution configuration.

Summary

The Cucumber Runner Class is a small but central part of a Cucumber JVM framework. It starts execution, locates feature files, connects glue code, applies tag filters, enables plugins, and produces reports. It acts as the bridge between Cucumber and test frameworks such as JUnit or TestNG.

The golden rule is to keep the runner class clean and configuration-focused. Put business behavior in Gherkin and step definitions, lifecycle logic in hooks, reusable actions in page objects or services, and environment values in configuration. When the runner is designed well, Cucumber execution becomes predictable, maintainable, and CI-friendly.