Integrating Selenium with Cucumber
What Is Selenium-Cucumber Integration?
Selenium-Cucumber integration means combining Selenium WebDriver browser automation with Cucumber BDD scenarios written in Gherkin. Cucumber describes the expected behavior in business-readable language, while Selenium performs the real browser actions needed to validate that behavior. This separation is the main reason the combination is popular in Java automation frameworks.
Cucumber manages feature files, scenarios, step definitions, hooks, tags, dry runs, and reports. Selenium manages browser sessions, element interactions, navigation, alerts, windows, waits, and UI verification. When the integration is designed well, a business user can read the scenario and understand the behavior, while the automation engineer can maintain the Selenium implementation behind the scenes.
Why Integrate Selenium with Cucumber?
Selenium alone gives powerful automation code, but the tests often become technical and difficult for non-technical stakeholders to read. Cucumber adds a collaboration layer. Product owners, business analysts, testers, developers, and automation engineers can discuss behavior using feature files before or during implementation. The same scenarios can then become living documentation and executable acceptance tests.
The practical value is not that Cucumber makes Selenium faster. The value is that Cucumber improves communication, traceability, and readability when it is used correctly. Selenium still does the browser work. Cucumber gives that work a behavior-focused structure.
High-Level Architecture
Feature File
-> Step Definition
-> Page Object
-> Selenium WebDriver
-> Browser
-> Application Under Test
This layered architecture is the foundation of a maintainable framework. Feature files should not mention Selenium locators, browser mechanics, or waits. Step definitions should coordinate the flow and delegate UI work. Page Objects should contain the Selenium locators and browser actions. Driver Factory should manage WebDriver creation and cleanup.
Required Framework Components
A typical Java framework contains runner classes, step definitions, hooks, page objects, utilities, driver management, configuration files, and feature files. The exact package names may differ, but the responsibilities are usually the same.
src/test/java
runners
stepdefinitions
hooks
pages
utilities
drivers
src/test/resources
features
config.properties
Keeping these responsibilities separate prevents the framework from becoming a collection of long step definition files. It also helps new team members find code quickly.
Maven Dependencies
A Selenium-Cucumber project usually needs Cucumber Java, a test framework integration such as Cucumber TestNG or Cucumber JUnit, Selenium Java, and the chosen assertion or test framework dependency.
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.x.x</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.x.x</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.x.x</version>
</dependency>
JUnit-based frameworks use the corresponding JUnit integration instead of TestNG. The important point is that Cucumber needs a way to run through a Java test framework, and Selenium needs the browser automation library.
Feature File Example
A good feature file describes behavior, not browser implementation. It should explain the business outcome in simple language.
Feature: Login
Scenario: Valid login
Given the user is on the login page
When the user enters valid credentials
Then the dashboard should be displayed
This scenario does not mention IDs, XPath, buttons, text boxes, or browser drivers. Those details are automation implementation details and belong in Java code.
Step Definition Example
The step definition maps Gherkin steps to Java methods. In a clean design, the step definition coordinates the flow by calling Page Object methods.
public class LoginSteps {
private LoginPage loginPage = new LoginPage();
@Given("the user is on the login page")
public void userIsOnLoginPage() {
loginPage.openLoginPage();
}
@When("the user enters valid credentials")
public void userEntersValidCredentials() {
loginPage.login("admin", "admin123");
}
@Then("the dashboard should be displayed")
public void dashboardShouldBeDisplayed() {
loginPage.verifyDashboard();
}
}
The step definition is intentionally thin. It does not contain locators, long Selenium flows, or repeated wait logic.
Page Object Example
The Page Object contains the Selenium details for a page or reusable page component. Locators and browser actions are kept here so they can be maintained in one place.
public class LoginPage {
private WebDriver driver = DriverFactory.getDriver();
private By username = By.id("username");
private By password = By.id("password");
private By loginButton = By.id("login");
private By dashboard = By.id("dashboard");
public void openLoginPage() {
driver.get(ConfigReader.getProperty("url"));
}
public void login(String user, String pass) {
driver.findElement(username).sendKeys(user);
driver.findElement(password).sendKeys(pass);
driver.findElement(loginButton).click();
}
public void verifyDashboard() {
Assert.assertTrue(driver.findElement(dashboard).isDisplayed());
}
}
When UI locators change, the Page Object is updated. The feature file and step definition can remain stable if the business behavior has not changed.
Driver Factory and Hooks
Driver Factory centralizes WebDriver creation and cleanup. Hooks decide when that lifecycle happens in Cucumber execution.
public class Hooks {
@Before
public void setup() {
DriverFactory.initializeDriver();
}
@After
public void tearDown() {
DriverFactory.quitDriver();
}
}
This keeps browser setup out of individual step definitions. Every scenario can start with a predictable browser state and end with a proper cleanup.
Runner Class
The runner connects feature files, glue packages, plugins, tag filters, and the test framework.
@CucumberOptions(
features = "src/test/resources/features",
glue = {"stepdefinitions", "hooks"},
plugin = {
"pretty",
"html:target/cucumber-report.html",
"json:target/cucumber.json"
},
monochrome = true
)
public class TestRunner extends AbstractTestNGCucumberTests {
}
If hooks are in a separate package, that package must be included in glue. Otherwise the browser may never open or screenshots may never attach.
Selenium Waits in Cucumber Frameworks
Wait logic should live in Page Objects or reusable wait utilities. Step definitions should not use Thread.sleep() or repeatedly create wait objects. A common approach is to build helper methods that wait for visibility, clickability, or presence before interacting with elements.
public WebElement waitForElement(By locator) {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
This makes synchronization consistent across the framework and reduces flaky UI tests.
Screenshot on Failure
Screenshots should be captured before quitting the driver. The @After hook can check whether the scenario failed and attach evidence to the Cucumber report.
@After
public void tearDown(Scenario scenario) {
if (scenario.isFailed()) {
byte[] screenshot = ((TakesScreenshot) DriverFactory.getDriver())
.getScreenshotAs(OutputType.BYTES);
scenario.attach(screenshot, "image/png", "Failure Screenshot");
}
DriverFactory.quitDriver();
}
This simple pattern improves debugging because failed scenario reports include visual evidence from the browser at the time of failure.
Parallel Execution Consideration
Parallel execution requires isolated WebDriver instances. A single static driver is risky because multiple threads may fight for the same browser session. Enterprise frameworks commonly use ThreadLocal<WebDriver> so each thread has its own driver.
Before enabling parallel execution, the framework should also make test data, reports, downloads, screenshots, and scenario context thread-safe.
Common Mistakes
The most common mistake is putting Selenium code directly inside step definitions. This makes Gherkin automation difficult to maintain and causes duplication. Another mistake is opening browsers inside step definitions instead of hooks. A third mistake is using hardcoded test data and hardcoded URLs throughout the code. A fourth mistake is forgetting to include hook packages in glue configuration.
Teams also struggle when feature files become UI scripts in English. Steps such as "click login button" and "enter username into text box" are not business behavior. They make scenarios fragile and unreadable.
Best Practices
Keep feature files business-readable. Keep step definitions thin. Put locators and Selenium actions inside Page Objects. Use hooks for browser setup and teardown. Use a Driver Factory for WebDriver management. Use explicit waits instead of hard sleeps. Capture screenshots on failure. Use tags for selective execution. Keep test data and environment settings outside step definitions.
The golden rule is simple: Cucumber should describe and coordinate behavior, while Selenium should perform browser automation through Page Objects.
Interview-Ready Summary
Selenium-Cucumber integration combines Cucumber JVM's BDD structure with Selenium WebDriver's browser automation capability. Cucumber provides feature files, step definitions, hooks, tags, and reports. Selenium performs UI interactions through Page Objects. A mature framework uses Driver Factory, hooks, explicit waits, screenshots, reporting plugins, and clean package organization.