For the last two JIRA releases we have been working hard to revamp our in-browser acceptance test suite by developing a library of reusable page objects. In this three-part blog series I will talk about what we did and how JIRA plugin developers can make use of that work.
Browser automation – the Beauty, or the Beast?
Our first attempt at developing an automated-browser test suite using Selenium was not really a huge success. At some point the number of non-deterministic failures in our Selenium suite was so high that we started loosing discipline in chasing down and fixing the failures, whether intermittent or caused by actual bugs. This was on one hand certainly due to deficiencies in Selenium – when it works, it’s a great tool, but trying to debug any problem can turn into a real nightmare! On the other hand we really could have done a better job at designing the suite. Instead it sprang up as a bunch of lengthy ‘macros’ calling low-level Selenium API line-by-line. This resulted in lots of duplication, the number one maintainability problem in acceptance tests. Every time we changed the ID of an element, or just managed to work around a problem in Selenium, the fix had to be replicated among all the affected tests.
Problems to solve
So we really had two problems: the one of technology, but most of all the one of design. With good design in place you can easily introduce work-arounds and mitigate the impact of the underlying framework on the stability of your test suite. Without good design even the best technology will fail.
Page objects – object oriented functional tests
Enter the world of page objects. Essentially, page objects are just an application of very established object oriented practices in the world of automated browser tests. When testing a web application, many tests must interact with the same functionality over and over again. And that functionality (which is basically visible as web pages) is what gets encapsulated by a set of objects.
These ‘page objects’ may then be reused across the whole test suite. Nothing new or fancy, just good old engineering principles at work. Nevertheless, done well, they greatly reduce the duplication across the test suite. They also make development of tests fast and the test code becomes much more readable.
Thinking big – Atlassian-wide page objects
As it happened, the JIRA team was not the only one in Atlassian re-thinking its approach to functional tests. As Atlassian products are increasingly integrated and more and more features are delivered as plugins, the code gets more and more decentralized. Therefore duplication hits not only single product code base, but all plugins and frameworks that ship with products like JIRA, but are not hosted in the same source tree. Obviously they also need browser tests and so far have had no easy way of reusing or sharing any such functionality.
Company Wide Solution
The need to create a common Atlassian-wide solution became quite obvious. It was implemented as part of the atlassian-selenium project (version 2.0) and is now getting increasingly used within Atlassian.
The idea is that each product, plugin or integration framework will provide its own library of page objects based on the common framework and using the same underlying browser automation technology – we now standardize on the Selenium2/WebDriver API. That way any project can consume page objects from other projects that it integrates with or builds upon.
So what’s in?
There are two new important modules in atlassian-selenium 2.0. The fundament is the API module that contains the page binder framework. All page objects are assumed to be simple POJOs that, when instantiated, go through a well defined life cycle. They may optionally hook into this life cycle by defining methods with dedicated annotations. The default page binder implementation uses Google Guice and thus supports dependency injection via the @javax.annotation.Inject
. Page binder makes creating your page objects a breeze because it lets you remove lots of the usual boilerplate. This is true in particular for complex page objects like JIRA’s ViewIssuePage or IssueNavigatorPage that have lots of inner page objects and otheer dependencies. In the next part I will show some simple examples of how to write page-binder-compatible page objects. Beyond the page binder, the module also provides API of tested product, which represents concrete application under test (e.g. JIRA) and is entry point to all other components of the framework.
The second, optional, addition to atlassian-selenium 2.0 is the PageElement framework. It builts on top of the page binder API and Selenium2/WebDriver to provide functionality similar to WebDriver’s WebElement, but with a number of very useful additions. Some of the important features of PageElement are as follows:
- it may be created even if the represented element on the page does not exist. It contains a boolean query to check whether the represented element does indeed exist. This is important difference to WebDriver that just throws exception when unable to locate a given element on the page
- it rebinds automatically if the represented element on the page is reloaded/removed. That means it never throws StaleElementException as its cousin WebElement does
- it has specialized extensions: SelectElement, MultiSelectElement and CheckboxElement to easily manipulate corresponding HTML primitves on the page
To be continued…
In the next part I will demonstrate the JIRA page objects, how to use them in your plugin’s tests and how to develop your own page objects. Stay tuned!
Interested in Atlassian Selenium 2.0? Check out the project page on studio.atlassian.com to learn more!