Page class design in Selenium Java - Build an Automation Script
import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; // Page class for Login Page public class LoginPage { private WebDriver driver; private WebDriverWait wait; private By emailField = By.id("email"); private By passwordField = By.id("password"); private By loginButton = By.id("loginBtn"); public LoginPage(WebDriver driver) { this.driver = driver; this.wait = new WebDriverWait(driver, Duration.ofSeconds(10)); } public void enterEmail(String email) { wait.until(ExpectedConditions.visibilityOfElementLocated(emailField)); driver.findElement(emailField).clear(); driver.findElement(emailField).sendKeys(email); } public void enterPassword(String password) { wait.until(ExpectedConditions.visibilityOfElementLocated(passwordField)); driver.findElement(passwordField).clear(); driver.findElement(passwordField).sendKeys(password); } public void clickLogin() { wait.until(ExpectedConditions.elementToBeClickable(loginButton)); driver.findElement(loginButton).click(); } } // Test class import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class LoginTest { private WebDriver driver; private LoginPage loginPage; @BeforeClass public void setUp() { // Set path to chromedriver executable if needed driver = new ChromeDriver(); driver.manage().window().maximize(); driver.get("https://example.com/login"); loginPage = new LoginPage(driver); } @Test public void testValidLogin() { loginPage.enterEmail("testuser@example.com"); loginPage.enterPassword("Test@1234"); loginPage.clickLogin(); // Wait for URL to contain /dashboard new org.openqa.selenium.support.ui.WebDriverWait(driver, Duration.ofSeconds(10)) .until(d -> d.getCurrentUrl().contains("/dashboard")); Assert.assertTrue(driver.getCurrentUrl().contains("/dashboard"), "Dashboard page should be displayed after login"); } @AfterClass public void tearDown() { if (driver != null) { driver.quit(); } } }
The LoginPage class encapsulates the login page elements and actions. It uses By locators with IDs, which are stable and fast. The constructor initializes the WebDriver and WebDriverWait for explicit waits.
Each method waits explicitly for the element to be visible or clickable before interacting. This avoids flaky tests due to timing issues.
The LoginTest class sets up the browser, navigates to the login page, and uses the page object methods to perform login steps. It then waits for the URL to contain '/dashboard' and asserts this condition to verify successful login.
Finally, the browser is closed in @AfterClass to clean up resources.
This design keeps page details separate from test logic, making tests easier to maintain and read.
Now add data-driven testing with 3 different sets of login credentials (valid and invalid) to verify login behavior.
Practice
Solution
Step 1: Understand the role of page classes
Page classes organize locators and actions related to a specific web page.Step 2: Identify the main purpose
This organization improves code readability and reuse by keeping page details in one place.Final Answer:
To group web page elements and actions in one place -> Option CQuick Check:
Page class purpose = Group elements and actions [OK]
- Confusing page classes with test classes
- Using page classes for database or API logic
- Writing assertions inside page classes
Solution
Step 1: Recall best practice for locators
Locators should be stored as By objects, not WebElement, to delay element lookup.Step 2: Check each option
private By submitButton = By.id("submit"); correctly declares a private By locator with By.id("submit").Final Answer:
private By submitButton = By.id("submit"); -> Option DQuick Check:
Locators = By objects [OK]
- Declaring locators as WebElement fields
- Using public visibility for locators
- Assigning void type to variables
loginPage.getTitle() return if the page title is "Welcome Page"?public class LoginPage {
private WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
}
public String getTitle() {
return driver.getTitle();
}
}Solution
Step 1: Understand getTitle() method
The method calls driver.getTitle(), which returns the current page title.Step 2: Match with given page title
The page title is "Welcome Page", so getTitle() returns this string.Final Answer:
"Welcome Page" -> Option AQuick Check:
driver.getTitle() = "Welcome Page" [OK]
- Assuming getTitle() returns page class name
- Expecting null if not set explicitly
- Confusing with element text retrieval
public void clickLogin() {
WebElement loginBtn = driver.findElement(By.id("login"));
loginBtn.click();
}Solution
Step 1: Review locator usage best practice
Locators should be stored as By fields in the page class, not found inside methods.Step 2: Identify the problem
Finding element inside method reduces reuse and violates page class design principles.Final Answer:
Should store locator as a By field, not find element inside method -> Option BQuick Check:
Locators as By fields = best practice [OK]
- Finding elements repeatedly inside methods
- Ignoring locator reuse
- Assuming exception handling is mandatory here
Solution
Step 1: Recall page class design principles
Locators should be private By fields; actions should be public methods for reuse and encapsulation.Step 2: Evaluate options
Store locators as private By fields; create public methods to enter username, enter password, and click login follows best practice by separating locators and actions clearly.Final Answer:
Store locators as private By fields; create public methods to enter username, enter password, and click login -> Option AQuick Check:
Private locators + public methods = best design [OK]
- Making locators public and exposing WebElements
- Putting test logic in constructors
- Using static methods without instance context
Ready to prove it?
Pass the test and this pattern moves unknown
