The Screenplay Test Design Pattern

   Quality Assurance
Screenplay Test Design Pattern

The Screenplay Test Design Pattern, also known as the Flow Pattern, has been around since 2007 and is brilliantly described by Antony Marcano, Andy Palmer, Jan Molak and John Ferguson Smart in their article Page Objects Refactored: SOLID Steps to the Screenplay/Journey Pattern. They have an excellent explanation about the concept of this pattern and what common problems it intends to address. The main issue is that the commonly used Page Object pattern can introduce a high maintenance cost if the test automation solution grows. Thus, a need for refactoring is inevitable and here is where the Screenplay Test Design Pattern comes to our rescue as it encourages engineers to write code according to the SOLID principles. More on this in the article linked above.

Most of the articles on the Internet about the Screenplay Test Design Pattern are implementations using Serenity.js. This is a design pattern like any other, so it doesn’t depend on a certain framework. The structure of the solution and the way of thinking about the users and their actions are the key concepts to reaping the benefits of this implementation model. The solution I’m presenting is written in Node.JS, with WebdriverIO and Cucumber as the test runner. I encourage you to try out the implementation on your project or use other frameworks even for the sake of learning something new. The thinking pattern is the same.

There is one more thing I need to mention before we roll up our sleeves and begin. In this demo, the application we are testing is WordPress, so you’ll need to install it locally in order to be able to run tests in the solution. Don’t worry! It’s a quick and easy install, just follow the steps described on the WordPress Codex page (if you’re a Windows user see this page – the installation is almost the same).

The Structure

Let’s dive into it, this is what the structure looks like:

>functionalUiTests
>--actions
>----interactions.js
>----interrogations.js
>--actors
>----actor.js
>----actorData.js
>----admin.js
>----subscriber.js
>--features
>----postsPage.feature
>--pageobjects
>----loginPage.js
>----page.js
>----postsPage.js
>----sideNav.js
>--stepDefinitions
>----given.js
>----then.js
>----when.js

While looking through the code you’ll notice that some terms or conventions have been changed. I didn’t see the point of using human names instead of user roles. I also used interrogations instead of questions since that seemed too informal to me. Nonetheless the structure is the same as described by the 4 gents mentioned at the beginning of the article.

Another thing you might notice is the pageObjects folder. Since this development model is a refined version of the Page Object model I don’t see these as excluding one another, just like you would use any other design patterns that complement each other.

The Github repo can be found here. Feel free to use it as a boilerplate project.

The Actors

Let’s look at the actors who will perform the interactions and send the interrogations:

export default [
   {
       username: "scrplayusr",
       password: process.env.WP_ADMIN_PASS,
       role: "admin"
   },
   {
       username: "scrplayusrbasic",
       password: process.env.WP_BASICUSER_PASS,
       role: "subscriber"
   }
];

As you can see we have the user data hard-coded in this file because this is data that will not be altered by the test so it’s safe to have it in this file. In your case you might retrieve it from the database.

The passwords are added as environment variables.

import _ from 'lodash';
import Interactions from '../actions/interactions';
import actorData from './actorData';

export default class Actor {
   constructor(username) {
       this.username = username;
       let actor = this.getActorData(username);
       this.password = actor.password;
       this.role = actor.role;
   }

   getActorData(username) {
       return _.find(actorData, {username});
   }

   login() {
       Interactions.logIntoAccount(this.username, this.password);
   }
}

This is the definition of an actor and what it can do.

import Actor from './actor';
import Interactions from '../actions/interactions';

export default class Admin extends Actor {
   constructor(username) {
       super(username);
   }

   navigateToPostsPageViaSideNav() {
       Interactions.navigateToPostsPageViaSideNav();
   }
}

The Admin is a type of actor and it can perform interactions that other actors cannot.

The Actions

An actor can perform interactions with the application and/ or can interrogate the state of the application:

import loginPage from '../pageObjects/loginPage';
import sideNav from '../pageObjects/sideNav';

/**
* Possible interactions with the applications.
* The level of detail expressed here depends on the project needs.
*/
export default new class Interactions {
   /**
    * Logs the user into his account based on user's credentials.
    * @param {string} username The user's account username.
    * @param {string} password The user's account password.
    */
   logIntoAccount(username, password) {
       // This dynamic wait is necessary when running multiple tests
       // one after the other.
       loginPage.loginForm().waitForVisible();
       loginPage.usernameField().setValue(username);
       loginPage.passwordField().setValue(password);
       loginPage.loginButton().click();
   }

   /*** Navigates to the Posts page by clicking the Posts link in the sideNav. */
   navigateToPostsPageViaSideNav() {
       sideNav.postsLink().click();
   }
}

Here are interactions that actors can perform on the application. Each actor’s ability (permission) to perform an interaction is defined in the actor type class.

import postsPage  from '../pageObjects/postsPage';
import sideNav from '../pageObjects/sideNav';

export default {
   checkExistenceOfPostsPageTitle() {
       return postsPage.postsPageTitle().isExisting();
   },

   checkExistenceOfPostsLinkInSideNav() {
       return sideNav.postsLink().isExisting();
   },

   checkExistenceOfSideNavMenu() {
       return sideNav.sideNavContainer().isExisting();
   }
}

In the Interrogations file we define what questions can be asked about the application’s state.

The Step Definitions

import expect from 'expect';
import { Given } from 'cucumber';
import loginPage from '../pageObjects/loginPage';
import accessPage from '../pageObjects/page';
import interrogations from '../actions/interrogations';
import Admin from '../actors/admin';
import Subscriber from '../actors/subscriber';

Given(/^(.*) user with (admin|subscriber) role authenticates into WP-Admin$/, (username, role) => {
   let user;
   if (role === 'admin') {
       user = new Admin(username);
       user.login();
   } else if (role === 'subscriber') {
       user = new Subscriber(username);
       user.login();
   } else {
       throw new Error("The user role does not exist.");
   }
   browser.params.scenarioContext.currentUser = user;
   // A check that the login action has actually happened is needed here.
   expect(interrogations.checkExistenceOfSideNavMenu()).toBeTruthy();
});

Given(/^the login page is loaded$/, () => {
   accessPage(loginPage.path());
});

In the step definition files we instantiate the users and put together the interactions and interrogations so that we compose the user flows.

An Overview of the Play

Now that we’ve seen each individual component let’s see the overall play: the actor performs interactions on the application in order to see an outcome and then interrogates the system about its state. The chaining of the interactions and interrogations is done in the step definition files, while the feature files constitute the whole orchestration. It’s actually beautiful how it all blends in. You can reap the benefits of this development pattern if your application supports multiple types of user roles with different functionalities available to each. This way you can reduce maintenance overhead. On the other side if your application provides the same functionality to all users or the number of different user roles is reduced then it might not make sense to put in the effort of developing tests using this pattern.

The Screenplay test design pattern can really improve your test automation solution, so I encourage you to use it, if it makes sense in your specific situation. This is not automagic (sadly), but certainly a good pattern to know and use along with others that complement it. If you’ve used this model I’d love to hear your story. Hopefully it’s a success story!


This website uses cookies

These cookies are used to collect information about how you interact with our website and allow us to remember you. We use this information in order to improve and customize your browsing experience, and for analytics and metrics about our visitors both on this website and other media. To find out more about the cookies we use, see our Privacy Policy.

Please consent to the use of cookies before continuing to browse our site.

Like What You See?

Got any questions?


>