Skip to main content

externalabstractQuestion <T>

Questions describe how actors should query the system under test or the test environment to retrieve some information.

Questions are the core building block of the Screenplay Pattern, along with actors, abilities, interactions, and tasks.

Learn more about:

Implementing a basic custom Question

 import { actorCalled, AnswersQuestions, UsesAbilities, Question } from '@serenity-js/core'
import { Ensure, equals } from '@serenity-js/assertions'

const LastItemOf = <T>(list: T[]): Question<T> =>
Question.about('last item from the list', (actor: AnswersQuestions & UsesAbilities & HasName) => {
return list[list.length - 1]
});

await actorCalled('Quentin').attemptsTo(
Ensure.that(LastItemOf([1,2,3]), equals(3)),
)

However, it's worth noting that sometimes you don't even need a custom question. You can instead use a QuestionAdapter, which proxies the known members of the type. In our list example, we can use a Serenity/JS List which conveniently already has a last().

import { Page } from '@serenity-js/web'
import { actorCalled, List } from '@serenity-js/core'
import { Ensure, equals, includes } from '@serenity-js/assertions'

const list = List.of([ 1, 2, 3 ]);

await actorCalled('Quentin').attemptsTo(
Ensure.that(list.last(), equals(3)),
)

const urlPath = () =>
Page.current()
.url() // ← QuestionAdapter<URL>, lets you access all the regular members of URL as QuestionAdapters
.pathname // ← QuestionAdapter<string>
.describedAs('the URL path'); // ← Optionally, add a clear and concise description

await actorCalled('Quentin').attemptsTo(
Ensure.that(urlPath(), includes('index.html')),
)

Implementing a Question that uses an Ability

Just like the interactions, a Question also can use actor's abilities.

Here, we use the ability to CallAnApi to retrieve a property of an HTTP response.

 import { AnswersQuestions, UsesAbilities, Question } from '@serenity-js/core'
import { CallAnApi } from '@serenity-js/rest'

const TextOfLastResponseStatus = () =>
Question.about(`the text of the last response status`, actor => {
return CallAnApi.as(actor).mapLastResponse(response => response.statusText)
})

Learn more

Mapping answers to other questions

Apart from retrieving information, questions can be used to transform information retrieved by other questions.

Here, we use the factory method Question.about to produce a question that makes the received actor answer LastResponse.status and then compare it against some expected value.

import { actorCalled, AnswersQuestions, UsesAbilities, Question } from '@serenity-js/core'
import { CallAnApi, LastResponse } from '@serenity-js/rest'
import { Ensure, equals } from '@serenity-js/assertions'

const RequestWasSuccessful = () =>
Question.about<boolean>(`the request was successful`, async actor => {
const status = await actor.answer(LastResponse.status());

return status === 200;
})

await actorCalled('Quentin')
.whoCan(CallAnApi.at('https://api.example.org/'));
.attemptsTo(
Send.a(GetRequest.to('/books/0-688-00230-7')),
Ensure.that(RequestWasSuccessful(), isTrue()),
)

Note that the above example is for demonstration purposes only, Serenity/JS provides an easier way to verify the response status of the LastResponse:

import { actorCalled } from '@serenity-js/core'
import { CallAnApi, LastResponse } from '@serenity-js/rest'
import { Ensure, equals } from '@serenity-js/assertions'

await actorCalled('Quentin')
.whoCan(CallAnApi.at('https://api.example.org/'));
.attemptsTo(
Send.a(GetRequest.to('/books/0-688-00230-7')),
Ensure.that(LastResponse.status(), equals(200)),
)

Hierarchy

Index

Methods

staticexternalabout

  • Factory method that simplifies the process of defining custom questions.

    Defining a custom question

    import { Question } from '@serenity-js/core'

    const EnvVariable = (name: string) =>
    Question.about(`the ${ name } env variable`, actor => process.env[name])

    Defining a question with extensions

    When the third argument is an object, its entries are added as methods to the returned adapter, allowing it to expose domain-specific behaviour that the generic proxy wouldn't provide.

    If the extensions include of, it defines rescoping behaviour and all extensions are automatically propagated to the rescoped result. Extension methods receive this bound to the proxy target, so actor.answer(this) resolves the current question.

    const Config = (path: string) =>
    Question.about(`config at ${ path }`, actor => readConfig(path), {
    of: (scope: string) =>
    Question.about(`config at ${ path }`, actor => readConfig(`${ scope }/${ path }`)),
    keys: function () {
    return Question.about(`keys of ${ this }`, async actor => {
    const config = await actor.answer(this);
    return Object.keys(config);
    });
    },
    })

    // keys() is available on the adapter and after .of() rescoping:
    Config('database').keys()
    Config('database').of('production').keys()

    When the third argument is a function, it defines .of() rescoping only — the original form, equivalent to { of: thatFunction }.


    Type parameters

    • Answer_Type
    • Supported_Context_Type: unknown

    Parameters

    Returns MetaQuestionAdapter<Supported_Context_Type, Awaited<Answer_Type>>

staticexternalfromObject

  • Generates a QuestionAdapter that recursively resolves any Answerable fields of the provided object, including Answerable fields of nested objects.

    Optionally, the method accepts overrides to be shallow-merged with the fields of the original source, producing a new merged object.

    Overrides are applied from left to right, with subsequent objects overwriting property assignments of the previous ones.

    Resolving an object recursively using Question.fromObject

    import { actorCalled, Question } from '@serenity-js/core'
    import { Send, PostRequest } from '@serenity-js/rest'
    import { By, Text, PageElement } from '@serenity-js/web'

    await actorCalled('Daisy')
    .whoCan(CallAnApi.at('https://api.example.org'))
    .attemptsTo(
    Send.a(
    PostRequest.to('/products/2')
    .with(
    Question.fromObject({
    name: Text.of(PageElement.located(By.css('.name'))),
    })
    )
    )
    );

    Merging objects using Question.fromObject

     import { actorCalled, Question } from '@serenity-js/core'
    import { Send, PostRequest } from '@serenity-js/rest'
    import { By, Text, PageElement } from '@serenity-js/web'

    await actorCalled('Daisy')
    .whoCan(CallAnApi.at('https://api.example.org'))
    .attemptsTo(
    Send.a(
    PostRequest.to('/products/2')
    .with(
    Question.fromObject({
    name: Text.of(PageElement.located(By.css('.name'))),
    quantity: undefined,
    }, {
    quantity: 2,
    })
    )
    )
    );

    Learn more


    Type parameters

    • Source_Type: object

    Parameters

    Returns QuestionAdapter<RecursivelyAnswered<Source_Type>>

staticexternalfromArray

staticexternalisAQuestion

  • isAQuestion<T>(maybeQuestion: unknown): maybeQuestion is Question<T>
  • Checks if the value is a Question.


    Type parameters

    • T

    Parameters

    • externalmaybeQuestion: unknown

      The value to check

    Returns maybeQuestion is Question<T>

staticexternalisAMetaQuestion

  • isAMetaQuestion<CT, RQT>(maybeMetaQuestion: unknown): maybeMetaQuestion is MetaQuestion<CT, RQT>
  • Checks if the value is a MetaQuestion.


    Type parameters

    Parameters

    • externalmaybeMetaQuestion: unknown

      The value to check

    Returns maybeMetaQuestion is MetaQuestion<CT, RQT>

staticexternalformattedValue

  • Creates a MetaQuestion that can be composed with any Answerable to produce a single-line description of its value.

    import { actorCalled, Question } from '@serenity-js/core'
    import { Ensure, equals } from '@serenity-js/assertions'

    const accountDetails = () =>
    Question.about('account details', actor => ({ name: 'Alice', age: 28 }))

    await actorCalled('Alice').attemptsTo(
    Ensure.that(
    Question.formattedValue().of(accountDetails()),
    equals('{ name: "Alice", age: 28 }'),
    ),
    )

    Parameters

    Returns MetaQuestion<any, Question<Promise<string>>>

staticexternalvalue

  • Creates a MetaQuestion that can be composed with any Answerable to return its value when the answerable is a Question, or the answerable itself otherwise.

    The description of the resulting question is produced by calling Question.describedBy on the provided answerable.

    import { actorCalled, Question } from '@serenity-js/core'
    import { Ensure, equals } from '@serenity-js/assertions'

    const accountDetails = () =>
    Question.about('account details', actor => ({ name: 'Alice', age: 28 }))

    await actorCalled('Alice').attemptsTo(
    Ensure.that(
    Question.description().of(accountDetails()),
    equals('account details'),
    ),
    Ensure.that(
    Question.value().of(accountDetails()),
    equals({ name: 'Alice', age: 28 }),
    ),
    )

    Type parameters

    • Answer_Type

    Returns MetaQuestion<Answer_Type, Question<Promise<Answer_Type>>>

externalabstractansweredBy

externaldescribedAs

  • Changes the description of this object, as returned by Describable.describedBy and Describable.toString.


    Parameters

    • externaldescription: Answerable<string> | MetaQuestion<Awaited<T>, Question<Promise<string>>>

      Replaces the current description according to the following rules:

      • If description is an Answerable, it replaces the current description
      • If description is a MetaQuestion, the current description is passed as context to description.of(context), and the result replaces the current description

    Returns this

externaldescribedBy

  • Resolves the description of this object in the context of the provided actor.


    Parameters

    Returns Promise<string>

externaltoString

  • toString(): string
  • Returns a human-readable description of this object.


    Returns string

Questions

staticexternalcreateAdapter

  • Wraps a Question in a QuestionAdapter proxy.

    The proxy intercepts property access on the resolved answer type, allowing the result to be used as both a Question and a transparent wrapper over the answer's own methods and properties.

    Methods defined directly on the statement object (such as .isPresent(), .describedAs(), or custom methods on Question subclasses) take priority over proxied methods from the resolved answer type.

    Use this method when creating custom Question subclasses that need to expose domain-specific methods through the proxy. For example, @serenity-js/web uses this to make .element() and .elements() available on PageElement.located() results.


    Type parameters

    • AT

    Parameters

    Returns QuestionAdapter<Awaited<AT>>

Screenplay Pattern

publicexternalas

  • Maps this question's resolved answer to a QuestionAdapter of a different type.

    Provide a function to transform the resolved answer:

    import { Question } from '@serenity-js/core'

    const number = Question.about('number returned as text', () => '42')
    .as(Number)

    const name = Question.about('name with whitespace', () => ' Alice ')
    .as(value => value.trim())

    const firstValue = Question.about('available values', () => ['first', 'second'])
    .as(values => values[0])

    Alternatively, provide a constructor. When you call .as(MyClass), Serenity/JS passes the resolved answer as MyClass's sole constructor argument. This is particularly useful when wrapping a mounted UI component in an Interaction Object for component testing:

    const card = story('components/UserCard/Default', { name: 'Alice' }).as(UserCard)

    To distinguish a mapping function from a constructor, Serenity/JS calls the mapping as a function first. If that throws a TypeError and the mapping has a prototype (indicating an ES6 class), Serenity/JS retries with new. This preserves the primitive result of Number(42), rather than producing a Number wrapper object.

    Learn more


    Type parameters

    • O

    Parameters

    • externalmapping: (answer: Awaited<T>) => O | Promise<O>

      A function that transforms the resolved answer, or a constructor that receives it as its sole argument.

      Returns QuestionAdapter<O>

      A QuestionAdapter that resolves to the mapped answer.