externalabstractAbility
Hierarchy
Index
Constructors
Methods
Constructors
externalconstructor
Returns Ability
Methods
staticexternalas
Used to access an actor's ability of the given type from within the
Interaction
andQuestion
classes.Retrieving an ability in an interaction definition
import { Actor, Interaction } from '@serenity-js/core'
import { BrowseTheWeb, Page } from '@serenity-js/web'
export const ClearLocalStorage = () =>
Interaction.where(`#actor clears local storage`, async (actor: Actor) => {
const browseTheWeb: BrowseTheWeb = BrowseTheWeb.as(actor) // retrieve an ability
const page: Page = await browseTheWeb.currentPage()
await page.executeScript(() => window.localStorage.clear())
})Retrieving an ability in a question definition
import { Actor, Question } from '@serenity-js/core'
import { BrowseTheWeb, Page } from '@serenity-js/web'
import { CallAnApi } from '@serenity-js/rest'
const LocalStorage = {
numberOfItems: () =>
Question.about<number>(`number of items in local storage`, async (actor: Actor) => {
const browseTheWeb: BrowseTheWeb = BrowseTheWeb.as(actor) // retrieve an ability
const page: Page = await browseTheWeb.currentPage()
return page.executeScript(() => window.localStorage.length)
})
}Type parameters
- A: Ability
Parameters
externalthis: AbilityType<A>
externalactor: UsesAbilities
Returns A
staticexternalabilityType
Returns the most abstract type of this Ability class, specifically the first class in the inheritance hierarchy that directly extends the
Ability
class.import { Ability } from '@serenity-js/core';
class MyAbility extends Ability {}
class MySpecialisedAbility extends MyAbility {}
MyAbility.abilityType(); // returns MyAbility
MySpecialisedAbility.abilityType(); // returns MyAbilityReturns AbilityType<Ability>
externaltoJSON
Returns a JSON representation of the ability and its current state, if available. The purpose of this method is to enable reporting the state of the ability in a human-readable format, rather than to serialise and deserialise the ability itself.
Returns SerialisedAbility
externalabilityType
Returns the most abstract type of this Ability instance, specifically the first class in the inheritance hierarchy that directly extends the
Ability
class.import { Ability } from '@serenity-js/core';
class MyAbility extends Ability {}
class MySpecialisedAbility extends MyAbility {}
new MyAbility().abilityType(); // returns MyAbility
new MySpecialisedAbility().abilityType(); // returns MyAbilityReturns AbilityType<Ability>
Abilities enable actors to perform interactions with the system under test and answer questions about its state.
From the technical perspective, abilities act as wrappers around any integration libraries required to communicate with the external interfaces of system under test, such as web browser drivers or an HTTP client. They also enable portability of your test code across such integration libraries.
Abilities are the core building block of the Screenplay Pattern, along with actors, interactions, questions, and tasks.
Learn more about:
Giving actors the abilities to interact
Serenity/JS actors are capable of interacting with any interface of the system under test, be it a web UI, a mobile app, a web service, or anything else that a Node.js program can talk to. This flexibility is enabled by a mechanism called abilities and achieved without introducing any unnecessary dependencies to your code base thanks to the modular architecture of Serenity/JS.
Actors have abilities that enable them to perform interactions and answer questions.
From the technical perspective, an ability is an adapter around an interface-specific integration library, such as a web browser driver, an HTTP client, a database client, and so on. You give an actor an ability, and it's the ability's responsibility to provide a consistent API around the integration library and deal with any of its quirks. Abilities encapsulate integration libraries and handle their configuration and initialisation, the process of freeing up any resources they hold, as well as managing any state associated with the library.
Portable interactions with web interfaces
To make your Serenity/JS actors interact with web interfaces, you call the
Actor.whoCan
method and give them an implementation of the ability toBrowseTheWeb
, specific to your web integration tool of choice.Note how
BrowseTheWebWithPlaywright
,BrowseTheWebWithWebdriverIO
, andBrowseTheWebWithProtractor
all extend the base ability toBrowseTheWeb
.Playwright
WebdriverIO
Protractor
Retrieving an ability
Use
PerformActivities
} to retrieve an ability in a customInteraction
orQuestion
implementation.Here,
Ability
can be the integration library-specific class, for exampleBrowseTheWebWithPlaywright
, or its library-agnostic parent class, likeBrowseTheWeb
.To make your code portable across the various integration libraries, retrieve the ability using the library-agnostic parent class:
As you can already see, providing encapsulation and a cleaner API around the integration libraries are not the only reasons why you'd want to use the abilities.
Another reason is that the Serenity/JS implementation of the Screenplay Pattern lets you completely decouple the actor from the integration libraries and make the abilities of the same type interchangeable. For example, Serenity/JS web modules offer an abstraction that lets you switch between web integration libraries as vastly different as Selenium, WebdriverIO, or Playwright without having to change anything whatsoever in your test scenarios.
What this means is that your test code can become portable and reusable across projects and teams, even if they don't use the same low-level integration tools. It also helps you to avoid vendor lock-in, as you can wrap any third-party integration library into an ability and swap it out for another implementation if you need to.
However, Serenity/JS doesn't prevent you from using the integration libraries directly. When you need to, you can use a library-specific ability like
BrowseTheWebWithPlaywright
to trade portability for access to library-specific low-level methods:While Serenity/JS provides you with escape hatches and ways to access the low-level APIs of your integration libraries, doing so can reduce the portability of your code. Only do it when you have a good reason to trade portability for low-level access.
Associating actors with data
One more reason to use abilities is that abilities can also help you to associate actors with data they need to perform their activities. For example, a commonly used ability is one to
TakeNotes
, which allows your actors to start the test scenario equipped with some data set, or record information about what they see in the test scenario so that they can act upon it later:Actors with multiple abilities
Of course, an actor can have any number of abilities they need to play their role. For example, it is quite common for an actor to be able to
BrowseTheWeb
,TakeNotes
, andCallAnApi
:Writing custom abilities
If your system under test provides a type of interface that Serenity/JS doesn't support yet, you might want to implement a custom
Ability
, as well as interactions and questions to interact with it.The best way to start with that is for you to review the examples in the Screenplay Pattern API docs, as well as the Serenity/JS code base on GitHub. Also note that all the Serenity/JS modules have their automated tests written in such a way to not only provide an extremely high test coverage for the framework itself, but to be accessible and act as a reference implementation for you to create your own integrations.
If you believe that the custom integration you've developed could benefit the wider Serenity/JS community, please consider open-sourcing it yourself, or contributing it to the main framework.
Defining a custom ability to
MakePhoneCalls
Defining a custom interaction using the custom ability
Using the custom ability and interaction in a test scenario
Using auto-initialisable and auto-discardable abilities
Abilities that rely on resources that need to be initialised before they can be used, or discarded before the actor is dismissed can implement the
Initialisable
orDiscardable
interfaces, respectively.Defining a custom ability to
QueryPostgresDB
Defining a custom question using the custom ability
Using the custom ability and question in a test scenario
Learn more
AbilityType
Initialisable
Discardable
BrowseTheWeb
CallAnApi
TakeNotes