How To Automate: Understanding Screenplay – Help!

Welcome to our latest series of How To articles on EuroSTAR Huddle. This three part series is on Understanding Screenplay and is brought to you by Matt Wynne, one of the world’s leading BDD practitioners and co-founder of Cucumber.

Part 2

In the first post in this series we introduced the concept of the Screenplay pattern and busted a couple of popular myths about it. Now it’s time to start digging into some code to give us a real example to demonstrate the pattern on.

The problem with writing this kind of tutorial, always, is finding the right balance between an example that’s so complicated it gets in the way of your understanding the thing we’re actually trying to learn about, and one that’s so simplistic that the need for any kind of software design seems superfluous. If you’ll forgive me, we’ll err towards the simplistic here, and I’ll trust that you’ve seen enough complex code in the wild to recognise the need for some design work.

Time for an example

Turn your imagination up to max and pretend we’re building a web-based app that needs people to be able to sign up. This sign up feature is going to be the big differentiator to help us stand out in the market, I can feel it.

Sign-up is a two-step process, as described in this feature file:

Screenplay scenarios Matt Wynne

So far so good. We have one happy path scenario for Tanya creating then activating her account, and being able to sign in. We have another that illustrates what happens if hapless Bob tries to sign in before he’s got around to activating the account.

Now let’s drop down a layer and have a look at the step definitions we’ve written that bring these scenarios to life our JavaScript project:

Screenplay javascript object

There’s a couple of things going on here, so let’s pick them apart.

Info

We can see that each step definition uses the cucumber expression syntax, avoiding the need for ugly regular expressions, and instead capturing the name of the user with the built in {word} parameter type. Partly, I just wanted to show off this new feature, but it’s also going to come in handy later.

Next, notice that each of the step definition functions is a one-liner, delegating to a method on this. Delegating the automation work to helper methods allows us to keep a separation of concerns between these two layers:

  • Step Definitions translate from plain English in the scenarios into code.
  • Helper, or driver methods actually interact with the app.

Screenplay step definitions

Working this way, we build up a library of re-usable driver code that does the actual leg-work of automating our app. Gradually, we develop an API (some people even call it a DSL) for driving our application from our tests.

Introducing this separation also allows us the flexibility to swap in different implementations of the driver API, when we want to connect our automation at different layers of the app. If we want to, we can have a SeleniumDriver that uses a browser (perhaps via page objects), and a DomainDriver that does the same things directly against the domain model.

But I digress.

Where do the helper methods live?

In cucumber-js projects, this in the context of a step definition function is the World, an object that is created by Cucumber for the duration of the scenario, which we can add custom methods and properties to. Ruby’s flavour of Cucumber offers the same concept. In Cucumber-JVM and SpecFlow projects you have to inject your own objects that contain your helpers, but the principle is the same.

We won’t dig any deeper into our automation stack right now. Let’s just trust that those helper methods are there, doing what they say they do.

A new requirement

As they tend to do from time to time, our product owner would like this app of ours to do something new. She has an idea that our app will have projects that users with existing accounts can create. I don’t know where she gets these wild ideas.

Here’s the feature file:

Screenplay project owner feature file

We’ve introduced a handful of new steps here. Let’s look at how these are defined:

Understanding Screenplay Matt Wynne

This all seems quite straightforward in our simple example, but if we’re really sensitive to them, we might notice a couple of issues that could cause concern as the codebase grows.

First, we have duplication of the project name, a-project. We’ve made the sensible choice to push this detail down out of the scenarios to make them more readable (avoiding incidental details), but we’re now left with the problem of needing that information in two places in our code. One solution might be to stash the project name on the World, like this:

Screenplay stash the project name on the World

If you use Cucumber-Ruby you’ve probably done this kind of thing using an instance variable. SpecFlow or Cucumber-JVM practitioners maybe have used a ScenarioContext object. Like using helper methods, this is common practice in the Cucumber community. While this will work OK for now, what we’re starting to see is that the World object is just getting more and more complicated.

It’s becoming a God object.

Info

Well-designed objects have the property of cohesion. That means the methods on that object all have a reason to be together, such as sharing the same internal state. God objects don’t have this property. They’re just a grab-bag of methods that have no reason to be on the same class other than for a lack of a better place to put them.

Another niggling concern is that we’ve broken our idiom of having one-liner step definitions. We’re introduced the concept of “signing up” which rolls together the two granular actions of creating an account and then activating it.

At this stage, a two-line step definition is not a big deal, but it’s the wrong direction of travel. As we build up a more rich and interesting system we’ll have more and more of these abstractions. We could push this down into a signUp helper method on the world, but this doesn’t actually tackle the complexity, it just pushes it away somewhere we can’t see it so easily.

Cracks are appearing

Let’s take a step back for a minute and review what we do and don’t like about this code so far.

On the up-side:

  • The scenarios read really nicely. We’re not being forced to put details into the scenarios for the sake of what’s easy to code.
  • We’ve got a good separation of concerns, with the step definitions staying simple, delegating the work of actually driving the application to helper methods.
  • The language in the step definition code is consistent with the language of the Gherkin steps. For example, we see calls to signUp and activateAccount methods rather than any details about click or fillIn. There’s no translation happening in the step defintions, so we can trust them.

But we’re concerned that:

  • we have duplication of hard-coded details like the name of the project, when we need to share implicit context (i.e. “the project”) between steps.
  • adding more and more helper methods onto the World means that class will just grow and grow, and it will lack cohesion.

At the moment, in this tiny example codebase, these concerns are only hairline cracks; but we know what happens as our codebase evolves: small problems turn into big problems and, before you know it, you’re stuck in a codebase you hate.

What about Page Objects?

What if we were to use page objects? Could they help us?

Certainly, having different objects to represent the sign up form, the login form, and the new project page, would avoid us lumping all our automation code into one World object. However, this doesn’t stop the bloat. Page objects that model every button and interaction point on a page can become huge and unwieldy.

A more insidious problem is that page objects are based around the UI. This means there would have to be a leap in translation in our step defintions: we’d jump from a problem-domain concept like create a project to the nitty-gritty interactions with the app that will cause this action to happen. This isn’t such a clean separation of concerns, and distracts us from focussing on the behaviour and intent of our users. If we’re not careful, these detailed interactions can start to leak out into the Gherkin.

So now we have a sense of some of the problems with the typical approaches to organising acceptance test automation code.

In the next post we’ll have a look at what Screenplay could offer us to solve these problems, and start using it in our example application.

In the Understanding Screenplay series:

Part 1: Myths & Fundamentals

Part 2: Help! Maybe my helpers aren’t so helpful after all?

Part 3: Refactoring to Screenplay

See other Test Automation Resources on EuroSTAR Huddle.

About the Author

Matt Wynne

In 2008 Matt joined the Cucumber open source project as a developer. In 2011 The Cucumber Book was published which he co-authored with Aslak Hellesøy. In 2013 he co-founded Cucumber Ltd, dedicated to inspiring meaningful and effective collaboration in every software organisation. Cucumber Ltd was acquired by SmartBear in June 2019, and Matt now works as a BDD Advocate, working to spread good software development and agile testing practices around the world.
Find out more about @mattwynne