If you are engaged in testing web interfaces, then you probably thought about how to make interaction with web pages in tests as convenient as possible. Among testers, the Page Object design pattern is very widely known. But, despite the many advantages, this approach has some drawbacks that make it very difficult to apply.
The most significant of them are:
- the impossibility of reusing code page-objects for pages with the same elements;
- poor readability and lack of clarity of the code for pages with a large number of elements;
- no typing of elements.
From this post you will learn how we in Yandex solve these problems using the open-source framework HTML Elements. It extends the concept of the Page Object template and allows you to make interaction with elements on web pages simple, flexible and convenient.
We will not dwell on the description of the pattern itself and its principles, since it is most certainly familiar to most of you. If someone has not met him, then you can learn about him from this
post or
master class . Also, speaking of the use of the Page Object pattern, we will imply its
Java implementation in the Selenium WebDriver framework .
')
Code reuse
Imagine that you needed to write tests not on any separate page, but on the whole web service. On its pages, certain blocks of elements will probably be found: heders, footers, perhaps some similar forms, etc. For example, on the main page of Yandex there is a search form, which is preserved when you go to the page with the search results.


It is also found on other Yandex services: for example, on Yandex.Avto, Yandex.Market and Yandex.Work.
The authorization form can also be seen not only on the main page, but also, for example, on the Yandex.Passport page or on Yandex.Market. The logic of interaction with common blocks on every page is absolutely the same. But when you need to write page-objects for these pages, you will be forced in each of them to duplicate the code that implements the interaction with these blocks.

You, probably, already understood what I am getting at? Yes, it would be great to be able to describe the blocks of elements and the logic of interaction with them separately, and collect page-objects from them already. And the HTML Elements framework allows you to do this. For example, let's describe a search form with it:
@Block(@FindBy(className = "b-head-search")) public class SearchArrow extends HtmlElement { @FindBy(name = "text") private WebElement requestInput; @FindBy(xpath = "//input[@type='submit']") private WebElement searchButton; public void search(String request) { requestInput.sendKeys(request); searchButton.click(); } }
As well as the authorization form:
@Block(@FindBy(className = "b-domik__form")) public class AuthorizationForm extends HtmlElement { @FindBy(id = "b-domik-username") WebElement loginField; @FindBy(id = "b-domik-password") WebElement passwordField; @FindBy(xpath = "//input[@type='submit']") WebElement submitButton; public void login(String login, String password) { loginField.sendKeys(login); passwordField.sendKeys(password); submitButton.click(); } }
Then the page-object for the main page of Yandex will look like this:
public class SearchPage { private SearchArrow searchArrow; private AuthorizationForm authorizationForm; // Other blocks and elements here public SearchPage(WebDriver driver) { HtmlElementLoader.populatePageObject(this, driver); } public void search(String request) { searchArrow.search(request); } public void login(String login, String password) { authorizationForm.login(login, password); } // Other methods here }
By the way, did you notice that the selectors of the block elements are set relative to the selector of the block itself? This is very convenient, since the block can be on different pages for different selectors. In this case, the internal structure of the block will not change. In this case, when the block is turned on in the page-object, it is enough to overload the selector of the block itself. For example, on the Yandex.Auto service pages, the search form should be searched differently than on the main page:
public class AutoHomePage { @FindBy(className = "b-search") private SearchArrow searchArrow; // Other blocks and elements here public AutoHomePage(WebDriver driver) { HtmlElementLoader.populatePageObject(this, driver); } public void search(String request) { searchArrow.search(request); } // Other methods here }
Readability and visibility
To fully cover a particular web service page with tests, you will need to use all of its elements. And they can be very much. For example, on the main page of Yandex. Auto there is a form of car search by parameters. There are more than 30 items on it, taking into account the advanced search, as well as a list of car brands, news block, car news block, etc.

If we write the page-object for this page, using only the capabilities of the Selenium WebDriver framework, we get a very large class with a long canvas of elements and a huge number of methods that implement interaction with all these elements. Agree, such a class will be very beloved and poorly readable.
But if you have the ability to separately create blocks of elements, then this problem is also solved. Page-object will contain only a few blocks, and their structure and logic of interaction with them will be described separately.
Item typing
In Selenium WebDriver, all page elements — be it a button, a checkbox, or a text entry field — are described using the WebElement interface. Therefore, it has many methods that are peculiar to elements of different types. But if we, for example, interact with a button, then we would hardly want to insert text there.
On the other hand, pages often contain complex elements, interaction with which cannot be described using only WebElement'a. Say a group of radio-buttons, a drop-down list or a date picker.
In both cases, the same solution suggests itself: to introduce typed elements, which in the first case will narrow the WebElement interface, and in the second, implement interaction with more complex elements. This is what we did in the HTML Elements framework.
For example, the description of the search form using typed elements will look like this:
@Block(@FindBy(className = "b-head-search")) public class SearchArrow extends HtmlElement { @FindBy(name = "text") private TextInput requestInput; @FindBy(xpath = "//input[@type='submit']") private Button searchButton; public void search(String request) { requestInput.sendKeys(request); searchButton.click(); } }
And this will look like the description of the form for selecting a language in the search settings, where there is a drop-down list:

@Block(@FindBy(id = "lang")) public class LanguageSelectionForm extends HtmlElement { @FindBy(className = "b-form__select") private Select listOfLanguages; @FindBy(xpath = "//input[@type='submit']") private Button saveButton; @FindBy(xpath = "//input[@type='button']") private Button returnButton; public void selectLanguage(String language) { listOfLanguages.selectByValue(language); saveButton.click(); } }
We have already implemented support for such basic elements as TextInput, Button, CheckBox, Select, Radio and Link. You, too, can very simply write your own typed elements and extend existing ones.
***
The HTML Elements framework is a tool that allows you to build page-objects as a constructor. From typed elements, you can assemble the blocks you need, which can be combined, combined with each other, and collected from them page-objects. This greatly increases the degree of reuse of the code, making it more readable and descriptive, and writing tests to be easier. HTML Elements is available on open source. You can try it in and see the code on
GitHub .
In one of the following posts about testing in Yandex, we will tell you more about the framework itself. You will learn what other useful features it has and how it is convenient to use it to test web interfaces.