Using PhantomJS with Play

September 18, 2013
Play! Play Testing PhantomJS

Out of the box for the Play framework, you have two options for browser based testing, Firefox or HtmlUnit.

Basically, you’ve got this test that is created for you when you generate a new Play application. Switch over the HTMLUNIT constant to  FIREFOX  to begin using Firefox through webdriver.

    @Test
    public void test() {
        running(testServer(3333, fakeApplication(inMemoryDatabase())), HTMLUNIT, new Callback() {
            public void invoke(TestBrowser browser) {
                browser.goTo(“http://localhost:3333");
                assertThat(browser.pageSource()).contains(“Your new application is ready.”);
            }
        });
    }
HtmlUnit (http://htmlunit.sourceforge.net/) does have a couple advantages over a real browser like Firefox.
  1. It’s Faster
  2. It’s headless, therefore doesn’t need a display. Useful in CI environments like Travis (https://travis-ci.org/). Yes you can use Xvfb (http://en.wikipedia.org/wiki/Xvfb) but that’s just another dependency.  Also handy locally so you don’t have numerous browser windows popping up when running tests
But the biggest drawback is that the Javascript support within HtmlUnit is pretty average. I’ve always run into issues using it when I’ve done anything remotely complicated in Javascript. It uses Mozilla’s Rhino (https://developer.mozilla.org/en/docs/Rhino) under the covers.



So enter PhantomJS (http://phantomjs.org/). It’s a headless WebKit browser with a Javascript API. Being WebKit based, I’ve found that it supports Javascript much better than HtmlUnit.

So how do you integrate it into your Play application? In a few steps.


  1. Firstly, you need to install PhantomJS. There are a number of ways to achieve this, but I just use brew (http://brew.sh/). So I run the following command.

    brew install phantomjs
    On the phantomjs website (http://phantomjs.org/) there are instructions for other Operating Systems.

  2. Add GhostDriver (https://github.com/detro/ghostdriver) which is an implementation of WebDriver for PhantomJS. To achieve this go to your
    Build.scala
    and add GhostDriver.

      val appDependencies = Seq(
        // Add your project dependencies here,
        javaCore,
        javaJdbc,
        javaEbean,
        “com.github.detro.ghostdriver” % “phantomjsdriver” % “1.0.4” % “test”
      )
    
  3. Change the test to use GhostDriver.
        public void test() {
            running(testServer(3333, fakeApplication(inMemoryDatabase())), org.openqa.selenium.phantomjs.PhantomJSDriver, new Callback() {
                public void invoke(TestBrowser browser) {
                    browser.goTo(“http://localhost:3333");
                    assertThat(browser.pageSource()).contains(“Your new application is ready.”);
                }
            });
        }
    
  4. Re-run the test

    $ play test
    If everything has worked correctly, you should have output like the following

    2013-09-19 11:29:06.727 phantomjs[4326:707] *** WARNING: Method userSpaceScaleFactor in class NSView is deprecated on 10.7 and later. It should not be used in new applications. Use convertRectToBacking: instead.
    PhantomJS is launching GhostDriver…
    [INFO  - 2013-09-19T01:29:06.832Z] GhostDriver - Main - running on port 15803
    [INFO  - 2013-09-19T01:29:06.993Z] Session [e04d1ec0-20ca-11e3-89ad-45bbd56cac5d] - _decorateNewWindow - page.settings: {“XSSAuditingEnabled”:false,“javascriptCanCloseWindows”:true,“javascriptCanOpenWindows”:true,“javascriptEnabled”:true,“loadImages”:true,“localToRemoteUrlAccessEnabled”:false,“userAgent”:“Mozilla/5.0 (Macintosh; PPC Mac OS X) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.9.2 Safari/534.34”,“webSecurityEnabled”:true}
    [INFO  - 2013-09-19T01:29:06.993Z] Session [e04d1ec0-20ca-11e3-89ad-45bbd56cac5d] - page.customHeaders:  - {}
    [INFO  - 2013-09-19T01:29:06.993Z] Session [e04d1ec0-20ca-11e3-89ad-45bbd56cac5d] - CONSTRUCTOR - Desired Capabilities: {“platform”:“ANY”,“browserName”:“phantomjs”,“version”:“”}
    [INFO  - 2013-09-19T01:29:06.993Z] Session [e04d1ec0-20ca-11e3-89ad-45bbd56cac5d] - CONSTRUCTOR - Negotiated Capabilities: {“browserName”:“phantomjs”,“version”:“1.9.2”,“driverName”:“ghostdriver”,“driverVersion”:“1.0.4”,“platform”:“mac-10.8 (Mountain Lion)-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  - 2013-09-19T01:29:06.993Z] SessionManagerReqHand - _postNewSessionCommand - New Session Created: e04d1ec0-20ca-11e3-89ad-45bbd56cac5d
    [INFO  - 2013-09-19T01:29:07.949Z] ShutdownReqHand - _handle - About to shutdown
    [info] IntegrationTest
    [info] + IntegrationTest.test
    [info]
    [info]
    [info] Total for test IntegrationTest
    [info] Finished in 0.0 seconds
    
  5. You can also take screenshots with PhantomJS, which is very useful for diagnosing failures and debugging.

        @Test
        public void test() {
            running(testServer(3333, fakeApplication(inMemoryDatabase())), org.openqa.selenium.phantomjs.PhantomJSDriver.class, new Callback() {
                public void invoke(TestBrowser browser) {
                    browser.goTo(“http://localhost:3333");
                    assertThat(browser.pageSource()).contains(“Your new application is ready.”);
                    browser.takeScreenShot(“/tmp/screenshot.jpg”);
                }
            });
        }
    
I’ve created a sample app here with it all set up https://github.com/codingricky/sample-play.

Happy testing!