Cucumber
Cucumber is a popular collaboration tool and a test runner capable of executing test scenarios written in plain language.
In this article, you will learn:
- How to use Serenity/JS reporting services, including the Serenity BDD reporter, even if your test scenarios don't follow the Screenplay Pattern yet
- How to implement Cucumber step definitions using reusable Serenity/JS Screenplay Pattern APIs
Examples and Project Templates​
If you'd like to dive straight into the code, Serenity/JS GitHub repository provides:
- Serenity/JS + Cucumber project templates, which are the easiest way to start with the framework
- Examples and reference implementations, demonstrating using Serenity/JS with Cucumber to write both REST API- and web-based acceptance tests
Using Serenity/JS reporting services​
To use Serenity/JS reporting services in a Cucumber project, you need to:
- attach the
@serenity-js/cucumber
test runner adapter to Cucumber - configure Serenity/JS to use the reporting services you want to use,
such as the
ConsoleReporter
orSerenityBDDReporter
Serenity/JS test runner adapters turn internal, test runner-specific events into Serenity/JS domain events that can contribute to test execution reports produced by Serenity/JS reporting services.
@serenity-js/cucumber
module provides a set of test runner adapters
you can attach to any version of Cucumber test runner.
The module detects the version of Cucumber you're using and picks the most appropriate adapter automatically.
Integration architecture depicted below applies to invoking cucumber-js
command line interface directly,
for example for domain-level, REST/HTTP API-level,
or web-based testing using Playwright.
If you want your Cucumber scenarios to interact with web interfaces using Selenium Webdriver protocol, or connect them to a Selenium Grid, you should use Cucumber via Protractor or WebdriverIO instead.
Installing Serenity/JS test runner adapter​
Assuming you already have a Node.js project and Serenity/JS runtime dependencies set up, add the following Node modules:
To do that, run the following command in your terminal:
- npm
- Yarn
npm install --save-dev @serenity-js/core @serenity-js/cucumber @cucumber/cucumber
yarn add --dev @serenity-js/core @serenity-js/cucumber @cucumber/cucumber
If you'd like to implement your test suite in TypeScript, also run:
- npm
- Yarn
npm install --save-dev typescript ts-node @types/node
yarn add --dev typescript ts-node @types/node
Configuring Serenity/JS​
If you intend to run your Cucumber scenarios using the Cucumber CLI,
the best way to configure Serenity/JS is to invoke the Serenity/JS configure
function
in the Cucumber BeforeAll
hook:
- TypeScript
- JavaScript
import { BeforeAll } from '@cucumber/cucumber'
import { configure } from '@serenity-js/core'
BeforeAll(() => {
// Configure Serenity/JS
configure({
crew: [
'@serenity-js/console-reporter',
'@serenity-js/serenity-bdd',
[ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
// ... any other reporting services
],
})
})
const { BeforeAll } = require('@cucumber/cucumber')
const { configure } = require('@serenity-js/core')
BeforeAll(() => {
// Configure Serenity/JS
configure({
crew: [
'@serenity-js/console-reporter',
'@serenity-js/serenity-bdd',
[ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
// ... any other reporting services
],
})
})
To learn more about installing and configuring Serenity/JS reporting services appropriate for your project, follow the Serenity/JS reporting guide.
Serenity understands the structure of your Cucumber .feature
files and will augment your test execution reports
with feature and scenario descriptions, detailed information about Cucumber steps, and much more!
Configuring Cucumber profile​
To make sure Cucumber loads your Serenity/JS configuration file and correctly interprets TypeScript (if you're using it),
create a cucumber.yaml
profile:
- TypeScript
- JavaScript
# This is a Cucumber.js profile
# See https://github.com/cucumber/cucumber-js/blob/main/docs/profiles.md
default:
requireModule:
- ts-node/register # use TypeScript in-memory transpiler, ts-node
format:
- '@serenity-js/cucumber' # attach Serenity/JS Cucumber adapter
require:
- ./features/**/*.steps.ts # load step definition libraries
- ./features/**/*.config.ts # load configuration files
# This is a Cucumber.js profile
# See https://github.com/cucumber/cucumber-js/blob/main/docs/profiles.md
default:
format:
- '@serenity-js/cucumber' # attach Serenity/JS Cucumber adapter
require:
- ./features/**/*.steps.js # load step definition libraries
- ./features/**/*.config.js # load configuration files
The above configuration works with the latest version of the cucumber.Cli
available as part of
the @cucumber/cucumber
module.
Consult the @serenity-js/cucumber
documentation to learn how to configure
the adapter with older versions of the runner.
Using Serenity/JS Screenplay Pattern APIs​
Serenity/JS actor model is a natural fit for Cucumber scenarios
and Serenity/JS Screenplay Pattern APIs can help your team implement
Cucumber step definitions that are as easy to read and understand as your .feature
files.
The fastest way to get started with Serenity/JS and Cucumber is to use one of the Serenity/JS + Cucumber project templates. However, if you're adding Serenity/JS to an existing project or simply want to understand how the integration works, this guide is for you.
Configuring a cast of actors​
Serenity/JS Screenplay Pattern is an actor-centred model, so the first thing you need to do is to tell Serenity/JS what cast of actors you want to use.
If you're planning to run Cucumber scenarios using the Cucumber CLI directly,
you can configure the actors in a BeforeAll
hook, for example:
import { BeforeAll, AfterAll } from '@cucumber/cucumber'
import { configure, Cast } from '@serenity-js/core'
import { BrowseTheWebWithPlaywright } from '@serenity-js/playwright'
import * as playwright from 'playwright'
let browser: playwright.Browser;
BeforeAll(async () => {
// Launch the browser once before all the tests
// Serenity/JS will take care of managing Playwright browser context and browser tabs.
browser = await playwright.chromium.launch({
headless: true,
});
// Configure Serenity/JS
configure({
actors: Cast.where(actor =>
actor.whoCan(BrowseTheWebWithPlaywright.using(browser, {
baseURL: 'https://todo-app.serenity-js.org/',
}))
),
crew: [
'@serenity-js/console-reporter',
'@serenity-js/serenity-bdd',
[ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
// ... any other reporting services
],
})
})
AfterAll(async () => {
// Close the browser after all the tests are finished
await browser?.close()
})
Consult the respective test runner instructions if you're invoking Cucumber indirectly, so via Protractor or WebdriverIO.
Referring to actors in test scenarios​
When using Serenity/JS actors to represent user personas or important external systems interacting with the system under test, a common strategy is to refer to them in your Cucumber scenarios using their name and pronouns :
Feature: Todo list
Scenario: Starting fresh
When Alice opens the todo app for the first time
Then her todo list should be empty
To make Cucumber understand that Alice
is a name of an actor, and that her
means the most recent actor we accessed,
you need to define custom parameter types.
All the Serenity/JS + Cucumber Project Templates
use the below definitions, which use actorCalled
and actorInTheSpotlight
:
import { defineParameterType } from '@cucumber/cucumber'
import { actorCalled, actorInTheSpotlight } from '@serenity-js/core'
defineParameterType({
regexp: /[A-Z][a-z]+/,
transformer(name: string) {
return actorCalled(name)
},
name: 'actor',
})
defineParameterType({
regexp: /he|she|they|his|her|their/,
transformer() {
return actorInTheSpotlight()
},
name: 'pronoun',
})
With actor
and pronoun
parameter types defined,
you can refer to them in Cucumber expressions
describing your Cucumber steps definitions:
import { When, Then } from '@cucumber/cucumber'
import { Ensure, equals } from '@serenity-js/assertions'
import { Actor } from '@serenity-js/core'
import { Navigate, PageElements, By } from '@serenity-js/web'
When('{actor} opens the todo app for the first time', async (actor: Actor) => {
await actor.attemptsTo(
Navigate.to('https://todo-app.serenity-js.org/')
)
})
Then('{pronoun} todo list should be empty', async (actor: Actor) => {
const displayedItems = () =>
PageElements.located(By.css('.todo-list li'))
.describedAs('displayed items')
await actor.attemptsTo(
Ensure.that(displayedItems().count(), equals(0))
)
})