Web UI tests reinforcement with webtau framework (Selenium based)
One of the biggest problem with Web UI tests is their brittleness. Moving a page element around or replacing an input box with a drop down can cause a ripple effect that breaks dozens of your tests.
In this article I will show you how to encapsulate your UI implementation details so your tests can focus on testing features.
I will be using the webtau framework that has primitives designed to help with tests brittleness.
First test attempt
Let’s test a search feature of an imaginary app.
- open a search page
- set a value to an input box that can be located by
- simulate enter key and check that there are some results
I claim that there should be only one reason to change this test and that is if the actual functionality of your search experience changes (Single Responsibility Principle anyone?).
However there are four non functional reasons that can force our test to change:
- search page url changes
- the way we initiate the search changes: enter key vs mouse click
- ids/classes/attributes associated with elements are changed
- server response time becomes slower
Let’s address them one by one.
Extract elements definition
$(css) creates an instance of PageElement that you use to simulate user actions and query values.
Created instances are lazy and can be defined before the browser is even opened.
This is a step in a right direction, but we still have exposed page url and hardcoded Enter key press.
Extract user actions
Let’s create a
submit method to encapsulate the way users are supposed to initiate the search.
I think the changes we made make the test easier to reason about. But I still want to give it a final touch: move definitions out of a test file so they can be potentially used by other test scenarios.
Let’s move the action and page elements definitions to a separate class. You may have heard of the
PageObject concept before.
In webtau page objects are simple class instances. It is the lazy nature of
PageElement that makes this simplicity possible.
It doesn’t matter when we create the instance of the class since
numberOfResults will be lazily initiated.
Let’s create the
Pages class with static page object instances to make it convenient to access them from different scenarios.
Our page object is one import away.
Dealing with asynchronous brittleness
Our test is now reinforced and can sustain UI non-functional changes. But there is one unprotected area left uncovered.
During local development our server is blazingly fast and search results are produced in under 5ms. Weeks later we will run the tests in QA environment and most likely we will be dealing with the failed assertion.
Let’s do a final test reinforcement.
waitToBe. As a result, instead of failing the assertion right away
waitTo will re-query
numberOfResults multiple times until it times out (driven by configuration).
Groovy, Java and beyond
I have been using Groovy for testing for the last decade and I think it is perfect for the job. But if you are not a Groovy fan, you can use Java or other JVM languages.
If you are interested in Java or other JVM examples, please hit me in the comments and I will be happy to help.