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.
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.
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>
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 2public 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)@ElementBy
annotation defines how to search for the element; you can search by things like id, cssSelector, name, and tagisAt
method is used by AbstractJiraPage
to detect if page loading was finished and the expected page was loaded; it uses the @WaitUntil
annotation internallygetUrl
method defined in AbstractJiraPage
indicates where to go to open the expected pagepageBinder
object is responsible for binding the Page Object to the DOM, next method shows how to navigate from one page to anotherAs 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.
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 2public 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.
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 2public 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 2public 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.
Imagine we want to check whether the page displays correct hints for the user. Here's a Page Object:
1 2public 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 2Poller.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")));
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.
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!
Rate this page: