When writing test code, it’s important to exercise the same care that you’d use when developing production code. Here are common ways to use OO principles when implementing test code.
Encapsulation
The Page Object Model design pattern is commonly used in test automation. This pattern prescribes creating a class to interact with each page of the application under test. Within this class are the locator objects for the elements of the web page, and the methods to interact with those elements.
To keep things clean and safe, it’s best to properly encapsulate by restricting access to the locators themselves and only exposing their corresponding methods.
public class SearchPage {
private By searchButton = By.id(“searchButton”);
private By queryField = By.id(“query”);
public void search(String query) {
driver.findElement(queryField).sendKeys(query);
driver.findElement(searchButton).click();
}
}
Example of Page Object Model design pattern. Note: driver
declaration omitted for brevity.
Inheritance
While inheritance should not be abused, it can certainly be useful in test code. For example, given there’s a header and footer component that exists on every page, it’s redundant to create fields and methods for interacting with the elements of these components within every Page Object class. Instead, create a base Page
class containing the common fields and methods that exist on every page, and have your Page Object classes inherit from this class. By using this approach, your test code will have access to anything in the header and footer no matter what Page Object they are currently interacting with.
Another good use case for inheritance within test code is when a given page has various implementations. For example, your app may contain a User Profile page which has different functionality based on roles (e.g. Administrator, Member). While there are differences, there could also be functionality that is the same. Creating two classes and duplicating code in both is not ideal. Instead, create a ProfilePage
class which contains the common elements/interactions, and create subclasses (e.g. AdministratorProfilePage
, MemberProfilePage
) that implement the unique interactions and inherit the common ones.
Polymorphism
Assume we have a convenience method that goes to the User Profile page. This method doesn’t know what type of profile page it is — an Administrator or a Member.
You’re faced with a design decision here. Do you make two methods — one for each of the profile types? This seems like overkill since they both would do the exact same thing but just have a different return type.
Instead, return the super class (ProfilePage
) since both AdministratorProfilePage
and MemberProfilePage
are both subclasses of ProfilePage
. The test method which is calling this convenience method has more context and can cast accordingly.
@Test
public void badge_exists_on_admin_profile(){
var adminProfile =
(AdministratorProfilePage)page.goToProfile("@admin");
//…
}
Abstraction
Abstraction is used sparingly in test code, but there are valid use cases. Consider a type of widget that has been customized for different usages throughout the app. Creating an abstract class that specifies the behaviors expected is helpful when developing classes that interact with specific implementations of that widget.
public abstract class ListWidget { protected abstract List<WebElement> getItems(); int getNumberOfItems() {
return getItems().size();
}
}public class ProductList extends ListWidget { private By productLocator = By.cssSelector(".product-item"); @Override
protected List<WebElement> getItems() {
return driver.findElements(productLocator);
}
}
Angie Jones is the Director of Test Automation University. Check out her courses here.
Test Automation University is sponsored by Applitools.