This post follows on from the previous (see part 1 and part 2). Here I’ll close out this series of posts by performing a bit more abstraction and finish up with some thoughts about how and to what extent the Cucumber abstraction fits in.
Continuing on directly from the last post, we have a fully working implementation of a Cucumber feature file. This feature file is matched up to a series of step definitions which in turn delegate to a page object. In this post, I want to abstract away from the page object. But how? What I want to do is focus on a user who is acting out a series of steps.
Provide a Steps Library Reference
Let’s change our RecordTodoSteps like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.testerstories.tutorial.todos.features.steps; import com.testerstories.tutorial.todos.pages.TodoPage; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; import net.thucydides.core.annotations.Steps; import static org.assertj.core.api.Assertions.assertThat; public class RecordTodoSteps { @Steps User user; TodoPage todoPage; @Given("^the todo application$") public void start_the_todo_application() { todoPage.open(); } @When("^the todo action '(.*)' is added$") public void add_a_todo_action(String actionName) { todoPage.addActionCalled(actionName); } @Then("^'(.*)' should appear in the todo list$") public void action_should_appear_in_my_todo_list(String action) { assertThat(todoPage.getActions()).contains(action); } } |
Here I’m using the @Steps annotation. The @Steps annotation marks a Serenity step library.
In Serenity, the idea of a Step Library is to add a layer of abstraction between the intent and the implementation. I talked a little about this in my somewhat generic post on Java Automation with Serenity. This idea, however, takes on an interesting focus when you throw Cucumber into the mix. After all, the idea of Cucumber feature files — and their associated step definitions — already suggest such a separate of intent (“what”) from implementation (“how”).
So are just adding a superfluous layer here? In my view, not necessarily. The step library concept of Serenity can help you organize your overall code into more reusable components. This is the case regardless of whether Cucumber is in place or not.
With this current example, what I’m suggesting here is a move away from page objects and instead saying that Cucumber steps should be done in the context of a user’s actions. So, for that, I’ve established that there will be an instance of a User class. Where should this class go?
I’m going to put this in my src/main/java root package. Specifically I’ll create a steps package that is at the same level as the pages package we created in the previous post. In this package, create a User class. Here’s what your structure will look like:

Provide a Steps Library
To get ourselves going, let’s extend this User class from ScenarioSteps:
|
1 2 3 4 5 6 |
package com.testerstories.tutorial.todos.steps; import net.thucydides.core.steps.ScenarioSteps; public class User extends ScenarioSteps { } |
ScenarioSteps refers to a set of reusable steps for use in an acceptance test suite. The idea is that any class that extends the ScenarioSteps class calls the step library. So keep in mind what we have here. We have a test steps file (RecordTodoSteps) that has a particular instance (user) annotated with @Steps. That tells Serenity that this class is going to contain a series of steps that should be reported on as part of test execution. In effect, the User class is going to be the step library.
So let’s change our test class to start doing this. Change your @Given method statement as such:
|
1 2 3 4 |
@Given("^the todo application$") public void start_the_todo_application() { user.opens_todo_application(); } |
Keep in mind what we’ve done here. This is important.
We changed the the code from todoPage.open() to user.opens_todo_application(). Is that a big deal? Well, sort of. It means we’re abstracting away from the page and more onto the user. If the user needs to delegate to the page, then that will be handled as part of the user actions. In fact, let’s create the opens_todo_application() method on the User class:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.testerstories.tutorial.todos.steps; import com.testerstories.tutorial.todos.pages.TodoPage; import net.thucydides.core.annotations.Step; import net.thucydides.core.steps.ScenarioSteps; public class User extends ScenarioSteps { TodoPage todoPage; @Step public void opens_todo_application() { todoPage.open(); } } |
Notice that here I’m referencing the page object (todoPage) in this class and I have a @Step annotated method that indicates that when this method is executed, it should be reported on in the test reporting. Keep in mind that the user class is a step library. That means it is composed of methods that are steps. You indicate which methods are steps by the @Step annotation.
Let’s replace our other statements in our test class so that they call methods on the user rather than on the page. In fact, here’s what the test class should look like (and I’ve removed imports when necessary):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.testerstories.tutorial.todos.features.steps; import com.testerstories.tutorial.todos.steps.User; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; import net.thucydides.core.annotations.Steps; public class RecordTodoSteps { @Steps User user; @Given("^the todo application$") public void start_the_todo_application() { user.opens_todo_application(); } @When("^the todo action '(.*)' is added$") public void add_a_todo_action(String actionName) { user.adds_an_action_called(actionName); } @Then("^'(.*)' should appear in the todo list$") public void action_should_appear_in_my_todo_list(String action) { user.should_see_action_called(action); } } |
Notice that any references to the page object are now gone. This means the User class, with the above new methods created, should look like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.testerstories.tutorial.todos.steps; import com.testerstories.tutorial.todos.pages.TodoPage; import net.thucydides.core.annotations.Step; import net.thucydides.core.steps.ScenarioSteps; import static org.assertj.core.api.Assertions.assertThat; public class User extends ScenarioSteps { TodoPage todoPage; @Step public void opens_todo_application() { todoPage.open(); } @Step public void adds_an_action_called(String actionName) { todoPage.addActionCalled(actionName); } @Step public void should_see_action_called(String action) { assertThat(todoPage.getActions()).contains(action); } } |
Notice that each method has a @Step annotation.
Go ahead and run this again. You should everything passes. In the test report, the steps for your scenario should show up like this:

The Abstractions
Yeah, that’s great and all … but haven’t I really just shuffled code around? In one sense, yes. But in another, very real sense, no. What I’ve done is moved the potentially more volatile code further down the stack. So my Cucumber feature file provides a certain level of intent, my test steps provide a certain level of implementation. That implementation is rooted in the actions of a user. That user delegates to a page object in this case, because that’s what is being tested. But that same user could be delegating down to a mobile emulator or to a REST service.
One thing I’ll say though is that the Cucumber abstraction still sticks out like a sore thumb to me. That’s mainly because it’s really just repeating what the code does. Consider my feature file and my code steps:
Given the todo application
When the todo action 'Digitize Supreme Power Collection' is added
Then 'Digitize Supreme Power Collection' should appear in the todo list
|
1 2 3 |
user.opens_todo_application(); user.adds_an_action_called(actionName); user.should_see_action_called(action); |
If you replace the variable names in the code with their values, you see how close everything aligns:
|
1 2 3 |
user.opens_todo_application(); user.adds_an_action_called("Digitize Supreme Power Collection"); user.should_see_action_called("Digitize Supreme Power Collection"); |
So what I really want is my Cucumber features to be a bit more declarative. How about this?
Feature: Add new todos
Users need to be able to quickly add tasks as fast as they can think of them.
Scenario: Add a new todo
* users can add an action to the todo list
Here I’ve essentially reduced the scenario to one line, that is very declarative in nature. With this in place, let’s change the RecordToDo file as such:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.testerstories.tutorial.todos.features.steps; import com.testerstories.tutorial.todos.steps.User; import cucumber.api.java.en.Given; import net.thucydides.core.annotations.Steps; public class RecordTodoSteps { @Steps User user; @Given("^users can add an action to the todo list$") public void user_adds_an_action_to_the_todo_list() { String action = "Digitize Supreme Power Collection"; user.opens_todo_application(); user.adds_an_action_called(action); user.should_see_action_called(action); } } |
Notice here that I didn’t have to change my User class at all nor did I have to change the page object, TodoPage.
If you run this, you’ll find the test report looks like this:

Pull Down vs Push Up
While this is admittedly a very simple example, I do think it showcases exactly how you want to think about “pulling down English” versus “pushing up English”. Notice, however, that to do this I had to use the “*” operator in Gherkin, which effectively just means “any step.” Not all BDD tools support this. JBehave, for example, does not. This is where I think Gherkin can becoming constricting.
Contrast some of this, incidentally, with the logic I presented in my Screenplay Serenity posts. At one point, you’ll end up with code like this:
|
1 2 3 |
givenThat(jeff).wasAbleTo(StartWith.anEmptyTodoList()); when(jeff).attemptsTo(AddATodoItem.called("Digitize JLA vol 1 collection")); then(jeff).should(seeThat(TodoItemsList.displayed(), hasItem("Digitize JLA vol 1 collection"))); |
Yes, it’s “busier” than pure English would be but, on the other hand, as we’ve just seen I can keep my English fairly constrained by sticking just to the business rules. Instead of trying to “modularize” at the level of natural language, I modularize at the code level, where that kind of practice belongs.
Further, it allows me to push English back up, so that if people do want the implementation details, the automation can serve not just as an execution mechanism but also as a documentation generator.
How Business Speaks
To drive this home a bit, here’s an example of how most business teams I work with want to describe features and scenarios.
Doctors provide information about medical conditions to patients via targeted articles. Patients need to be able to access those articles conveniently. Patients must be able to print an article Patients must be able to reprint an article Patients must be able to send an article link to an email address Doctors must be able to view activity on the article Doctors must be able to deactivate an article Doctors must be able to edit provider and location for a scheduled article
This, to me, is perfect. It’s exactly what I want from product and business teams. A statement of why this overall feature adds value. Then a breakdown of how it adds value by talking about a high-level interpretation of what the feature does to provide value. It’s now up to developers and testers to work out the logistics of what exactly the solution will look like.
The development code and test code should be developed in tandem. This way both sets of code become a common specification and thus a single source of truth. This is somewhat in line with some of my thinking from modern testing.
Notice, too, that the concept of specific users can be directly translated from those business requirements into a particular type of class (DoctorUser, PatientUser). Even better, if you go with the Screenplay approach that I talked about in previous posts, you can treat Doctor and Patient as particular types of Actor instances.
This gives you a lot more flexibility to change based on development spikes or the product team refining its understanding. How you manage that flexibility is the effective use of abstraction layers. You’ll notice that, in this post, that meant going towards a very minimal implementation of a “Gherkin” style specification.
To Cuke Or Not To Cuke
I’ll close off here with this final consideration.
Ask yourself this: why are “Gherkin-like” approaches chosen? Usually because those are “easier” to automate. But it’s not necessarily because it’s easier to speak in. People don’t “speak Gherkin.” The above example of Patient/Doctor was plenty good enough to spark further conversations and collaborations. And you’ll notice not one bit of that is in Gherkin.
So while the posts in this series have focused on the integration of Cucumber and Serenity, I want to leave you with the thought that you should, at minimum, consider the level of expression in terms of how you use Cucumber. Beyond that I would hope you consider whether or not Cucumber, and the focus on Gherkin-style feature files, is even the most effective or efficient approach.
I have read all your posts about Serenity (about Screenplay pattern and Cucumber integration) and I think that these are the best texts about it. You are writing step by step what is going there and it is very simple to understand. You helped me understand Screenplay pattern so I am changing my POM framework to it at the moment. Thanks for good job!
Hi,
Great post! But I have some questions about having multiple pages to interact with inside User @Steps. In your example you have only to interact with TodoPage, but let’s say you have two pages where in each of them you have to fill a form, eg.: Login, ResetPassword. In this example I will have to enter e-mail address on both pages. Would your code look like this?
public class Example { @Steps User user; @When("^I enter e-mail address on Login page$") public void enter_email_address_on_login_page(String email) { user.enter_email_address_on_login(email); } @When("^I enter e-mail address on Reset Password page$") public void enter_email_address_on_reset_password_page(String email) { user.enter_email_address_on_reset_password(email); } }I have this problem right now. I have many pages where I perform similar actions but in different pages. What I did in a previous project is to use @Steps per page. I used the following:
But I don’t know if there is a better way of doing it. What do you think?
Hello,
Thank you for the articles, it was very helpful, however, I have a question about how can I manipulate interactions between pages in the user class, let’s say I am on a loginPage and to verify that I was able to log in I would use an element in the other page, in other words, can you explain more how serenity deals with the webdriver and sessions
Hey there. Unfortunately, I don’t really use Serenity anymore. I just found it got a bit too heavy and cumbersome. That’s just my opinion and certainly not an indictment of the tool in some wider sense. But the reason I give you that context is because I’m probably not the best one to answer your questions. There is a Google Group and, I believe, a Slack channel that will probably serve you much better than I could at this point.
Jeff;
Another guy searching for better documentation for Serenity and Cucumber JVM test automation. I happened to read your series on the topic and thoroughly reading them again and again.
BTW, in your reply to Majda’s question you wrote “There is a Google Group and, I believe, a Slack channel that will probably serve you much better”. Would you mind posting the URLs for those two resources please ?
Appreciate your contribution to the Serenity-Cucumber JVM documentation,
–Prasad
The Google groups that I’ve searched out are the Serenity BDD Developers Group (https://groups.google.com/forum/#!forum/thucydides-dev) and the Serenity BDD Users Group (https://groups.google.com/forum/#!forum/thucydides-users). Their Slack channel has apparently been moved to the Serenity BDD Rocket.Chat (https://serenity-bdd.rocket.chat).