HTTPS SSH

Arquillian Containers for Atlassian Applications

Running tests inside Atlassian Applications using Arquillian for the win.

Maven repository

This has yet to be published to a central repository, but is available from the Adaptavist
public Maven repository. Add the following to your pom.xml <repositories> section:

<repository>
    <id>adaptavist-external</id>
    <url>https://nexus.adaptavist.com/content/repositories/external</url>
    <snapshots>
        <enabled>false</enabled>
    </snapshots>
    <releases>
        <enabled>true</enabled>
        <checksumPolicy>fail</checksumPolicy>
    </releases>
</repository>

Adding POM dependencies

Add the relevant Arquillian dep (eg. junit), and the Atlassian remote container:

<dependency>
    <groupId>org.jboss.arquillian.junit</groupId>
    <artifactId>arquillian-junit-container</artifactId>
    <version>1.4.0.Final</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.adaptavist.arquillian.atlassian</groupId>
    <artifactId>remote-atlas-container</artifactId>
    <version>3.5.1</version>
    <scope>test</scope>
</dependency>

Spring Scanner dependencies

The remote-atlas-container uses the Spring Scanner, although its not required that your plugin
under test uses it. You may need to add these dependencies if you don't already include them:

<dependency>
    <groupId>com.atlassian.plugin</groupId>
    <artifactId>atlassian-spring-scanner-annotation</artifactId>
    <version>${atlassian.spring.scanner.version}</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    <scope>provided</scope>
</dependency>

IntelliJ IDEA

If you are using JetBrains IntelliJ IDEA, it comes with a plugin for Arquillian, but unfortunately this extension does
not work with it out of the box - we recommend that you just disable the Arquillian plugin in IDEA, unless you are
familiar with its configuration. If you do manage to get them to work together we'd love to hear about - please raise
an issue or a PR for this documentation.

Arquillian configuration

Create src/test/resources/arquillian.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

    <container qualifier="confluence" default="true">
        <configuration>
            <property name="app">confluence</property>
        </configuration>
    </container>
</arquillian>

Set the appropriate "app" property for your target Atlassian app:

  • jira
  • confluence
  • bitbucket
  • bamboo
  • fecru

Test plugin deployment

Then write your tests in the usual Arquillian way, for example:

@RunWith(Arquillian.class)
public class MyFirstTest {

    // Annotate with @ComponentImport & @Inject to import the service from another bundle and inject here
    @ComponentImport
    @Inject
    public HitService hitService;

    // This deploys these tests as a plugin into the Atlassian app
    @Deployment
    public static Archive<?> deployTests() {
        return ShrinkWrap.create(JavaArchive.class, "tests.jar");
    }

    @Test
    public void hitIt_increments_the_counter() {
        hitService.reset();

        assertThat(hitService.getCount().count, is(0L));

        hitService.hitIt();

        assertThat(hitService.getCount().count, is(1L));
    }
}

The AtlassianPluginDeploymentPackager does the work of preparing the @Deployment Archive.
Any regular JavaArchive will be treated as a 'Test Plugin' and will be transformed using the
Atlassian Spring Scanner, all the necessary Atlassian plugin gubbins, eg. atlassian-plugin.xml,
will be added and a MANIFEST will be created using bnd.

Default Deployment provider

The Arquillian 1.3.0 release provided the ability to auto-generate a deployment for a test class, avoiding the need
for the static @Deployment method. So since release 3.3.0 we've taken advantage of this by providing a default
deployment to make it easier to get started.

So the @Deployment in the test case above could be excluded:

@RunWith(Arquillian.class)
public class MyFirstTest {

    @ComponentImport
    @Inject
    public HitService hitService;

    // No @Deployment necessary

    @Test
    public void hitIt_increments_the_counter() {
        ...
    }
}

Our default deployment will play nicely with your existing tests, it will only provide a Deployment if the test class
contains no other @Deployment methods.

The default deployment creates a JavaArchive called tests.jar - and adds the superclasses of the test class.

If your test case needs to provide a more complex archive or deployment options, you just include the static
@Deployment method as usual, or use a @BeforeDeployment method to modify the default deployment archive:

@BeforeDeployment
public static Archive<?> addDeploymentContent(JavaArchive archive) {
    // Modify Archive
    return archive.addClass(Foo.class);
}

Future releases may provide more automation of the archive building, eg. interpreting annotations, adding test
components that have been injected etc.

Avoiding Deployment

If a deployment archive is empty its deployment will not be attempted at all, the test case can still run though.
The only time this might happen is when @Deployment(testable = false) and nothing has been added to the archive.

This can be very useful for running client only tests but still making use of various Arquillian features, such as
injection of the base URI.

To aid this use-case further, since 3.5.0, the DefaultDeployment provider will set testable = false on the default
deployment if the test class is annotated with @RunAsClient, hence the test plugin will not be deployed.

@RunWith(Arquillian.class)
@RunAsClient
public class MyClientSideTest {

    @ArquillianResource
    public URI baseURI;

    // No deployment will happen

    @Test
    public void test_something_client_side() {
        ...
    }
}

Plugin Modules

Plugin module configurations that would usually go inside an atlassian-plugin.xml
can be added via xml files in the META-INF/plugin-descriptors resource folder.

For example:

If you have an xml file that defines plugin modules at src/test/resources/META-INF/plugin-descriptors/my-web-resources.xml:

<atlassian-plugin>
    <web-resource key="my-web-resource">
        ...
    </web-resource>
</atlassian-plugin>

In your Arquillian test deployment method, just add the resource:

return ShrinkWrap.create(JavaArchive.class, "tests.jar")
        .addAsPluginDescriptorResource("my-web-resources.xml");

Internally this is performed by adding the following instruction to the MANIFEST of the test plugin:

Atlassian-Scan-Folders: META-INF/plugin-descriptors,atlassian-plugin

(Prior to 3.5.0, the suggested folder was atlassian-plugin, this is still supported for backwards compatibility)

Installing additional plugins

Pre-built plugins may also be deployed during a test, by returning a PluginArchive from a
@Deployment method. See the PluginArchiveHelper for static methods that will load a plugin
from a local file or maven.

Example of loading a plugin from another module in the local maven project:

import static com.adaptavist.arquillian.atlassian.remote.container.util.PluginArchiveHelper.*;

@Deployment
public static PluginArchive createPluginDeployment() {
    return pluginFromFile("glob:**/target/another-great-plugin-*.jar", inThisMavenProject());
}

Deploying an obr

If you want to deploy a pre-built obr of plugins in your tests you need to include a dependency for it:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>foo</artifactId>
    <version>1.2.3</version>
    <type>obr</type>
    <scope>test</scope>
</dependency>

Note the type of 'obr'. Then add a deployment to your test class using the PluginArchiveHelper:

@Deployment
public static PluginArchive deployMyObr() {
    return PluginArchiveHelper.createFromObr("com.example:foo")
            .doNotUndeploy();
}

Note, obr's cannot currently be undeployed, but you should also explicitly call doNotUndeploy() otherwise the undeploy
will fail. (Undeploy of obr's may be implemented in the future if required)

Dependency conflicts

You may find a couple of dependency conflicts between this library and some Atlassian artifacts...

JIRA API

The Xerces implementation in jira-api conflicts with that used by Arquillian, so it needs to be excluded:

<dependency>
    <groupId>com.atlassian.jira</groupId>
    <artifactId>jira-api</artifactId>
    <scope>provided</scope>
    <exclusions>
        <exclusion>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Atlassian Plugins OSGI

The version of the bndlib used for building test archives may conflict, so may need excluding:

<dependency>
    <groupId>com.atlassian.plugins</groupId>
    <artifactId>atlassian-plugins-osgi</artifactId>
    <scope>provided</scope>
    <exclusions>
        <exclusion>
            <groupId>biz.aQute.bnd</groupId>
            <artifactId>biz.aQute.bndlib</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Testing on localhost

We've found that some Atlassian applications (Confluence & FishEye/Crucible) attempt to use the name of your host
rather than just 'localhost' for the base URL of app when started via the SDK. The Atlassian Arquillian container
will always default to 'localhost' if not specifically set in the arquillian.xml - so we recommend explicitly setting
server to 'localhost' in your AMPS configuration, eg:

<plugin>
    <groupId>com.atlassian.maven.plugins</groupId>
    <artifactId>maven-amps-plugin</artifactId>
    <version>${amps.version}</version>

    <configuration>
        <server>localhost</server>
        ...
    </configuration>
</plugin>

How test deployment plugins are built

Prior to 3.4.0 the test plugin building and deployment were all integrated in one place, as of 3.4.0 they are split.

The shrinkwrap-atlassian-plugin has been introduced as a new type of ShrinkWrap Archive (AtlassianPluginArchive),
that can be used independently of the remote-atlas-container. It can take an Archive of classes, run the
Spring Scanner over it, add an atlassian-plugin.xml and generate the manifest - to produce a complete Atlassian
plugin ready for installation.

Take a look at com.adaptavist.arquillian.atlassian.remote.container.AtlassianPluginDeploymentPackager.java for an
example of usage - and how it's used to build the plugin prior to deployment.

Customising the deployed Atlassian plugin

Prior to 3.5.0 the deployment packager would not process a AtlassianPluginArchive if given one, so customising the
deployed test plugin was quite difficult.

Now it will accept a partially completed AtlassianPluginArchive and process it, adding just what is necessary to
produce a test plugin. For example, it will only generate a atlassian-plugin.xml or MANIFEST.MF if they are not
already present in the archive.

NOTE: We make no attempt to verify your own atlassian-plugin.xml or MANIFEST.MF files, so only add them
manually if you know what you are doing.

So it is now also possible to control every aspect of the plugin build in a @Deployment or @BeforeDeployment
if you wish. For example, providing a custom atlassian-plugin.xml, or setting manifest properties...

@BeforeDeployment
public static Archive<?> customiseDeployment(Archive<?> archive) {
    return archive.as(AtlassianPluginArchive.class)
        .addAsResource("atlassian-plugin.xml")
        .setManifestProperty("Export-Package", "com.example");
}

(setting of manifest properties was also added in 3.5.0 btw)

How plugins are deployed

Plugins will be installed into the Atlassian app using Quick Reload if available,
and fallback to the UPM (Universal Plugin Manager) if not.

By default all plugins will be installed before and uninstalled after the test run,
PluginArchives may be flagged to not be re-installed if already present, and
also to not be uninstalled after tests - this can be useful for creating
'bootstrap' tests for setting up an Application ready for manual use, or to
speed up test runs.

PluginArchiveHelper also provides a shortcut for installing Quick Reload into
the application if its not already present - to speed up further deployments.

Spring Scanner 1 & 2 support

Test plugins are built to work with Spring Scanner 2 by default, but
backwards compatibility with Spring Scanner 1 is still supported for older
app versions and apps that don't yet incorporate Scanner 2.

Either set -Datlassian.spring.scanner.one=true for your test executions to
force all test plugins to be build with Scanner 1 support, or configure via the AtlassianPluginArchive...

@BeforeDeployment
public static Archive<?> useSpringScannerOne(Archive<?> archive) {
    return archive.as(AtlassianPluginArchive.class)
        .withSpringScannerOne(true);
}

Prior to 3.5.0, you could add the SpringScannerOne class to your test archive, but this is now deprecated.

Debugging of Spring Scanner may be enabled with -Datlassian.spring.scanner.debug=true,
this will enable verbose output from the scanner, log the name of the temporary
jar files before and after scanning, and not delete these after test completion.
Or via .withSpringScannerDebugging(true) on the AtlassianPluginArchive.

If for some reason, you do not wish to run the spring scanner on the archive it can also be disabled via
.disableSpringScanner() on AtlassianPluginArchive.

NOTE: You may encounter problems if you use Scanner 2 in your build process
with Scanner 1 in test classes. Ensure you remove any of the Scanner 1 switches above
if upgrading a plugin build to Scanner 2.

Since 3.5.0, you may supply your own META-INF/spring/scanner.xml file, in which case it will not be overridden,
but the scanner will still run to generate the META-INF/plugin-components. But if that folder is already present
in the archive the scanner will not run.

Hacking this project

Build

atlas-mvn clean install -DskipTests

Test

atlas-mvn verify

or test against individual Atlassian apps:

atlas-mvn verify -DtestGroups=bamboo
atlas-mvn verify -DtestGroups=bitbucket
atlas-mvn verify -DtestGroups=confluence
atlas-mvn verify -DtestGroups=fecru
atlas-mvn verify -DtestGroups=jira

or start up an app and run tests from your IDE:

atlas-debug -pl remote-atlas-container -Dproduct=bamboo
atlas-debug -pl remote-atlas-container -Dproduct=bitbucket
atlas-debug -pl remote-atlas-container -Dproduct=confluence
atlas-debug -pl remote-atlas-container -Dproduct=fecru
atlas-debug -pl remote-atlas-container -Dproduct=jira

then run the tests appropriate to the started app from: remote-atlas-container/src/test/java/it/com/adaptavist/testing/arquillian

Once an application is running, you can hack at the main remote-atlas-container code and rerun tests without rebuilding the project.

TODO

  • Test with the new Arquillian @Rules instead of the test-runner and an alternative runner such as Spock