Testing in Yandex. HTML Elements framework: what is missing in Page Object and how to fix it

    If you are testing web interfaces, then you probably thought about how to make interacting 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 greatly complicate its application.

    The most significant of them:
    • the inability to reuse the code of page-objects for pages with the same elements;
    • poor readability and lack of visibility of the code for pages with a large number of elements;
    • lack of typification of elements.

    In this post, you will learn how we at Yandex solve these problems using the open-source HTML Elements framework. 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 most of you probably know it well. If someone has not met with him, then you can learn about him from this post or a master class . Also, speaking about the use of the Page Object pattern, we will mean its Java implementation in the Selenium WebDriver framework .

    Code reuse


    Imagine that you needed to write tests not on a separate page, but on the entire web service. On its pages, common blocks of elements will surely be found: headers, footers, possibly some identical forms, etc. For example, on the Yandex main page there is a search form, which is saved when you go to a page with search results.

    image


    image

    It is also found on other Yandex services: for example, Yandex.Auto, 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 the common blocks on each page is exactly the same. But, when you need to write page objects for these pages, you will be forced to duplicate code in each of them that implements interaction with these blocks.

    image

    You probably already understand what I'm getting at? Yes, it would be great to be able to describe blocks of elements and the logic of interaction with them separately, and already assemble page objects from them. And the HTML Elements framework allows you to do this. For example, we will use it to describe the search form:

    @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 Yandex main page 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 block element selectors are set relative to the block selector itself? This is very convenient, since the block can be on different pages by different selectors. In this case, the internal structure of the block will not change. In this case, when you include a block in a page object, it is enough to overload the selector of the block itself. For example, on the pages of the Yandex.Auto service, 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 page of a web service with tests, you will need to use all its elements. And there can be a lot of them. For example, on the main page of Yandex.Avto there is a form for searching for a car by parameters. There are more than 30 elements on it, taking into account the advanced search, as well as a list of car brands, a news block, a block of automotive innovations, etc.

    image

    If we write a page object for this page, using only the capabilities of the Selenium WebDriver framework, we will get a very large class with a long canvas of elements and a huge number of methods that interact with all these elements. Agree, such a class will be very beloved and poorly readable.

    But if you have the opportunity to separately create blocks of elements, then this problem is also solved. A page object will contain only a few blocks, and their structure and the logic of interaction with them will be described separately.

    Typing Elements


    In Selenium WebDriver, all page elements — whether it be a button, checkbox, or text input field — are described using the WebElement interface. Therefore, it has many methods that are characteristic of elements of different types. But if, for example, we interact with a button, then we are unlikely to want to drive text there.

    On the other hand, pages often contain complex elements, the interaction with which cannot be described using WebElement alone. Let's say a group of radio buttons, a drop-down list or a date picker.

    In both cases, the same solution arises: introduce typed elements, which in the first case will narrow the WebElement'a interface, and in the second - implement interaction with more complex elements. This is what we did in the HTML Elements framework.

    For example, a description of a search form using typed elements would 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 so the description of the form for choosing the language in the search settings, where there is a drop-down list, will look like:

    image
    @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 easily write your own typed elements and extend existing ones.

    ***

    HTML Elements framework is a tool that allows you to assemble page objects as a constructor. From typed elements, you can collect the blocks you need, which can be combined, combined with each other and assemble page objects from them. This significantly increases the degree of code reuse, makes it more readable and intuitive, and writing tests is easier. HTML Elements is available in open source. You can try it out and see the code on GitHub .

    In one of the following posts about testing in Yandex, we will talk more about the framework itself. You will find out what other useful features it has and how it is convenient to test web interfaces with its help.

    Also popular now: