Intermediate Chapter 10 · 12 min read

Page Object Model with Cucumber

Integrate the Page Object pattern with Cucumber step definitions — keep steps thin, delegate to page objects, and share state via dependency injection.

POM + Cucumber — Clean Architecture

Step definitions should be thin orchestrators. All page interactions belong in Page Objects. This separation means when the UI changes, you update one page class — not dozens of step files.

Sharing State Between Steps

In Java, use Dependency Injection (PicoContainer or Spring) to share state (like the WebDriver) across step definition classes. In Python, Behave's context object serves as the shared state container.

TestContext.java
package com.bdd.course.context;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import com.bdd.course.pages.LoginPage;
import com.bdd.course.pages.HomePage;

// Shared state via dependency injection (PicoContainer)
// Add: cucumber-picocontainer dependency in pom.xml
public class TestContext {

    private WebDriver driver;
    private LoginPage loginPage;
    private HomePage homePage;

    public WebDriver getDriver() {
        if (driver == null) {
            driver = new ChromeDriver();
            driver.manage().window().maximize();
        }
        return driver;
    }

    public LoginPage getLoginPage() {
        if (loginPage == null) {
            loginPage = new LoginPage(getDriver());
        }
        return loginPage;
    }

    public HomePage getHomePage() {
        if (homePage == null) {
            homePage = new HomePage(getDriver());
        }
        return homePage;
    }

    public void tearDown() {
        if (driver != null) {
            driver.quit();
            driver = null;
        }
    }
}
LoginStepsPOM.java
package com.bdd.course.stepdefinitions;

import io.cucumber.java.en.*;
import com.bdd.course.context.TestContext;
import com.bdd.course.pages.LoginPage;
import com.bdd.course.pages.HomePage;
import org.testng.Assert;

public class LoginStepsPOM {

    // Injected by PicoContainer automatically
    private final TestContext context;

    public LoginStepsPOM(TestContext context) {
        this.context = context;
    }

    @Given("the user is on the login page")
    public void userOnLoginPage() {
        context.getDriver().get("https://example.com/login");
    }

    @When("the user enters username {string} and password {string}")
    public void enterCredentials(String user, String pass) {
        context.getLoginPage()
            .enterUsername(user)
            .enterPassword(pass);
    }

    @When("the user clicks the login button")
    public void clickLogin() {
        context.getLoginPage().clickLogin();
    }

    @Then("the user should be redirected to the dashboard")
    public void verifyDashboard() {
        Assert.assertTrue(
            context.getHomePage().isDashboardDisplayed());
    }

    @Then("the welcome message should display {string}")
    public void verifyWelcome(String expected) {
        Assert.assertEquals(
            context.getHomePage().getWelcomeMessage(), expected);
    }
}
pages/login_page.py
# pages/login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class LoginPage:
    URL = "https://example.com/login"
    USERNAME = (By.ID, "username")
    PASSWORD = (By.ID, "password")
    LOGIN_BTN = (By.CSS_SELECTOR, "button[type='submit']")
    ERROR_MSG = (By.CSS_SELECTOR, ".error-message")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def navigate(self):
        self.driver.get(self.URL)
        return self

    def enter_username(self, username):
        el = self.wait.until(EC.visibility_of_element_located(self.USERNAME))
        el.clear()
        el.send_keys(username)
        return self

    def enter_password(self, password):
        el = self.driver.find_element(*self.PASSWORD)
        el.clear()
        el.send_keys(password)
        return self

    def click_login(self):
        self.driver.find_element(*self.LOGIN_BTN).click()
        return self

    def get_error_message(self):
        return self.driver.find_element(*self.ERROR_MSG).text

    def login_as(self, username, password):
        return self.enter_username(username).enter_password(password).click_login()
login_steps_pom.py
# features/steps/login_steps_pom.py
from behave import given, when, then
from pages.login_page import LoginPage
from pages.home_page import HomePage

@given('the user is on the login page')
def step_on_login(context):
    context.login_page = LoginPage(context.driver)
    context.login_page.navigate()

@when('the user enters username "{user}" and password "{pwd}"')
def step_enter_creds(context, user, pwd):
    context.login_page.enter_username(user).enter_password(pwd)

@when('the user clicks the login button')
def step_click_login(context):
    context.login_page.click_login()
    context.home_page = HomePage(context.driver)

@then('the user should be redirected to the dashboard')
def step_verify_dashboard(context):
    assert context.home_page.is_dashboard_displayed()

@then('the welcome message should display "{expected}"')
def step_verify_welcome(context, expected):
    assert context.home_page.get_welcome_message() == expected

Cucumber BDD Intermediate Page Object Model with Cucumber

Written by PV

© 2026 All Rights Reserved