Using Cucumber JVM with Selenium WebDriver

I previously posted a tutorial on using Cucumber JVM to test Java code. However, many testers are in the position of using Cucumber JVM to test web-based applications using tools like Selenium. In this post I’ll show how to use these tool solutions together.

To follow along you’re going to need a Java development environment on your machine. I’m also going to be using Maven. If you’re not sure how to get started with the Java ecosystem, you can look at the “Setting Up Java” portion of my Open Source Automation Setup page. The technology stack used for Java solutions is relatively broad and involved so what I’m going to present here is a high-level overview of using these tools together.

I’ll note that most Java-based tutorials tend to suggest using an IDE like Eclipse, IntelliJ or NetBeans. Given the static typing of Java and the helpfulness of IDEs in this context, I would generally recommend this as well. However, in this post I’m going to be assuming that you are not using an IDE. I’m going to create every file from scratch and run everything from the command line. I believe this is important as you should be able to create test solutions from the bare minimum before worrying about supporting elements like IDEs.

Creating a Project with Maven

Let’s get started by creating a project directory. You can create this wherever you want. Everything we do in this post will be in this single directory. Once you have your directory create a pom.xml file.

The pom should look like this, although after the code listing I’ll call out a few things you might want to change:

As far as the things you can change for your specific example, notice the coordinates I set for groupId and artifactId. Those can clearly be whatever you want. Or you can simply use what I have. If you’re wondering what the heck I mean by “coordinates”, this is part of Maven-speak. Check out the Maven Coordinates documentation. You can also change the ‘name’, ‘description’, and ‘url’ elements. In fact, I didn’t really need to include those with this example but I like to show pom files with the contents they should have.

As far as the other material, I’ve set up a property to reference the Cucumber version I’m going to use. Then in the ‘dependencies’ section I have a ‘dependency’ for two aspects of Cucumber.

Here cucumber-java is an important one. This is what is called a “backend module.” The choice of a backend module specifies which language you are going to write what is sometimes called the “Cucumber glue.” This “glue” refers to step definitions and hooks in Cucumber. Speaking of those step definitions, the backend module defines the format of the snippets that are generated when Cucumber discovers a missing step definition. So if you wanted to write code in Groovy, Scala or JRuby, you would instead include modules cucumber-groovy, cucumber-scala, cucumber-jruby and so on.

The cucumber-junit is referred to as a “runner module.” Cucumber provides a class that offers a command-line interface for running your test logic. That said, it’s quite likely that you want to run your features from within an IDE and/or from a continuous integration server. In that case, you may want to interact with the features via a common test API. JUnit serves as one of those common test APIS and that’s what I’m using here. You could also use cucumber-testng here.

Try this at the command line:

mvn dependency:tree

You should see something like this in the output:

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ symbiote ---
[INFO] com.testerstories.testing:symbiote:jar:0.1.0-SNAPSHOT
[INFO] +- info.cukes:cucumber-java:jar:1.2.4:test
[INFO] |  \- info.cukes:cucumber-core:jar:1.2.4:test
[INFO] |     +- info.cukes:cucumber-html:jar:0.2.3:test
[INFO] |     +- info.cukes:cucumber-jvm-deps:jar:1.0.5:test
[INFO] |     \- info.cukes:gherkin:jar:2.12.2:test
[INFO] \- info.cukes:cucumber-junit:jar:1.2.4:test
[INFO]    \- junit:junit:jar:4.11:test
[INFO]       \- org.hamcrest:hamcrest-core:jar:1.3:test

Here you’ll see that you get a few more dependencies than I specified in the POM. The cucumber-core is always needed. As its name would imply, this is what implements the core functionality of Cucumber. If you’re using a dependency management tool, like we are with Maven here, then you’ll never need to specify cucumber-core because the other Cucumber jars that you have in place will depend on it and thus automatically put it in place for you. However, if you’re downloading JARs manually, then you’ll need to download this. As far as downloading manually, this would mean you are grabbing jars from the cukes portion of the public Maven repo.

There are some JARs that Cucumber depends on that are not part of a specific Cucumber release and you can also see those in the tree above. Gherkin is the name of the dialect that Cucumber uses to specify feature files. As you can see, the relevant gherkin jar is automatically a part of your dependency. Cucumber makes use of some other tools as well that have a lot to do with the internal plumbing of how the feature file constructs connect up with step definitions. These, and others, are packaged in the cucumber-jvm-deps jar.

Finally, Cucumber allows different reporting options. The most common of those, beyond the console output, is HTML. That dependency is automatic as well.

Create the Supporting Directories

From your project, create a directory structure of src/test/resources. This is where feature files are going to be stored.

Also from your project, create a directory structure of src/test/java. This is where we’re going to store our test logic. Specifically, we’re going to put our test execution logic here as well as our test runner.

I should note that it’s a common practice to use a package structure within this directory. In my case, I’ll use “com.testerstories” — which happens to match what I put in my pom file, although that’s not strictly necessary. What that means is I’ll create a further directory structure of src/test/java/com/testerstories.

So if you follow these steps, you’ll end up with the following:

.
pom.xml
 src
    test
        java
            com
                testerstories
        resources

Much of this may seem like a lot of busy work but do keep in mind I’m doing all this manually to show how things are setup. If you are using an IDE to create your Maven projects, or set up your packages, things are often a bit less cumbersome.

Create the Test Runner

In the src/test/java/com/testerstories directory, we’re going to create a test runner that will use Cucumber. So in that directory create a file called TestRunner.java. Start it off by putting the following Java code in place:

Here I’m importing the JUnit runner. This means Cucumber will use JUnit to execute the tests. The TestRunner class contain no code and will remain empty. All this class will do is tell JUnit to invoke the Cucumber JUnit runner. How will this work? Let’s make a slight change:

Here I’m using Junit’s RunWith to override the default runner and instead tell JUnit to use the Cucumber runner, which is available in the Cucumber class. This means the TestRunner class has been instrumented in such a way that it will search for feature files and run them, providing the output back to JUnit in a format that it understands. You can control how Cucumber runs by annotating the test class with various options. Let’s add the following:

This is setting Cucumber so that it will be display a “pretty” format, which basically means console output in color, as well as an HTML report that will be stored in the target/cucumber-html-report directory.

Just to make sure everything is hooked up, try the following command:

mvn clean test

This will run the ‘clean’ and ‘test’ goals of Maven and, given the setup in the POM file, that should trigger Cucumber to execute. You should see these results on the command line:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.testerstories.TestRunner
No features found at [src/test/resources]

0 Scenarios
0 Steps
0m0.000s

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.217 sec

Results :

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

Create a Feature File

Now let’s create a feature file in src/test/resources. Note that you can make various directories within this directory if you want to segregate how your features are stored. For now just create a login.feature and put the following in it:

Feature: Authentication

  Scenario: Successfully logging in
    Given the Symbiote home page
    When logging in as an admin
    Then the home page navigation is available

Now you can run the mvn clean test command again and you should find that you get step definitions provided for you by Cucumber in the console. If you were doing this via an IDE, the step definitions would be listed in the console view of the IDE.

Create a Steps File

Let’s create a place for those step definitions. In your src/test/java/com/testerstories/ directory, create a file called LoginSteps.java. Put the following in that file:

Note that I’ve basically just pasted the output of Cucumber but you do have to paste that inside the LoginSteps class. Also make sure that you have the appropriate imports at the top, as shown above.

If you run the following (using mvn clean test as before), you’ll see that the feature file and the step definitions are connected. However, since the initial step throws a PendingException, execution stops immediately.

Java 8 Step Definitions

I’ll do a bit of a digression here. Notice the format of those step definitions? You can, according to some people, make these a little nicer with Java 8 lambdas. However, there’s a major bug currently in place (as I write this) that will cause this to not work. I’ll document the change here just so you can see how things would work.

You have to change your POM so the cucumber-java dependency to this:

This will tell Cucumber to generate Java 8 style step definitions, which basically means lambdas. If you are using Java 8 and this approach, your LoginSteps.java file would look like this:

I’m actually not convinced this is any better than the non-lambda route, to be honest. Regardless of that, as I mentioned there is currently an open issue with Cucumber JVM and Java 8. Or, more specifically, about some JDK versions. That issue states that the problem is with OpenJDK only but that’s not the case. The issue happens with Oracle JDK versions as well.

One thing I should note is that if you’re going the Java 8 route, you might get this error when you try to run the above:

lambda expressions are not supported in -source 1.5

This has to do with the maven-compiler-plugin and what version of Java it’s compiling for. By default, it seems to use Java 1.5. So you have to add this plugin to your POM and then update it for a more recent Java version. You can add a ‘build’ section under the ‘dependencies’ section as such:

This will tell the compiler to use Java version 1.8 (which is Java 8). Again, though, if the issue I mentioned earlier with Cucumber JVM and Java 8 is still present, you will get an error like this:

Caused by: java.lang.IllegalArgumentException: Wrong type at constant pool index

I’m going to stick with the non-Java 8 approach. That being said, you can still keep the maven-compiler-plugin in place if you added it. You’ll probably want to do that anyway so you can be sure you are compiling to a more recent Java. And, to be sure, once that issue is fixed, the Java 8 version of the logic I just showed you should work perfectly.

Adding Logic To the Steps

Picking up where I left off, if you now run your tests, you’ll find that the first step fails indicating that it is pending. This is where we have to bring in WebDriver because the steps are going to call out to my web application. We’ll have to add this as a dependency to our pom. First let’s update the properties:

Now add the following to your dependencies:

As with Cucumber you could apt to grab any necessary Selenium WebDriver jars manually. You can find all Selenium Maven artifacts directly in the SeleniumHQ Maven repository.

Now let’s change our step file:

My goal here isn’t to explain every nuance of Selenium WebDriver just as it isn’t to explain every nuance of Cucumber JVM. Instead my goal is to get you a working start so you can explore further. However, in the interests of not throwing you totally in the deep end without any help, you’ll note in the above code that I import that necessary WebDriver classes that will be used for example.

Line 16 is where I instantiate the Firefox implementation of the WebDriver interface. WebDriver is an interface whose concrete implementation is done in two classes: RemoteWebDriver and HtmlUnitDriver. FirefoxDriver is a subclass of the RemoteWebDriver class, which extends the RemoteWebDriver class more specifically for the Firefox browser. Similarly, there are InternetExplorerDriver, ChromeDriver, SafariDriver, AndroidDriver, and IPhoneDriver classes, which are specific implementations for the respective browsers and devices.

In line 17, I use one of the methods of the WebDriver interface called get() to make the browser load the requested web page on it. If the browser, in this case Firefox, is not already opened, it will launch a new browser window.

In line 18, I’m using a basic JUnit assertion to check if the title of the page that was navigated to is “Symbiote”. Notice that make this check by calling yet another method on the WebDriver interface, getTitle().

Finally, in line 19 I call the quit() method on the driver to close the interface, which makes sure that the browser itself is shut down and exits cleanly.

There’s lots of problems with this code and I bet you can spot some of them right away. Rather than delineate all of the problems, I’ll simply show you how you can make this a little more robust:

Here I’m setting up a WebDriver instance outside of any of the methods. I’m using a @Before hook to say that prior to any test, the new driver will be created. I’m using an @After hook to say that after the test as a whole has been completed, the driver will be closed down. Notice that these @Before and @After hooks take place in the context of an individual test. When using Cucumber all of the steps in a scenario are considered a single test. So @Before is run once and after that the @Given, @When, @Then methods are run. Only after those three step methods have completed is the @After method run.

If you run this, you should find that it opens a Firefox browser and navigates to the page just as it did before.

Filling Out the Steps

Now let’s add logic to the remaining steps:

A web page is comprised of many different HTML elements, such as buttons, links, a body, labels, forms, and so on, that are named WebElements in the context of WebDriver. Together, these elements on a web page will achieve the business functionality. Consider the login functionality on my site:

As you can see in the test logic I added, I’m using the findElement() method quite a bit. Along with that, I’m using the By.id() method, and the WebElement interface. The findElement() and By() methods instruct WebDriver to locate a WebElement on a web page, and once found, the findElement() method returns the WebElement instance of that element. Locating an element is clearly the first step before executing any user actions on it. WebDriver’s findElement() method is your way of locating an element on the web page.

To explain the code a bit, the input parameter for the findElement() method is the By instance. The By instance is a WebElement-locating mechanism. There are eight different ways to locate a WebElement on a web page. They are located by Name, ID, TagName, Class, LinkText, PartialLinkText, XPath, and CSS.

The return type of the findElement() method is the WebElement instance that represents the actual HTML element or component of the web page. The method returns the first WebElement that the driver comes across which satisfies the locating-mechanism condition. This WebElement instance will then act as a handle to that component from then on. Appropriate actions can be taken on that component by the test script developer using this returned WebElement instance. You can see I do that with the click(), sendKeys() and submit() methods.

Incidentally, if WebDriver doesn’t find the element, it throws a runtime exception named NoSuchElementException, which the invoking class or method should ideally handle although I’m not going to get into that here.

You’ll notice I’m also giving a simple example of one way of using “wait state” functionality in Selenium. Lines 46 and 47 establish a WebDriverWait instance, which will wait for a certain condition on the driver for ten seconds. If within ten seconds that condition has not become true, the exception is thrown. In this case, I use the ExpectedConditions interface to state that the condition is visibilityOfElementLocated and I pass in By.id() method call to find a particular element. So the simple way to describe that code is “wait 10 seconds for the element with an id of ‘navlist’ to be visible on the browser.”

In this case, “visible” means that the element in question is present in the DOM but is also visible on the page. If you only want to check for the presence of the item in the DOM, you could change the method to presenceOfElementLocated.

Wrapping Up (For Now)

This was a lot to cover in one post so I’ll stop here. If you were able to follow along, you’ve now seen how to use Cucumber JVM along with Selenium WebDriver and have Gherkin-style test specifications used to drive actions against a web application in a Java context.

In my next post on this topic, I’ll cover abstracting some of the details away from the test logic by using the common page object pattern. I’ll also add a bit more logic to the feature file so we can see how this all works running more than one test.

Share

About Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.
This entry was posted in Automation, Cucumber-JVM, Java, Selenium. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *