Page Factory vs Custom Pages

What Is the Difference?

Page Factory and custom Page Objects are two approaches for implementing the Page Object Model in Selenium. Page Factory uses annotations such as @FindBy and initializes WebElement fields using PageFactory.initElements(). Custom Page Objects store locators as By objects and locate elements explicitly when actions are performed.

Both approaches can support Cucumber frameworks because step definitions can call page methods without knowing how elements are stored internally. The design choice affects maintainability, wait handling, stale element behavior, and framework flexibility.

What Is Page Factory?

Page Factory is an annotation-based Selenium support mechanism. Instead of writing driver.findElement(By.id("username")) every time, a field can be declared with @FindBy.

@FindBy(id = "username")
WebElement txtUsername;

The page class then calls PageFactory.initElements(driver, this) to initialize those fields. This makes small page classes look concise.

What Is a Custom Page Object?

A custom Page Object is a regular Java class that stores locators as By variables and calls WebDriver explicitly.

private By txtUsername = By.id("username");

driver.findElement(txtUsername).sendKeys("admin");

This approach is slightly more verbose, but it integrates naturally with explicit waits and fresh element lookup.

Architecture Comparison

Page Factory:
Feature File -> Step Definition -> Page Object -> @FindBy -> WebElement -> Browser

Custom Page:
Feature File -> Step Definition -> Page Object -> By Locator -> findElement() -> Browser

The Cucumber layer does not need to change when switching approaches. The difference lives inside the Page Object implementation.

Page Factory Example

public class LoginPage {
    WebDriver driver;

    @FindBy(id = "username")
    WebElement txtUsername;

    @FindBy(id = "password")
    WebElement txtPassword;

    @FindBy(id = "login")
    WebElement btnLogin;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void login(String user, String pass) {
        txtUsername.sendKeys(user);
        txtPassword.sendKeys(pass);
        btnLogin.click();
    }
}

This style is compact and familiar in many older Selenium frameworks.

Custom Page Example

public class LoginPage {
    private WebDriver driver;

    private By txtUsername = By.id("username");
    private By txtPassword = By.id("password");
    private By btnLogin = By.id("login");

    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }

    public void login(String user, String pass) {
        driver.findElement(txtUsername).sendKeys(user);
        driver.findElement(txtPassword).sendKeys(pass);
        driver.findElement(btnLogin).click();
    }
}

No annotations are required. The code is explicit about when element lookup happens.

Element Lookup Behavior

With Page Factory, elements are represented as fields and initialized through Selenium support classes. With custom pages, elements are found when the method needs them. In dynamic web applications, fresh lookup often reduces stale element issues because the element is retrieved closer to the interaction time.

Wait Handling

Explicit waits work naturally with By locators. A custom page can wait for visibility or clickability using the locator, then interact with the fresh element.

wait.until(ExpectedConditions.elementToBeClickable(btnLogin)).click();

Page Factory can also use waits, but teams often find By-based waits clearer and easier to reuse.

Dynamic Elements

Modern applications built with React, Angular, Vue, or other dynamic frameworks often update the DOM after the page first loads. Elements can be recreated after AJAX calls, validation changes, or component rerenders. Custom pages work well in these situations because each interaction can locate the element again.

Stale Element Reference

Stored WebElement references can become stale when the DOM changes. This can happen after page refresh, table update, modal open, or component rerender. Custom Page Objects using locators reduce this risk by avoiding long-lived WebElement references.

Readability and Maintainability

Page Factory is concise. Custom pages are explicit. In enterprise frameworks, explicitness often wins because locators, waits, and interactions are easier to reason about. New Selenium frameworks commonly prefer custom Page Objects with By locators and reusable wait methods.

Selenium 4 Context

Selenium still provides Page Factory support through selenium-support, but modern framework design does not depend on it as heavily as older examples did. Many teams now prefer regular Page Objects because they are simple Java classes, require fewer annotations, and make dynamic wait handling straightforward.

Using Either Approach with Cucumber

Step definitions should remain independent of the Page Object implementation. A step definition should call loginPage.login(), not care whether the page uses @FindBy or By locators internally.

Comparison Table

AspectPage FactoryCustom Pages
Element style@FindBy WebElement fieldsBy locators
WaitsPossibleVery direct
Fresh lookupLess explicitNatural
Dynamic DOMNeeds careUsually easier
Modern preferenceCommon in legacy frameworksCommon in new frameworks

Common Mistakes

Common mistakes include mixing Page Factory and custom locators without a reason, putting Selenium code in step definitions, using long-lived WebElement fields on dynamic pages, creating huge page classes, and using hard sleeps instead of explicit waits. Another mistake is choosing Page Factory only because it looks shorter, without considering maintainability.

Best Practices

For new frameworks, prefer custom Page Objects with By locators unless the team has a clear reason to use Page Factory. Keep Selenium code inside Page Objects. Use explicit waits. Keep step definitions thin. Use business-oriented page methods. Avoid long-lived WebElement references on pages that update frequently.

Interview-Ready Summary

Page Factory is an annotation-based Page Object implementation using @FindBy and PageFactory.initElements(). Custom pages use By locators and explicit WebDriver calls. Modern Selenium-Cucumber frameworks often prefer custom pages because they work cleanly with explicit waits, reduce stale element risk, and provide better flexibility for dynamic web applications.