Screenplay Pattern with Java, Part 3

This post follows on from the previous (see part 1 and part 2). Here we’ll wrap up the test we started with by making sure the test actually performs some sort of verification. This will complete my initial tour of screenplay-based functionality within the context of Serenity.

At this point, we’ve done a lot of coding and setting up our test. But we don’t actually have a test because we don’t check anything! So now let’s go back to our test class — AddNewTodo — and figure out what we want to check for to say that this test has been successful.

Going back to the domain model we’ve been building up, as with all test steps, it’s important that we consider the tasks from the point of view of the actor. In this case, what would Jeff expect to see so that he knew that the action he took was successful? Think of how you might explain it to a person: “Jeff should see that the displayed todo items list has a list item called ‘Digitize JLA vol 1 collection’.” So let’s translate that basic statement into code. Add the following line to your test method (and I’m showing the relevant imports as well here):

Here I’m finally using the final of the triumvirate: a then() method to go along with the given and when methods used previously. I’ll quickly note here that the seeThat() method is provided as part of the GivenWhenThen class. Your IDE will likely wrap up the imports at this point, as I’m showing in line 14.

The one thing that will be unresolved at this point is the TodoItemsList. Before we worry about implementing that, let’s take a step back and unwrap a bit of what is going on with our newly added test line.

The should() method, which is being called on an Actor instance, takes a list of Consequence objects. These are objects that you expect to return true. You create a Consequence using the GivenWhenThen.seeThat() static method.

That method is taking two parameters. It takes a Question object, which should be an object that is checking some aspect of state on the application, and a Matcher object which effectively serves as an assertion or expectation. Just take a moment to make sure you see how this bit of code is hanging together. Our Question should return a value representing some aspect of state and our Matcher should describe what we expect that value to be.

In this particular case, you’ll note that I’m using a Hamcrest matcher, but you can certainly use any assertion library you prefer. AssertJ, for example, is a very good one.

The idea of a Consequence class is deliberate in terms of the naming. The idea is that there is an expected consequence as a result of a scenario being carried out. This consequence is placed in the “viewpoint” (code context) of the actor via the should() method.

Questions

So let’s start implementing some of these ideas in code by creating our TodoItemsList. Let’s create a new package called questions. This will be in the src/main/java root package and will sit at the same level as the tasks and ui packages.

Let’s have this Question class implement the Question interface. A Question object must provide a static method called answeredBy() and your IDE will likely prod you to include this. So to start off with your class will look like this:

Looking at this code you might recognize that this is somewhat similar, from a code perspective, to what we did when we handled tasks. In fact, Question objects are similar to Task and Action objects. One major difference, of course, is that instead of the performAs() method used for Tasks and Actions, a Question class needs to implement the answeredBy() method.

A few things to note here is that the answeredBy() method is returning a generic Object. We’ll want to change that. Also, we most certainly don’t want to return null. To understand what we want to return, let’s consider what we’re expecting to happen in this method. Basically, we want to return the list of text values from elements that match the todo list. We’re doing that because ultimately we want to check if the list item we entered shows up in that list. So that brings up a couple of implementation details: I need to be able to locate the todo list, then I need to be able to get that list into a format that I can check.

One thing to note is that the Question interface expects to be bounded by a type. That type should contain the “answer type” that you expect to be returned when the question is answered. In this case I’m going to be getting a list of text items from the web page. Or, rather, I plan on getting a bunch of text from a particular element on the page. I want to store that text in a list of strings. So let’s add this to our TodoItemsList class:

Here I’m bounding the parameter of the Question interface by saying that the type of answer it will be dealing with is a list of strings. Further note that I’m also changing the return type of answeredBy() from a generic Object to a list of string. Before dealing with the return statement, let’s also add the displayed() static method that we call from the test then() statement. If you auto-generate this method via your IDE you will likely see an interesting signature:

What we want to is return a Question object bounded by the “answer type.” So change that bit of code to this:

Great, so now we have two methods returning null! Okay, let’s stick with displayed() for a second. This is a static method that we are going to use to create a Question instance. What Question instance? Well, an instance of the class we’re in right now. Change the return statement of the displayed() method to this:

So now we have our displayed() method return a new instance of the Question object but our question object itself is still returning null via the answeredBy() method. What we want that to do is return the list from the TodoMVC app page. Let’s change our answeredBy() method like this:

This will require an import of “import net.serenitybdd.screenplay.questions.Text”. But what is that?

Serenity provides a number of interaction classes that let you query the web page using a fluent API. These interaction classes are used to wrap WebDriver calls that are used to query the web browser about the state of the page or objects within the page. Here the Text class does just what it sounds like: it gets the text that is contained by a given element.

Here I’m using the Text class. As you saw with the Action classes (like Open and Enter), Serenity provides a set of Question classes that can help query a web page for certain aspects of state. Here what I was attempting to do was return the text of a locator specified by “.view label”. You’ll find this won’t compile, however, and that’s because the .of() method is meant to return a target. Well, we already saw targets, right? Let’s add the following target to our TodoList class:

Now let’s use that in our .of() method:

I performed the static import just to make the code read a little clearer.

Is this enough? Clearly not because you’ll find that your return statement cannot compile. Why not? Keep in mind that answeredBy() should be returning a list of string. However, the call to Text.of() is not returning such a type.

One thing we have to specify, when asking a question, is who is asking the question or, rather, who the result of the question is being asked in the context of. A viewedBy() method is provided for this. We can pass the actor instance into this method. So add this refinement:

This still won’t compile because you still aren’t returning a list. A final refinement is to then add a toList() method to that. So let’s refine our answeredBy() method one more time:

So here we return the list of text values from elements matching the locator target and this is being done in the context of the actor. Those text values are then converted to a list of strings.

Execute the Test

Let’s run our test:

gradle clean test aggregate

You should see that the test passes. You should find that it does. If you check the output in the report, you’ll see a line like this:

Then todo items list should be a collection containing 'Digitize JLA vol 1 collection'

To prove the test is actually passing, though, make sure you change then then() statement in your AddNewTodo to this:

Now when you run you will get an error. If you view the stack trace you will see message like this:

Expected: a collection containing "testing" but: was "Digitize JLA vol 1 collection"

Okay, so that’s good. Make sure you change your test statement back to what it was. What this proves it that you have a working test and, further you can get an appropriate indication of when a test failed.

Recap of Material

This seems like a good place to end this particular series of tutorials. We were able to look at many of the core classes of Serenity in terms of how those classes are used to implement the screenplay pattern.

So let’s recap a few points that hopefully came out of this series of posts.

One is the Goal / Tasks / Actions distinction. As you’ve seen the structure of this idea is maintained by conventions within Serenity, where actors are called upon in a goal (a test scenario, in a method designated by @Test). That scenario makes declarative calls to tasks, which implement a performsAs() method to execute the tasks. That in turn leads to actions, which are performed via an attemptsTo() method that is called in the context of the actor.

Beyond the matching of the domain to the code, this distinction matches that found in User-Centered Design (UCD) as well as Human Computer Interface (HCI) design, centered around the idea of Task Analysis. The three layers of detail in those disciplines is also goal, task, action.

My focus here was clearly on the screenplay pattern and I did this in large part to contrast with the often used page object pattern. In many automation libraries or frameworks, the idea of page objects is to encapsulate logic related to the page, or a part of the page. The logic is the actions that someone can take on the page. But it’s also easy to allow page objects to become a form of “element store.” The problem is you introduce a chain of dependencies focused on these page objects. They tend to become harder to maintain as a test suite grows, which means automation engineers tend to have pages extending other pages, which just furthers the anti-pattern even more.

What you’ve seen with the screenplay pattern is a focus on composition over inheritance. And when you get into composition, you start to think more in terms of the workflow of behavior rather than a simple navigation of pages. This means that page objects in a screenplay pattern tend to be quite a bit more focused, sometimes just containing targets to represent elements, as you’ve seen in these posts.

You’ve seen that same idea of reducing the need for things to change by how focused tasks, actions, and questions are as well. The idea is that each component should only have one reason to change.

Finally, this overall design goal allows you to abstract over certain things, like, for example, the API that executes a browser (WebDriver) and the API that interacts with the browser (Selenium). When you have abstracted over those kinds of entities, you are not as bound to changes in their implementation. Further, you can smooth over some of their “rough spots” — i.e., unclear or heavily-implementation specific code of the API. This also allows you to extend the behavior of these entities in a way that still allows you to use their API, but with your own declarative code guiding your expression.

Overall, I hope this series of posts was helpful. I do think Serenity is one of the better libraries out there in terms of attempting to apply good patterns. I would encourage all automation engineers to look at the library. Even if your language of choice is not Java, focus instead on the screenplay pattern itself and think about whether it makes conceptual and practical sense. I would most definitely like to see this pattern implemented in other languages and see more testers talking about this pattern and how it can be reflective of the business domain.

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.

7 Responses to Screenplay Pattern with Java, Part 3

  1. Really great articles! Excellent work, and I really appreciate the effort.

    Really helps me get started with Serenity.

    We have tried to use selenium with Cucumber before it seemed like a lot of work,

    maybe Serenity will be something for us.

    • Jeff Nyman says:

      Thanks for the comment, Rolf.

      Indeed, Serenity is something I’ve been playing around with more and more. I do feel it has a lot of good aspects to it.

      Serenity also hooks into Cucumber, incidentally. If it helps, I did provide a few recent posts on that specifically. Not only do I cover Cucumber working with Serenity, but I do digress a bit into whether Cucumber makes sense given a focus that Serenity has of generating living documentation.

      This is an area I’m hoping to explore a little more as time goes on.

  2. Martin Zehle says:

    Hi Jeff,

    the sample tess ran fine but after updating IntelliJ to 2016.2 I use to get this Exeption

    ...
    
    1) No implementation for net.thucydides.core.webdriver.WebdriverManager was bound.
      while locating net.thucydides.core.webdriver.WebdriverManager
    
    1 error
    
    ...
    
    com.testerstories.tutorial.todos.features.add_todo.AddNewTodo.jeffCanBrowseTheWeb(AddNewTodo.java:25)
    
    ...

    I checked the location of chromedriver and tried with command line arguments as well as configuration files but nothing helped by now. Would you mind giving me a hint what might be wrong over here?

    Kind Regards,

    Martin

    • Jeff Nyman says:

      I also upgraded to IntelliJ 2016.2 and I don’t see the issue that you are referring to, unfortunately.

      The “no implementation was bound” type error tends to be something with Guice and the injection mechanism. Serenity is certainly using that.

      So what’s happening here appears to be in the internals for some reason. Just to be clear, you get this error when you run the following from the command line:

      gradle clean test aggregate

      Is that correct?

      Is it also correct to assume that you are not seeing any errors in the code itself? Meaning, is IntelliJ showing you any problems in any of the files. I wouldn’t think it is in this case because the problem, based on your error message, is how the WebdriverManager is being injected. But I just want to see if I can isolate what the problem is.

  3. Artyom says:

    Hi Jeff,

    Thank you for your really good material, can you please provide a small example how work with implicit/explicit waits using this pattern?

    • Jeff Nyman says:

      I apologize for the incredibly long delay on responding to this. I’m currently working on another iteration of tutorials for Serenity that do in fact cover various ways of waiting. Serenity is undergoing a series of changes right now so I’m trying to make sure the dust has settled enough to make a next set of tutorials useful.

  4. Rodrigo Martin says:

    Thanks for this series of posts. I am preparing a presentation of the Screenplay Pattern (which I’ll give in 2 days actually!), and your series of posts will for sure help me out a lot to convey the message.

     

    Cheers from Argentina!

Leave a Reply

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