After a fun year of ups and downs, backwards and sideways, I am happy to announce that we finally brought Selenium 3 support to Confluence Enterprise development !
In this article, I am going to cover some of my team’s best practices around plugin development and share examples of how to run Selenium 3 tests using Confluence’s Stateless Test Runner. I will also share simple build configurations to get those Selenium 3 tests up and running on Bitbucket Pipelines
note: All of the examples assume the use of Maven, but the concepts are transferable to other build tools
If you are building a Confluence plugin, I would recommend to structure its modules the following way:
project-root
> plugin
> integration-tests
This is because we want to have more control over our production and integration test dependencies; it also gives us the flexibility to upgrade one before the other.
We typically define our dependency management as follows:
- plugin ... <dependencyManagement> <dependencies> <dependency> <groupId>com.atlassian.confluence</groupId> <artifactId>confluence-plugins-platform-pom</artifactId> <version>${confluence.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
- integration-tests ... <dependencyManagement> <dependencies> <dependency> <groupId>com.atlassian.confluence</groupId> <artifactId>confluence-plugins-platform-test-pom</artifactId> <version>${confluence.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
I understand that this is not the default project structure you get when using atlas-create-confluence-plugin command from the Atlassian Plugin SDK; but while the team is revisiting the existing tooling to account for our revised development best practices, you can refer to the following repo as boilerplate for all your future plugins atlassian/confluence-devrel-plugin.
Using Bitbucket Pipeline Maven starter, we end up with a simple configuration which we have to tweak a little. It means that we have to use a different maven docker image that specifies the JDK in use ... otherwise, the default image uses latest JDK … which Confluence is yet to add support for.
This translates to using either one of the following images based on the Confluence version we want to test our plugin against.
image: maven:3.6.3-jdk-11-slim pipelines: default: - parallel: - step: name: Confluence 7.4.6 caches: - maven script: - mvn -B verify --file pom.xml -Dconfluence.version=7.4.6 after-script: - pipe: atlassian/checkstyle-report:0.2.0 - step: name: Security Scan script: - pipe: atlassian/git-secrets-scan:0.4.3
In order for the build to resolve all the required dependencies, we need to ensure that our Maven project is configured to consume the Atlassian public package repositories.
<repositories> <repository> <id>atlassian-public</id> <url>https://packages.atlassian.com/mvn/maven-external/</url> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> <checksumPolicy>warn</checksumPolicy> </snapshots> <releases> <enabled>true</enabled> <checksumPolicy>warn</checksumPolicy> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>atlassian-public</id> <url>https://packages.atlassian.com/mvn/maven-external/</url> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> <checksumPolicy>warn</checksumPolicy> </snapshots> <releases> <enabled>true</enabled> <checksumPolicy>warn</checksumPolicy> </releases> </pluginRepository> </pluginRepositories>
I am going to skip over our Frontend Development best practices which involve using React to build new UI components in Confluence Enterprise … because it really deserves its own article; instead I am going to touch on using Confluence Stateless Test Runner to write web-driver integration tests.
We need to enrich our intergation-tests module definition with the following dependencies
<properties> <atlassian.selenium.and.webdriver.version>3.1.5</atlassian.selenium.and.webdriver.version> </properties> ... <!-- we need to override the versions of these dependencies as they are yet to be included in our platform-pom note that these versions bring in support for Selenium 3 --> <dependency> <groupId>com.atlassian.confluence</groupId> <artifactId>confluence-stateless-test-runner</artifactId> <version>8.0.0</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.confluence</groupId> <artifactId>confluence-webdriver-pageobjects</artifactId> <version>11.0.3</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.selenium</groupId> <artifactId>atlassian-webdriver-core</artifactId> <version>${atlassian.selenium.and.webdriver.version}</version> <scope>test<scope> </dependency> <dependency> <groupId>com.atlassian.selenium</groupId> <artifactId>atlassian-pageobjects-api</artifactId> <version>${atlassian.selenium.and.webdriver.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.selenium</groupId> <artifactId>atlassian-pageobjects-elements</artifactId> <version>${atlassian.selenium.and.webdriver.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.browsers</groupId> <artifactId>atlassian-browsers-auto</artifactId> <version>3.0.2</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>26.0-jre</version> <scope>test</scope> </dependency>
To generate fake content, we like to use DiUS/java-faker library, it allows us to rely on different locales, and therefore uncover any encoding issues.
<!-- fake data --> <dependency> <groupId>com.github.javafaker</groupId> <artifactId>javafaker</artifactId> <version>1.0.2</version> <scope>test</scope> </dependency>
Confluence Stateless Test Runner has also some dependencies that are required at runtime; these are a set of functional tests plugins that need to be installed on Confluence. So in order for the test runner to resolve them properly, they need to be available on the classpath too:
<!-- needed by stateless-test-runner at runtime --> <dependency> <groupId>com.atlassian.confluence.plugins</groupId> <artifactId>confluence-functestrpc-plugin</artifactId> <version>${confluence.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.confluence.plugins</groupId> <artifactId>confluence-functest-rest-plugin</artifactId> <version>${confluence.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.atlassian.confluence.plugins</groupId> <artifactId>confluence-scriptsfinished-plugin</artifactId> <version>${confluence.version}</version> <scope>test</scope> </dependency> <!-- the version of this can varry based on the version of confluence, so be ready to tweak your build configs a little --> <dependency> <groupId>com.atlassian.upm</groupId> <artifactId>atlassian-universal-plugin-manager-javascript-tests</artifactId> <version>4.0.13</version> <scope>test</scope> </dependency>
With all these on the classpath, we can now write a simple test that creates a page, types types types, publishes the page, and asserts the content value.
I know … it is a lot of XML, but it unlocks precious lines of Java that will increase your confidence in the quality of your plugin.
This is what a simple web-driver test looks like.
@RunWith(ConfluenceStatelessTestRunner.class) public class DevRelPageStatelessTest { @Inject private static ConfluenceTestedProduct product; @Fixture private static final UserFixture user = userFixture().build(); @Fixture private static final UserFixture anotherUser = userFixture().build(); @Fixture private static final SpaceFixture space = spaceFixture() .permission(user, REGULAR_PERMISSIONS) .permission(anotherUser, REGULAR_PERMISSIONS) .build(); private static final Faker faker = Faker.instance(); @Test public void editingAndPublishing() { final CreatePage createPage = product.loginAndCreatePage(user.get(), space.get()); final String title = format("%s - %s", faker.rickAndMorty().character(), faker.rickAndMorty().location()); final String content = faker.rickAndMorty().quote(); createPage.setTitle(title); EditorContent editorContent = createPage.getEditor().getContent(); editorContent.type(content); editorContent.type(Keys.ENTER.toString()); ViewPage publishedPage = createPage.save(); assertThat(publishedPage.getTitle(), equalTo(title)); waitUntil(publishedPage.getRenderedContent().getTextTimed(), containsString(content)); } }
Note the usage of :
@Fixture which allows us to create all the data needed to orchestrate our test flow; these must be declared as static fields
@Inject which gives us access to services that allow us to navigate around Confluence and interact with its user interface
Without going into too much detail, this is how stateless test runner does its magic:
I would recommend discovering our Stateless and WebDriver PageObjects API by poking around your IDE a little; for instance, look up implementations of:
com.atlassian.confluence.test.stateless.TestFixture to find out which fixtures you can create
There are quite a few options to run selenium tests on Bitbucket Pipelines, for instance using browserstack, but the option I am describing here relies on using selenium-standalone docker images which are defined in SeleniumHQ/docker-selenium
We need to define our browsers as services:
definitions: services: chrome: image: selenium/standalone-chrome:3.11.0 firefox: image: selenium/standalone-firefox:3.11.0
Then make use of those services in our build steps:
- step: size: 2x name: "[chrome] Confluence 7.4.6" caches: - maven services: - chrome script: - mvn -B verify --file pom.xml -Dxvfb.enable=false -Dwebdriver.browser="chrome:url=http://127.0.0.1:4444" -Dconfluence.version=7.4.6 after-script: - pipe: atlassian/checkstyle-report:0.2.0
Notice the use of:
size: 2x , this is because we are adding a new service into the mix which shares parts of the resources with Confluence, so we need to throw more power onto our build agents. For more details please read up on supporting large builds in Bitbucket Pipelines.
-Dxvfb.enable=false -Dwebdriver.browser="chrome:url=http://127.0.0.1:4444" , these properties tell Confluence Stateless Test Runner how to access the remote selenium browser.
Now that we have our integration tests running, it would be nice to see them reported on the pipeline build results. According to test reporting in Bitbucket Pipelines , the Maven failsafe/surefire reports are expected to be available in one of the following directories:
./**/surefire-reports/**/*.xml ./**/failsafe-reports/**/*.xml ./**/test-results/**/*.xml ./**/test-reports/**/*.xml
… with a directory depth of 3 levels …
Looking at our default project structure and configuration, we can clearly see that the integration tests reports are generated as follows.
This means that we have to go back to our project definition, and make some more adjustments to our lovely XML.
We have to override where maven-failsafe-plugin stores its reports, we can just place them in the project’s root target directory:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.0.0-M5</version> <configuration> <reportsDirectory>${project.basedir}/../target/failsafe-reports/integration-tests</reportsDirectory> </configuration> </plugin>
And we have to let confluence-maven-plugin know about our configuration override.
<plugin> <groupId>com.atlassian.maven.plugins</groupId> <artifactId>confluence-maven-plugin</artifactId> <version>${amps.version}</version> <extensions>true</extensions> <executions> <execution> <id>integration-tests</id> <phase>integration-test</phase> <goals> <goal>integration-test</goal> </goals> </execution> </executions> <configuration> <versionOverrides> <versionOverride>maven-failsafe-plugin</versionOverride> </versionOverrides> ... </configuration> ... </plugin>
Voilà !
In case of test failures, our Stateless Test Runner is configured to capture screenshots which can help you understand and troubleshoot your tests. This is great, because we can update our pipelines configuration to extract them as artifacts !
... - step: size: 2x name: "[chrome] Confluence 7.4.6" caches: - maven services: - chrome artifacts: - "integration-tests/target/webdriverTests/**/*.png" ...
Voilà !
This was just an introduction to using Confluence’s Stateless Test Runner in order to run Selenium 3 WebDriver tests on Bitbucket Pipelines.
We will come back after the holiday season with more articles around the hidden gems of our team’s test libraries.
Hasnae
Senior Developer
Atlassian
Sydney
6 accepted answers
1 comment