Sometimes in a project there is the need to run tests for your client-side code, written in JavaScript from a Maven build.
One reason might be that Maven manages a complex build life-cycle in your project and you need a close integration for your JavaScript tests, another one might be that you’re in an environment where it is complicated to install and manage additional software like an integration- or build-server.

javascript greeting class
Figure 1. A Javascript Class to be tested

Maven Dependencies

We only need two dependencies here for the Jasmine Maven Plug-in and the PhantomJS Maven Plug-in. To use them, we’re adding the following lines to our pom.xml:

<build>
	<plugins>
		<plugin>
			<groupId>com.github.klieber</groupId>
			<artifactId>phantomjs-maven-plugin</artifactId>
			<version>0.2.1</version>
			<executions>
				<execution>
					<goals>
						<goal>install</goal>
					</goals>
				</execution>
			</executions>
			<configuration>
				<version>1.9.2</version>
			</configuration>
		</plugin>
		<plugin>
			<groupId>com.github.searls</groupId>
			<artifactId>jasmine-maven-plugin</artifactId>
			<version>1.3.1.4</version>
			<executions>
				<execution>
					<goals>
						<goal>test</goal>
					</goals>
				</execution>
			</executions>
			<configuration>
				<jsSrcDir>src/main/webapp/js/app</jsSrcDir>
				<jsTestSrcDir>src/test/webapp/js/specs</jsTestSrcDir>
				<preloadSources>
					<source>src/main/webapp/js/app/GreetingService.js</source>
				</preloadSources>
				<webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
				<webDriverCapabilities>
					<capability>
						<name>phantomjs.binary.path</name>
						<value>${phantomjs.binary}</value>
					</capability>
				</webDriverCapabilities>
				<haltOnFailure>true</haltOnFailure>
			</configuration>
		</plugin>
	</plugins>
</build>

JavaScript Class

This is our JavaScript client code to be tested in form of a greeting service that ..erm.. greets things with names …

function GreetingService() {
}

GreetingService.prototype.greeting = "Hello";

GreetingService.prototype.greet = function(name) {
	'use strict';
	if (!name) {
		name = "anonymous";
	}
	return this.greeting + ", " + name;
};

Jasmine Test Specs

This is our Jasmine test spec testing different possible execution paths for the GreetingService.

As I’d like to demonstrate what a failing test looks like, the third test is written to fail.

describe("the greeting service", function () {
	var greetingService;

	beforeEach(function(){
		greetingService = new GreetingService();
	});

    it("must create a valid greeting", function () {
        var greet = greetingService.greet("foo");
        expect(greet).toBe("Hello, foo");
    });

    it("must use an altered greeting", function(){
        greetingService.greeting = 'Hey';
        var greet = greetingService.greet("bar");
        expect(greet).toBe("Hey, bar");
    });

    it("must use fallback if no name given", function(){
    	var greet = greetingService.greet();
    	expect(greet).toBe("This test will be failing");
    });
});

Directory Structure

Now that we’ve written everything we need, our final directory structure should look similar to this one:

.
├── pom.xml
├── README.md
└── src
    ├── main
    │   ├── java
    │   └── webapp
    │       └── js
    │           └── app
    │               └── GreetingService.js
    └── test
        ├── java
        └── webapp
            └── js
                └── specs
                    └── greetingServiceTest.js

Running the Tests

We’re now able to run the tests using your IDE of choice with a decent Maven Builder or using Maven on the command line – and as you can see Maven downloads the phantomjs binary for your target system for you, starts webdriver using ghostdriver and evaluates the jasmine specs with the third test failing as expected:

$ mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building maven-jasmine-phantomjs-samples 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO] --- phantomjs-maven-plugin:0.2.1:install (default) @ maven-jasmine-phantomjs-samples ---
[INFO] Downloading phantomjs binaries from https://phantomjs.googlecode.com/files/phantomjs-1.9.2-linux-x86_64.tar.bz2
[INFO] Extracting /data/project/maven-jasmine-phantomjs-tutorial/target/phantomjs-maven-plugin/phantomjs-1.9.2-linux-x86_64.tar.bz2/phantomjs-1.9.2-linux-x86_64/bin/phantomjs to /data/project/maven-jasmine-phantomjs-tutorial/target/phantomjs-maven-plugin/phantomjs-1.9.2-linux-x86_64/bin/phantomjs
[INFO]
[INFO] --- jasmine-maven-plugin:1.3.1.4:test (default) @ maven-jasmine-phantomjs-samples ---
2014-04-29 22:41:25.485:INFO:oejs.Server:jetty-8.1.14.v20131031
2014-04-29 22:41:25.505:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:56635
[INFO] Executing Jasmine Specs
Apr 29, 2014 10:41:25 PM org.openqa.selenium.phantomjs.PhantomJSDriverService
INFO: executable: /data/project/maven-jasmine-phantomjs-tutorial/target/phantomjs-maven-plugin/phantomjs-1.9.2-linux-x86_64/bin/phantomjs
Apr 29, 2014 10:41:25 PM org.openqa.selenium.phantomjs.PhantomJSDriverService
INFO: port: 15008
Apr 29, 2014 10:41:25 PM org.openqa.selenium.phantomjs.PhantomJSDriverService
INFO: arguments: [--webdriver=15008, --webdriver-logfile=/data/project/maven-jasmine-phantomjs-tutorial/phantomjsdriver.log]
Apr 29, 2014 10:41:25 PM org.openqa.selenium.phantomjs.PhantomJSDriverService
INFO: environment: {}
PhantomJS is launching GhostDriver...
[INFO  - 2014-04-29T20:41:25.673Z] GhostDriver - Main - running on port 15008
[INFO  - 2014-04-29T20:41:26.205Z] Session [a22ffc70-cfde-11e3-8869-a76f5a409c4d] - _decorateNewWindow - page.settings: {"XSSAuditingEnabled":false,"javascriptCanCloseWindows":true,"javascriptCanOpenWindows":true,"javascriptEnabled":true,"loadImages":true,"localToRemoteUrlAccessEnabled":false,"userAgent":"Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.9.2 Safari/534.34","webSecurityEnabled":true}
[INFO  - 2014-04-29T20:41:26.205Z] Session [a22ffc70-cfde-11e3-8869-a76f5a409c4d] - page.customHeaders:  - {}
[INFO  - 2014-04-29T20:41:26.205Z] Session [a22ffc70-cfde-11e3-8869-a76f5a409c4d] - CONSTRUCTOR - Desired Capabilities: {"javascriptEnabled":true,"phantomjs.binary.path":"/data/project/maven-jasmine-phantomjs-tutorial/target/phantomjs-maven-plugin/phantomjs-1.9.2-linux-x86_64/bin/phantomjs"}
[INFO  - 2014-04-29T20:41:26.205Z] Session [a22ffc70-cfde-11e3-8869-a76f5a409c4d] - CONSTRUCTOR - Negotiated Capabilities: {"browserName":"phantomjs","version":"1.9.2","driverName":"ghostdriver","driverVersion":"1.0.4","platform":"linux-unknown-64bit","javascriptEnabled":true,"takesScreenshot":true,"handlesAlerts":false,"databaseEnabled":false,"locationContextEnabled":false,"applicationCacheEnabled":false,"browserConnectionEnabled":false,"cssSelectorsEnabled":true,"webStorageEnabled":false,"rotatable":false,"acceptSslCerts":false,"nativeEvents":true,"proxy":{"proxyType":"direct"}}
[INFO  - 2014-04-29T20:41:26.205Z] SessionManagerReqHand - _postNewSessionCommand - New Session Created: a22ffc70-cfde-11e3-8869-a76f5a409c4d
[INFO  - 2014-04-29T20:41:26.556Z] ShutdownReqHand - _handle - About to shutdown
[INFO]
-------------------------------------------------------
 J A S M I N E   S P E C S
-------------------------------------------------------
[INFO]
the greeting service
  must create a valid greeting
  must use an altered greeting
  must use fallback if no name given <<< FAILURE!
    * Expected 'Hello, anonymous' to be 'This test will be failing'.

1 failure:

  1.) the greeting service it must use fallback if no name given <<< FAILURE!
    * Expected 'Hello, anonymous' to be 'This test will be failing'.

Results: 3 specs, 1 failures

2014-04-29 22:41:27.081:INFO:oejsh.ContextHandler:stopped o.e.j.s.h.ContextHandler{/,file:/data/project/maven-jasmine-phantomjs-tutorial/}
2014-04-29 22:41:27.082:INFO:oejsh.ContextHandler:stopped o.e.j.s.h.ContextHandler{/spec,file:/data/project/maven-jasmine-phantomjs-tutorial/}
2014-04-29 22:41:27.082:INFO:oejsh.ContextHandler:stopped o.e.j.s.h.ContextHandler{/src,file:/data/project/maven-jasmine-phantomjs-tutorial/}
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

Alternate Download Directory for PhantomJS

In the output from the test run above, we’re able to see that the phantomjs binaries are downloaded and stored in the target directory of our project directory (“target/phantomjs-maven-plugin/phantomjs-1.9.2-linux-x86_64/bin/phantomjs“).

In some occasions, we might want to override this e.g. to avoid downloading the binaries after every mvn clean test or something similar that wipes the target directory.

We’re able to change this path by setting the phantomjs.outputDir to a directory of our choice – in the following example we’re using the system’s temp directory as a location for our binaries.

<properties>
		<phantomjs.outputDir>${java.io.tmpdir}/phantomjs</phantomjs.outputDir>
</properties>

Update: PhantomJS Maven Plugin >= 0.3

Kyle Lieber, the author of the PhantomJS Maven Plugin has released an update now pulling the binaries from the central Maven repository. This allows us to cache those files in our local Maven repository and therefore eliminates the need to specify an alternate directory as described above.

The only addition prerequisite is, that you need to use Maven >= 3.1.0.

So an updated pom.xml could look like this one:

<project>
	[..]
	<prerequisites>
		<maven>3.1.0</maven>
	</prerequisites>

	<build>
		<plugins>
			<plugin>
				<groupId>com.github.klieber</groupId>
				<artifactId>phantomjs-maven-plugin</artifactId>
				<version>0.3</version>
				[..]
			</plugin>

			[..]
		</plugins>
	</build>

	[..]
</project>

I’ve pushed the update to the new version in the following branch in my git repo here.

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/maven-jasmine-phantomjs-example.git