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 (part one), I talk about what we did and how JIRA plugin developers can make use of that work.

After reading this part you should already have idea what page objects are and how you can use the library to write better tests and improve quality of your plugin.

JIRA page objects

Getting started

In the first part I presented the new page binder framework that we use to develop libraries of object-oriented page abstractions for testing. JIRA has been leading the charge in using of the new framework to deliver a robust library of reusable page objects. As a result, JIRA 4.4 shipped with a set of about 200 page objects and supporting classes, mostly covering new features in the administration section. To use it in your plugin tests, just add the following maven dependency to your module:

1
2
3
4
5
dependency
<groupId>com.atlassian.jira</groupId>
<artifactId>atlassian-jira-pageobjects</artifactId>
<version>4.4</version>
<dependency>

And that’s it! Configure your module to use AMPS for running JIRA (if you had your plugin generated by Atlassian Plugin SDK you will get that for free) and you are ready to write and run functional tests using page objects.

Writing a test

You will hopefully be spending most of your time writing tests that use the page objects created by us and shipped with JIRA 4.4. How to create a test that makes use of the library? As I mentioned before, the starting point is an implementation of tested product, which in case of JIRA is called (yes, you guessed it!) JiraTestedProduct. If you’re running your JIRA using AMPS (which we highly recommend), just use the TestedProductFactory from atlassian-selenium to instantiate an AMPS-compatible version of it:

1
JiraTestedProduct jira = TestedProductFactory.create(JiraTestedProduct.class);

This will also instantiate an underlying instance of WebDriver which may be customized using the webdriver.browser system property. The atlassian-browsers framework will even take care of running a browser instance appropriate for your operating system, as long as it is able to find a matching one. Otherwise your OS is expected to host a browser compatible with the above property (which currently by default is firefox-3.5, but is in the process of updating). A very trivial test could then look like this:

1
2
3
4
5
6
7
8
9
public class TestViewProjects {
private static JiraTestedProduct jira = TestedProductFactory.create(JiraTestedProduct.class);
@Test
void testThatThereIsOneProject() {
// logs as sysadmin and goes to view projects...
ViewProjectsPage viewProjects = jira.goToLoginPage().loginAsSysAdmin(ViewProjectsPage.class);
assertEquals(1, viewProjects.getProjects().size());
// etc...
}

Note that the TestedProduct instance in the above example does not need to be static, but on the other hand it does not hurt and saves the time necessary to initialize underlying page binder (which in turn initializes a new Guice container) each time the tested product is created.

Coding your own page objects

You can only use the provided page objects up to some point. Eventually you will need to test features in your plugin, or use page objects that the library simply doesn’t contain yet. Worry not: you can easily develop your own page objects understandable by the page binder. So how does a JIRA page object look like? Say you added an administration page and a web item with a link to it. To be able to test if the link is displayed and your page opens when you click it, you would write a following page object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class MyAdminPage extends AbstractJiraAdminPage {
// URI relative to the app context
private final static String URI = "/secure/admin/MyAdminPage.jspa";
@ElementBy(id = "admin-options")
private PageElement optionsContainer;
private SelectElement optionOne;
private SelectElement optionTwo;
@Init
private void init() {
optionOne = optionsContainer.find(By.name("option-one"));
optionTwo = optionsContainer.find(By.name("option-two"));
}
@ValidateState
private void validateOptionsNotSelected() {
assertEquals("-1", optionOne.getSelected().value());
assertEquals("-1", optionTwo.getSelected().value());
}
// methods inherited from Page, AbstractJiraPage and AbstractJiraAdminPage
@Override
public TimedCondition isAt() {
return optionsContainer.timed().isPresent();
}
@Override
public String getUrl() {
return URI;
}
@Override
public String linkId() {
// HTML ID of the generated link (corresponds to ID of the link element in the web item definition)
return "my_admin_page";
}
// ... more methods to select options
}
// the test...
@Test
public void iWantToAccessMyAdminPageViaAdminMenu() {
// go to MyAdminPage via admin menu
MyAdminPage myPage = jira.goToLoginPage().loginAsSysAdmin(JiraAdminHomePage.class).adminMenu().goToAdminPage(MyAdminPage.class);
// do stuff...
}

A couple of important points with regards to the above examples:

  • page object not always means an object representing a whole page (for instance, see ViewProjectsPage.getProjects() method from the first example). It can be anything from a page to a section (panel) of a page, a row in a table or even a single dropdown menu. The higher level page object (container) is generally responsible for providing its lower-level components. We chose this approach to further reduce duplication, by encapsulating standard components that are used throughout the whole application (e.g. a standard AUI dropdown menu may be found basically anywhere in JIRA)
  • the @Inject, WebDriver’s @FindBy and our own @ElementBy annotation will be processed by the page binder and the appropriate dependencies injected. @ElementBy is a rough equivalent of WebDriver’s @FindBy for PageElement
  • @Init method may be used to initialize additional state if necessary (e.g. as above – initialize elements within another element)
  • @WaitUntil may be used to wait until the modelled component is in a known state (e.g. page has loaded, JavaScript components finished loading etc.) – in the above example this is handled in AbstractJiraPage using the isAt() method
  • @ValidateState may be used to verify that the page object is in the right state (e.g. in this case we check that the option dropdowns are have no selection on page load)
  • the above mechanism allow for scraping all the usual boilerplate, making page objects easy to develop and more readable

To be continued…

In the last part of this series we will go through some more advanced concepts and utilities that we use internally to test JIRA and that can help you debug and fix your tests easier. If you are eager to check page objects out and use the library, just add the Maven dependency to your plugin project and give it a go! If you have any issues, check out the JIRA Page Objects project on studio.atlassian.com.
Stay tuned!