Absolutely! Let’s continue building a comprehensive guide based on your detailed transcript. This section will cover the Page Object Model (POM) in Selenium with TestNG, explaining its significance, implementation approaches, and practical examples. Understanding POM is crucial for designing scalable, maintainable, and efficient automation frameworks.
Table of Contents
1. Introduction to Page Object Model (POM)
• What is Page Object Model?
Page Object Model (POM) is a design pattern in Selenium that creates an object repository for web elements. It enhances test maintenance and reduces code duplication by separating the page structure and test scripts. In POM, each web page of the application is represented by a separate class, containing locators and methods to interact with those elements.
• Why Use Page Object Model?
- Maintainability: Changes in the UI require updates only in the page classes, not in every test script.
- Reusability: Common actions can be reused across multiple tests.
- Readability: Test scripts become more readable and concise, focusing on test logic rather than UI interactions.
- Scalability: Easier to manage as the number of tests grows.
• Problems Without POM
When not using POM, test scripts often intertwine with UI locators and interaction logic, leading to several challenges:
- Duplication of Locators:
- Locators for the same web elements are repeated across multiple test cases.
- Example: Locating the username field in every test that requires login.
- Difficulty in Maintenance:
- Any change in the UI requires updates in every test script where the locator is used.
- Example: If the XPath of the login button changes, all tests need to be updated individually.
- Reduced Readability:
- Test scripts become cluttered with locator definitions and interaction logic, making them harder to read and understand.
- Limited Reusability:
- Common actions cannot be easily reused, leading to redundant code.
Illustration Without POM:
Test Case 1:
- Locate username field
- Enter username
- Locate password field
- Enter password
- Locate login button
- Click login
- Validate login success
Test Case 2:
- Locate username field
- Enter username
- Locate password field
- Enter password
- Locate login button
- Click login
- Perform search
- Validate search results
Issues:
- Locators for username, password, and login button are duplicated.
- Maintenance becomes cumbersome with increasing test cases.
2. Implementing Page Object Model
• Step 1: Identify Pages and Create Page Classes
- Identify the Pages:
- Analyze the application under test (AUT) and identify distinct pages or components.
- Example: Login Page, Search Page, Advanced Search Page.
- Create Page Classes:
- For each identified page, create a corresponding Java class.
- Each class will contain:
- Locators: Web elements on the page.
- Action Methods: Methods to interact with the web elements (e.g., enter text, click buttons).
Directory Structure:
project-root/
├── src/
│ └── test/
│ └── java/
│ └── day47/
│ ├── pages/
│ │ ├── LoginPage.java
│ │ ├── SearchPage.java
│ │ └── AdvancedSearchPage.java
│ ├── tests/
│ │ └── LoginTest.java
│ └── utils/
│ └── [Utility Classes]
├── pom.xml
└── testng.xml
• Step 2: Creating a Page Object Class Without PageFactory
Constructor
Purpose: Initialize the WebDriver instance to interact with web elements.
Implementation:
- Accept WebDriver as a parameter.
- Assign it to the class’s WebDriver variable.
package day47.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage {
WebDriver driver;
// Constructor
public LoginPage(WebDriver driver) {
this.driver = driver;
}
// Locators
By txtUsername = By.xpath("//input[@id='username']");
By txtPassword = By.xpath("//input[@id='password']");
By btnLogin = By.xpath("//button[@id='loginButton']");
// Action Methods
public void setUsername(String username) {
driver.findElement(txtUsername).sendKeys(username);
}
public void setPassword(String password) {
driver.findElement(txtPassword).sendKeys(password);
}
public void clickLogin() {
driver.findElement(btnLogin).click();
}
}
Locators
Definition: Use By class to define locators for web elements.
Best Practices:
- Use meaningful and consistent naming conventions.
- Prefer unique locators like id or name over complex xpath or cssSelector.
Action Methods
Purpose: Encapsulate interactions with web elements.
Implementation:
- Methods like
setUsername
,setPassword
, andclickLogin
perform actions on the respective elements.
• Step 3: Creating a Page Object Class Using PageFactory
Using @FindBy Annotations
Purpose: Declaratively define locators using annotations.
Advantages:
- Cleaner code with separation of locators and actions.
- Improved readability and maintainability.
package day47.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class LoginPagePF {
WebDriver driver;
// Constructor
public LoginPagePF(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
// Locators using @FindBy
@FindBy(id = "username")
WebElement txtUsername;
@FindBy(id = "password")
WebElement txtPassword;
@FindBy(id = "loginButton")
WebElement btnLogin;
// Action Methods
public void setUsername(String username) {
txtUsername.sendKeys(username);
}
public void setPassword(String password) {
txtPassword.sendKeys(password);
}
public void clickLogin() {
btnLogin.click();
}
}
Initializing Elements with PageFactory
Code Snippet:
PageFactory.initElements(driver, this);
Explanation:
- Initializes the web elements defined with
@FindBy
annotations. - Must be called within the constructor after assigning the WebDriver.
Action Methods
Purpose: Similar to the non-PageFactory approach, but directly interact with the annotated WebElements without needing to call findElement
.
3. Practical Example: Automating a Login Test
Let’s implement a practical example to demonstrate both approaches of POM.
• Page Object Class Without PageFactory
LoginPage.java
package day47.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage {
WebDriver driver;
// Constructor
public LoginPage(WebDriver driver) {
this.driver = driver;
}
// Locators
By txtUsername = By.xpath("//input[@id='username']");
By txtPassword = By.xpath("//input[@id='password']");
By btnLogin = By.xpath("//button[@id='loginButton']");
// Action Methods
public void setUsername(String username) {
driver.findElement(txtUsername).sendKeys(username);
}
public void setPassword(String password) {
driver.findElement(txtPassword).sendKeys(password);
}
public void clickLogin() {
driver.findElement(btnLogin).click();
}
}
• Page Object Class Using PageFactory
LoginPagePF.java
package day47.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class LoginPagePF {
WebDriver driver;
// Constructor
public LoginPagePF(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
// Locators using @FindBy
@FindBy(id = "username")
WebElement txtUsername;
@FindBy(id = "password")
WebElement txtPassword;
@FindBy(id = "loginButton")
WebElement btnLogin;
// Action Methods
public void setUsername(String username) {
txtUsername.sendKeys(username);
}
public void setPassword(String password) {
txtPassword.sendKeys(password);
}
public void clickLogin() {
btnLogin.click();
}
}
• Test Case Class
LoginTest.java
package day47.tests;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.*;
import day47.pages.LoginPage;
import day47.pages.LoginPagePF;
public class LoginTest {
WebDriver driver;
// Uncomment the desired Page Object Class
// LoginPage loginPage; // For non-PageFactory
LoginPagePF loginPagePF; // For PageFactory
@BeforeClass
public void setup() {
System.setProperty("webdriver.chrome.driver", "path/to/chromedriver");
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(java.time.Duration.ofSeconds(10));
driver.get("https://www.orangehrm.com");
}
@Test
public void testLogin() {
// Initialize the Page Object Class
// loginPage = new LoginPage(driver); // For non-PageFactory
loginPagePF = new LoginPagePF(driver); // For PageFactory
// Perform actions
// loginPage.setUsername("admin"); // For non-PageFactory
// loginPage.setPassword("password123"); // For non-PageFactory
// loginPage.clickLogin(); // For non-PageFactory
loginPagePF.setUsername("admin"); // For PageFactory
loginPagePF.setPassword("password123"); // For PageFactory
loginPagePF.clickLogin(); // For PageFactory
// Validation
String expectedTitle = "OrangeHRM";
String actualTitle = driver.getTitle();
Assert.assertEquals(actualTitle, expectedTitle, "Login failed or page title mismatch.");
}
@AfterClass
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
• TestNG XML Configuration
testng.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="POMSuite">
<test name="LoginTest">
<classes>
<class name="day47.tests.LoginTest"/>
</classes>
</test>
</suite>
• Running the Test
- Without PageFactory:
- Ensure that the non-PageFactory code sections are uncommented in
LoginTest.java
. - Run the test via Eclipse:
- Right-click on
LoginTest.java
. - Select Run As > TestNG Test.
- Right-click on
- Or via TestNG XML:
- Right-click on
testng.xml
. - Select Run As > TestNG Suite.
- Right-click on
- Ensure that the non-PageFactory code sections are uncommented in
- With PageFactory:
- Ensure that the PageFactory code sections are uncommented in
LoginTest.java
. - Run the test using the same method as above.
- Ensure that the PageFactory code sections are uncommented in
Expected Outcome:
- Browser launches and navigates to the OrangeHRM login page.
- Enters the provided username and password.
- Clicks the login button.
- Validates the page title to confirm successful login.
- Test passes if the title matches; otherwise, it fails.
4. Best Practices for Page Object Model
- Separate Locators and Tests:
- Keep all locators within page classes and avoid placing them in test scripts.
- Meaningful Naming Conventions:
- Use clear and consistent names for locators and methods.
- Example:
txtUsername
for the username text box,btnLogin
for the login button.
- Encapsulate Page Actions:
- Only expose methods that represent user interactions.
- Avoid exposing WebElement instances directly.
- Maintain Single Responsibility:
- Each page class should represent a single page or component.
- Avoid mixing functionalities of different pages within a single class.
- Use PageFactory Where Beneficial:
- Utilize
@FindBy
annotations for cleaner and more maintainable code. - Ensure elements are initialized properly in the constructor.
- Utilize
- Implement Wait Strategies:
- Incorporate explicit waits within action methods to handle dynamic elements.
- Avoid Hardcoding Test Data:
- Use external files or data providers for test data to enhance flexibility.
5. Common Pitfalls and How to Avoid Them
- Overcomplicating Page Classes:
- Issue: Adding too many methods or logic within page classes.
- Solution: Keep page classes focused on UI interactions only. Business logic should reside in test scripts or separate service classes.
- Not Handling Dynamic Elements:
- Issue: Failing to interact with elements that load dynamically.
- Solution: Implement explicit waits or use dynamic locators to handle such scenarios.
- Improper Locator Strategies:
- Issue: Using brittle locators like overly complex XPaths.
- Solution: Prefer stable locators like id or name. Use CSS selectors judiciously.
- Neglecting to Initialize PageFactory Elements:
- Issue: Forgetting to call
PageFactory.initElements(driver, this);
leading toNullPointerException
. - Solution: Always initialize PageFactory elements within the constructor.
- Issue: Forgetting to call
- Duplicating Test Logic:
- Issue: Repeating the same steps across multiple test scripts.
- Solution: Abstract common actions into reusable methods within page classes.
- Ignoring Maintenance:
- Issue: Allowing page classes to become outdated with UI changes.
- Solution: Regularly update page classes to reflect UI changes and ensure tests remain valid.
6. Conclusion
The Page Object Model (POM) is an essential design pattern in Selenium that promotes better test maintenance, scalability, and readability. By separating the web elements and their interactions from the test scripts, POM allows for more organized and efficient automation frameworks.
Key Takeaways:
- Separation of Concerns: Keep UI locators and actions within page classes, and test logic within test scripts.
- Maintainability: Easier to manage and update tests when the UI changes.
- Reusability: Common actions can be reused across multiple tests, reducing code duplication.
- Flexibility: POM supports different approaches, such as using PageFactory or traditional locator methods, catering to various project needs.
Next Steps:
- Implement POM in Larger Projects:
- Apply POM to projects with multiple pages and complex interactions to fully leverage its benefits.
- Integrate with TestNG Features:
- Combine POM with TestNG listeners, reports (like Extent Reports), and data providers for a robust framework.
- Enhance with Advanced Features:
- Incorporate design patterns like Singleton for WebDriver management, and utilize utilities for common tasks like screenshot capturing.
- Continuous Learning:
- Explore other design patterns and best practices in test automation to build more efficient and reliable frameworks.
Happy Testing! 🚀