How to use Page Objects pattern in the project.
In my recent article, I reviewed the main automation patterns that can be applied in your work if necessary.
Today I will start a series of articles devoted to the consideration of individual patterns. The first and most famous pattern in test automation is the Page Object or Page Object Model. This pattern is representative of the structural patterns group.
The main goal of structural patterns is to structure test code to simplify maintenance, avoid duplication, and increase clarity. By doing that we will make it easier for other test engineers, not familiar with other codebases, to understand and to start working with the tests right away.
Page Object is one of the most useful and used architectural solutions in automation. This design pattern helps to encapsulate the work with the individual elements of the page that allows you to reduce the amount of code and simplify its support. If, for example, the design of one of the pages is changed, we will only need to rewrite the corresponding class that describes this page.
When you write tests against a web page, you need to refer to elements within that web page in order to click links and determine what’s displayed. However, if you write tests that manipulate the HTML elements directly your tests will be brittle to changes in the UI. A page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML.
There are 3 problems that the Page Object pattern helps solve:
- First problem. There is a logical structure of the application when creating a test in the code, we do not understand exactly where we are now.
When creating, we don’t see the UI directly with our test. Where are we after step 15? On which page?
What actions can I do there? Can I, for example, call the login method again after step 15? - Second problem. I want to separate the technical details (in this case, speaking about the web, these are elements in the browser, elements that perform certain actions), spread them out, and remove them from the logic of my tests, so that the test logic remains clean and transparent.
- Third problem. I want to reuse the code that I put in the pages later. Because if a lot of scripts go through the same pages, I will constantly re-use my code.
The Page Object pattern approach suggests creating one java class per application page (web page) and separate test class for each web page.
For this article, I will use the demo website http://automationpractice.com/ Navigate to this website and try to get familiar with the basic functionality. The first page displayed on navigation is the landing page. Searching for any product should display the product search page. Similarly, clicking on the Sign In button should display the Sign In page. Try to imagine the implementation of these pages in your head using the Page object pattern.
I divided it into several pages:
LandingPage.class
/**
* The class Landing page.
*/
public class LandingPage {
/**
* Default constructor.
*/
public LandingPage() {
super();
//empty
return;
}
/**
* Click login button authorization page.
*
* @return the authorization page
*/
public AuthorizationPage openAuthenticationPage() {
$(".login").click();
return new AuthorizationPage();
}
}
AuthorizationPage.class
/**
* The class Authorization page.
*/
public class AuthorizationPage {
/**
* The private Selenide Elements.
*/
private SelenideElement
createEmail = $("input[id='email_create']"),
email = $("input[id='email']"),
password = $("input[id='passwd']"),
createAccount = $("button[id='SubmitCreate']"),
submitLogin = $("button[id='SubmitLogin']");
/**
* Instantiates a new Authorization page.
*/
public AuthorizationPage() {
super();
//empty
return;
}
/**
* Generate random email.
*
* @return the authorization page
*/
@NotNull
protected static String createEmailForNewUser() {
final String todayData = String.valueOf(new Date().getTime());
return "hf_test" + todayData + "@qa.team";
}
/**
* Add email for user authorization page.
*
* @return the authorization page
*/
public AuthorizationPage addEmailForUser() {
createEmail.setValue(createEmailForNewUser());
return this;
}
/**
* Enter user password authorization page.
*
* @param userPassword the user password.
* @return the authorization page
*/
public AuthorizationPage enterUserPassword(final String userPassword) {
password.setValue(userPassword);
return this;
}
/**
* Create account for new user.
*
* @return the authorization page
*/
public CreateAccountPage clickCreateAccountButton() {
createAccount.click();
return new CreateAccountPage();
}
/**
* Enter user email authorization page.
*
* @param userEmail the user email
* @return the authorization page
*/
public AuthorizationPage enterUserEmail(final String userEmail) {
email.setValue(userEmail);
return this;
}
/**
* Click sign in button account page.
*
* @return the account page
*/
public AccountPage clickSignInButton() {
submitLogin.click();
return new AccountPage();
}
ShoppingCartPage.class
/**
* The class Shopping cart page.
*/
public class ShoppingCartPage {
/**
* Constant DELAY.
*/
private static final int DELAY = 5000;
/**
* Private selenide elements.
*/
private final SelenideElement
proceedToCheckout = $("div[id='center_column'] a[title='Proceed to checkout']");
/**
* The constructor.
*/
public ShoppingCartPage() {
super();
//empty
return;
}
/**
* Proceed to order shopping cart page.
*
* @return the shopping cart page
*/
public ShoppingCartPage proceedToOrder() {
proceedToCheckout.waitUntil(Condition.enabled, DELAY).click();
$("button[name='processAddress']").waitUntil(Condition.enabled, DELAY).click();
$("div[id='uniform-cgv']").waitUntil(Condition.enabled, DELAY).click();
$("button[name='processCarrier']").waitUntil(Condition.enabled, DELAY).click();
return this;
}
/**
* Pay order order confirmation page.
*
* @return the order confirmation page
*/
public OrderConfirmationPage payOrder() {
$("div[id='HOOK_PAYMENT'] [title='Pay by bank wire']").waitUntil(Condition.enabled, DELAY).click();
return new OrderConfirmationPage();
}
}
OrderConfirmationPage.class
/**
* Class Order confirmation page.
*/
public class OrderConfirmationPage {
/**
* Constant DELAY.
*/
private static final int DELAY = 5000;
/**
* Confirmation order order confirmation page.
*
* @return the order confirmation page
*/
public OrderConfirmationPage confirmationOrder() {
$("div[id='center_column'] button[type='submit']").waitUntil(Condition.enabled, DELAY).click();
return this;
}
/**
* The constructor.
*/
public OrderConfirmationPage() {
super();
//empty
return;
}
/**
* Check information order confirmation page.
*
* @return the order confirmation page
*/
public OrderConfirmationPage checkInformation() {
assertTrue(url().contains("controller=order-confirmation"));
return this;
}
}
Below I will give a conditional example of my implementation. For a more detailed study, you can check out the full source code of automation scripts in Github
Best Practices.
Now when we know what Page Object is and how it can be utilized in our project let’s dive into more advanced techniques and best practices of development with it.
- Test Logic. There’s generally good advice to keep all your test logic (including assertions) away from Page Objects.
- Reusable elements. If several app views contain the same widget or menu it’s always a good move to create separate objects for the common element or extend both page objects from one superclass with extracted common logic.
- Chain Methods. Chain methods are considered the industry standard in designing Page Objects since they allow you to write automated tests in the fashion you write your usual test cases.
- Waits. In the real-world apps usually have dynamic elements and complicated animations. Thus another best practice is to wait until your view is opened in the constructor of your Page Object class.
There are other design patterns that we will also look at in the following articles. Some use the page factory to create instances of their page objects. A discussion of all this is beyond the scope of this article. In this article, I just wanted to introduce concepts to make the reader aware of the Page Object pattern.