In this post I want to show you how to integrate Cucumber into a Play (2.2.3) application.

It assumes some previous Play experience and that you know what Cucumber is about.

The example is minimal, but hopefully it is enough to get you started for bigger projects.

Setting up the SBT Plugin

Let’s start by setting up the SBT plugin. Add to “project/plugins.sbt” the following lines:

resolvers += "Templemore Repository" at "http://templemore.co.uk/repo"

addSbtPlugin("templemore" % "sbt-cucumber-plugin" % "0.8.0")

This will make it possible for SBT to find and load the plugin. However, your project still lacks the corresponding settings (refer to the official page for a complete list). For this tutorial, we’ll be overriding some defaults. Add these lines to your “build.sbt”:

cucumberSettings

cucumberFeaturesLocation := "./test/features"

cucumberStepsBasePackage := "features.steps"

Note

If you ever need to clone the github project and publish locally it could be that Play does not find your local Ivy2 (as it was in my case). The solution is to add this resolver to “project/plugins.sbt”:

resolvers += Resolver.file("Local repo", file(System.getProperty("user.home") + "/.ivy2/local"))(Resolver.ivyStylePatterns)

Setting up Cucumber-Scala

Until now we just added the ability to SBT to find and deal with cucumber files. In order to implement cucumber steps (and have good IDE integration) we need to add cucumber as a dependency.

We’ll be using the snapshot versions, so add:

resolvers += "Sonatype-Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

And finally add “cucumber-scala” as a dependency:

"info.cukes" % "cucumber-scala_2.10" % "1.1.5" % "test"

My complete “build.sbt” file looks like this:

name := "PlayProjectName" // replace with your own

version := "1.0-SNAPSHOT"

scalaVersion := "2.10.3"

resolvers += "Sonatype-Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

libraryDependencies ++= Seq(
  jdbc,
  anorm,
  cache,
  "org.scalatest" % "scalatest_2.10" % "2.1.6" % "test",
  "info.cukes" % "cucumber-scala_2.10" % "1.1.5" % "test"  
)     

play.Project.playScalaSettings

cucumberSettings

cucumberFeaturesLocation := "./test/features"

cucumberStepsBasePackage := "features.steps"

Note the ScalaTest dependency, which we’ll use later.

Features and Step-Definitions

Under the “test” directory, create the package “features.steps”. This will result in 2 directories being created (“features” and “steps”). We’ll put the “.feature” files directly under “features” and the Scala step-definitions under “steps”.

Note that we already wrote the corresponding settings for SBT to find both features and step-definitions in these locations. The reason I like to specify the locations instead of letting the SBT plugin scan the whole classpath is of course because it’s faster (it just looks on one place instead of everywhere).

When running Cucumber on the SBT console, a message should come that no features were found. Fine.

Feature

Create a file called “up-and-running.feature” under “features” (the name does not really matter, but of course you want to choose a good name).

In the file, write the following content:

Feature: Application up and running

  As a Play developer I want to see that I integrated Cucumber correctly

  Scenario: Seeing that the application is up and running
    Given my application is running
    When I go to the "start" page
    Then I should see "Hello world!"

Definitions

Let’s create a file which will contain the step definitions, but let’s leave it empty for now. Create a class “StepDefinitions” in the package “features.steps” like this:

package features.steps

import org.scalatest.Matchers
import cucumber.api.scala.{ ScalaDsl, EN }
import cucumber.api.DataTable
import cucumber.api.PendingException

class StepDefinitions extends ScalaDsl with EN with Matchers {

  // step definitions will come here...
}

Now if you run the “cucumber” on SBT you’ll get some messages telling you about unimplemented steps. You are given snippets which you can copy-paste into the Scala class and continue from there:

Given("""^my application is running$""") { () =>
  //// Express the Regexp above with the code you wish you had
  throw new PendingException()
}

When("""^I go to the "([^"]*)" page$""") { (pageName: String) =>
  //// Express the Regexp above with the code you wish you had
  throw new PendingException()
}

Then("""^I should see "([^"]*)"$""") { (expectedText: String) =>
  //// Express the Regexp above with the code you wish you had
  throw new PendingException()
}

Save the file and re-run the task. Now we get a “cucumber.api.PendingException”. Good.

Play Integration

If you look at Play’s automatically generated IntegrationSpec, you’ll see that it looks very clean and lean. That’s because of facilities provided by Play’s “WithBrowser” class.

We want to re-produce the same goodness for our cucumber-based acceptance testing!

Inside of our test-definitions, we’ll not only start and stop our Play application, but also a server to host that application and a browser to inspect / interact with the UI.

Add this import line:

import play.api.test._

Add these lines to your step-definitions class, at the beginning:

val webDriverClass = Helpers.HTMLUNIT

val app = FakeApplication()
val port = 3333 // or whatever you want

lazy val browser: TestBrowser = TestBrowser.of(webDriverClass, Some("http://localhost:" + port))
lazy val server = TestServer(port, app)
def driver = browser.getDriver()

Before() { s =>
  // initialize play-cucumber
  server.start()
}

After() { s =>
  // shut down play-cucumber
  server.stop()
  browser.quit()
}

Implementing the Steps

Now we are ready to really implement the steps. Let’s start with the navigation.

Add these imports:

import play.api._
import play.api.mvc._

Let’s implement this step like this:

When("""^I go to the "([^"]*)" page$""") { (pageName: String) =>
  val pageUrl = pageName match {
    case "start" => controllers.routes.Application.index.url
    case _ => throw new RuntimeException(s"unsupported page: $pageName")
  }
  browser.goTo(pageUrl)
}

Note the beauty of using type-safe URLs here. Even if our case is trivial, and indeed controllers.routes.Application.index.url is much longer than writing "/", it is a best practice that can pay-off later in a real and complex project.

Let’s leave this step empty:

Given("""^my application is running$""") { () =>
  Logger.debug("Yeah, application IS running")
}

Just to make sure the tests will fail let’s modify “index.scala.html” like this:

@(message: String)

@main("Welcome to Play") {

    Not what you are expecting...

}

And now let’s run the cucumber task. The steps pass because we are not done yet…

Using Fluentlenium

For the last step implementation, we are using Fluentlenium, which is a Selenium wrapper already included in Play. Add these import statements:

import org.openqa.selenium._
import org.fluentlenium.core.filter.FilterConstructor._

(the first one is not strictly necessary in this case, but you might find it useful in your projects)

And let’s implement the step like this:

Then("""^I should see "([^"]*)"$""") { (expectedText: String) =>
  val element = browser.find("body", withText().contains(expectedText))
  withClue("Expected text not found in body: " + expectedText) {
    element shouldNot be(empty)
  }
}

(Note that using browser.find("body", withText(expectedText)) would be incorrect since it would mean “the body tag which contains exactly the expected text”. It would pass in our case but only as long as the body’s content didn’t include anything else)

Let’s run the cucumber task… it should fail. Excellent.

Now that we are sure, let’s change our content of “index.scala.html” to the correct one:

@(message: String)

@main("Welcome to Play") {

    Hello world!

}

Running the cucumber task should show all green.

Recap

In this post I showed you:

  • how to setup your project to use the cucumber SBT-plugin
  • how to get started with cucumber
  • how to implement some basic Play integration
  • how to navigate to an URL type-safely
  • how to make basic usage of Fluentlenium

I hope you found it useful! Happy cucking!