Serenity/JS: Next Generation Acceptance Testing in JavaScript

DZone 's Guide to

Serenity/JS: Next Generation Acceptance Testing in JavaScript

SerenityJS brings the scalability of the Screenplay Pattern and the power of Serenity BDD Reports to JavaScript projects. We believe that this combination will open a number of exciting possibilities to build well-tested and valuable software.

· Web Dev Zone ·
Free Resource

Automated acceptance criteria are an essential part of any modern development project, and, for JavaScript-savvy front-end developers, being able to automate these tests has a lot of appeal. However, on larger projects with many developers and testers, writing consistent, high quality automated tests quickly and reliably can become a major headache.

Collaboration tools such as Cucumber have gone great lengths to help us express the intent behind the requirements and improve the communication between the business, developers, and testers. Cucumber scenarios work best when they focus on high-level business goals and tasks, and not on detailed screen interactions.

WebDriver gave us the power to easily manipulate complex interfaces of our systems, while Protractor with it’s intimate knowledge of Angular.js is an excellent low-level API to script the interactions with the system.

So, is there something missing then?

The key to writing high quality, scalable automated acceptance tests is to shift the focus from low-level interactions with the system to thinking about who the users of your system are, what is it that they want to achieve by their interaction with your system, and how exactly they’re going to do it.

Serenity/JS builds on the Screenplay Pattern to do just that. This new library focuses on making developers and testers more productive by making acceptance tests faster to write, cheaper to maintain, and easier to scale to multiple projects and teams. It also gives you the power of Serenity BDD Reports to help improve the visibility and build trust between developers, testers, and the business.

In this article, we’ll show you how to get started.

The Screenplay Pattern

The Screenplay Pattern is new to JavaScript but has been around for a while in various forms. It was originally proposed by Antony Marcano in 2007 and you can learn more about the origin and history of this model in “Page Objects Refactored: SOLID Steps to the Screenplay Pattern” (by Antony Marcano, Andy Palmer, John Ferguson Smart, and Jan Molak) and from the Serenity/JS Screenplay Tutorial.

Screenplay by Example

Examples presented in this section are based on the project described in Beyond Page Objects: Next Generation Test Automation with Serenity and the Screenplay Pattern.

The best way to illustrate the Screenplay Pattern is through a practical example. Suppose we are writing some tests for a Todo application like the one you can find on the TodoMVC site. Consider the following scenario:

Feature: Add new items to the todo list
  As James (the just-in-time kinda guy)
  I want to capture the most important things I need to do
  So that I don’t leave so many things until the last minute

  Scenario: Adding the first todo item
    Given that James has an empty todo list
     When he adds Buy some milk to his list
     Then his todo list should contain Buy some milk

Let’s think about this scenario from a couple of different angles:

  • Who is this for? What role do they play?
  • Why are they here and what outcome do they hope for? What is their goal?
  • What will they need to do to achieve their goal? What tasks do they need to perform?
  • How will they complete each task? How will their specific interactions with the system look?


We believe that the feature described in the above scenario will be useful to a “just-in-time kinda guy.” James is a persona that we are using to understand this specific role.


Each role we discover needs an actor to play it. In Serenity/JS, an actor is defined as follows:

let james = Actor.named('James');

Such actors can perform tasks:

    AddATodoItem.called('Buy some milk')

...and ask questions about the state of the application in order to verify its correctness:

   .eventually.equal([ 'Buy some milk' ]);


The goal of an actor is represented by the subject of the scenario:

Scenario: Adding the first todo item

Here, James should be able to add the first todo item to his list.


Tasks represent high-level activities that an actor needs to perform in order to achieve their goal.

Tasks are named using the vocabulary of the problem domain, such as: “Pay with a default credit card”, “Book a plane ticket”, or “Add item to the basket”.

A good example of a task in our scenario could be to AddATodoItem.called(‘Buy some milk’), which mapped to a Cucumber.js step definition would look like this:

this.When(/^he adds (.*?) to his list$/, (name: string) => {
    return james.attemptsTo(

You might have noticed that the examples are implemented in TypeScript. The code should be easy to follow even if you’re not yet familiar with it and if you’re curious, you might want to watch Anders Hejlsberg’s “Introducing TypeScript”.


An Interaction is a low-level activity directly exercising the actor’s ability to interact with a specific external interface of the system—such as a website, a mobile app, or a web service.

Interactions are named using the vocabulary of the solution domain, such as: “Click a button”, “Enter password into a form field”, or “Submit JSON request”.

Actors should not exercise interactions directly in the Cucumber step definitions. Instead, a group of one of more interactions should be encapsulated and represented as a task. For example:

export class Login implements Task {
    performAs(actor: PerformsTasks) {
        // The Login Task is composed of three Interactions:
        return actor.attemptsTo(


Similarly to an interaction, a question directly exercises actor’s ability to interact with a specific external interface of the system—such as a website, a mobile app, or a web service.

Asking a question results in a promise that is eventually resolved to a specific value or a list of values.

Consider a “web question” used in the example scenario:

export class TodoListItems {
    static Displayed = Text.ofAll(TodoList.Items);

Calling  Text.ofAll(…)  returns a question implementing the Question<string[]> interface.

When such question is applied to TodoList.Items defined as:

export class TodoList {
    static Items = Target.the('List of Items')
                         .located(by.repeater('todo in todos'));

It eventually resolves to a list of strings, representing the text of the web elements matched by the  TodoList.ItemsTarget .

All of this means that we can have a Cucumber step defined as follows:

this.Then(/^.* todo list should contain (.*?)$/, (expectedItems: string) => {
    let answer = james.toSee(TodoListItems.Displayed);
    return expect(answer).to.eventually.equal(expectedItems);

...or simply:

this.Then(/^.* todo list should contain (.*?)$/, (expectedItems: string) => expect(james.toSee(TodoListItems.Displayed))


In order to interact with the system, an actor needs abilities, which encapsulate interface-specific clients.

Those clients could be a web browser, a web service client, a mobile device driver, and so on.

BrowseTheWeb is a good example of an ability that ships with Serenity/JS. To assign the ability to an actor:

let james = Actor.named('James')

Once the actor has the ability, it can be used in an interaction, such as  Click.on(LoginForm.Submit_Button) :

export class Click implements Interaction {

    public static on(target: Target): Click {

        return new Click(target);


    performAs(actor: PerformsTasks & UsesAbilities) {

        return BrowseTheWeb.as(actor).locate(this.target).click();


    constructor(private target: Target) { }


...or a question, such as  Text.of(Address.Post_Code) :

export class Text implements Question<string> {

    public static of(target: Target): Text {
        return new Text(target);

    answeredBy(actor: UsesAbilities): PromiseLike<string[]> {
        return BrowseTheWeb.as(actor).locate(this.target).getText();

    constructor(private target: Target) {

Transparent Reporting

Serenity/JS integrates with the Serenity BDD family to provide powerful reporting capabilities. Serenity/JS provides reporting at multiple levels: first of all, it records and documents every action a user undertakes during a test, providing a light-weight form of functional documentation about each feature:

Image title

But that’s not all. Serenity/JS also provides powerful reporting at the feature and capability level, allowing you to structure your test reporting in a way that is most meaningful to your stakeholders. Serenity/JS will not only report on what tests were executed, but it will tell you how well features were tested, and even what features and capabilities were not tested.

The aim of Serenity BDD reporting is not just to provide test reports, but to provide live documentation about the features of your system and their release-readiness.

Image title

Want to See It in Action?

Serenity/JS brings the scalability of the Screenplay Pattern and the power of Serenity BDD Reports to JavaScript projects. We believe that this combination will open a number of exciting possibilities to build well-tested and valuable software.

You can start experimenting with Serenity/JS today—check out the tutorials that we have prepared for you and let us know what you think!

angularjs, bdd, javascript, testing, typescript

Published at DZone with permission of Jan Molak , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}