Skip to content

Commit

Permalink
feat(web): ElementExpectation makes it easier to define custom PageEl…
Browse files Browse the repository at this point in the history
…ement-related Expectations

To be used with Ensure, Wait, Check, and so on.
  • Loading branch information
jan-molak committed Dec 29, 2021
1 parent 5314246 commit 92ebf7d
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 5 deletions.
88 changes: 83 additions & 5 deletions packages/web/src/expectations/ElementExpectation.ts
Expand Up @@ -3,20 +3,98 @@ import { AnswersQuestions, Expectation, ExpectationMet, ExpectationNotMet, Expec
import { PageElement } from '../screenplay';

/**
* @access private
* @desc
* A convenience method to create a custom {@link PageElement}-related {@link @serenity-js/core/lib/screenplay/questions~Expectation}
*
* @example <caption>Defining an expectation</caption>
* import { Expectation } from '@serenity-js/core';
* import { ElementExpectation, PageElement } from '@serenity-js/web';
*
* export function isPresent(): Expectation<boolean, PageElement> {
* return ElementExpectation.forElementTo('become present', actual => actual.isPresent());
* }
*
* @example <caption>Using an expectation in an assertion</caption>
* import { Ensure } from '@serenity-js/assertions';
* import { actorCalled } from '@serenity-js/core';
* import { By, PageElement } from '@serenity-js/web';
*
* const submitButton = () =>
* PageElement.located(By.css('.submit')).describedAs('submit button');
*
* actorCalled('Izzy').attemptsTo(
* Ensure.that(submitButton(), isPresent())
* );
*
* @example <caption>Using an expectation in a synchronisation statement</caption>
* import { actorCalled, Duration } from '@serenity-js/core';
* import { By, PageElement, Wait } from '@serenity-js/web';
*
* const submitButton = () =>
* PageElement.located(By.css('.submit')).describedAs('submit button');
*
* actorCalled('Izzy').attemptsTo(
* Wait.upTo(Duration.ofSeconds(2)).until(submitButton(), isPresent())
* );
*
* @public
* @extends {@serenity-js/core/lib/screenplay/questions~Expectation}
*/
export class ElementExpectation extends Expectation<any, PageElement> {
static forElementTo(message: string, fn: (actual: PageElement) => Promise<boolean>): Expectation<any, PageElement> {
return new ElementExpectation(message, fn);

/**
* @desc
* Creates an {@link @serenity-js/core/lib/screenplay/questions~Expectation}
*
* @example <caption>Defining an expectation</caption>
* import { Expectation } from '@serenity-js/core';
* import { ElementExpectation, PageElement } from '@serenity-js/web';
*
* export function isPresent(): Expectation<boolean, PageElement> {
* return ElementExpectation.forElementTo('become present', actual => actual.isPresent());
* }
*
* @param {string} description
* A description of the expectation.
* Please note that Serenity/JS will use it to describe your expectation in sentences like these:
* - `actor ensures that <something> does <description>`
* - `actor ensures that <something> does not <description>`
* - `actor waits until <something> does <description>`
* - `actor waits until <something> does not <description>`
*
* To work with the above templates, the description should be similar to
* - `become present`,
* - `become active`,
* - `equal X`,
* - `has value greater than Y`.
*
* Descriptions like "is present", "is active", "equals X", "is greater than Y" WILL NOT work well.
*
* @param {function(actual: PageElement): Promise<boolean>} fn
* An asynchronous callback function that receives a {@link PageElement} and returns a {@link Promise}
* that should resolve to `true` when the expectation is met, and `false` otherwise.
*
* @returns {ElementExpectation<any, PageElement>}
*/
static forElementTo(description: string, fn: (actual: PageElement) => Promise<boolean>): Expectation<any, PageElement> {
return new ElementExpectation(description, fn);
}

/**
* @param {string} description
* @param {function(actual: PageElement): Promise<boolean>} fn
*/
constructor(
subject: string,
description: string,
private readonly fn: (actual: PageElement) => Promise<boolean>,
) {
super(subject);
super(description);
}

/**
* @param {@serenity-js/core/lib/screenplay/actor~AnswersQuestions} actor
* @returns {function(actual: PageElement): Promise<ExpectationOutcome<boolean, PageElement>>}
*/
answeredBy(actor: AnswersQuestions): (actual: PageElement) => Promise<ExpectationOutcome<boolean, PageElement>> {
return (actual: PageElement) =>
this.fn(actual)
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/expectations/index.ts
@@ -1,3 +1,4 @@
export * from './ElementExpectation';
export * from './isActive';
export * from './isClickable';
export * from './isEnabled';
Expand Down

0 comments on commit 92ebf7d

Please sign in to comment.