UNPKG

@serenity-js/core

Version:

The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure

231 lines 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Expectation = void 0; const io_1 = require("../../io"); const __1 = require("../"); const index_1 = require("../index"); const Question_1 = require("../Question"); const questions_1 = require("../questions"); /** * Defines an expectation to be used with [`Wait.until`](https://serenity-js.org/api/core/class/Wait/#until), * [`Check.whether`](https://serenity-js.org/api/core/class/Check/#whether), * [`Ensure.that`](https://serenity-js.org/api/assertions/class/Ensure/#that) * and as part of the Page Element Query Language with [`PageElements.where`](https://serenity-js.org/api/web/class/PageElements/#where) * and [`List.where`](https://serenity-js.org/api/core/class/List/#where). * * @group Expectations */ class Expectation extends questions_1.Describable { functionName; predicate; /** * A factory method to that makes defining custom [expectations](https://serenity-js.org/api/core/class/Expectation/) easier * * #### Defining a custom expectation * * ```ts * import { Expectation } from '@serenity-js/core' * import { PageElement } from '@serenity-js/web' * * const isEmpty = Expectation.define( * 'isEmpty', // name of the expectation function to be used when producing an AssertionError * 'become empty', // human-readable description of the relationship between expected and actual values * async (actual: PageElement) => { * const value = await actual.value(); * return value.length === 0; * } * ) * ``` * * #### Using a custom expectation in an assertion * * ```ts * import { Ensure } from '@serenity-js/assertions' * import { actorCalled } from '@serenity-js/core' * import { By, Clear, PageElement } from '@serenity-js/web' * * const nameField = () => * PageElement.located(By.css('[data-test-id="name"]')).describedAs('name field'); * * await actorCalled('Izzy').attemptsTo( * Clear.the(nameField()), * Ensure.that(nameField(), isEmpty()) * ) * ``` * * #### Using a custom expectation in a control flow statement * * ```ts * import { not } from '@serenity-js/assertions' * import { actorCalled, Check, Duration, Wait } from '@serenity-js/core' * import { By, PageElement } from '@serenity-js/web' * * const nameField = () => * PageElement.located(By.css('[data-test-id="name"]')).describedAs('name field'); * * await actorCalled('Izzy').attemptsTo( * Check.whether(nameField(), isEmpty()) * .andIfSo( * Enter.theValue(actorInTheSpotlight().name).into(nameField()), * ), * ) * ``` * * #### Using a custom expectation in a synchronisation statement * * ```ts * import { not } from '@serenity-js/assertions' * import { actorCalled, Duration, Wait } from '@serenity-js/core' * import { By, PageElement } from '@serenity-js/web' * * const nameField = () => * PageElement.located(By.css('[data-test-id="name"]')).describedAs('name field'); * * await actorCalled('Izzy').attemptsTo( * Enter.theValue(actorInTheSpotlight().name).into(nameField()), * * Wait.upTo(Duration.ofSeconds(2)) * .until(nameField(), not(isEmpty())), * ) * ``` * * #### Learn more * - [`Ensure`](https://serenity-js.org/api/assertions/class/Ensure/) * - [`Check`](https://serenity-js.org/api/core/class/Check/) * - [`Wait`](https://serenity-js.org/api/core/class/Wait/) * * @param functionName * Name of the expectation function to be used when producing an [`AssertionError`](https://serenity-js.org/api/core/class/AssertionError/) * * @param relationship * Human-readable description of the relationship between the `expected` and the `actual` values. * Used when reporting [activities](https://serenity-js.org/api/core/class/Activity/) performed by an [actor](https://serenity-js.org/api/core/class/Actor/) * * @param predicate */ static define(functionName, relationship, predicate) { return Object.defineProperty(function (...answerableArguments) { const description = typeof relationship === 'function' ? relationship(...answerableArguments) : (answerableArguments.length === 1 ? (0, index_1.the) `${{ toString: () => relationship }} ${answerableArguments[0]}` : relationship); return new Expectation(functionName, description, async (actor, actualValue) => { const predicateArguments = await (0, io_1.asyncMap)(answerableArguments, answerableArgument => actor.answer(answerableArgument)); const actual = await actor.answer(actualValue); const result = await predicate(actual, ...predicateArguments); const descriptionText = await actor.answer(description); const expectationDetails = __1.ExpectationDetails.of(functionName, ...predicateArguments); const expected = predicateArguments.length > 0 ? predicateArguments[0] : true; // the only parameter-less expectations are boolean ones like `isPresent`, `isActive`, etc. return result ? new __1.ExpectationMet(descriptionText, expectationDetails, expected, actual) : new __1.ExpectationNotMet(descriptionText, expectationDetails, expected, actual); }); }, 'name', { value: functionName, writable: false }); } /** * Used to define a simple [`Expectation`](https://serenity-js.org/api/core/class/Expectation/) * * #### Simple parameterised expectation * * ```ts * import { actorCalled, Expectation } from '@serenity-js/core' * import { Ensure } from '@serenity-js/assertions' * * function isDivisibleBy(expected: Answerable<number>): Expectation<number> { * return Expectation.thatActualShould<number, number>('have value divisible by', expected) * .soThat((actualValue, expectedValue) => actualValue % expectedValue === 0); * } * * await actorCalled('Erica').attemptsTo( * Ensure.that(4, isDivisibleBy(2)), * ) * ``` * * @param relationshipName * Name of the relationship between the `actual` and the `expected`. Use format `have value <adjective>` * so that the description works in both positive and negative contexts, e.g. `Waited until 5 does have value greater than 2`, * `Expected 5 to not have value greater than 2`. * * @param expectedValue */ static thatActualShould(relationshipName, expectedValue) { return ({ soThat: (simplifiedPredicate) => { const message = relationshipName + ' ' + (0, io_1.d) `${expectedValue}`; return new Expectation('unknown', message, async (actor, actualValue) => { const expected = await actor.answer(expectedValue); const actual = await actor.answer(actualValue); const result = await simplifiedPredicate(actual, expected); const expectationDetails = __1.ExpectationDetails.of('unknown'); return result ? new __1.ExpectationMet(message, expectationDetails, expected, actual) : new __1.ExpectationNotMet(message, expectationDetails, expected, actual); }); }, }); } /** * Used to compose [expectations](https://serenity-js.org/api/core/class/Expectation/). * * #### Composing [expectations](https://serenity-js.org/api/core/class/Expectation/) * * ```ts * import { actorCalled, Expectation } from '@serenity-js/core' * import { Ensure, and, or, isGreaterThan, isLessThan, equals } from '@serenity-js/assertions' * * function isWithin(lowerBound: number, upperBound: number) { * return Expectation * .to(`have value within ${ lowerBound } and ${ upperBound }`) * .soThatActual( * and( * or(isGreaterThan(lowerBound), equals(lowerBound)), * or(isLessThan(upperBound), equals(upperBound)), * ) * ) * } * * await actorCalled('Erica').attemptsTo( * Ensure.that(5, isWithin(3, 6)), * ) * ``` * * @param relationshipName * Name of the relationship between the `actual` and the `expected`. Use format `have value <adjective>` * so that the description works in both positive and negative contexts, e.g. `Waited until 5 does have value greater than 2`, * `Expected 5 to not have value greater than 2`. */ static to(relationshipName) { return { soThatActual: (expectation) => { return new Expectation('unknown', relationshipName, async (actor, actualValue) => { return await actor.answer(expectation.isMetFor(actualValue)); }); }, }; } constructor(functionName, description, predicate) { super(description); this.functionName = functionName; this.predicate = predicate; } /** * Returns a [`QuestionAdapter`](https://serenity-js.org/api/core/#QuestionAdapter) that resolves to [`ExpectationOutcome`](https://serenity-js.org/api/core/class/ExpectationOutcome/) * indicating that the [expectation was met](https://serenity-js.org/api/core/class/ExpectationMet/) * or that the [expectation was not met](https://serenity-js.org/api/core/class/ExpectationNotMet/) * * @param actual */ isMetFor(actual) { return Question_1.Question.about(this.getDescription(), actor => this.predicate(actor, actual)); } /** * @inheritDoc */ describedAs(description) { super.setDescription(description); return this; } } exports.Expectation = Expectation; //# sourceMappingURL=Expectation.js.map