Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

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!

Posted in Quality Assurance
Share this

Sergiu Bacanu

Sergiu Bacanu is a test automation engineer at Modus Create. His goal is to help teams incorporate testing in their development process so that it contributes to business success. He enjoys discovering and trying out new frameworks, methodologies and techniques that aid testing activities. When not working you can find him playing sports, hiking, reading or playing video games.
Follow

Related Posts

  • Using the Page Object Design Pattern in Sencha Test
    Using the Page Object Design Pattern in Sencha Test

    Don’t Repeat Yourself (DRY) is a software development principle that is equally important to automation…

  • Using the Page Object Design Pattern in Sencha Test
    Using the Page Object Design Pattern in Sencha Test

    Don’t Repeat Yourself (DRY) is a software development principle that is equally important to automation…

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
IT modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Careers
  • Let’s talk
  • EN
  • FR