Last updated Mar 22, 2024

Writing integration tests using PageObjects

Level of experience: Intermediate

Our tutorials are classified as 'beginner', 'intermediate' and 'advanced'. This one is at 'intermediate' level. If you have never developed a plugin before, you may find this one a bit difficult.

Overview

This tutorial shows you how to write PageObjects for your plugin's pages and use them in integration tests.

You should be already familiar with writing a JIRA plugin and unit testing it. Also you can read about the previous method of creating integration tests using FuncTestCase.

There are various definitions available for what integration testing actually means. For the purposes of this tutorial, integration testing is defined as running a set of automated tests against the web-UI of your plugin, using HTTP requests and responses to assert that the web-UI is exhibiting the correct behaviour.

Step 1. Setting up your plugin

The best way to start creating your own Page Objects is to include JIRA Page Objects as a reference and base for your objects. So you need to modify your pom.xml and add following:

1
2
<dependency>
    <groupId>com.atlassian.jira</groupId>
    <artifactId>atlassian-jira-pageobjects</artifactId>
    <version>${jira.version}</version>
    <scope>test</scope>
</dependency>

Step 2. Anatomy of Page Object

Let's imagine you're writing an integration test for CSV Importer. The page has a few elements like File Input, Checkbox, Input Fields and Buttons. Here's a simple class that you could use to test and drive this page.

1
2
public class CsvSetupPage extends AbstractJiraPage {
    @ElementBy(id = "nextButton")
    protected PageElement nextButton;
 
    @ElementBy(cssSelector = "#advanced h3.toggle-title")
    private PageElement advanced;
 
    @Override
    public TimedCondition isAt() {
        return nextButton.timed().isVisible();
    }
    
    @Override
    public String getUrl() {
        return "/secure/admin/views/CsvSetupPage!default.jspa?externalSystem=com.atlassian.jira.plugins.jira-importers-plugin:csvImporter";
    }
 
    public CsvSetupPage setCsvFile(String resource) {
        csvFile.type(resource);
        return this;
    }
 
    public CsvSetupPage setDelimiter(String delimiter) {
        showAdvanced();
        this.delimiter.clear();
        this.delimiter.type(delimiter);
        return this;
    }
 
    public CsvProjectMappingsPage next() {
        Poller.waitUntilTrue(nextButton.timed().isEnabled());
        nextButton.click();
        return pageBinder.bind(CsvProjectMappingsPage.class);
    }
}

Let's introduce couple of elements:

  • PageElement is a DOM element that you want to interact with (modify, click, read or write to)
  • The @ElementBy annotation defines how to search for the element; you can search by things like id, cssSelector, name, and tag
  • The isAt method is used by AbstractJiraPage to detect if page loading was finished and the expected page was loaded; it uses the @WaitUntil annotation internally
  • The getUrl method defined in AbstractJiraPage indicates where to go to open the expected page
  • The pageBinder object is responsible for binding the Page Object to the DOM, next method shows how to navigate from one page to another

As you can see, you can type or click on elements. You can also get their attributes, search for page elements inside of page elements, and so on. What's great is that Page Objects are run in the real browser so you really imitate the user and their experience.

Step 3. Using Page Objects in your tests

I assume you're using JIRA Func Test Basics to create an integration test. To simplify things, let's assume you extended TestBase (check JIRA Func Test Basics documentation for a better, suggested way of writing integration tests).

1
2
public class TestCsvSetupPage extends TestBase {
    @Test
    public void testSetupPage() {
        CsvSetupPage setupPage = jira().gotoLoginPage()
            .loginAsSysAdmin(CsvSetupPage.class);
        CsvProjectMappingPage mappingPage = setupPage
            .setCsvFile("JIM-77.csv").setDelimiter(";").next();
    }
}

This simple test case filled in form and clicked next button, then tested if user is correctly moved to the next wizard page which is CsvProjectMappingPage.

Step 4. Writing assertions for elements

Here's how you can test whether your code does what you expect it to do. To avoid race conditions, your Page Object should always return a TimedQuery or TimedCondtion. Let's check whether userEmailSuffix was correctly read from the configuration.

1
2
public class CsvProjectMappingPage extends AbstractJiraPage {
    @ElementBy(name = "userEmailSuffix")
    PageElement userEmailSuffix;
 
    // removed for brevity
    
    public TimedQuery<String> getUserEmailSuffix() {
        return userEmailSuffix.timed().getAttribute("value");
    }
}

Your test could look as follows:

1
2
public TestCsvProjectMappingPage extends TestBase {
    @Test
    public void testIfConfigurationIsReadProperly() {
        CsvSetupPage setupPage = jira().gotoLoginPage()
            .loginAsSysAdmin(CsvSetupPage.class);
        CsvProjectMappingPage projectMappingPage = setupPage
            .setCsvFile("JIM-80.csv")
            .setConfigurationFile("JIM-80.config").next();
        Poller.waitUntil(projectMappingsPage.getUserEmailSuffix(), 
            (Matcher<String>) equalTo("atlassian.com"));
    }
}

Use Poller.waitUntil to check the assertion. This will poll the Page Object until the assumption is met or the timeout has passed. This is great if you have elements that are dynamically created, or you use AJAX to retrieve data. It will make sure that there are no race conditions in your code.

Step 5. Writing assertions for collections

Imagine we want to check whether the page displays correct hints for the user. Here's a Page Object:

1
2
public class CsvFieldMappingsPage extends AbstractJiraPage {
    @Inject private Timeouts timeouts;
    @Inject private ExtendedElementFinder extendedElementFinder;
 
    @ElementBy (className = "bottom-wizard-hints")
    PageElement bottomWizardHints;
 
    // removed for brevity
 
    public TimedQuery<Iterable<PageElement>> getBottomWizardHints() {
        return Queries.forSupplier(timeouts, 
                extendedElementFinder.within(bottomWizardHints)
                    .newQuery(By.className("hint"))
                    .supplier());
    }
}

You can check if the page includes the correct hints in your test as follows:

1
2
Poller.waitUntil(fieldMappingsPage.getBottomWizardHints(), 
    IsIterableWithSize.<PageElement>iterableWithSize(3));
assertThat(PageElements.asText(fieldMappingsPage.getBottomWizardHints().now()),
                IsCollectionContaining.<String>hasItems(
                        containsString("For issues with multiple"),
                        containsString("Existing custom fields must"),
                        containsString("CSV file has at least one empty")));

Step 6. Running tests on CI

There's also a simple way to run integration tests in headless mode on CI, all you need to do is edit pom.xml and set the xvfb.enable property:

1
2
<properties>
    <xvfb.enable>true</xvfb.enable>
</properties>

The Atlassian Plugin SDK will automatically launch Xvfb and run your tests using it.

Step 7. Even more resources on Page Objects

There's also in-depth guide that was published on Developer Blog by Dariusz Kordonski. Check it out to learn more!

Smarter acceptance testing with JIRA page objects - part I

Smarter acceptance testing with JIRA page objects - part II

Smarter acceptance testing with JIRA page objects - part III

Congratulations, that's it

You should now be able to write integration tests for JIRA. Oh and don't forget to have a chocolate!

Writing integration tests for your JIRA plugin

Smarter integration testing with TestKit

Rate this page: