Running Robot Framework's Remote Server as Java agent

14 Sep, 2016
Xebia Background Header Wave
Robot Framework is a great automated testing tool that uses a keyword-driven approach. When you want to run Robot Framework tests within the context of a running system-under-test you can load Robot Framework's RemoteServer as a java agent. This is not something that comes out of the box so we will explain how to do it here. Robot Framework has a great and simple mechanism to execute tests remotely. What this mechanism also provides, is a means of running test libraries that have been implemented in languages that are incompatible with the interpreter that Robot Framework is running on. For instance, when running Robot Framework on Python, it is possible to use test libraries that have been written in Java. To be able to do the latter, we will have to use the Java implementation of the Robot Framework Remote Server. That is what we will be doing within the context of this post. Please see this blog post for a detailed description of these and other aspects of the Remote Library Interface. Please see these posts for more information about the Robot Framework in general. However, what the Java remote server does by default is to load classes within its own classloader. This is fine if you have nicely partitioned code where you can mock and stub to your heart's content, but it does not help you if you have a massive legacy system that you have to fully run before anything works. So what we'd like is for that remote server to run within the JVM of the system under test so you can access its objects. The solution is to use the java agent mechanism. A java agent is loaded before the main class of the regular application is run, and can instrument all classes before they get loaded by the application. We don't need to use the instrumentation feature here: all we need is the fact that the agent uses the same classloader as the regular application and therefore can access the application's code. Yes, this is indeed a back door into the application, so only load the agent on your testing environment! :-) The trick to Java agents has three parts:
  1. A main class that has a "premain" method similar to the regular "main" method.
  2. A manifest file in the jar with a Premain-Class entry.
  3. Start up the JVM with the -javaagent parameter.

Step 0: get the RemoteServer code

Our work is relatively easy because we are leveraging the RemoteServer that has been created by ombre42. It instantiates a Jetty server that implements the xml-rpc protocol that connects the Robot Framework client with remote servers. It also has a nice feature in that you can pass test library classes as parameters on the command line so you don't have to repackage the remote server when you change or add a test library. Ensure these dependencies are in your POM (changing version numbers as needed): [xml] <dependencies> <dependency> <groupId>com.github.ombre42</groupId> <artifactId>jrobotremoteserver</artifactId> <version>3.0</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> </dependencies> [/xml]

Step 1: create the premain class

[java] package com.xebia.robotbackdoor; import java.lang.instrument.Instrumentation; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.robotframework.remoteserver.RemoteServer; public class RobotBackdoor { public static void premain(String args, Instrumentation inst) throws Exception { RemoteServer.main(args.split(" ")); } } [/java] As you can see we leverage the RemoteServer implementation by ombre42: all we do is to run this in the premain and pass the arguments on (see step 3 for the arguments to pass on the command line). However, if you want to code the settings yourself (and package the test libraries with the premain class), you can code it like this: [java] ...other imports... import com.xebia.mysystem.test.robotlibraries.MyTestLibrary; public class RobotBackdoor { public static void premain(String args, Instrumentation inst) throws Exception { RemoteServer.configureLogging(); Logger.getRootLogger().setLevel(Level.INFO); RemoteServer server = new RemoteServer(); server.putLibrary("/spam", new MyTestLibrary()); server.setPort(9999); server.start(); } } [/java] In case you're wondering (I did): the server will keep itself running and will not get garbage collected.

Step 2: Add a Premain-Class entry in the jar manifest

An agent class is started similar to main classes (Main-Class entry): with a Premain-Class entry in the jar manifest file. In our example this one line does the magic: Premain-Class:com.xebia.robotbackdoor.RobotBackdoor If you're using Maven you will have to manipulate the packaging to add the entry. This is done with the following additions to your POM: [xml] <plugins> ...other plugins... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifestEntries> <Premain-Class>com.xebia.robotbackdoor.RobotBackdoor</Premain-Class> </manifestEntries> </archive> </configuration> </plugin> </plugins> [/xml]

Extra: package as a fat jar

You may or may not need this, but we found it convenient to create a "fat jar" that has all the dependencies the RemoteServer needs. This is done with the following addition to your POM: Note we are using the shade plugin which correctly allows the other plugin to add the manifest entry. Another "fat jar" plugin that we tried just plonked in its own manifest without allowing other plugins to manipulate it. [xml] <plugins> ...other plugins... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>false</minimizeJar> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> </plugins> [/xml] The exclusions are there because many dependency jars have these security files in them and the JVM will complain about it otherwise.

Step 3: Start up the JVM with the -javaagent parameter.

A Java agent gets loaded with the -javaagent parameter as follows (where you have packaged the above code into a "robotremoteagent.jar"): java -javaagent:/path/to/robotremoteagent.jar -jar MySystemUnderTest.jar This works fine if you have the "hardcoded" version of the Premain class, but if you use the dynamic loading mechanism of RemoteServer you will need to pass in parameters, which are distinct from the regular parameters. This is done with by adding ="my parameter list" to the -javaagent parameter: java -javaagent:/path/to/robotremoteagent.jar="--library com.xebia.mysystem.test.robotlibraries.MyTestLibrary:/spam --port 9999" -jar MySystemUnderTest.jar You can add multiple --library entries.

Conclusion and downloads

Executing Robot Framework tests within the context of a running system can be achieved by loading the RemoteServer as a Java agent. You can find the pom.xml and the .java file here: robotremoteagent-code. For the truly lazy: here is the packaged jar file of the remote agent (up to date as of the writing of this post): robotremoteagent-1.0.jar.

Get in touch with us to learn more about the subject and related solutions

Explore related posts