I wrote about Serenity with Java in a previous post. There I covered just the basics to get you up and running. Here I want to talk about what I think is the primary value-add of Serenity, which is its focus on the screenplay pattern. I think it’s beyond debate that automation engineers should know of this pattern and use it. It’s up for debate whether Serenity’s implementation is a good way to go about it. I leave answering that latter question to the reader. Here I just want to explore the idea as it is implemented.
This first post is going to talk about the theory. An immediate follow up post is going to put the theory into practice. I’m doing this so that I don’t get too defocused with either by trying to interleave theory and practice.
Serenity is an open source library designed to help you write what are now called “automated acceptance tests.” We don’t have to worry about the different ways people define “acceptance test” or even whether those definitions hold up when those tests are automated. In the context of Serenity, the idea is that the nature of acceptance tests — essentially being user-facing — are carried forward into the code in terms of how the logic is expressed in a domain-based way. This idea also carries into the test reports produced by the library, with the idea being that these reports are meant to be in line with “living documentation” practices.
Of particular note, Serenity has built-in support for the screenplay pattern which is what I’m going to talk about here. Without getting into all the history right now, the screenplay pattern is a variation on the much less well-known workflow pattern. The screenplay pattern first saw the light of day around 2007 and was refined in 2009 in a tool called JNarrate, written by Antony Marcano and Andy Palmer. It was enshrined as the journey pattern around 2013. Both Andy and Antony were key players in terms of implementing the Screenplay pattern in Serenity.
Another pattern, quite well known, came on the scene in 2009. The page object pattern came out of the WebDriver project, and the pattern then got enshrined in Selenium when the two projects merged to become Selenium WebDriver. The main problem with this is that this enshrined a bad domain model upon a “generation” of automation. So let’s first talk about why page objects are not the best domain model to apply.
The Page Object Model
There is no doubt that page objects were created as training wheels for testers who were called to act as programmers to design automation, but without having a lot of object-oriented design experience. That said, even though page objects were created more as a reaction to a lack, there is a good theory behind the design itself.
Consider this theory in the context of WebDriver. WebDriver provides an API to interact with elements on a Web page as rendered in a browser. But meaningful end-user scenarios generally are not going to be expressed in terms of Web elements. Rather those scenarios are expressed in terms of the application domain. So a pattern for writing WebDriver tests was to use page objects, which provided an API over domain concepts implemented on top of Web page elements. So, as Martin Fowler more than adequately describes, you have an API about HTML and you have an API about your application.
The problem is that page objects have remained a drop-in, plug-and-play solution for many people who — I’m just going to say it — claim to be automation engineers. To be fair, page objects are a good place to start for engineers who are not familiar with patterns and design principles. The problem is many automation engineers simply stop at page object design, never investigating better solutions.
But why does this matter? If page objects work, what’s the problem? One of the problems is that page objects are meant hide the specific element locators used — for example, to find buttons, text fields, buttons, etc — and the details of the underlying actions being taken to manipulate elements based on those locators. As a result, it is certainly true that the test scenarios can be more readable and easier to maintain if page details change.
But here’s the big problem: what if the workflow of pages change? What pattern do you use then? This is where many automation engineers get stuck. Because their automation is not based on a composable workflow, but rather on the static presumption of actions based on a specific page order, the automation becomes just as brittle as it was before the introduction of page objects.
Various means have been attempted to mitigate this problem, such as a refinement called page scopes, which I would argue is based on the old View Layout File concept popular in the days of WinRunner and QA Partner. Another was the refinement via state-based generalization of page objects. In this idea, you move page objects to the state level, which leads to the idea of state objects. In fact, many automation engineers I’ve worked with don’t understanding the notion of page objects returning page objects. That matters because that concept is the precursor to understanding state objects. This is mainly because a page object returning a page object is a form of model for navigation. In any event, the overall approach is that given a set of state objects, test cases describe relevant scenarios as paths through the state machine.
In fact, I risk a useless digression here, but in some ways this notion of state objects (with triggers and interactions) is how “inference engines” were created in tools like SilkTest. (I even tried my hand one one of these, which you can feel free to read about in my paper SilkTest Inference Engine.)
The Journey / Screenplay Model
All of these pattern ideas were swirling around the idea of allowing the focus of taking the context of user stories and allowing them to be expressed in code by hiding implementation details at a lower level of detail.
User stories should be focused on describing something that a user will do to achieve value from some functionality once that functionality is implemented. This is essentially putting the focus on behavior. This matches nicely with the idea that tests should primarily be concerned with valuable behavior rather than getting mired in implementation details. When you extend this idea with a focus on the story part of user story, each test scenario becomes a narrative describing the tasks and how you expect a story to play out for a given goal that the user has.
The journey pattern was originally established by applying very solid design principles to automation frameworks that were using simplistic designs like page objects. In fact, it wasn’t just solid design principles, it was SOLID design principles. See what I did there? If not, consider that SOLID is part of the Principles of Ood, specifically:
- Single Responsibility Principle
- Open Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
The screenplay pattern refines the journey pattern, applying those design principles not just to automation but to automated acceptance testing in particular. The journey pattern put emphasis on composition rather than inheritance. The journey pattern also took to heart concepts from the practice of Domain Driven Design, encouraging the use of layers of abstraction that reflected the language of the business domain as applied within acceptance tests. The screenplay pattern extends all of this to apply a domain-specific language on top of a fluent API. The goal is declarative code written in a way that reads like business language and that is in turn reflected in report output.
Contrast Pages with Journeys/Screenplays
I’ve been a little opinionated and blunt when talking about page objects, so let’s now consider this from another viewpoint given the design principles just talked about. Page objects very often lead to an anti-pattern called “large class” which is really speaking to a violation of the SRP and OCP principles from SOLID. Let’s break this down a little bit. Page objects commonly have the following responsibilities:
- Provide an abstraction to the location of elements on a page.
- Describe the tasks that can be completed on a page using its elements.
That’s at minimum. Two other responsibilities often occur:
- A page object returns a page object, thus partially responsible for representing navigation.
- Contain assertions that check for aspect of state to be true or false.
The last one there is a particular anti-pattern committed by testers who are new to programming automation. While there are some ideas out there that assertions in page objects are not a bad thing (such as for spin asserts), this is usually the sign of someone of bad automated design practice.
The point here is that all of these responsibilities are in a single class. So when the way you locate a specific element is change, this class requires a change. If the sequence of interactions required to complete a given change, again so must this class change. Right there you have more than one reason for a class to change?and that?violates the SRP.
Another principle that tends to get violated with page objects is OCP. The basic idea of this principle is that you should write code that doesn’t have to be changed every time the requirements change. This gets a little involved but basically the idea is your class should be open for extension. This means that the behavior of class can be extended to make it behave in new and different ways. However, the class should be closed for modification. This means you don’t actually change the class itself. In a dynamic language like Ruby, you might use mix in modules for this, whereas in a static language like Java you might use polymorphism to invoke extended functionality.
This can get a little involved to demonstrate and would take me far afield of the point. Suffice it to say that you can probably see that by adhering to SRP, it makes it much easier for you to adhere to OCP and vice versa. It was forcing automation to rigidly adhere to these principles that led away from the page object pattern and towards the journey pattern.
The narrative perspective, combined with good design principles based on SOLID, is what has removed the need for a focus on page object model and instead put the focus on the behavior model, which is focused on workflows and journeys. Incidentally, this also matches the thinking behind personas as in Agile modeling and exploratory tours, as covered in the book Exploratory Software Testing book by James Whittaker.
As a sidenote but an important one: when I interview automation-focused testers, they must know much of what I’ve written here. Unless considering entry-level folks, which is also perfectly viable. But if the person doesn’t know much of this, they are entry-level to me.
The Birth of the Screenplay
Earlier I mentioned JNarrate. Another tool, around 2012, called Screenplay came on the scene. The two of them combined became known as WebUser. This was then called Screenplay4J. The evolution doesn’t really matter that much so much as that the idea of the first tool (JNarrate) was that the narrative of a user makes up a journey. The idea of the second tool (Screenplay) was that the narrative for any scenario is a script within which a cast of actors play certain roles.
So That’s (Some Of) The Theory
Thus ends this first post, dealing solely with the theory. One thing I should note is that while my post title is stated as a dichotomy, in fact there are page objects that are used within a screenplay pattern context. However they are by no means the primary pattern being adhered to and, in fact, are heavily streamlined because of the fact that the SOLID design principles are applied to them.
I should also note that while I’ve predicated this post on the idea of Serenity being the tool within the context of the Java language, the screenplay pattern, just like all such patterns, is language-agnostic. While not referred to as the screenplay or journey, a good example is the behaviors design pattern that is seeing usage in languages like C#.
I will be quickly making a second post that is the start of the practice by showing how the screenplay pattern is put to use in the context of a particular language (Java) and a particular library (Serenity).