Customising actors
Each actor comes with a set of abilities that enable them to interact with the system under test. By default, these abilities include:
BrowseTheWebWithPlaywright- Allows interaction with the browser using the defaultpagefixture.TakeNotes.usingAnEmptyNotepad()- Facilitates storing and retrieving information during the test run.CallAnApi- Enables interaction with RESTful APIs using an Axios HTTP client configured withbaseURL,extraHTTPHeaders, and proxy configuration derived from theproxyfixture.
- One-off customisation in a single test → Override abilities inline with
actor.whoCan(...). - Same customisation for all tests in a file → Use
test.usewithextraAbilities. - Same customisation across multiple files → Use
useFixturesto centralise configuration. - Full control over actor creation → Replace the default actors with a custom
Cast.
An actor can only have one instance of each ability type at a time.
Therefore, providing a new instance of the same type via the actor.whoCan method overrides any existing ability of that type.
Overriding abilities inline​
You can override the default abilities inline or add custom abilities
inline within a test scenario using the actor.whoCan method:
import { describe, it, beforeEach } from '@serenity-js/playwright-test'
import { TakeNotes, Notepad } from '@serenity-js/core'
describe('Todo App', () => {
describe('actor', () => {
it('can share notes with another actor', async ({ actor, actorCalled, browser }) => {
const sharedNotepad = Notepad.empty()
// Override the default actor's ability to take notes
await actor
.whoCan(TakeNotes.using(sharedNotepad))
.attemptsTo(
// ...
)
// Create an extra actor with access to the same notepad
// to allow for sharing data between actors
await actorCalled('Bob')
.whoCan(TakeNotes.using(sharedNotepad))
.attemptsTo(
// ...
)
})
})
})
Overriding abilities via test.use​
To avoid adding or overriding abilities inline, you can define them via test.use and
using the extraAbilities fixture.
This approach enables you to reuse the same configuration for all the test scenarios within the given describe block.
import { describe, it, test } from '@serenity-js/playwright-test'
import { MyAbility } from './MyAbility'
describe('Todo App', () => {
test.use({
extraAbilities: [ new MyAbility() ]
});
// ...
})
Overriding abilities via useFixtures​
When you have multiple test files that require the same customisations to your actor configuration or the same custom fixtures, you'll want to
centralise it in a file like serenity-api.ts and leverage the useFixtures function.
This function returns a test API, including functions such as describe, it, beforeEach, or test, which you can use instead of the default ones provided by the framework.
import { useFixtures } from '@serenity-js/playwright-test'
import { TakeNotes, Notepad } from '@serenity-js/core'
export interface MyNotes {
testListId: string;
}
export interface TestScopeFixtures {
notepad: Notepad<MyNotes>
}
export const { describe, it, beforeEach, test } = useFixtures<TestScopeFixtures>({
notepad: Notepad.with<MyNotes>({
testListId: process.env.TEST_LIST_ID,
}),
extraAbilities: async ({ notepad }, use) => {
const extraAbilities = [
TakesNotes.using(notepad),
];
await use(extraAbilities);
},
})
import { Navigate } from '@serenity-js/web'
import { notes, q } from '@serenity-js/core'
import { describe, it, beforeEach, test, type MyNotes } from './test-api.ts'
describe('Todo App', () => {
it('uses shared notes', async ({ actor }) => {
await actor.attemptsTo(
Navigate.to(q`/lists/${ notes<MyNotes>().testListId }`),
// ...
)
});
// ...
})
Using multiple browsers​
By default, scenarios with multiple actors reuse the same underlying page instance to align with Playwright Test defaults.
While this is useful for simulating multiple users interacting within the same browser session, it may not be appropriate for scenarios where different actors represent distinct users or roles.
For instance, a doctor and a patient using a healthcare system would not share the same browser session or user interface.
To address this, use the actor.whoCan method to override the default ability to BrowseTheWebWithPlaywright.
This allows actors who require a separate browser to have their own instance.
In this modified implementation below, Alice uses the default browser instance, associated with the page fixture,
while Bob uses a separate browser instance launched using the browser fixture.
import { describe, it, beforeEach } from '@serenity-js/playwright-test'
import { BrowseTheWebWithPlaywright } from '@serenity-js/playwright'
import { Navigate } from '@serenity-js/web'
import { Ensure, equals } from '@serenity-js/assertions'
import { TodoApp } from './screenplay/TodoApp'
describe('Todo App', () => {
describe('Guest user', () => {
it('does not share lists across browsers', async ({ actorCalled, browser }) => {
await actorCalled('Alice').attemptsTo(
Navigate.to('https://todo-app.serenity-js.org/#/'),
TodoApp.recordItem('Read a book'),
Ensure.that(TodoApp.recordedItems(), equals([
'Read a book'
])),
)
await actorCalled('Bob')
.whoCan(BrowseTheWebWithPlaywright.using(browser))
.attemptsTo(
Navigate.to('https://todo-app.serenity-js.org/#/'),
Ensure.that(TodoApp.recordedItems(), equals([ ])),
)
})
})
})
If your scenario requires more actors using separate browser instances, you can override the default ability to BrowseTheWeb for each actor in the same way.
Sharing notes between actors​
By default, each actor receives an ability to TakeNotes using their own notepad.
To share notes between actors, you can introduce a notepad fixture and override the default actorCalled function to make the actors reuse the same notepad.
Overriding the actorCalled function allows you to customise the abilities of the actors created via the actorCalled fixture,
as well as the default actor injected via the actor fixture.
import { useFixtures } from '@serenity-js/playwright-test';
import { Notepad, notes, TakeNotes } from '@serenity-js/core';
export interface SharedNotes {
items: string[];
}
export interface TestScopeFixtures {
notepad: Notepad<SharedNotes>
}
export const { describe, it, beforeEach, afterEach, test } = useFixtures<TestScopeFixtures>({
// Shared notepad instantiated before each test
notepad: async ({ }, use) => {
// Create an empty notepad to share between actors
const notepad = Notepad.empty<SharedNotes>()
// Alternatively, pre-populate the notepad with some data
// const notepad = Notepad.with<SharedNotes>({
// items: ['strawberries', 'bananas'],
// })
await use(notepad)
},
// Override the actorCalled fixture to use the shared notepad
extraAbilities: async ({ notepad }, use) => {
await use(actorName => {
// Return the same set of extra abilities for all the actors,
// or make it conditional based on the actorName
return [
TakeNotes.using(notepad),
]
})
},
})
import { notes } from '@serenity-js/core'
import { Ensure, equals } from '@serenity-js/assertions'
import { describe, it, SharedNotes } from './test-api'
describe('Todo App', () => {
describe('actor', () => {
it('can access a shared notepad', async ({ actor, actorCalled, browser }) => {
// default actor
await actor.attemptsTo(
notes<SharedNotes>().set('items', [
'apples',
'oranges',
]),
)
// additional actor, sharing the same notes
await actorCalled('Bobby').attemptsTo(
Ensure.that(
notes<SharedNotes>().get('items'),
equals([
'apples',
'oranges',
]),
),
)
})
})
})
Replacing the default actors​
For advanced Serenity/JS users, rather than modifying individual abilities, you can replace the default Cast of actors with a custom implementation.
For example, a custom cast can ensure each actor receives their own browser instance while sharing notes.
import { useFixtures } from '@serenity-js/playwright-test'
import { TakeNotes, Notepad } from '@serenity-js/core'
export interface MyNotes {
testListId: string;
}
export const { describe, it, beforeEach, test } = useFixtures<{ notepad: Notepad<MyNotes> }>({
notepad: Notepad.with<MyNotes>({
testListId: process.env.TEST_LIST_ID,
}),
actors: async ({ browser, contextOptions, notepad }, use) => {
const cast = Cast.where(actor => actor.whoCan(
BrowseTheWebWithPlaywright.using(browser, {
...contextOptions,
userAgent: `${ actor.name }`
}),
TakeNotes.using<MyNotes>(notepad),
))
await use(cast)
},
})
Overriding actors replaces the cast used to configure the actors instantiated via the actorCalled fixture, as well as the default actor injected via the actor fixture.
What you learnt​
- Actors come with default abilities (
BrowseTheWebWithPlaywright,TakeNotes,CallAnApi) that you can override. - Use
actor.whoCan(...)for one-off changes,test.usefor file-level config, anduseFixturesfor cross-file reuse. - Give actors separate browser instances with
BrowseTheWebWithPlaywright.using(browser). - Share data between actors by providing them with the same
Notepadinstance. - For full control, replace the entire
Castvia theactorsfixture.
Next step​
Learn about the reporting capabilities available with Serenity/JS and Playwright Test.