Skip to content
Permalink
Browse files
fix(docs): documented changes to Target.of in the Serenity/JS 2.0 upg…
…rade guide

Closes #598
  • Loading branch information
jan-molak committed Jun 19, 2020
1 parent 3cdd74c commit 43bec7b86d16f977bcd059d271cae76bab81171d
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 0 deletions.
@@ -964,6 +964,88 @@ class ToDoListApp {
To learn more, check out the examples in the [API docs of the `Target` class](modules/protractor/class/src/screenplay/questions/targets/Target.ts~Target.html).
#### Nested Targets
Serenity/JS 1.x provided a simple `Target.of` API that helped you replace parts of your locator dynamically during the test.
For example, you might have located a header of a table column using a `Target` defined as follows:
```typescript
import { Target } from 'serenity-js/protractor';
import { by } from 'protractor';
class DataTable {
static Column_Header = Target.the('column header')
.located(by.xpath(
`//*[@id="data-table"]` +
`//div[contains(@class, "ag-header-cell-label")]` +
`//span[text()[contains(.,"{0}")]]`
));
}
```
You'd then configure such `Target` dynamically in your test scenario or task:
```typescript
const SortBy = (columnName: string) =>
Task.where(`#actor sorts the data by ${ columnName }`,
Click.on(
DataTable.Column_Header.of(columnName),
),
);
```
Serenity/JS 2.x allows you to nest `Target`s, which should help you get rid of some of those terrible `xpath` locators
from your codebase:
```typescript
import { Target } from '@serenity-js/protractor';
import { by } from 'protractor';
class DataTable {
static component =
Target.the('data table component').located(by.id('data-table'));
static columnHeaders =
Target.all('column headers').of(DataTable.component).located(by.css('.ag-header-cell-label'));
}
```
But what about picking a header with the right name?
This is where [`Pick`](/modules/protractor/class/src/screenplay/questions/Pick.ts~Pick.html) can help you:
```typescript
import { Target } from '@serenity-js/protractor';
import { includes } from '@serenity-js/assertions';
import { by } from 'protractor';
class DataTable {
static component =
Target.the('data table component').located(by.id('data-table'));
static columnHeaders =
Target.all('column headers').of(DataTable.component).located(by.css('.ag-header-cell-label'));
static columnHeaderCalled = (name: string) =>
Pick.from<ElementFinder, ElementArrayFinder>(DataTable.columnHeaders)
.where(Text, includes(name))
.first();
}
```
And then our `SortBy` task becomes:
```typescript
const SortBy = (columnName: string) =>
Task.where(`#actor sorts the data by ${ columnName }`,
Click.on(
DataTable.columnHeaderCalled(columnName),
),
);
```
Learn more about `Pick` from its [unit tests](/modules/protractor/test-file/spec/screenplay/questions/Pick.spec.ts.html).
#### Targets as arguments
Since the responsibilities of the 2.x `Target` differ from its predecessor, if you have written any custom [`Activity`](/modules/core/class/src/screenplay/Activity.ts~Activity.html) classes in your project where a [`Target`](/modules/protractor/class/src/screenplay/questions/targets/Target.ts~Target.html) is passed as an argument (for example in a constructor or a method call), you'll need to change the signatures to receive a `Question<ElementFinder>` for single-element activities or `Question<ElementArrayFinder>` for multi-element activities.
@@ -1,3 +1,5 @@
import 'mocha';

import { expect } from '@integration/testing-tools';
import { contain, Ensure, equals, startsWith } from '@serenity-js/assertions';
import { actorCalled, Question } from '@serenity-js/core';
@@ -51,30 +53,35 @@ describe('Pick', () => {

const picked = Pick.from<ElementFinder, ElementArrayFinder>(ShoppingList.Titles);

/** @test {Pick} */
it('gets the number of items', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

Ensure.that(picked.count(), equals(3)),
));

/** @test {Pick} */
it('picks all the items', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

Ensure.that(Text.ofAll(picked.all()), contain('coconut milk')),
));

/** @test {Pick} */
it('picks the first item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

Ensure.that(Text.of(picked.first()), equals('oats')),
));

/** @test {Pick} */
it('picks the last item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

Ensure.that(Text.of(picked.last()), equals('coffee')),
));

/** @test {Pick} */
it('picks the nth item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

@@ -87,18 +94,22 @@ describe('Pick', () => {

const picked = Pick.from(ShoppingList.Items);

/** @test {Pick} */
it('returns the number of items', () =>
expect(picked.count().toString())
.to.equal('the number of the shopping list items'));

/** @test {Pick} */
it('picks all the items', () =>
expect(picked.all().toString())
.to.equal('the shopping list items'));

/** @test {Pick} */
it('picks the first item', () =>
expect(picked.first().toString())
.to.equal('the first of the shopping list items'));

/** @test {Pick} */
it('picks the last item', () =>
expect(picked.last().toString())
.to.equal('the last of the shopping list items'));
@@ -130,30 +141,35 @@ describe('Pick', () => {

describe('lets the actor filter the list of matching elements so that it', () => {

/** @test {Pick} */
it('gets the number of items', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks all the items', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks the first item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks the last item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks the nth item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

@@ -163,18 +179,22 @@ describe('Pick', () => {

describe('provides a sensible description when it', () => {

/** @test {Pick} */
it('returns the number of answers', () =>
expect(picked.count().toString())
.to.equal(`the number of the shopping list items where CSSClasses property does contain 'buy'`));

/** @test {Pick} */
it('picks all the items', () =>
expect(picked.all().toString())
.to.equal(`the shopping list items where CSSClasses property does contain 'buy'`));

/** @test {Pick} */
it('picks the first item', () =>
expect(picked.first().toString())
.to.equal(`the first of the shopping list items where CSSClasses property does contain 'buy'`));

/** @test {Pick} */
it('picks the last item', () =>
expect(picked.last().toString())
.to.equal(`the last of the shopping list items where CSSClasses property does contain 'buy'`));
@@ -204,30 +224,35 @@ describe('Pick', () => {

describe('lets the actor filter the list of matching elements so that it', () => {

/** @test {Pick} */
it('gets the number of items', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks all the items', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks the first item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks the last item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

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

/** @test {Pick} */
it('picks the nth item', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

@@ -237,18 +262,22 @@ describe('Pick', () => {

describe('provides a sensible description when it', () => {

/** @test {Pick} */
it('returns the number of answers', () =>
expect(picked.count().toString())
.to.equal(`the number of the shopping list items where CSSClasses property does contain 'buy' and Text property does start with 'coconut'`));

/** @test {Pick} */
it('picks all the items', () =>
expect(picked.all().toString())
.to.equal(`the shopping list items where CSSClasses property does contain 'buy' and Text property does start with 'coconut'`));

/** @test {Pick} */
it('picks the first item', () =>
expect(picked.first().toString())
.to.equal(`the first of the shopping list items where CSSClasses property does contain 'buy' and Text property does start with 'coconut'`));

/** @test {Pick} */
it('picks the last item', () =>
expect(picked.last().toString())
.to.equal(`the last of the shopping list items where CSSClasses property does contain 'buy' and Text property does start with 'coconut'`));
@@ -286,6 +315,7 @@ describe('Pick', () => {

const LinkTo = (item: Question<ElementFinder> | ElementFinder) => Target.the('link to element').of(item).located(by.css('a'));

/** @test {Pick} */
it('makes it easy for an actor to pick the element of interest', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

@@ -294,6 +324,7 @@ describe('Pick', () => {
Ensure.that(CSSClasses.of(ItemCalled('coffee')), contain('buy')),
));

/** @test {Pick} */
it('makes it easy for an actor to pick all elements of interest', () => actorCalled('Peter').attemptsTo(
Navigate.to(shoppingListPage),

0 comments on commit 43bec7b

Please sign in to comment.