FacebookLinkedInShare

Representations of business logics is the earliest challenge of any front end testing project. Testing infrastructures need to represent business logic from an end user perspective to faithfully mirror user stories. The Page Object design pattern is the most commonly used for testing web and app pages. It is used to embody the business functionality available to the user on a page.  While Page Object is sufficient for testing simple apps or websites, some issues arise with complexity and scale.

Let’s consider the web pages below.

 

Above is a category search result, below is a keyword search result.

 

It’s easy to see both pages share many common elements – The top action bar, search filter widget, the sorting menu, and even the search results themselves. It should be easy to implement an abstract search result page object and have the concrete classes representing both pages inherit it. But what happens if we have a third page that isn’t a search result page but uses the same sort menu? What happens if locating the same search filter in each page requires a different locating strategy?

Now let’s look at controllers. Both pages contain buttons, dropdown menus, text labels, images, etc. However, selenium find methods always result in WebElements. Now, say we’re holding WebElement members in our abstract page object to represent all the search filters from the filter widget. Applying a filter requires clicking the dropdown menu, searching the labels for our desired filter, and clicking it. Since our filters are represented by generic WebElements we’ll have to explicitly repeat this same business logic. Bottom line, neither page objects nor selenium provide a normalized solution for handling controllers.

Enter yandex’s HTML Elements. HTML Elements is a concept that expands and normalizes the use of the Page Object design pattern. It retains the role of the Page Object as the tester’s access point to business functionality, but under the hood, removes all logic implementation from the Page Object class to be handled instead by smaller business units called HTMLElements. HTMLElements define business units from search arrows to tables or table rows/columns to widgets. HTMLElements are representations of page sections wrapping a web element. They can be easily shared between different pages holding the same components without the use of inheritance.

Let’s create a simple implementation of the Filter Widget from the examples above:

@FindBy(css = ".search-filter")
public class SearchFilterWidget extends HTMLElement

    //TextBlock typified element, used for labels, static texts, etc.
@FindBy(css = ".h4")
private TextBlock title;

    //TextInput typified element used for text boxes etc.
@FindBy(name = "searchTerm")
private TextInput keywordsInput;

    //Select typified element used for dropdown menus, auto complete etc.
@FindBy(name = "price")
private Select priceRange;

@FindBy(name = "availability")
private Select availability;

@FindBy(name = "language")
private Select language;

@FindBy(name = "format")
private Select format;

    //Button typified element used for very simple buttons
@FindBy(css = ".btm")
private Button refineResults;

public void filterSearch(CharSequence keywords, String price, String availability, String language, String format) {
keywordsInput.sendKeys(keywords);
priceRange.selectByValue(price);
this.availability.selectByValue(availability);
this.language.selectByValue(language);
this.format.selectByValue(format);
refineResults.click();
}
}

Take a look at the members of SearchFilterWidget. HTMLElements normalizes controller handling by introducing Typified Elements. Typified Elements are, quite literally, web elements with a type. As we mentioned before, selenium only provides a generic web element for operating elements on a page, so the same object type will represent a text block, a button, a textbox, and a table. WebElement is a generic interface that exposes logic that isn’t necessarily relevant to each element instance. This is adverse to object oriented principles, as it breaks encapsulation. If we get back to SearchFilterWidget, imagine all fields were standard WebElements. We’d have to implement the selection for each dropdown menu individually. We’d also have no indication whether sending keys to keywordsInput is a valid business operation, or we could use a click operation on title. As you can see, managing generic web elements is error prone and messy. Typified elements solve this issue by wrapping generic selenium web elements with object classes representing the controller type of the element. This solves encapsulation by only allowing relevant operations to be performed on an element in the context of its type. It is also flexible enough to allow inheritance and polymorphism for element types that share behaviors.

 

How it works:

Both HTMLElements and TypifiedElements are wrappers for selenium web elements, for different purposes. TypifiedElements access their wrapped elements to perform controller actions. HTMLElements  serve as containers forHTMLElements or TypifiedElements. Their wrapped elements are used as search contexts to their member. This means that members of an HTMLElement are only looked up inside it’s wrapped element, not in the entire page. Essentially, accessing a member of a HTMLElement results in two searches: First the HTMLElement will be looked up, then the member will be looked up inside the HTMLElement. HTMLElements can be members of other HTMLElements, providing a chain of context lookups, which results an abstraction of the element hierarchy on the page. This provides an added bonus of simplifying element search. Because elements are only searched in the context provided by the wrapped element, there’s no need to worry about searches returning all relevant results from the page.

Going back to our website, SearchFilterWidget will be an HTMLElement member of both page types, applying the same business logic. The only prerequisite is to provide the member with a correct locator in case the default one does not apply. This locator will also be the search context for all of SearchFilterWidget members. Inside the widget, dropdown menus are represented by the Select class, the button by a Button class, and the textbox by a TextInput class. This allows us to apply the same business logic to all dropdown menus without code duplication, and prevents us from applying said logic to buttons.

Search contexts are provided by a @FindBy annotation, which is part of the selenium framework. Yandex improved on it by providing the search context mechanism. The annotation can be provided for a HTMLElement class object, in which case every instance of the HTMLElement will be located using the same search context, or for individual members of a Page Object or HTMLElement. Member annotations override class annotations, and so can be used for special cases.

HTMLElements are created recursively, so it’s enough to declare them as members of a PageObject or another HTMLElement and you’re good to go.

Disadvantages:

Because HTMLElements are meant to represent narrower business units than Page Objects, they are created only in the search context provided by their annotations. This means that HTMLElements do not have access to Selenium web drivers. As a result, performing browser related operations is impossible within an HTMLElement. This is mostly a problem if the element contains an iFrame and you’d like to switch the browser context to said iFrame. These kind of actions are only available through the web driver.

We solved this issue by refactoring the Yandex code and providing each element with a driver reference alongside its search context. This maintains the original design while at the same time provides HTMLElements all the freedom only allowed for Page Object originally. This solution can cause trouble if not used carefully, but a coder would have to be extremely uninformed to use this feature in a destructive manner.

Other than that, HTMLElements create greater complexity for non-deterministic business actions.

Consider a widget appearing in two different pages. This widget has a button that performs a logic action based another element on the page, for example, the page title, or another text box on the page. Since the HTMLElement instance is decoupled of the page, it does not have enough information to determine what the result of the business action should be. This extra information would have to be provided to the HTMLElement when performing such an action.

If this action should result in a navigation, this also means that it would have to return a generic type instead of a concrete type. This complication may result in harder refactors, and make it more difficult to extend the use of such features. One must keep in mind, though, that non-deterministic business actions are a testing annoyance to begin with, and while they may be slightly more difficult to implement when using HTMLElements, the benefit of easy code reuse and the normalization of controller handling far outweighs this disadvantage.

 

In conclusion, HTMLElements break down representation of pages to smaller business units. This allows us to share business units between pages without the use of inheritance, using independent locating strategies for each instance of the business unit when required. TypifiedElements annihilate the inherent design flaw in selenium WebElements by introducing encapsulation to element instances, eliminating the ability to utilize WebElements wrongly. The loss of page context within the business units was handled by introducing WebDrivers into HTMLElements and TypifiedElements, granting them all the privileges previously owned exclusively by page objects. HTMLElements and TypifiedElements add consistency and prevent code duplication, allowing for smoother and more stable development of the testing infrastructure.

 

Yandex HTMLElements: http://htmlelements.qatools.ru/

GIT repo: https://github.com/yandex-qatools/htmlelements