The Arquillian Drone extension for Arquillian provides a simple way of including functional tests for your web based application. Arquillian Drone manages the life cycle of web testing tool, which is either Arquillian Ajocado, Selenium or WebDriver. Arquillian Drone automatically manages life cycle of objects required for interaction between browser and deployed application.
The following example illustrates how Arquillian Drone can be used with Arquillian Ajocado. This example is a part of Arquillian Drone test classes, so you are free to experiment with it. Arquillian Ajocado is a Selenium on steroids, because it provides type safe API over classic DefaultSelenium object, has extended support for handling AJAX based UI and adds pretty fast JQuery locators to you browser, so your test are executed faster. If you are not experienced with Arquillian Ajocado, you can still use DefaultSelenium or WebDriver specific browser, such as
FirefoxDriver. The beauty of Arquillian Drone is that is supports all of them and their usage is pretty much the same.
package org.jboss.arquillian.drone.example;
import static org.jboss.arquillian.ajocado.Ajocado.elementPresent;
import static org.jboss.arquillian.ajocado.Ajocado.id;
import static org.jboss.arquillian.ajocado.Ajocado.waitForHttp;
import static org.jboss.arquillian.ajocado.Ajocado.waitModel;
import static org.jboss.arquillian.ajocado.Ajocado.xp;
import java.io.File;
import java.net.URL;
import org.jboss.arquillian.ajocado.framework.AjaxSelenium;
import org.jboss.arquillian.ajocado.locator.IdLocator;
import org.jboss.arquillian.ajocado.locator.XPathLocator;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.importer.ZipImporter;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests Arquillian Drone extension against Weld Login example.
* Uses Ajocado driver bound to Firefox browser.
*
*/
@RunWith(Arquillian.class)
public class AjocadoTestCase extends AbstractTestCase
{
// load ajocado driver
@Drone
AjaxSelenium driver;
// Load context path to the test
@ArquillianResource
URL contextPath;
protected XpathLocator LOGGED_IN = xp("//li[contains(text(),'Welcome')]");
protected XpathLocator LOGGED_OUT = xp("//li[contains(text(),'Goodbye')]");
protected IdLocator USERNAME_FIELD = id("loginForm:username");
protected IdLocator PASSWORD_FIELD = id("loginForm:password");
protected IdLocator LOGIN_BUTTON = id("loginForm:login");
protected IdLocator LOGOUT_BUTTON = id("loginForm:logout");
@Deployment(testable=false)
public static WebArchive createDeployment()
{
return ShrinkWrap.create(WebArchive.class, "weld-login.war")
.addClasses(Credentials.class, LoggedIn.class, Login.class, User.class, Users.class)
.addAsWebInfResource(new File("src/test/webapp/WEB-INF/beans.xml"))
.addAsWebInfResource(new File("src/test/webapp/WEB-INF/faces-config.xml"))
.addAsWebInfResource(new File("src/test/resources/import.sql"))
.addAsWebResource(new File("src/test/webapp/index.html"))
.addAsWebResource(new File("src/test/webapp/home.xhtml"))
.addAsWebResource(new File("src/test/webapp/template.xhtml"))
.addAsWebResource(new File("src/test/webapp/users.xhtml"))
.addAsResource(new File("src/test/resources/META-INF/persistence.xml"), ArchivePaths.create("META-INF/persistence.xml"))
.setWebXML(new File("src/test/webapp/WEB-INF/web.xml"));
}
@Test
public void testLoginAndLogout()
{
driver.open(contextPath);
waitModel.until(elementPresent.locator(USERNAME_FIELD));
Assert.assertFalse("User should not be logged in!", driver.isElementPresent(LOGOUT_BUTTON));
driver.type(USERNAME_FIELD, "demo");
driver.type(PASSWORD_FIELD, "demo");
waitHttp(driver).click(LOGIN_BUTTON);
Assert.assertTrue("User should be logged in!", driver.isElementPresent(LOGGED_IN));
waitHttp(driver).click(LOGOUT_BUTTON);
Assert.assertTrue("User should not be logged in!", driver.isElementPresent(LOGGED_OUT));
}
}
As you can see, execution does not differ from common Arquillian test much. The only requirement is actually running Arquillian in client mode, which is enforced by marking deployment as testable = false or alternatively by @RunAsClient annotation. The other annotations present in the test are used to inject web test framework instance (@Drone) and context path (@ArquillianResource) for deployed archive into your test. Their life cycle is completely managed by Arquillian Drone, as described in extensions.drone.lifecycle. The instance is used in test method to traverse UI of application via Firefox browser, fill user credentials and signing up and out. Test is based on JUnit, but Arquillian Drone, as well as the rest of Arquillian supports TestNG as well.
Supported frameworks and their versions
Framework name and implementation class
|
Tested version
|
Additional information
|
Arquillian Ajocado - AjaxSelenium
|
1.0.0.CR1
|
Requires Selenium Server running
|
Selenium - DefaultSelenium
|
2.5.0
|
Requires Selenium Server running
|
Selenium - Android, Chrome, Firefox, HtmlUnit, InternetExplorer, IPhone Drivers
|
2.5.0
|
Selenium Server is not required
|
This combination matrix (Chrome, Firefox, HtmlUnit) is tested and known to work. However, we expect that all WebDriver interface based browsers will work. Arquillian Drone does not force you to use a specific version of web framework test implementation, so feel free to experiment with it.
Maven setup example
Arquillian Drone requires a few test dependencies which are marked as provided to let you choose their versions. Add following code into your Maven dependecies to enable Arquillian Drone functionality in your test cases.
<properties>
<version.arquillian.drone>1.0.0.CR2</version.arquillian.drone>
</properties>
<dependencyManagement>
<dependencies>
<!-- Arquillian Drone dependencies and Selenium dependencies -->
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-bom</artifactId>
<version>${version.arquillian.drone}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Add following dependencies into <dependencies> section -->
<!-- Arquillian Drone support in Arquillian tests -->
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-impl</artifactId>
<scope>test</scope>
</dependency>
<!-- Support for Selenium in Arquillian Drone -->
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium</artifactId>
<scope>test</scope>
</dependency>
<!-- Support for Selenium Server in Arquillian Drone -->
<!-- Required only if you want Arquillian to control Selenium Server life cycle -->
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium-server</artifactId>
<scope>test</scope>
</dependency>
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<!-- Selenium Server -->
<!-- Required only if you want Arquillian to control Selenium Server life cycle -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<scope>test</scope>
</dependency>
<!-- Selenium Server dependency -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId> <!-- choose different underlying implementation if you want -->
<scope>test</scope> <!-- up to you, tested with 1.5.10 -->
</dependency>
To use WebDriver, simply replace org.jboss.arquillian.extension:arquillian-drone-selenium with org.jboss.arquillian.extension:arquillian-drone-webdriver.
You can combine as many frameworks together as you want. There is no problem to have DefaultSelenium, ChromeDriver and FirefoxDriver in one test suite
To use Arquillian Ajocado, dependencies are already prepared for you. Simply use either JUnit or TestNG artifact, which
includes all Drone and Selenium dependencies:
<dependency>
<groupId>org.jboss.arquillian.ajocado</groupId>
<artifactId>arquillian-ajocado-junit</artifactId>
<type>pom</type>
<scope>test</scope>
</dependency>
Arquillian Ajocado is not yet updated to Drone 1.0.0.CR2, you can expect it soon to happen.
Life cycle of @Drone objectsArquillian Drone does not allow you to control life cycle of web testing framework objects, but it provides two different scenarios which should be sufficient for most usages required by developers. These are
-
Class based life cycle
-
Method based life cycle
For class based life cycle, configuration for the instance is created before a test class is run. This configuration is used to propertly initialize an instance of the tool. The instance is injected into the field and hold until the last test in the test class is
finished, then it is disposed. You can think of @BeforeClass and @AfterClass equivalents. On the other hand, for method based life cycle, an instance is configured and created before Arquillian enters test method and it is disposed after method finishes. You can think of @Before and @After equivalents.
It is import to know that you can combines multiple instances in one tests and you can have them in different scopes. You can as well combine different framework types. Following example shows class based life cycle instance foo of type AjaxSelenium (Arquillian Ajocado) combined with method based life cycle bar of type DefaultSelenium (Selenium).
@RunWith(Arquillian.class)
@RunAsClient
public class EnrichedClass
{
@Drone AjaxSelenium foo;
public void testRatherWithSelenium(@Drone DefaultSelenium bar)
{
...
}
}
Keeping multiple @Drone instances of the same typeWith Arquillian Drone, it is possible to keep more than one instance of a web test framework tool of the same type and determine which instance to use in a type safe way. Arquillian Drone uses concept of @Qualifier, which may be known to you from CDI. @Qualifier is a meta-annotations which allows you to annotate annotation you create to tell instances appart. By default, if no
@Qualifier annotation is present, Arquillian Drone uses @Default. Following code defines new qualifying annotation
package org.jboss.arquillian.drone.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jboss.arquillian.drone.api.annotation.Qualifier;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Qualifier
public @interface Different
{
}
Once you have defined a qualifier, you can use it in you tests, for example in following way, having two distinct class based life cycle instances of DefaultSelenium.
@RunWith(Arquillian.class)
@RunAsClient
public class EnrichedClass
{
@Drone DefaultSelenium foo;
@Drone @Different DefaultSelenium bar;
public void testWithBothFooAndBar()
{
...
}
}
Configuring @Drone instances
@Drone instances are automatically configured from arquillian.xml descriptor file, with possibility of overridding arquillian.xml configuration by system properties. Every type of @Drone instance has its own configuration namespace, although namespace overlap in areas where it makes sense, such as sharing a part of configuration between Selenium and Selenium server. System properties always take precedence.
If you are using @Qualifiers, you can use them to modify configuration, such as create a special configuration for a method based life cycle browser.
Arquillian Ajocado configuration
Property name
|
Default value
|
Description
|
contextRoot
|
http://localhost:8080
|
Web Server URL
|
contextPath
|
(empty)
|
Application URL of your deployed application
|
browser
|
*firefox
|
Browser type, following Selenium conventions
|
resourcesDirectory
|
target/test-classes
|
Directory where additional resources are stored
|
buildDirectory
|
target
|
Directory where application is built
|
seleniumHost
|
localhost
|
Name of the machine where Selenium server is running
|
seleniumPort
|
14444
|
Port on machine where Selenium server is running
|
seleniumMaximize
|
false
|
Maximize browser window
|
seleniumDebug
|
false
|
Produce debug output in browser console
|
seleniumNetworkTrafficEnabled
|
false
|
Capture network traffic in browser console
|
seleniumSpeed
|
0
|
Delay in ms before each command is sent
|
seleniumTimeoutDefault
|
30000
|
Default timeout in ms
|
seleniumTimeoutGui
|
5000
|
Timeout of GUI wait in ms
|
seleniumTimeoutAjax
|
15000
|
Timeout of AJAX wait in ms
|
seleniumTimeoutModel
|
30000
|
Timout of Model wait in ms
|
Arquillian Ajocado uses ajacodo namespace. This means, you can define properties either in arquillian.xml
<extension qualifier="ajocado">
<property name="seleniumHost">myhost.org</property>
</extension>
Or you can convert property name to name of system property, using following formula arqullian. + (namespace) + . + (property name converted to dotted lowercase). For instance, seleniumNetworkTrafficEnabled will be converted to arquillian.ajocado.selenium.network.traffic.enabled System property name.
Selenium configuration
Property name
|
Default value
|
Description
|
serverPort
|
14444
|
Port on machine where Selenium server is running
|
serverHost
|
localhost
|
Name of the machine where Selenium server is running
|
url
|
http://localhost:8080
|
Web Server URL
|
timeout
|
60000
|
Default timeout in ms
|
speed
|
0
|
Delay in ms before each command is sent
|
browser
|
*firefox
|
Browser type, following Selenium conventions
|
Selenium uses selenium namespace.
WebDriver configuration
Property name
|
Default value
|
Description
|
implementationClass
|
org.openqa.selenium.htmlunit.HtmlUnitDriver
|
Determines which browser instance is created for WebDriver testing
|
All WebDriver drivers use webdriver namespace. WebDriver configuration is shared between all instances, the other drivers have more possibilities.
AndroidDriver
Property name
|
Default value
|
Description
|
androidRemoteAddress
|
|
Default address for remote driver to connect
|
ChromeDriver
Property name
|
Default value
|
Description
|
chromeBinary
|
|
Path to chrome binary
|
chromeDriverBinary
|
|
Path to chromedriver binary
|
chromeSwitches
|
|
List of space separated chrome switches
|
FirefoxDriver
Property name
|
Default value
|
Description
|
firefoxBinary
|
|
Path to firefox binary
|
firefoxProfile
|
|
Path to Firefox profile
|
HtmlUnitDriver
Property name
|
Default value
|
Description
|
applicationName
|
|
Application agent name
|
applicationVersion
|
|
Application agent version
|
userAgent
|
|
User agent identification
|
browserVersionNumeric
|
|
Version of the application agent
|
useJavaScript
|
false
|
Use JavaScript
|
IPhoneDriver
Property name
|
Default value
|
Description
|
iphoneRemoteAddress
|
|
Default address for remote driver to connect
|
InternetExplorerDriver
Property name
|
Default value
|
Description
|
iePort
|
|
Default port where to connect for Internet Explorer driver
|
Selenium Server configuration
Property name
|
Default value
|
Description
|
avoidProxy
|
false
|
Do not use proxy for connection between clients and server
|
browserSessionReuse
|
false
|
Reuse browser session
|
browserSideLog
|
false
|
Enable logging in browser window
|
debug
|
false
|
Enable debug messages
|
dontTouchLogging
|
false
|
Disable Selenium specific logging configuration
|
ensureCleanSession
|
false
|
Automatic cleanup of the session
|
firefoxProfileTemplate
|
|
Path to the profile used as a template
|
forcedBrowserMode
|
|
Mimic browser mode no matter which one is used to start the client
|
honorSystemProxy
|
false
|
Use system proxy for connections
|
host
|
localhost
|
Name of the machine where to start Selenium Server
|
logFile
|
|
Path to log file
|
nonProxyHosts
|
value of http.nonProxyHosts property
|
List of hosts where proxy settings are ignored
|
port
|
14444
|
Port on machine where to start Selenium Server
|
profilesLocation
|
|
Where profiles are located
|
proxyHost
|
value of http.proxyHost property
|
Name of proxy server
|
proxyInjectionMode
|
false
|
Use proxy approach between Selenium server and client
|
proxyPort
|
value of http.proxyPort property
|
Port of proxy server
|
retryTimeoutInSeconds
|
10
|
Timeout for commands to be retried
|
singleWindow
|
false
|
Use single window
|
skip
|
false
|
Do not manage Selenium Server lifecycle
|
systemProperties
|
|
Arbitrary system properties in -Dproperty.name=property.value format
|
timeoutInSeconds
|
Integer.MAX_VALUE
|
Timeout for Selenium Server
|
trustAllSSLCertificates
|
false
|
Trust all SSL certificates
|
trustStore
|
value of javax.net.ssl.trustStore property
|
Trust store path
|
trustStorePassword
|
value of javax.net.ssl.trustStorePassword property
|
Trust store password
|
userExtensions
|
|
Path to user extension files
|
Selenium Server uses selenium-server namespace.
Please note that non-letter characters are converted to dots, so for instance to disable Selenium Server via System property, you have to set arquillian.selenium.server.skip to true.
Selenium Server has different life cycle than @Drone instance, it is created and started before test suite and disposed after test suite. If you have your own Selenium Server instance running, you simply omit its configuration, however specifying it is the simplest way how to start it and have it managed by Arquillian.
Configuring @Qualifier'd Drone instances
If you are wondering how to define configuration for @Qualifier @Drone instance, it's very easy. Only modification you have to do is to
change namespace to include - (@Qualifier annotation name converted to lowercase). Please note, that for System properties are all non-letter characters converted to dots. For instance, if you qualified Arquillian Ajocado instance with @MyExtraBrowser, its namespace will become ajocado-myextrabrowser.
The namespace resolution is a bit more complex. Arquillian Drone will search for configuration in following order:
-
Search for the exact match of namespace (e.g. ajocado-myextrabrowser) in arqullian.xml, if found, step 2 is not performed
-
Search for a match of base namespace, without qualifier (e.g. ajocado) in arqullian.xml
Then System property overriddes are applied in the same fashion.
Arquillian Drone SPI
The big advantage of Arquillian Drone extension is its flexibility. We provide you reasonable defaults, but if they are not sufficient or if they do not fulfill your needs, you can change them. You can change behaviour of existing implemenation or implement support for your own testing framework. See JavaDoc/sources for more details, here is an enumeration of classes you should focus on:
Implementations of Configurator, Instantiator and Destructor are searched on the class path and they are sorted according to precedence they declare. Arquillian Ajocado default implemenation has precedence of 0, so if your implementation has bigger precedence and instantiates type T with configuration C, Arquillian Drone will use it. This provides you the ultimate way how to change behavior if desired. Of course, you can provide support for your own framework in the very same way, so in your test you can use @Drone annotation to manage instance of arbitrary web testing framework.
Arquillian Drone SPI extensions are activated in a LoadableExtension, which is a default extension method in Arquillian.