Skip to content

Commit

Permalink
feat(web): Text.ofAll accepts mapped PageElements
Browse files Browse the repository at this point in the history
For example:
```typescript
Text.ofAll(
  PageElements.located(By.css('li'))
    .where(CssClasses, include('expected class'))
    .eachMappedTo(PageElement.located(By.css('a')))
)
```
  • Loading branch information
jan-molak committed Dec 29, 2021
1 parent c36e210 commit 5314246
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 55 deletions.
54 changes: 33 additions & 21 deletions integration/web-specs/spec/screenplay/models/PageElements.spec.ts
Expand Up @@ -91,7 +91,7 @@ describe('PageElements', () => {

it('all elements relative to another page element', () =>
actorCalled('Elle').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/shopping_list.html'),

Ensure.that(Text.ofAll(ShoppingList.boughtItems()), equals([ 'coffee' ])),
));
Expand Down Expand Up @@ -126,7 +126,7 @@ describe('PageElements', () => {
});
});

describe.only('when iterating over page elements', () => {
describe('when iterating over page elements', () => {

beforeEach(() =>
actorCalled('Elle').attemptsTo(
Expand All @@ -135,14 +135,22 @@ describe('PageElements', () => {

it('lets the actor perform a given task for each one of them', () =>
actorCalled('Elle').attemptsTo(

Ensure.that(
Text.ofAll(AdvancedShoppingList.items()),
equals(['oats x', 'coconut milk x', 'coffee x'])
Text.ofAll(
AdvancedShoppingList
.items()
.eachMappedTo(AdvancedShoppingList.itemName())
),
equals(['oats', 'coconut milk', 'coffee'])
),
Ensure.that(
Text.ofAll(AdvancedShoppingList.items().where(CssClasses, contain('buy'))),
equals(['oats x', 'coconut milk x'])
Text.ofAll(
AdvancedShoppingList
.items()
.where(CssClasses, contain('buy'))
.eachMappedTo(AdvancedShoppingList.itemName())
),
equals(['oats', 'coconut milk'])
),

AdvancedShoppingList.items()
Expand All @@ -151,8 +159,12 @@ describe('PageElements', () => {
)),

Ensure.that(
Text.ofAll(AdvancedShoppingList.items().where(CssClasses, contain('buy'))),
equals(['coffee x'])
Text.ofAll(
AdvancedShoppingList.items()
.where(CssClasses, contain('buy'))
.eachMappedTo(AdvancedShoppingList.itemName())
),
equals(['coffee'])
),
));
});
Expand Down Expand Up @@ -241,35 +253,35 @@ describe('PageElements', () => {

it('gets the number of items', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(list.count(), equals(2)),
));

it('picks all the items', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.ofAll(list), contain('coconut milk x')),
));

it('picks the first item', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.of(list.first()), startsWith('oats')),
));

it('picks the last item', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.of(list.last()), startsWith('coconut milk')),
));

it('picks the nth item', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.of(list.get(1)), startsWith('coconut milk')),
));
Expand Down Expand Up @@ -322,35 +334,35 @@ describe('PageElements', () => {

it('gets the number of items', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(list.count(), equals(1)),
));

it('picks all the items', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.ofAll(list), contain('coconut milk x')),
));

it('picks the first item', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.of(list.first()), startsWith('coconut milk')),
));

it('picks the last item', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.of(list.last()), startsWith('coconut milk')),
));

it('picks the nth item', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Ensure.that(Text.of(list.get(0)), startsWith('coconut milk')),
));
Expand Down Expand Up @@ -398,7 +410,7 @@ describe('PageElements', () => {

it('makes it easy for an actor to pick the element of interest', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Click.on(LinkTo(ItemCalled('coffee'))),

Expand All @@ -407,7 +419,7 @@ describe('PageElements', () => {

it('makes it easy for an actor to pick all elements of interest', () =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/screenplay/questions/page-elements/advanced_shopping_list.html'),
Navigate.to('/screenplay/models/page-elements/advanced_shopping_list.html'),

Click.on(LinkTo(ItemCalled('coconut milk'))),
Click.on(LinkTo(ItemCalled('coffee'))),
Expand Down
4 changes: 2 additions & 2 deletions integration/web-specs/spec/screenplay/questions/Text.spec.ts
Expand Up @@ -72,9 +72,9 @@ describe('Text', () => {
Navigate.to('/screenplay/questions/text/shopping_list.html'),

Ensure.that(
Text.ofAll(Shopping_List_Items).of(
Text.ofAll(Shopping_List_Items.of(
PageElement.located(By.css('body')).describedAs('body')
),
)),
equals(['milk', 'oats'])
),
));
Expand Down
45 changes: 13 additions & 32 deletions packages/web/src/screenplay/questions/Text.ts
@@ -1,9 +1,11 @@
import { Adapter, Answerable, AnswersQuestions, createAdapter, MetaQuestion, Question, UsesAbilities } from '@serenity-js/core';
import { Adapter, Answerable, AnswersQuestions, createAdapter, format, MetaQuestion, Question, UsesAbilities } from '@serenity-js/core';
import { asyncMap } from '@serenity-js/core/lib/io';

import { PageElement, PageElements } from '../models';
import { PageElement } from '../models';
import { ElementQuestion } from './ElementQuestion';

const f = format({ markQuestions: false });

/**
* @desc
* Resolves to the visible (i.e. not hidden by CSS) `innerText` of:
Expand Down Expand Up @@ -100,19 +102,18 @@ export class Text {
* Retrieves text of a group of {@link WebElement}s,
* represented by Answerable<{@link @wdio/types~ElementList}>
*
* @param {Answerable<PageElements>} elements
* @param {Answerable<PageElement[]>} elements
* @returns {Question<Promise<string[]>> & MetaQuestion<Answerable<PageElement>, Promise<string[]>>}
*
* @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
*/
static ofAll(elements: PageElements):
Question<Promise<string[]>> & // eslint-disable-line @typescript-eslint/indent
MetaQuestion<Answerable<PageElement>, Promise<string[]>> & // eslint-disable-line @typescript-eslint/indent
Adapter<string[]> // eslint-disable-line @typescript-eslint/indent
{
return createAdapter<Promise<string[]>, ElementQuestion<Promise<string[]>> & MetaQuestion<Answerable<PageElement>, Promise<string[]>>>(
new TextOfMultipleElements(elements)
);
static ofAll(elements: Answerable<PageElement[]>): Question<Promise<string[]>> & Adapter<string[]> {
return Question.about(f `the text of ${ elements }`, async actor => {

const pageElements: PageElement[] = await actor.answer(elements);

return asyncMap(pageElements, element => element.text());
})
}
}

Expand All @@ -129,28 +130,8 @@ class TextOfSingleElement
}

async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<string> {
const element = await this.resolve(actor, this.element);
const element = await actor.answer(this.element);

return element.text();
}
}

class TextOfMultipleElements
extends ElementQuestion<Promise<string[]>>
implements MetaQuestion<Answerable<PageElement>, Promise<string[]>>
{
constructor(private readonly elements: PageElements) {
super(`the text of ${ elements }`);
}

of(parent: Answerable<PageElement>): Question<Promise<string[]>> {
// todo: return Question.about directly?
return new TextOfMultipleElements(this.elements.of(parent));
}

async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<string[]> {
const elements: PageElement[] = await this.resolve(actor, this.elements);

return asyncMap(elements, element => element.text());
}
}

0 comments on commit 5314246

Please sign in to comment.