
Tips for Healthy Page Object Classes
It’s the beginning of a new year and people all over the world have made resolutions to become healthier. That’s wonderful! In fact, your frontend tests want in on the action as well.
The most popular design pattern used in web UI test codebases is the Page Object Model (POM) design pattern. This pattern suggests modeling a class to represent a single page of your system under test. Using this model, the class would contain properties that represent the elements of the UI page and methods that interact with these elements.
Given this is the Login Page of our application, let’s discuss tips for building a class using POM.
The Class
We make a single class in our automation framework to represent this page. It’s recommended to ensure the name of the class is representative of the page in the application so that other developers can quickly find the class associated with a given UI page.
So, in this example, we’d make a class called LoginPage
.
1 2 3 4 5 6 7 8 |
public class LoginPage { private WebDriver driver; public LoginPage(WebDriver driver){ this.driver = driver; } } |
Properties
LoginPage should contain properties for each of the elements on the page that will be used within your tests. The value of these properties are locators to the elements on the actual page. By defining these locators in one place, you won’t need to hardcode them in every place that you use them – thus, eliminating duplication.
1 2 3 4 5 6 7 8 |
private By usernameField = By.id("username"); private By passwordField = By.id("password"); private By signInButton = By.id("log-in"); private By rememberMeCheckbox = By.id("rememberMe"); private By twitterIcon = By.id("twitterIcon"); private By facebookIcon = By.id("fbIcon"); private By linkedInIcon = By.id("linkedInIcon"); private By errorMessage = By.id("alert"); |
Methods
In addition to the properties, the class should also contain methods that enable a test to interact with the application, such as setting input fields and clicking buttons. Here are some tips on designing these methods to be optimally used by your tests.
Add Getter and Setters
The purpose of POM classes are to set and get the state of your application. So, you’ll need methods to do this.
For example, we definitely need to set the username and password fields, so set methods should be added.
1 2 3 4 5 6 7 |
public void setUsername(String username) { driver.findElement(usernameField).sendKeys(username); } public void setPassword(String password) { driver.findElement(passwordField).sendKeys(password); } |
We also need to know the content of the error message, so this will need a getter method.
1 2 3 |
public String getErrorMessage() { return driver.findElement(errorMessage).getText(); } |
Only Add What’s Currently Needed
Notice, we didn’t add getter and setter methods for every field, because we don’t have a need for them in our tests. Only add what’s currently needed. You can always add more if a new test requires such. This will prevent you from needing to maintain unused code.
Transitions Should Return New Objects
You’ll also need methods for clicking links and buttons. However, when your click results in a page change, your method should return a handle to the class representing the UI page you’ve transitioned to.
For example, if clicking the sign-in button will lead to the home page of your application, then your method should return a handle to the class representing that home page.
1 2 3 4 |
public HomePage clickSignInButton() { driver.findElement(signInButton).click(); return new HomePage(); } |
By providing this, all calling tests know that a transition will occur by calling this method, and the test has a handle to the necessary object to continue interacting with the application.
Don’t Be Afraid to Make Convenience Methods
Right now, logging into the application would take at least three method calls (setUsername, setPassword, and clickSignInButton). As these three are commonly used together, it makes sense for your class to also provide a convenience method so that your tests only need to call one method.
1 2 3 4 5 |
public HomePage login(String username, String password) { setUsername(username); setPassword(password); return clickSignInButton(); } |
Notice we did not implement the logic within this method, but instead called the individual methods. It’s important to have those separate methods available, so that negative tests (e.g. set username but not password) can utilize them.
Be Mindful of State
For checkbox elements (or other toggles), simply clicking them may not satisfy the desired intent of the test. For example, what if your test wants the box to be checked, but it’s already checked? Calling a click method would give you the opposite of what you want. Instead, make your method more sophisticated by implementing logic that will only check the box if it’s needed to give the user their desired result. To do this, instead of blindly clicking, accept an argument that specifies intent (i.e. do they want it selected or deselected) and act accordingly.
1 2 3 4 5 6 7 8 9 10 11 12 |
public boolean isRememberUsernameChecked() { return driver.findElement(rememberMeCheckbox).isSelected(); } public void setRememberUsername(boolean checkIt) { boolean alreadySelected = isRememberUsernameChecked(); //only click if the test's intent does not match the current state if( (checkIt && !alreadySelected) || (!checkIt && alreadySelected) ) { driver.findElement(rememberMeCheckbox).click(); } } |
Keep POM Classes Neutral
Your POM class should be a neutral entity that interacts with and gathers information about your application. Be careful to keep your methods neutral and ensure they are generic enough to be used by any test that wishes to interact with the page. This basically means do not include assertions within these methods.
By keeping the methods neutral, they can be used in both positive and negative tests. The test can use the information returned from the class for its own purposes in determining whether that information warrants a pass or fail.
For example, what if our method that gets the error message also fails the test if the message is visible?
1 2 3 4 5 6 7 8 |
//This is bad. don't copy this public String getErrorMessage() { String message = driver.findElement(errorMessage).getText(); if(message.isDisplayed()){ assert.fail(message); } return message; } |
I cannot reuse this method for tests where I want to make sure an error message is shown when the username or password is incorrect or not provided. This is because the POM class has taken it upon itself to determine that the display of an error message is a failure; when in these cases, that’s not so. Instead, just return the state and allow the test to determine what that means.
1 2 3 |
public String getErrorMessage() { return driver.findElement(errorMessage).getText(); } |
Healthy Code Makes Healthy Tests
These tips should help you design healthy classes that implement the Page Object Model design pattern. The use of this pattern promotes separation of concerns (e.g. tests vs managing state), reduces code duplication, and provides reusable methods. Happy testing!
Tom
Angie Jones
If you instantiate at declaration, wouldn’t it look for all those elements at that time? Sometimes those elements are not there yet, as they can be dynamic based on other interactions on the page.
Cesar M
This is a great article and very eye-opening. I have been doing it wrong all this time!! Thank you, Angie.
John
This is great! I’m just starting and will be good to know.
Roman
Angie Jones
The creator of WebDriver, Simon Stewart, has recommended against using PageFactory.
https://twitter.com/shs96c/status/880809924607057920
ali
Hi angie,
ali
Mateusz
What about returning “this” from page objects methods insted of returning void? By this you gain the possibility to chain acctions.
Ryla Burges
What if I wanna store methods in separate action class? how to instantiate variables in that case so that it won’t throw null pointer exception?
Dihfahsih
Great stuffs from Angie
Zeeshan Ali
is there any method used as POM for sorting array. which return sorted array when called this method in Testng framework
Dinesh
Great article Angie!
Raja
Great article!
Angie Jones
That’s a great question, because clickSignInButton() is assuming success. You don’t *have* to use the object that is returned. If you’re calling clickSignInButton, you’re doing so with a LoginPage object already. So in your test, just continue using that same object, since the app is still on that same page.
Jorge Rizado
Techie
I have same query… Did you get the answer for this.. If yes please do share… TIA
Owyn Dwight
Hi Angie,
Love the posts! Quick question, would you ever declare the By properties as static? And why/why not?
Thanks in advance!
Seyi T
I’m new to this and found this information insightful. Thanks.
yasmina
i would like to know why you make the element locator have private access ?>