Thursday, February 17, 2011

Using Google's js-test-driver and Maven

Using Google's js-test-driver and Maven


Here at Edmunds we've been looking into adopting TDD and continuous integration practices for javascript development for some time.  We've written tests in YUI test, but since we never automated them, they quickly became outdated, as any tests that are not automated will. 


We already attempt to follow TDD and continious integration best practices for our java development, but haven't really had good tools to do the same thing with javascript.  There are a number of javascript testing frameworks, such as YUITest, JSUnit, etc, that do a good job at allowing you to test your code via assertions in multiple browsers, etc.  However they usually don't integrate that well with Maven (which we use) and can be a bit cumbersome to automate. Google's js-test-driver allows us to easily integrate with our existing continious automation processes (hudson, etc) which make it such an attractive option.

After experimenting with  Js-test-driver for a day , it seems to have thought of almost everything to facilitate for TDD and continuous build practices.  I am not going to get into the specifics of TDD with javascript or attempt to outline all the features js-test-driver has, but I'll attempt to show you how to do 2 things in this post that took me a little bit to sort out:




  1. How to automate your tests using the js-test-driver Maven plugin and the latest js-test-driver core.

  2. Setup a slave js-test-driver server that uses both Safari and Firefox to run your tests against.





I assume you already know some java basics, how to use Maven and understand the concepts of using js-test-driver.  If you don't you should read up on these before continuing.  Another thing to note is that I did all this on my Mac, so there may be some corner cases if you run on Windows or another OS :) 


Required Artifacts:


Listed out below are the required artifacts you'll need to work through this post.



Building the js-test-driver Maven Plugin


Before you actually build the plugin locally you need to download the source files by running this command below:

svn checkout http://jstd-maven-plugin.googlecode.com/svn/trunk/ jstd-maven-plugin-read-only


Once you've got the plugin sources downloaded to your local machine you'll need to update the js-unit-test-driver dependency from 1.2.2 to 1.3.0. To upgrade the dependency, modify the Maven pom.xml file located at: "jstd-maven-plugin-read-only/pom.xml". I couldn't get the drivers to work without the upgrade. See snippet below:

....
 <dependency>
     <groupId>com.google.jstestdriver</groupId>
     <artifactId>jstestdriver</artifactId>
     <version>1.3.0</version> 
</dependency>
....




Next you need to install the updated JsTestDriver.jar included in the hello-world.zip to your local maven repository.  Go to the root directory of "hello-world/" and run this command:


mvn install:install-file -Dfile=JsTestDriver.jar -DgroupId=com.google.jstestdriver 
-DartifactId=jstestdriver -Dversion=1.3.0 -Dpackaging=jar



Once you've installed the latest version of js-test-driver you should be ready to install the plugin to you local maven repository, using the command:


mvn install


At this point you should have the js-test-driver maven plugin installed which uses version 1.3.0 of js-test-driver.   To verify that all this worked run this command:

mvn dependency:list | grep "com.google.jstestdriver:jstestdriver"

You should see this output below.


[INFO]    com.google.jstestdriver:jstestdriver:jar:1.3.0:test




If you don't then the plugin you built and installed locally didn't work as expected (post a comment and I'll get back to you).


Now we'll move on to the next step which is actually starting up the slave js-test-driver server...

Starting up a slave server



A slave server is the server that the tests will actually be run on. For demonstration purposes, I am going to show you how to do this on the same machine that is running the maven build. Once you are comfortable running this locally, a more realistic setup this would involve a couple of slave servers running various versions of windows, osx, linux, etc. By running multiple operating systems and browser combinations you will be able to really test out a wide range behaviors to ensure you haven't made any cross platform errors. To allow jsTestDriver to use the browsers on the slave server you need to run jsTestDriver and tell it what browsers you want to test with, then you need to start each browser and "capture" the browser window to make it available to jsTestDriver.











Starting the jsTestDriver appliction



To start up jsTestDriver you need to go to the "hello-world/" root directory and run the command below. Notice that the command specifies two browsers after the -browser argument. Since I am running everything on my mac laptop, I am going to test with both Firefox and Safari.


java -jar JsTestDriver.jar --config src/test/resources/server-jsTestDriver.conf 
--port 9876 --runnerMode INFO
--browser /Applications/Firefox.app/Contents/MacOS/firefox-bin,
/Applications/Safari.app/Contents/MacOS/Safari



Capturing Browsers



Next you need to "capture" the browser windows for the tests to be able to run.  To do this open up both Safari and Firefox (assuming you are using these same browsers) and go to "http://localhost:9876", in each browser.  You should see a "Capture this browser" link.  Click on this link.  The browsers should now be in a "captured" state, so that the tests can run against them. See the screen shots below of how the browser should look in Firefox: 



Browser not yet captured. Notice link to capture browser below:
Screen shot 2011-02-18 at 2.57.10 PM.png



Browser in captured state below:
Screen shot 2011-02-18 at 2.57.22 PM.png



Once a browser is captured you shouldn't have to re-open the browsers if you restart the slave servers, as js-test-driver will auto detect them. The browsers stay in a listenting state. That said I have seen issues with the way Safari keeps the captured browser open (you may notice how the progress bar doesn't ever actually finish loading) and will sometimes not re-register itself correctly with the slave server. Meaning that js-test-drive will think it has the browser captured when it actually doesn't. You'll know this is the case if when you run your tests you don't see them run against Safari, just Firefox. In this case it's best to just restart the slave server and browsers, and re-capture them.

Now we'll move on to actually running the tests via the maven plugin using the slave server we just configured.



Running the tests using Maven

To run the tests using Maven all you should have to do (assuming all the steps you did above are correct) is run this command below from the "helloworld/" root directory.

mvn -Pjstests test

The command above tells maven to use the profile jstests.  You should now see output similar to this below. Notice the log output indicates that the tests ran in both Safari and Firefox.
INFO: Running com.google.jstestdriver.browser.BrowserActionExecutorAction@3da1c42f
Firefox: Runner reset.
Safari: Runner reset.
Firefox 3.6 Mac OS loaded /test/main/webapp/js/Greeter.js
Firefox 3.6 Mac OS loaded /test/test/webapp/js/GreeterTest.js
Safari 533.19.4 Mac OS loaded /test/main/webapp/js/Greeter.js
Safari 533.19.4 Mac OS loaded /test/test/webapp/js/GreeterTest.js
Safari 533.19.4 Mac OS [PASSED] GreeterTest.testGreet
[LOG] JsTestDriver Hello World!
[LOG] Hello Browser!
Firefox 3.6 Mac OS [PASSED] GreeterTest.testGreet
[LOG] JsTestDriver Hello World!
[LOG] Hello Browser!
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (2.00 ms)
Safari 533.19.4 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
Firefox 3.6 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (0.00 ms)





Conclusion

Hopefully at this point all this worked for you as it did for me and you can begin to see how one could adopt practices such as TDD and continuous integration, traditionally used in the Java world when developing javascript applications.