Java Automation with Serenity

Serenity is a Java-based library for test automation that wraps and extends WebDriver and JUnit functionality. Serenity also wraps around BDD style tools like Cucumber and jBehave. It even has some nice Jira integration. These benefits aside, Serenity can be a little hard to wrap your head around so I’m hoping this in-depth post will aid automaters who want to give it a try.

In an almost perfect example of what I talked about regarding putting thought into test tool names, the Serenity project used to be called Thucydides and I mention that because some of the documentation (and the web site as a whole) still use that name and, at the time of writing this, some of the Java packages you’ll see are still called that as well. Serenity actually goes by the name Serenity BDD, but I think adding the BDD is not a good choice since this is an approach you can use Serenity with, rather than one that Serenity forces you to adopt.

Serenity is designed by John Ferguson Smart and the library clearly is a very opinionated micro-framework in many ways, essentially encoding Smart’s ideas of what makes good automation. It’s a modular library and you can see that in the Serenity GitHub repo. Perhaps oddly, the library bills itself as an “automated acceptance tests reporting library” but it actually encodes an entire aspect of convention-based execution, usually fueled by annotations. So I would argue it’s more than just a “reporting library.”

When I first started looking at Serenity, I was very much put off by its seemingly unfocused presentation not to mention the poor description attributed to it. However, when I looked past that and just started getting familiar with it, it’s quite a good tool to be aware of.

As mentioned, Serenity sits squarely in the Java world. You can use Maven, Gradle, or Ant to build Serenity style projects. In this article, I’ll use Gradle just because I tend to prefer more compact formats than Maven’s XML. This means that if you want to play along, I’ll assume you’re more than able to install Java and Gradle on your operating system of choice. You can certainly use any IDE but I won’t be assuming any particular one for this article.

Getting The Project Set Up

In your IDE of choice, go ahead and create a gradle project. I’ll assume you have some directory called learn-serenity that is going to be your project root. If you don’t have an IDE or are not creating a Gradle project via the IDE, just create the directory and then enter this at the command line in that directory:

$ gradle init

Edit the build.gradle file and put the following in place:

I’m not going to talk too much about Gradle here. The main thing to know is that you have to apply the Serenity BDD plugin. This plugin is then placed into the Gradle build path in the buildscript section. This is what lets Gradle find and apply the plugin to your project. In the dependencies section, you’ll see that I’ve added the Serenity BDD dependencies. You’ll pretty much always have the serenity-core and then you’ll choose some other dependency that corresponds to the testing library you are using. Here I’m using JUnit, but there are also specific dependencies for Cucumber and jBehave.

The serenity-gradle-plugin adds some tasks to your project. I won’t go into too much detail about those here except to say that, when learning, your most likely path is to run some tests and produce what’s called an aggregate report, no matter what the test results are. This means that you can run your tests from the command like this:

$ gradle test aggregate

Incidentally, the continueOnFailure setting does just what it sounds like. It allows tests to keep running even if one of them fails. All reports will be stored in a target/site/serenity directory by default.

The official documentation covers using Maven for Serenity projects, should that be your choice.

As one final note, in the logic that follows I’m going to use my own Decohere test site as my running example. You should feel free to use whatever you want. A lot of people like to use The Internet Examples archive.

Creating the Test

If you’ve created a Gradle project, you’re going to have the standard path of src/test/java. Within that directory, create a package that will be the root package of your project. I’ve called mine com.testerstories.tutorial.serenity but, of course, you can do whatever you like.

Now, within that package, create another package: features.authentication.

Incidentally, if you work with Java, you know that packages are just a structuring element. You don’t have to create exactly these packages in terms of what Serenity wants. I’m doing that because it will make some of this easier to describe and because it’s likely going to be what you see in Serenity documentation or other tutorials.

Within the features.authentication package, create a class called WhenAuthenticating. If you’re not using an IDE which tends to abstract away the filenames, this class corresponds to a file called WhenAuthenticating.java. Here’s what things will look like conceptually:

This will be a test class, using jUnit. So start with this:

Notice that I’m annotating a particular method in this class with @Test, just as I would any jUnit execution. If you’re not using an IDE to help you along, make sure you add the appropriate imports.

Here I’m essentially mapping out how I want this jUnit test to execute. I’m doing this in the tradition of “test first”, in that I’m writing logic that uses classes and methods that don’t exist yet. So what is the “user” going to be? That user is going to be an instance of a persona. So now let’s create that user type. Add the following:

This is going to be the representation of a user on the Decohere site. This class is going to be a “steps library” because it has a number of steps that the user goes through as the test is performed. So we’re going to annotate that user object with the @Steps annotation.

The @Steps annotation basically tells Serenity that the user instance is a Step Library. Step Libraries are used to add a layer of abstraction between the “what” and the “how” of the tests. From a code perspective, what this will do is inject an object into the tests that you can use to perform the individual steps or actions that carry out the test. You’ll note that you never directly instantiate this instance. Anything with the @Steps annotation is automatically instantiated for you.

At this point, let’s create a steps package, on the same level as features. Just to be clear, here’s what this will look like:

In that package, add the class DecohereUser. Again, this corresponds to a file called DecohereUser.java. And then make sure that your test references this new class.

Now you want to create methods for each of the steps on that class. The IDE will make this quite easy but essentially just make the DecohereUser class look like this:

You have to make sure to tell Serenity that those methods are each a step that will be reported on in the test. You can do that using the @Step annotation.

The @Step annotation marks a particular method as being one that will be recorded as part of test execution and thus will be generated as part of the test report. By default, the name of a step is derived from the method name. If you wanted something more readable, you could add a String parameter to the @Step annotation. For example, the second method above could be done like this:

Creating the Test Runner

Now let’s go back to the WhenAuthenticating class. You have to tell jUnit that this class is a Serenity test. You can use the JUnit @RunWith annotation and pass it the SerenityRunner class.

Add WebDriver To The Mix

This is a web test that we’re going to execute against a browser. This means you’re going to need to give Serenity a WebDriver instance. To do that, create an instance of the WebDriver class. Then you can annotate it with the @Managed.

The @Managed annotation also provides several useful parameters. The driver parameter lets you define what WebDriver driver you want to run these tests in. The default is firefox. You can set it to run a different browser if you want, as such:

This will inject the driver into the test and into any page objects. This way you don’t have to worry about setting up the driver, opening a specific page with it, closing the driver, and so on.

Implement the Test Logic

Now the test logic itself has to be put in place. This means filling in those @Step methods. As I did with the test method in the WhenAuthenticating class, I’ll start to define things as I want them to be and add in the appropriate classes as I go.

This means I need a decohereHomePage instance.

First, let’s make a package called ui, at the same level as features and steps.

Now create the class DecohereHomePage in that package. Remember that this is a file called DecohereHomePage.java. That home page class is going to extend page object.

After you create this, make sure your DecohereUser class is updated:

One thing you’ll notice is that once you have done this, the open() method is automatically recognized. That’s happening because when a class is declared as a page object, Serenity automatically makes certain methods on that class available, one of them being the open() method. Going back to the DecohereHomePage class, since we’re using this class to open the home page, we should declare a default URL. This is the URL that will be used when that open() method is called. This can be overridden via command line properties.

Now we have to add logic in the other methods.

With that in place, add the loginAsAdmin() method to the DecohereHomePage class and put the following logic in it:

You’ll see here that Serenity allows you to mimic the popular jQuery selector pattern. Specifically, you can use the $() method with an XPath or CSS expression. When you do this, Serenity, behind the scenes, uses a WebDriver “find by” to get the element. You could also do the traditional @FindBy annotation and the PageFactory if you wanted to go that route.

Let’s fill in our last step:

Note that I’m using AssertJ here so the import needs to be in place. You could certainly use Hamcrest or any other assertion library that you like. I’m partial to AssertJ. But where’s that landingPage in line 7 coming from? Well, there’s another class I need to create.

Create a class in the ui package called LandingPage (corresponding to a LandingPage.java file). Put the following in it:

Now back in the DecohereUser class, add an instance variable for the LandingPage:

That should be it! Now you can either run that single class via your IDE or, at the command line, just run

$ gradle test aggregate

You should find this passes. You can check the report that was generated by looking at:

target/site/serenity/index.html

Adding Navigation

Let’s add some more logic just to make sure we’ve got the hang of this. I’ll move a little faster this time. Create a new package called features.navigation. Here’s what this looks like:

In that package, create a class called WhenNavigating. Let’s start off with this:

You’ll see I’m reusing the DecohereUser we already set up. Of that logic, the calls to navigatesToSiteArea() and shouldSeePageTitleContaining() do not yet exist.

Before you can create the navigatesToSiteArea() method, you have to deal with SiteArea. I’m using that as a way to model my site. The site is made up of various areas, one of which happens to be a Project Overlord page. So create another package, this one called models. This will be on the same level as features, steps, and ui.

Create an enum in the models package called SiteArea. It can look like this:

Now you can add the navigateToSiteArea() method to the DecohereUser class. Do remember that this needs a @Step annotation.

What I want to do here is have the navigation bar be represented by its own “page” object. So let’s do this:

We’ll want to add an instance of this as we did with DecohereHomePage and LandingPage:

Create a class SiteAreaNavigation in the ui package. It will, like all the others in that package, extend PageObject. You can then create the selectArea() method in that class and you should have this:

The action in that method can be as follows:

Here I’m clicking my Pages dropdown link, which exposes the menu. Then I’ve shown how you can combine the jQuery style approach and the traditional “find by” approach of Selenium. Admittedly, in the normal course of affairs I could create @FindBy objects, as mentioned before. Here I purposely didn’t go the traditional page object route of Selenium just to show you a slightly different way of handling things.

Now, finally, we still have one method missing from our user class. In WhenNavigating, we have a method shouldSeePageTitleContaining. Go ahead and define that on the user class. Remember to annotate it with @Step. Let’s also add an assertion (using AssertJ) that checks for the title:

But what’s that currentPage? That currentPage is going to be a placeholder class. This is another class that we can define that class in the ui package. First make sure you declare an instance as we did with the other:

Then create the CurrentPage user in the ui package and have it look like this:

This class doesn’t do much. All it does is return an instance of page object that represents the current page we are on. This is another example of Serenity providing convention-based logic on your behalf.

Once again, at this point, you should be able to run all the tests via the same command as before. If you are using an IDE, like IntelliJ or Eclipse, keep in mind that Serenity is simply wrapping jUnit, which means you can run the tests from the IDE just as you would any other jUnit test.

Whew!

Okay, that was a whirlwind tour of some of the basics of using Serenity. I’ve presented most of this in the lowest common denominator way, in terms of using simple page objects and doing nothing more than running my tests via jUnit.

Serenity also encapsulates what it calls a Screenplay pattern — what you might know as a Journey or Workflow pattern. I personally find this to be a much more effective way to structure automated logic. Serenity also allows you to put a BDD style abstraction in front of your tests, using Cucumber-JVM or jBehave, and then having those abstraction layers delegate down to test logic.

I’ll cover these various aspects of Serenity in a future post.

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, Java, Serenity. Bookmark the permalink.

3 Responses to Java Automation with Serenity

  1. Rodrigo says:

    Very useful example with clear explanations.

    Thank you!!!

  2. Santhi says:

    Good explanation. Could you please do one scenario with serenity screenplay .I am getting compilation errors in “when then” though I have screenplay jar file in maven.

    • Jeff Nyman says:

      I may get back into Serenity but, to be honest, I’m not sure. It changes quite a bit and those changes are rarely documented well, if at all. I do have the screenplay posts that I started awhile back.

      I may try to get back into this but I just don’t want to promise anything. While I appreciate the work that goes into Serenity, I just think it is become too bloated of a tool and I’m more in the habit of crafting solutions around microframeworks rather than the monolith that Serenity is becoming.

Leave a Reply

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