🍏OOPS + Selenium

Object-Oriented Programming (OOPS) concepts in Selenium:

  • Encapsulation - Selenium supports encapsulation by using private access modifiers and get-set methods to protect the internal state of page object classes. This helps to prevent unintended changes to the page object's state, ensuring that the test scripts are more reliable and robust.

  • Inheritance - Selenium supports inheritance through the Page Object Model (POM) with a BaseClass. By using inheritance, we can create a base class that contains common functionality and reuse it across multiple-page object classes.

  • Polymorphism - Selenium supports polymorphism through classes like Actions and Assert, which provide multiple versions of the same method with different parameters. This allows us to write test scripts that are more flexible and can handle a wider range of scenarios.

  • Abstraction - Selenium supports abstraction through the POM by separating locators from the test scripts. This hides the implementation details of how the page is implemented, making the test scripts more maintainable and flexible.

  • Interface - Selenium provides an interface called WebDriver that defines a common set of methods that can be used to interact with web pages. By using the WebDriver interface, we can write test scripts that are not dependent on a specific web browser or implementation, making the scripts more reusable and portable.

Encapsulation

Encapsulation is a process of hiding the implementation details of an object and exposing only the essential features. It is one of the fundamental principles of object-oriented programming. Encapsulation can be achieved by using access modifiers such as private, protected, and public to restrict the access level of variables and methods.

In Selenium, encapsulation can be used to create page objects that represent the web pages or elements under test. Page objects encapsulate the locators and actions of the web elements, and provide a clear interface for the test scripts to interact with them. This way, the test scripts do not need to know the details of how the web elements are located or manipulated, and the page objects can be reused and maintained easily.

💡 Design Pattern: Page Object Model (POM)

For example, suppose we have a web page that has a login form with username and password fields and a submit button. We can create a page object class for this page as follows:

public class LoginPage {
// Declare the web elements as private variables
private WebDriver driver;
private By usernameField = By.id("username");
private By passwordField = By.id("password");
private By submitButton = By.id("submit");

// Create a constructor that takes a WebDriver instance
public LoginPage(WebDriver driver) {
this.driver = driver;
}

// Provide public methods to set and get the username and password values
public void setUsername(String username) {
driver.findElement(usernameField).sendKeys(username);
}

public String getUsername() {
return driver.findElement(usernameField).getAttribute("value");
}

public void setPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
}

public String getPassword() {
return driver.findElement(passwordField).getAttribute("value");
}

// Provide a public method to click on the submit button
public void clickSubmit() {
driver.findElement(submitButton).click();
}
}

//Now, we can use this page object class in our test script as follows:

public class LoginTest {
// Create a WebDriver instance
WebDriver driver = new ChromeDriver();

// Create a LoginPage instance
LoginPage loginPage = new LoginPage(driver);

// Navigate to the login page
driver.get("<https://example.com/login>");

// Set the username and password values using the page object methods
loginPage.setUsername("testuser");
loginPage.setPassword("testpass");

// Click on the submit button using the page object method
loginPage.clickSubmit();

// Verify the login result using assertions
String expectedUrl = "<https://example.com/home>";
String actualUrl = driver.getCurrentUrl();
assertEquals(expectedUrl, actualUrl, "User is not redirected to home page after login");

String expectedMessage = "Welcome, testuser!";
String actualMessage = driver.findElement(By.id("welcome-message")).getText();
assertTrue(actualMessage.contains(expectedMessage), "Welcome message is not displayed after login");

}

Benefits of Encapsulation

  1. Maintainability: Encapsulation simplifies updating and maintaining code.

  2. Reusability: Encapsulation allows for code reuse across multiple test scripts.

  3. Reduced redundancy: Encapsulation reduces redundant code in test scripts.

  4. Readability: Encapsulation improves the clarity and readability of test scripts.

Inheritance

Inheritance is a mechanism in object-oriented programming where a new class is created by inheriting the properties and methods of an existing class. The new class is called the subclass or derived class, and the existing class is called the superclass or base class.

In Selenium, inheritance can be used to create page object classes that inherit common methods and variables from a base page object class. This can help to reduce code duplication and make the test scripts more maintainable.

For example, suppose we have a base page object class called BasePage that contains common methods and variables for all web pages in our application. We can create a subclass called LoginPage that inherits the methods and variables of the BasePage class and adds methods and variables specific to the login page.

public class BasePage {
// Declare the web elements as protected variables
protected WebDriver driver;
protected By header = By.tagName("h1");
protected By footer = By.tagName("footer");

// Create a constructor that takes a WebDriver instance
public BasePage(WebDriver driver) {
this.driver = driver;
}

// Provide public methods to get the page header and footer
public String getHeader() {
return driver.findElement(header).getText();
}

public String getFooter() {
return driver.findElement(footer).getText();
}
}

public class LoginPage extends BasePage {
// Declare the web elements as private variables
private By usernameField = By.id("username");
private By passwordField = By.id("password");
privateBy submitButton = By.id("submit");

// Create a constructor that takes a WebDriver instance
public LoginPage(WebDriver driver) {
super(driver);
}

// Provide public methods to set and get the username and password values
public void setUsername(String username) {
driver.findElement(usernameField).sendKeys(username);
}

public String getUsername() {
return driver.findElement(usernameField).getAttribute("value");
}

public void setPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
}

public String getPassword() {
return driver.findElement(passwordField).getAttribute("value");
}

// Provide a public method to click on the submit button
public void clickSubmit() {
driver.findElement(submitButton).click();
}
}

//Now, we can use this page object class in our test script as follows:

public class LoginTest {
// Create a WebDriver instance
WebDriver driver = new ChromeDriver();

// Create a LoginPage instance
LoginPage loginPage = new LoginPage(driver);

// Navigate to the login page
driver.get("<https://example.com/login>");

// Set the username and password values using the page object methods
loginPage.setUsername("testuser");
loginPage.setPassword("testpass");

// Click on the submit button using the page object method
loginPage.clickSubmit();

// Verify the login result using assertions
String expectedUrl = "<https://example.com/home>";
String actualUrl = driver.getCurrentUrl();
assertEquals(expectedUrl, actualUrl, "User is not redirected to home page after login");

String expectedMessage = "Welcome, testuser!";
StringactualMessage = driver.findElement(By.id("welcome-message")).getText();
assertTrue(actualMessage.contains(expectedMessage), "Welcome message is not displayed after login");
}

Benefits of Inheritance

1. **Code reuse:** Inheritance can help to reduce code duplication.
2. **Simplicity:** Inheritance can simplify code by inheriting common properties and methods.
3. **Maintainability:** Inheritance can make code more maintainable by reducing redundancy.
4. **Flexibility:** Inheritance can make code more flexible by allowing for customization in subclasses.

Polymorphism

Polymorphism is a concept in object-oriented programming that allows objects of different classes to be treated as if they are the same type. Polymorphism can be achieved in two ways: method overloading and method overriding.

Method Overloading (Compile Time Polymorphism)

Method overloading is a form of polymorphism where a class has multiple methods with the same name, but different parameters. This allows the same method name to be used for different operations.

In Selenium, method overloading can be used to provide different options for finding web elements. For example, the By class in Selenium provides several overloaded methods for finding web elements by different locators such as ID, name, class name, and CSS selector.

Here is an example of method overloading in Java:

public class SearchPage {
    private WebDriver driver;

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

    // Overloaded method to search by text
    public void search(String text) {
        WebElement searchBox = driver.findElement(By.id("search-box"));
        searchBox.sendKeys(text);
        searchBox.sendKeys(Keys.ENTER);
    }

    // Overloaded method to search by category
    public void search(String category, String text) {
        WebElement categoryDropdown = driver.findElement(By.id("category-dropdown"));
        categoryDropdown.click();
        WebElement categoryOption = categoryDropdown.findElement(By.xpath("//option[text()='" + category + "']"));
        categoryOption.click();
        WebElement searchBox = driver.findElement(By.id("search-box"));
        searchBox.sendKeys(text);
        searchBox.sendKeys(Keys.ENTER);
    }
}

In this example, the SearchPage class has two methods with the same name "search", but with different parameters. The first method takes a single String parameter for the search text, while the second method takes two String parameters for the category and search text. This allows the same method name to be used for different search operations, making the code more flexible and reusable.

Method Overriding (Run Time Polymorphism)

Method overriding is a form of polymorphism where a subclass provides a new implementation for a method that is already defined in its superclass. This allows the subclass to customize the behavior of the inherited method.

In Selenium, method overriding can be used to customize the behavior of page object methods in subclasses. For example, if a page object class inherits a method for clicking a web element from its superclass, the subclass can override the method to add additional steps before or after the click.

For example, suppose we have a base page object class called BasePage that contains a method for clicking a web element. We can create a subclass called LoginPage that overrides the click method to add a wait for the web element to be clickable.

public class BasePage {
// Declare the web elements as protected variables
protected WebDriver driver;

// Create a constructor that takes a WebDriver instance
public BasePage(WebDriver driver) {
this.driver = driver;
}

// Provide a public method to click on a web element
public void click(By locator) {
driver.findElement(locator).click();
}
}

public class LoginPage extends BasePage {
// Declare the web elements as private variables
private By submitButton = By.id("submit");

// Create a constructor that takes a WebDriver instance
public LoginPage(WebDriver driver) {
super(driver);
}

// Override the click method to add a wait for the web element to be clickable
@Override
public void click(By locator) {
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(locator));
element.click();
}
}

//Now, we can use this page object class in our test script as follows:

public class LoginTest {
// Create a WebDriver instance
WebDriver driver = new ChromeDriver();

// Create a LoginPage instance
LoginPage loginPage = new LoginPage(driver);

// Navigate to the login page
driver.get("<https://example.com/login>");

// Click on the submit button using the overridden click method in the LoginPage class
loginPage.click(submitButton);

// Verify the login result using assertions
String expectedUrl = "<https://example.com/home>";
String actualUrl = driver.getCurrentUrl();
assertEquals(expectedUrl, actualUrl, "User is not redirected tohome page after login");

String expectedMessage = "Welcome, testuser!";
String actualMessage = driver.findElement(By.id("welcome-message")).getText();
assertTrue(actualMessage.contains(expectedMessage), "Welcome message is not displayed after login");
}

Benefits of Polymorphism in Selenium

1. **Code reuse:** Polymorphism can help to reuse code by allowing the same method name to be used for different operations.
2. **Customization:** Polymorphism can make code more flexible by allowing for customization in subclasses.
3. **Simplicity:** Polymorphism can simplify code by allowing objects of different classes to be treated as if they are the same type.
4. **Maintainability:** Polymorphism can make code more maintainable by reducing redundancy.

Abstraction

Abstract classes and abstract methods are fundamental concepts in object-oriented programming that allow us to create classes and methods with no implementation. Abstract classes can be used as base classes or blueprints for other classes, while abstract methods can be used as placeholders for methods that will be implemented by subclasses.

Abstract Classes

An abstract class is a class that cannot be instantiated and can contain both abstract and non-abstract methods. Abstract classes can be used as base classes for other classes and can provide a common interface for all subclasses.

In Selenium, abstract classes can be used to create base page object classes that define common methods and variables for all web pages in our application. This can help to reduce code duplication and make the test scripts more maintainable.

For example, suppose we have a base page object class called BasePage that contains common methods and variables for all web pages in our application. We can make this class abstract to prevent it from being instantiated directly and to allow it to be used as a blueprint for other page object classes.

public abstract class BasePage {
// Declare the web elements as protected variables
protected WebDriver driver;
protected By header = By.tagName("h1");
protected By footer = By.tagName("footer");

// Create a constructor that takes a WebDriver instance
public BasePage(WebDriver driver) {
this.driver = driver;
}

// Provide public methods to get the page header and footer
public String getHeader() {
return driver.findElement(header).getText();
}

public String getFooter() {
return driver.findElement(footer).getText();
}

// Declare an abstract method to get the page title
public abstract String getPageTitle();
}

In this example, the BasePage class is declared as abstract with the "abstract" keyword, and contains an abstract method called "getPageTitle". This method is declared without implementation and is meant to be implemented by subclasses of BasePage.

Abstract Methods

An abstract method is a method that is declared without an implementation in an abstract class or interface. Abstract methods are used as placeholders for methods that will be implemented by subclasses and can provide a common interface for all subclasses.

In Selenium, abstract methods can be used as placeholders for page object methods that will be implemented by subclasses. This can help to provide a common interface for all page object classes and make the test scripts more maintainable.

For example, suppose we have a page object class called LoginPage that inherits from the abstract BasePage class. We can implement the abstract "getPageTitle" method in LoginPage to provide a custom implementation for getting the page title.

public class LoginPage extends BasePage {
// Declare the web elements as private variables
private By usernameField = By.id("username");
private By passwordField = By.id("password");
private By submitButton = By.id("submit");

// Create a constructor that takes a WebDriver instance
public LoginPage(WebDriver driver) {
super(driver);
}

// Provide public methods to set and get the username and password values
public void setUsername(String username) {
driver.findElement(usernameField).sendKeys(username);
}

public String getUsername() {
return driver.findElement(usernameField).getAttribute("value");
}

public void setPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
}

public String getPassword() {
return driver.findElement(passwordField).getAttribute("value");
}

// Provide a public method to click on the submit button
public void clickSubmit() {
driver.findElement(submitButton).click();
}

// Implement the abstract getPageTitle method
@Override
public String getPageTitle() {
return driver.getTitle();
}
}

In this example, the LoginPage class inherits from the abstract BasePage class and provides a custom implementation for the "getPageTitle" method using the "getTitle" method of the WebDriver interface.

Benefits of Abstract Classes and Abstract Methods

  1. Code reuse: Abstract classes and abstract methods can help to reduce code duplication by providing a common interface for all subclasses.

  2. Flexibility: Abstract classes and abstract methods can make code more flexible by allowing for customization in subclasses.

  3. Simplicity: Abstract classes and abstract methods can simplify code by providing a clear and consistent interface for all subclasses.

  4. Maintainability: Abstract classes and abstract methods can make code more maintainable by reducing redundancy and providing a common interface for all subclasses.

Interfaces

An interface is a collection of abstract methods and constants that can be used to define common behavior for classes. Interfaces can be implemented by classes, which must provide an implementation for all the methods defined in the interface.

In Selenium, interfaces can be used to define common behavior for page object classes. By defining an interface for page object classes, we can ensure that all page object classes provide the same set of methods.

For example, suppose we have an interface called Page that defines a common set of methods for all page object classes in our application.

public interface Page {
// Declare abstract methods to get the page header, footer, and title
public String getHeader();
public String getFooter();
public String getPageTitle();
}

In this example, the Page interface defines three abstract methods to get the page header, footer, and title. Any class that implements the Page interface must provide an implementation for all three methods.

Implementing an Interface

To implement an interface in Java, a class must use the "implements" keyword followed by the name of the interface. The class must provide an implementation for all the methods defined in the interface.

In Selenium, page object classes can implement the Page interface to ensure that they provide the same set of methods. By implementing the Page interface, we can ensure that all page object classes have a consistent interface, making the test scripts more maintainable.

For example, suppose we have a page object class called LoginPage that implements the Page interface. We can provide an implementation for all three methods defined in the Page interface to get the page header, footer, and title.

public class LoginPage implements Page {
// Declare the web elements as private variables
private WebDriver driver;
private By usernameField = By.id("username");
private By passwordField = By.id("password");
private By submitButton = By.id("submit");
private By header = By.tagName("h1");
private By footer = By.tagName("footer");

// Create a constructor that takes a WebDriver instance
public LoginPage(WebDriver driver) {
this.driver = driver;
}

// Provide public methods to set and get the username and password values
public void setUsername(String username) {
driver.findElement(usernameField).sendKeys(username);
}

public String getUsername() {
return driver.findElement(usernameField).getAttribute("value");
}

public void setPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
}

public String getPassword() {
return driver.findElement(passwordField).getAttribute("value");
}

// Provide a public method to click on the submit button
public void clickSubmit() {
driver.findElement(submitButton).click();
}

// Implement the Page interface methods to get the page header, footer, and title
@Override
public String getHeader() {
return driver.findElement(header).getText();
}

@Override
public String getFooter() {
return driver.findElement(footer).getText();
}

@Override
public String getPageTitle() {
return driver.getTitle();
}

Benefits of Interfaces

  • Code reuse: Interfaces can help to reduce code duplication by providing a common behavior for classes.

  • Flexibility: Interfaces can make code more flexible by allowing for customization in classes that implement the interface.

  • Simplicity: Interfaces can simplify code by providing a clear and consistent interface for all classes that implement the interface.

  • Maintainability: Interfaces can make code more maintainable by reducing redundancy and providing a common behavior for all classes that implement the interface.

Last updated