@serenity-js/core
Version:
The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure
162 lines (150 loc) • 7.16 kB
text/typescript
import { ErrorSerialiser } from '../../errors';
import { d } from '../../io';
import { stringified } from '../../io/stringified';
import { LogEntry, Name } from '../../model';
import type { UsesAbilities } from '../abilities';
import type { Answerable } from '../Answerable';
import type { Answered } from '../Answered';
import type { CollectsArtifacts } from '../artifacts';
import { Interaction } from '../Interaction';
import type { AnswersQuestions } from '../questions';
import type { DebuggingResult } from './DebuggingResult';
/**
* Instructs the [actor](https://serenity-js.org/api/core/class/Actor/) to evaluate and [log](https://serenity-js.org/api/core/class/Log/) the provided [answerable](https://serenity-js.org/api/core/#Answerable) values.
*
* Since this interaction **accepts a callback function** that receives the evaluated results,
* the best way to use it is while **running the test scenario via a Node.js debugger**.
* See the links below to learn how to do it in popular IDEs.
*
* ## Debugging Answerable values
*
* [`Debug.values`](https://serenity-js.org/api/core/class/Debug/#values) accepts a callback function that receives an array of [`DebuggingResult`](https://serenity-js.org/api/core/interface/DebuggingResult/) objects,
* as well as the result of evaluating each provided [answerable](https://serenity-js.org/api/core/#Answerable) with [`Actor.answer`](https://serenity-js.org/api/core/class/Actor/#answer).
*
* ```typescript
* import { actorCalled, Debug } from '@serenity-js/core'
* import { Navigate, Page } from '@serenity-js/web'
*
* // Running the below through the Node.js debugger makes the actor resolve the provided values
* // and return them to the debugger function, where you can investigate their contents,
* // or inspect any Errors the evaluation has resulted with.
* await actorCalled('Debbie').attemptsTo(
* Navigate.to('http://example.org'),
* Debug.values((results, title, url) => {
* // set a breakpoint here to view `results`, `title` and `url` in your IDE debugger
* }, Page.current().title(), Page.current().url()), // accepts multiple values
* );
* ```
*
* ## Accessing Playwright Page
*
* [Playwright Test for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
* provides features that allow for [experimenting with web UI locators](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright#tune-locators)
* while the test is paused at breakpoint.
*
* Since this functionality is specific to [Playwright](https://serenity-js.org/api/playwright),
* you can use it by passing [`PlaywrightPage.current().nativePage()`](https://serenity-js.org/api/playwright/class/PlaywrightPage/#current)
* to Serenity/JS [`Debug.values`](https://serenity-js.org/api/core/class/Debug/#values). Also make sure to name the evaluated value `page`, as this is the variable name that the Playwright VSCode extension expects.
*
* ```typescript
* import { actorCalled, Debug } from '@serenity-js/core'
* import { PlaywrightPage } from '@serenity-js/playwright'
*
* // Running the below through the Node.js debugger makes the actor resolve the provided values
* // and return them to the debugger function, where you can investigate their contents,
* // or inspect any Errors the evaluation has resulted with.
* await actorCalled('Debbie').attemptsTo(
* Navigate.to('http://example.org'),
* Debug.values((results, page) => {
* // set a breakpoint here to use Playwright locator debugging features
* page.locator('.example-css-class')
* // note that you can change this selector while having the test paused at breakpoint
* }, PlaywrightPage.current().nativePage()),
* );
* ```
*
* ## Learn more
* - [Node.js debugging - Getting started](https://nodejs.org/en/docs/guides/debugging-getting-started/)
* - [Node.js debugging in VS Code](https://code.visualstudio.com/docs/nodejs/nodejs-debugging)
* - [Running and debugging Node.js in WebStorm](https://www.jetbrains.com/help/webstorm/running-and-debugging-node-js.html)
* - [Playwright Test for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
*
* @group Activities
*/
export class Debug<Values extends Array<Answerable<unknown>>> extends Interaction {
/**
* Instructs the [actor](https://serenity-js.org/api/core/class/Actor/) to evaluate the provided `values`,
* log the results, and then pass them to your `debuggerFunction`.
*
* To use this interaction, run your test scenario in the Node.js debugger
* and set a breakpoint inside the `debuggerFunction`.
*
* @param debuggerFunction
* @param values
*/
static values<Values extends Array<Answerable<unknown>>>(debuggerFunction: (
results: { [ Index in keyof Values ]: DebuggingResult<Values[Index]> },
...answers: { [ Index in keyof Values ]: Answered<Values[Index]> }
) => Promise<void> | void, ...values: Values): Interaction {
return new Debug<Values>(
`#actor debugs ${ values.length } values`,
debuggerFunction,
values
);
}
/**
* @param description
* Description of this interaction
*
* @param debuggerFunction
* Callback function to receive the results of debugging
*
* @param values
* Value to be evaluated by the actor, and provided to debuggerFunction
*/
protected constructor(
description: string,
private readonly debuggerFunction: (
debuggingResults: { [ Index in keyof Values ]: DebuggingResult<Values[Index]> },
...args: { [ Index in keyof Values ]: Answered<Values[Index]> }
) => Promise<void> | void,
private readonly values: Values,
) {
super(description);
}
/**
* @inheritDoc
*/
async performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): Promise<void> {
const results = [] as { [ Index in keyof Values ]: DebuggingResult<Values[Index]> };
for (const value of this.values) {
const result: DebuggingResult<unknown> = {
description: d`${ value }`,
value: undefined,
error: undefined,
};
try {
result.value = await actor.answer(value);
}
catch (error) {
result.error = error;
}
actor.collect(
LogEntry.fromJSON({
data: JSON.stringify({
value: stringified(result.value),
error: result.error
? ErrorSerialiser.serialise(result.error)
: result.error
}),
}),
new Name(result.description)
);
results.push(result);
}
this.debuggerFunction(
results,
...results.map(result => result.value) as { [ Index in keyof Values ]: Answered<Values[Index]> }
);
}
}