Running JavaScript Tests with Maven, Jasmine and PhantomJS
May 4th, 2014 by Micha KopsSometimes 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.
Contents
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 Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/maven-jasmine-phantomjs-example.git
Resources
Tags: bdd, devop, ghostdriver, headless, javascript, js, maven, phantomjs, tdd, testing, webdriver, yasmine
May 18th, 2014 at 4:42 am
Hi, nice quick post on using the Jasmine Maven Plugin and the PhantomJS Maven Plugin. I’m the maintainer for both of those plugins and I just wanted to let you know that I released version 0.3 of the PhantomJS Maven Plugin on May 2nd. In this version PhantomJS is now downloaded out of the Maven Central repository and cached in your local repository so you’ll no longer need to do the workaround you’ve mentioned for avoiding losing the binaries during a mvn clean.
Also, just wanted to say I’m glad you find these plugins useful.
Thanks,
Kyle
May 18th, 2014 at 2:13 pm
Hi Kyle,
thanks for your remark and the helpful plugin update – I’ve updated my article to reflect these changes.
Keep up the good work!
Micha
November 22nd, 2014 at 12:37 pm
I’m using the exact same configurations found on the official plugin site and this blog but i get this error when i run mvn test
Unable to parse configuration of mojo com.github.searls:jasmine-maven-plugin:2.0-alpha-01:test for parameter phantomjs.binary.path: Cannot find default setter in class com.github.searls.jasmine.mojo.Capability
Not sure why. I’m using mvn 3.2.3
November 24th, 2014 at 5:47 pm
Hi, I’m unable to reproduce the error described. Did you clone my repository and run mvn test?
Perhaps the download of the artifact from the maven repository failed – you could have a look at your local maven repository at ~/.m2/repository and simply delete the directory containing the dependency posted above.
June 19th, 2015 at 3:57 am
I got this error, How could i address this configuration. Thank you very much
Failure to transfer com.github.klieber:phantomjs-maven-plugin:pom:0.4 from https://….somethig….
June 19th, 2015 at 12:17 pm
Hi Leelar,
the mentioned plugin is available on the global maven repository (up to version 0.7 atm): http://mavenrepository.com/artifact/com.github.klieber/phantomjs-maven-plugin.
If the download our the Maven artifacts failed, please remove the corresponding directory in home directory e.g. ~/.m2/repository/com/github/klieber/phantomjs-maven-plugin and run Maven again to download the dependencies again (and you might use the latest version available)
Please feel free to contact me if you need further help! :)
September 9th, 2015 at 10:52 pm
Hi,
Nice article to get started with.
Do we have a way to show the result on the console. (debug logs, result value etc…)
Thanks,
Raj