Hello, Serenity/JS!

In this chapter, you'll learn how to write your first Serenity/JS scenario in both JavaScript and TypeScript.

I will also show you how the core elements of Serenity/JS work, and how the framework is different from other tools you might have seen in the past.

If you prefer to dive straight into the code, you can also experiment with:

  • the finished project on Serenity/JS GitHub,
  • a Repl.it sandbox, where you can run the code in your web browser without having to install anything on your computer.

Prerequisites

Node.js LTS

Serenity/JS is a Node.js program, so in order to use the framework and complete this tutorial you'll need a recent Long-Term Support version of Node.js.

If you don't have Node.js already, follow the installation instructions.

Did you know? Every single build of Serenity/JS is tested against the latest three Node.js LTS versions, so ^12 | ^14 | ^16.

Node.js project

The second thing you'll need is a Node.js project, which basically means an empty directory for your code, e.g. tutorial-hello-serenity-js, with a package.json file in it.

You can create them by running the following commands in your computer terminal:

mkdir tutorial-hello-serenity-js
cd tutorial-hello-serenity-js
npm init --yes

To learn more about the npm init command, check out the official Node.js documentation.

Serenity/JS Core

Serenity/JS is a modular framework. The @serenity-js/core module is agnostic of any test runners and tools you can integrate it with. It provides the core interfaces you need to build your test frameworks, write test scenarios, and perform reporting.

Enabling ways to integrate your test scenarios with Web interafaces, REST APIs, or any other external interface of your systems is the responsibility of the auxiliary Serenity/JS modules, rather than the "core" itself.

However, it's worth noting that you can use @serenity-js/core without any test runners nor any auxiliary modules whatsoever. And this is exactly what we're going to do next!

To add the @serenity-js/core module to the Node.js project you've just created, run the following command in your computer terminal:

npm install --save-dev @serenity-js/core

Did you know? Apart from providing modules to integrate your test scenarios with the various interfaces of your system, Serenity/JS also provides adapters for popular test runners, such as Mocha, Jasmine, Cucumber, Protractor and WebdriverIO. We'll talk more about that in the next chapter.

Actors

One of the key features that set Serenity/JS apart from other test frameworks is that it strongly favours User-Centred Design. In fact, every Serenity/JS scenario needs at least one "actor" to represent a user or an external system interacting with the system under test. Actors perform the workflows that verify if your system meets its acceptance criteria and are the cornerstone of the Screenplay Pattern. We'll talk more about that later.

To create your first Serenity/JS actor, create a JavaScript file called hello-serenity.js with the following contents:

// tutorial-hello-serenity-js/hello-serenity.js
const { actorCalled } = require('@serenity-js/core')

const Alice = actorCalled('Alice')

In the code sample above, actorCalled(name: string) is a function that instantiates a new actor, or retrieves one that's already been instantiated with the same name within a given scope. We'll cover scopes later on.

Activities

The next novel thing about Serenity/JS is that it natively supports the Screenplay Pattern.

When you follow the Screenplay Pattern, each one of your test scenarios becomes like a little stage-play, with the workflows expressed as sequences of activities performed by the various actors.

Those activities are first-class citizens in your Serenity/JS scenarios, and are one of the primary building blocks you'll compose your scenarios from.

While the activities you'll use in your everyday work with Serenity/JS will typically represent high-level domain-specific tasks, such as "register a customer", or "book a flight", you need to know how to create such high-level tasks and compose them from interactions and lower-level tasks.

To help with that, let's start our exploration of the framework with a simple interaction to Log.the(value):

// tutorial-hello-serenity-js/hello-serenity.js
const { actorCalled, Log } = require('@serenity-js/core')   // 1

const Alice = actorCalled('Alice')

Alice.attemptsTo(
    Log.the('Hello Serenity/JS'),                           // 2
)

In the code sample above, you'll notice that we've:

  1. imported the interaction to Log from @serenity-js/core
  2. parametrised the interaction to Log with a string and asked the actor to attempt to perform it

Did you know? Log.the is a factory method that instantiates a new Interaction to Log.
All the built-in Serenity/JS interactions leverage either "factory method" or "builder" design patterns to help you write code that's easy to read and understand.

However, if you try to run the file with the above code, you'll notice that it doesn't produce any output:

node hello-serenity.js

To understand why this is the case, I need to tell you more about how Serenity/JS reporting features work.

Reporting

Whenever an actor performs an activity (a task or an interaction), it emits events that inform Serenity/JS reporting services about the activities taking place.

Did you know? The reason for the separation between interactions and reporting is so that no matter what and how many reporting services you use, they all have a complete picture of what the actors did. It also means that you'll never need to waste your time jumping between an HTML report and a log output of your build server to understand what happened in the test.

In short, in oder to see what activities our actors perform, we need to register at least one reporting service.

Serenity/JS reporting services ship in auxiliary Serenity/JS modules, so let's add @serenity-js/console-reporter to your project:

npm install --save-dev @serenity-js/console-reporter

Next, configure Serenity/JS to use the ConsoleReporter as follows:

// tutorial-hello-serenity-js/hello-serenity.js
const { actorCalled, configure, Log } = require('@serenity-js/core')    // 1 
const { ConsoleReporter } = require('@serenity-js/console-reporter')    // 2

configure({
    crew: [
        ConsoleReporter.forDarkTerminals(),                             // 3
    ]
})

const Alice = actorCalled('Alice')

Alice.attemptsTo(
    Log.the('Hello Serenity/JS'),
)

In the code sample above, we:

  1. import a function to configure Serenity/JS,
  2. import ConsoleReporter,
  3. tell Serenity/JS to register ConsoleReporter as a member of the stage crew.

Did you know? Just like the design patterns in the Serenity/JS scenarios revolve around the system metaphor of a stage performance, Serenity/JS reporting and supporting services follow the metaphor of a stage crew.

When you run the scenario again, you should now see the following output:

node hello-serenity.js 

  ✓ Alice logs: Hello Serenity/JS (2ms)
    'Hello Serenity/JS'

Using TypeScript

TypeScript is an amazing language that greatly improves developer productivity by catching errors and providing fixes before you run code. Serenity/JS itself is written in TypeScript and I strongly encourage you to consider using it to build your test frameworks and write test scenarios based on Serenity/JS.

If you haven't used TypeScript before, don't worry! I'll show you enough to become comfortable with it.

Did you know? Serenity/JS was born in June 2016, and to my knowledge was the first test framework written entirely in TypeScript from the very beginning.

In order to use TypeScript, you'll need to add the following modules to your project:

npm install --save-dev typescript @types/node ts-node

Above:

To turn your JavaScript scenario into one written in TypeScript, rename the file hello-serenity.js to hello-serenity.ts using your editor or your computer terminal:

mv hello-serenity.js hello-serenity.ts

Next, modify the require statements to become import statements instead. The rest of the file should remain as it was:

// tutorial-hello-serenity-js/hello-serenity.ts
import { actorCalled, configure, Log } from '@serenity-js/core' 
import { ConsoleReporter } from '@serenity-js/console-reporter'

configure({
    crew: [
        ConsoleReporter.forDarkTerminals(),
    ]
})

const Alice = actorCalled('Alice')

Alice.attemptsTo(
    Log.the('Hello Serenity/JS'),
)

Et voilà! Your first Serenity/JS scenario is now written in TypeScript.

To run it, use ts-node:

npx ts-node hello-serenity.ts

  ✓ Alice logs: Hello Serenity/JS (3ms)
    'Hello Serenity/JS'

The output produced by the TypeScript version of your scenario should remain the same.

Did you know? When Serenity/JS is used with a test runner like Cucumber or WebdriverIO, you can configure the test runner to use ts-node, so that you don't have to invoke the module explicitly.

We'll talk more about it in the next chapter

Exercises

Hungry for more? Try the below exercises to solidify what you've learnt:

  1. ConsoleReporter supports both dark and light terminals, study the API docs to learn how you can configure it to use the different colour themes.
  2. ConsoleReporter allows for the output stream to be redirected to a file instead of the terminal. Study the examples to learn how to do it.
  3. Log.the supports "rest parameters". See what happens when you provide more than one value, e.g. Log.the('Hello Serenity/JS', 42)
  4. Try to log something other than a string, try an object: { name: 'Alice' }, a Promise: Promise.resolve(42) or an Array: ['apples', 'bananas']
  5. actor.attemptsTo supports "rest parameters" too! Can you tell the actor to perform the interaction to Log more than once?

Try it yourself

Check out the finished project on Serenity/JS GitHub or experiment with the Repl.it sandbox above.

Summary

In this chapter, you've learnt how to write and run your first Serenity/JS scenario in both JavaScript and TypeScript.

In the next chapter, I'll show you how to use Serenity/JS with a test runner and how Serenity/JS reporting services will make it easier for you to analyse the results of your tests.

Your feedback matters!

If you've enjoyed this tutorial and would like to be notified when I publish the next part, follow us on Twitter:

Follow SerenityJS on Twitter Follow JanMolak on Twitter

If you have any questions or feedback about this tutorial or working with Serenity/JS in general, join the Serenity/JS Community Chat Channel on Gitter:

Join Serenity/JS Community Chat Channel!

If you'd like to say thank you, please give Serenity/JS a star on GitHub or become our GitHub Sponsor:

Star Sponsor

Found a typo? You can fix it yourself or raise a ticket on GitHub.