Skip to content

Commit

Permalink
feat(web): Value.of(pageElement) returns a QuestionAdapter
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-molak committed Mar 27, 2022
1 parent b32f280 commit c45b483
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 28 deletions.
107 changes: 94 additions & 13 deletions integration/web-specs/spec/screenplay/questions/Value.spec.ts
@@ -1,33 +1,114 @@
import 'mocha';

import { Ensure, equals } from '@serenity-js/assertions';
import { Ensure, equals, not } from '@serenity-js/assertions';
import { actorCalled } from '@serenity-js/core';
import { By, Navigate, PageElement, Value } from '@serenity-js/web';
import { Attribute, By, Navigate, PageElement, PageElements, Value } from '@serenity-js/web';
import { expect } from '@integration/testing-tools';

/** @test {Value} */
describe('Value', () => {

/** @test {Value.of} */
describe('of', () => {

/** @test {Value} */
/** @test {Value.of} */
it('allows the actor to read the "value" attribute of a DOM element matching the locator', () =>
const input = PageElement.located(By.tagName('input')).describedAs('username field');
const form = PageElement.located(By.tagName('form')).describedAs(`form`);

before(() =>
actorCalled('Bernie').attemptsTo(
Navigate.to('/screenplay/questions/value/username_form.html'),

Ensure.that(Value.of(PageElement.located(By.tagName('input')).describedAs('username field')), equals('jan-molak')),
));

/** @test {Value} */
/** @test {Value#of} */
it('allows for a question relative to another target to be asked', () =>
it('allows the actor to read the "value" attribute of a DOM element matching the locator', () =>
actorCalled('Bernie').attemptsTo(
Navigate.to('/screenplay/questions/value/username_form.html'),
Ensure.that(Value.of(input), equals('jan-molak')),
));

it('allows for a meta-question relative to another PageElement to be asked', () =>
actorCalled('Bernie').attemptsTo(
Ensure.that(
Value.of(PageElement.located(By.tagName('input')).describedAs('username field'))
.of(PageElement.located(By.tagName('form')).describedAs(`form`)),
Value.of(input).of(form),
equals('jan-molak'),
),
));

it('produces a sensible description of the question being asked', () => {
expect(Value.of(input).toString())
.to.equal(`the value of username field`);
});

it('produces a QuestionAdapter that enables access to the underlying value', () =>
actorCalled('Bernie').attemptsTo(
Ensure.that(
Value.of(input).length,
equals(9),
),
Ensure.that(
Value.of(input).toLocaleUpperCase(),
equals('JAN-MOLAK'),
),
));
});

describe('filtering', () => {

const NonEmptyInput =
PageElements.located(By.css('input'))
.describedAs('input elements with non-empty value')
.where(Value, not(equals('')))
.first();

before(() =>
actorCalled('Bernie').attemptsTo(
Navigate.to('/screenplay/questions/value/filtering.html'),
));

it('can be used to filter a list of elements', () =>
actorCalled('Wendy').attemptsTo(
Ensure.that(Attribute.called('name').of(NonEmptyInput), equals('non-empty')),
));
});

/** @test {Value#toString} */
describe('toString', () => {

const sections = PageElements.located(By.css('section')).describedAs('sections');
const section = PageElement.located(By.css('section')).describedAs('a section');
const input = PageElement.located(By.css('input')).describedAs('input field');

it('provides a human-readable description of a regular question', () => {
const description = Value.of(input).toString();

expect(description).to.equal(`the value of input field`)
});

it('allows for the description to be altered', () => {
const description = Value.of(input).describedAs('username').toString();

expect(description).to.equal(`username`)
});

it('provides a human-readable description of the meta-question', () => {
const description = Value.of(input).of(section).toString();

expect(description).to.equal(`the value of input field of a section`)
});

it('provides a human-readable description of a reqular question used in a filter', () => {
const found = sections.where(Value, equals('example'));

const description = found.toString();

expect(description).to.equal(`sections where Value does equal 'example'`)
});

it('provides a human-readable description of a meta-question used in a filter', () => {
const found = sections
.where(Value.of(input), equals('example'));

const description = found.toString();

expect(description).to.equal(`sections where the value of input field does equal 'example'`)
});
});
});
@@ -0,0 +1,8 @@
<html lang="">
<body>
<form>
<input name="empty" value="" />
<input name="non-empty" value="non-empty value" />
</form>
</body>
</html>
53 changes: 38 additions & 15 deletions packages/web/src/screenplay/questions/Value.ts
@@ -1,29 +1,47 @@
import { Answerable, AnswersQuestions, d, MetaQuestion, Question, UsesAbilities } from '@serenity-js/core';
import { Answerable, AnswersQuestions, d, MetaQuestion, Question, QuestionAdapter, UsesAbilities } from '@serenity-js/core';

import { PageElement } from '../models';

/**
* @desc
* Returns the `value` attribute of a given {@link WebElement},
* represented by Answerable<{@link @wdio/types~Element}>
* Retrieves the `value` attribute of a given {@link PageElement}.
*
* @example <caption>Example widget</caption>
* <input type="text" id="username" value="Alice" />
*
* @example <caption>Retrieve CSS classes of a given WebElement</caption>
* @example <caption>Retrieve the `value` of a given PageElement</caption>
* import { actorCalled } from '@serenity-js/core';
* import { Ensure, equals } from '@serenity-js/assertions';
* import { BrowseTheWeb, by, Value, Target } from '@serenity-js/webdriverio';
* import { By, PageElement, Value } from '@serenity-js/web';
* import { BrowseTheWebWithWebdriverIO } from '@serenity-js/webdriverio';
*
* const usernameField = () =>
* Target.the('username field').located(by.id('username'))
* PageElement.located(By.id('username')).describedAs('username field')
*
* actorCalled('Lisa')
* .whoCan(BrowseTheWeb.using(browser))
* .whoCan(BrowseTheWebWithWebdriverIO.using(browser))
* .attemptsTo(
* Ensure.that(Value.of(usernameField), equals('Alice')),
* )
*
* @example <caption>Using Value as QuestionAdapter</caption>
* import { actorCalled } from '@serenity-js/core';
* import { Ensure, equals } from '@serenity-js/assertions';
* import { By, PageElement, Value } from '@serenity-js/web';
* import { BrowseTheWebWithWebdriverIO } from '@serenity-js/webdriverio';
*
* const usernameField = () =>
* PageElement.located(By.id('username')).describedAs('username field')
*
* actorCalled('Lisa')
* .whoCan(BrowseTheWebWithWebdriverIO.using(browser))
* .attemptsTo(
* Ensure.that(
* Value.of(usernameField).toLocaleLowerCase()[0],
* equals('a') // [a]lice
* ),
* )
*
* @extends {@serenity-js/core/lib/screenplay~Question}
* @implements {@serenity-js/core/lib/screenplay/questions~MetaQuestion}
*/
Expand All @@ -37,11 +55,16 @@ export class Value
private subject: string;

/**
* @param {Answerable<PageElement>} element
* @returns {Value}
* @desc
* Retrieves the `value` attribute of a given {@link PageElement}.
*
* @param {@serenity-js/core/lib/screenplay~Answerable<PageElement>} element
* @returns {@serenity-js/core/lib/screenplay~QuestionAdapter<string>}
*
* @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
*/
static of(element: Answerable<PageElement>): Question<Promise<string>> & MetaQuestion<Answerable<PageElement>, Promise<string>> {
return new Value(element);
static of(element: Answerable<PageElement>): QuestionAdapter<string> & MetaQuestion<Answerable<PageElement>, Promise<string>> {
return Question.createAdapter(new Value(element)) as QuestionAdapter<string> & MetaQuestion<Answerable<PageElement>, Promise<string>>;
}

/**
Expand All @@ -54,11 +77,11 @@ export class Value

/**
* @desc
* Resolves to the value of a given [`input`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
* {@link WebElement}, located in the context of a `parent` element.
* Retrieves the `value` attribute of a given {@link PageElement}.
* located within the `parent` element.
*
* @param {Answerable<PageElement>} parent
* @returns {Question<Promise<string>>}
* @param {@serenity-js/core/lib/screenplay~Answerable<PageElement>} parent
* @returns {@serenity-js/core/lib/screenplay~QuestionAdapter<string>}
*
* @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
*/
Expand Down

0 comments on commit c45b483

Please sign in to comment.