In a real Cucumber framework, step definitions should NOT contain raw Selenium code. Step definitions call Page Object methods. Page Objects interact with the browser. This three-layer architecture keeps everything clean and maintainable.
Think of it like a restaurant: the feature file is the menu (what the customer wants), the step definition is the waiter (takes the order and delivers the food), and the page object is the chef (actually does the cooking). The waiter never cooks. The chef never talks to customers.
package com.autopractice.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class BankingLoginPage {
private WebDriver driver;
private WebDriverWait wait;
private By usernameField = By.id("username");
private By passwordField = By.id("password");
private By loginButton = By.cssSelector("button[type='submit']");
private By errorMessage = By.cssSelector(".error-message");
public BankingLoginPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
public void navigateTo() {
driver.get("https://www.testerrank.com/banking");
}
public void loginAs(String username, String password) {
wait.until(ExpectedConditions.visibilityOfElementLocated(
usernameField)).sendKeys(username);
driver.findElement(passwordField).sendKeys(password);
driver.findElement(loginButton).click();
}
public String getErrorMessage() {
return wait.until(ExpectedConditions.visibilityOfElementLocated(
errorMessage)).getText();
}
public boolean isDisplayed() {
return driver.findElements(usernameField).size() > 0;
}
}package com.autopractice.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class BankingDashboardPage {
private WebDriver driver;
private WebDriverWait wait;
private By welcomeMessage = By.id("welcome-message");
private By accountBalance = By.id("account-balance");
private By transactionHistory = By.id("transaction-history");
private By logoutButton = By.id("logout-btn");
public BankingDashboardPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
public boolean isLoaded() {
return wait.until(ExpectedConditions.visibilityOfElementLocated(
welcomeMessage)).isDisplayed();
}
public String getWelcomeMessage() {
return driver.findElement(welcomeMessage).getText();
}
public boolean isBalanceDisplayed() {
return driver.findElement(accountBalance).isDisplayed();
}
public boolean isTransactionHistoryVisible() {
return driver.findElement(transactionHistory).isDisplayed();
}
public void clickLogout() {
driver.findElement(logoutButton).click();
}
}package com.autopractice.stepdefinitions;
import com.autopractice.hooks.Hooks;
import com.autopractice.pages.BankingLoginPage;
import com.autopractice.pages.BankingDashboardPage;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import org.testng.Assert;
public class BankingSteps {
private BankingLoginPage loginPage;
private BankingDashboardPage dashboardPage;
public BankingSteps() {
var driver = Hooks.getDriver();
this.loginPage = new BankingLoginPage(driver);
this.dashboardPage = new BankingDashboardPage(driver);
}
@Given("the user is on the Banking Portal login page")
public void userIsOnLoginPage() {
loginPage.navigateTo();
}
@When("the user logs in with username {string} and password {string}")
public void userLogsIn(String username, String password) {
loginPage.loginAs(username, password);
}
@Then("the user should be on the dashboard page")
public void userOnDashboard() {
Assert.assertTrue(dashboardPage.isLoaded(),
"Dashboard should be loaded");
}
@Then("the account balance should be displayed")
public void balanceDisplayed() {
Assert.assertTrue(dashboardPage.isBalanceDisplayed(),
"Balance should be visible");
}
@Then("the transaction history section should be visible")
public void transactionHistoryVisible() {
Assert.assertTrue(
dashboardPage.isTransactionHistoryVisible(),
"Transaction history should be visible");
}
@When("the user clicks the logout button")
public void userClicksLogout() {
dashboardPage.clickLogout();
}
@Then("the user should be on the login page")
public void userOnLoginPage() {
Assert.assertTrue(loginPage.isDisplayed(),
"Login page should be displayed after logout");
}
}Notice how clean the step definitions are. No By.id(), no findElement(), no WebDriverWait. They just call page object methods. If the locator changes, you update ONE page object class. All step definitions that use that page object automatically work with the new locator.
Never put Selenium code directly in step definitions. Step definitions should only call page object methods and assertions. If you see driver.findElement() in a step definition, refactor it into a page object method. This is the #1 maintainability rule in Cucumber frameworks.
Q: How do you integrate Cucumber with Selenium and POM?
A: Three layers: Feature files describe WHAT to test in Gherkin. Step definitions are the glue — each step calls page object methods instead of raw Selenium. Page objects encapsulate locators and browser interactions. The Hooks class manages WebDriver lifecycle with @Before and @After. If a locator changes, I update one page object. Step definitions and feature files remain unchanged. This separation makes the framework highly maintainable.
Key Point: Feature file → Step definition → Page object → Browser. Step definitions should never contain raw Selenium code.