Abilities
An "ability" is one of the five building blocks of the Screenplay Pattern. It's what enables the actor to interact with the system under test.
In more technical terms, the Ability
is an implementation of the adapter pattern.
An "ability" is a thin wrapper around a lower-level, interface-specific client such as a web browser driver, a HTTP client, a database client and so on, that you'd call from an interaction or question class to access the system under test.

Implementing Abilities
Consider the BrowseTheWeb
ability from the @serenity-js/protractor
module:
export class BrowseTheWeb implements Ability {
static using(browser: ProtractorBrowser): BrowseTheWeb {
return new BrowseTheWeb(browser);
}
static as(actor: UsesAbilities): BrowseTheWeb {
return actor.abilityTo(BrowseTheWeb);
}
constructor(private readonly browser: ProtractorBrowser) {
}
locate(locator: Locator): ElementFinder {
return this.browser.element(locator);
}
// rest omitted for brevity
}
In order to enable the actor to BrowseTheWeb
, we'd give it an instance of said ability, linked with the lower-level client - in this case protractor.browser
:
import { Actor } from '@serenity-js/protractor';
import { BrowseTheWeb } from '@serenity-js/protractor';
import { protractor } from 'protractor';
const actor = Actor.named('Abby').whoCan(BrowseTheWeb.using(protractor.browser));
So how would our actor make use of their newly acquired ability? That's what interactions are for!
Let's take the below, much simplified, version of the original Click
interaction, where BrowseTheWeb
is used to locate the element of interest and perform the .click()
:
import { Question } from '@serenity-js/core';
import { BrowseTheWeb } from '@serenity-js/protractor';
import { Locator } from 'protractor';
const Click = {
onElement: (locator: Locator) =>
Interaction.where(`#actor clicks on the ${ target }`, actor => {
return BrowseTheWeb.as(actor).locate(this.locator).click();
}),
}
With the interaction in place, we can tell the actor to perform the click as part of their interaction flow,
and therefore indirectly invoke their ability to BrowseTheWeb
:
actor.attemptsTo(
Click.onElement(by.css('button')),
)
If you're wondering why you'd want to create a wrapper around the lower-level client you could just call directly from an interaction, think about all the opportunities having a dedicated ability class presents:
- an ability can expose an API simpler than the one provided by the client you're using (i.e. see how
BrowseTheWeb
simplifies the API of the Protractorbrowser
) - since actors are effectively stateless, abilities can be used to store any state (i.e. see how
TakeNotes
can be used to store answers to questions asked during the test) - abilities can map client-specific errors to more informative ones, that Serenity/JS can also report on better (i.e. see how
CallAnApi
maps the various kinds of Axios errors)
PRO TIP: This is a simplified implementation of the Click
interaction that
doesn't account for the many quirks of the Protractor API and is intended for demonstration purposes only. Please use the original Click
in your tests instead.