Skip to content

Commit 32a2589

Browse files
authored
Merge pull request #41 from TurekBot/proofread-peql
Proofread "Page Element Query Language" Chapter
2 parents b7e90fc + 4dd2a15 commit 32a2589

File tree

1 file changed

+43
-38
lines changed

1 file changed

+43
-38
lines changed

src/docs/handbook/web-testing/page-element-query-language.md

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and **reducing test maintenance costs** across your organisation.
1212

1313
Serenity/JS Page Element Query Language uses **3 simple, composable abstractions** based on Screenplay [questions](/handbook/design/screenplay-pattern/#questions)
1414
that help you identify and interact with web elements of interest:
15+
1516
- **[`PageElement`](/api/web/class/PageElement)** - models a **single web element**,
1617
- **[`PageElements`](/api/web/class/PageElements)** - models a **collection of web elements**,
1718
- **[`By`](/api/web/class/By)** - represents **portable locators** used by your browser to identify web elements of interest.
@@ -22,9 +23,9 @@ For information on how to debug PEQL expressions, see the [debugging guide](/han
2223

2324
## Working with individual page elements
2425

25-
To show you how to work with **individual page elements**,
26+
To show you how to work with **individual page elements**,
2627
I'll use an example shopping basket widget and demonstrate locating its various parts.
27-
The widget is simple enough to help us focus on the important aspects of PEQL,
28+
The widget is simple enough to help us focus on the important aspects of PEQL,
2829
yet sophisticated enough to be representative of other widgets you're likely to come across in the wild:
2930

3031
```html
@@ -45,7 +46,7 @@ yet sophisticated enough to be representative of other widgets you're likely to
4546

4647
### Identifying individual page elements
4748

48-
One of the most common things to implement in a web-based test scenario is an interaction with a web element, like clicking on an button, entering a value into a form field, or asserting on some message
49+
One of the most common things to implement in a web-based test scenario is an interaction with a web element, like clicking on a button, entering a value into a form field, or asserting on some message
4950
presented to the end-user.
5051

5152
Of course, to interact with an element you need to tell your test how to find it.
@@ -60,11 +61,11 @@ export const basketTotal = () => // <- Function representing a do
6061
```
6162

6263
To define a page element:
64+
6365
- Create a [function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions) named after the **domain concept** represented by the UI widget, such as `basketTotal`.
6466
- Make the function return a [`PageElement`](/api/web/class/PageElement#located), configured to locate the element using one of the built-in [`By` selectors](/api/web/class/By).
6567
- Give your page element a **human-readable description** to be used when [reporting interactions](/handbook/reporting/) with the element.
6668

67-
6869
:::tip Writing portable test code
6970
Note how giving your test functions **meaningful names**, such as `basketTotal`, helps to make your code **easier to read** and **understand**. Also note how using the `PageElement` and `By` APIs
7071
helps your code remain **declarative**, **portable**, and agnostic of low-level integration tool-specific method calls, further improving its **reusability**.
@@ -77,7 +78,7 @@ to help your code achieve polymorphic behaviour and promote code reuse.
7778
In practice, this means that in order to retrieve a specific attribute of a `PageElement`, you compose the element
7879
with an [appropriate web question](/api/web).
7980

80-
For example, to retrieve the text value of a `PageElement` returned by `Basket.total()`, compose it with a question about its [`Text`](/api/web/class/Text):
81+
For example, to retrieve the text value of a `PageElement` returned by `basketTotal()`, compose it with a question about its [`Text`](/api/web/class/Text):
8182

8283
```typescript
8384
import { By, PageElement, Text } from '@serenity-js/web'
@@ -97,7 +98,7 @@ and others.
9798

9899
### Using selector aliases
99100

100-
In scenarios where elements use a consistent pattern allowing them to be easily identified,
101+
In scenarios where elements use a consistent pattern allowing them to be easily identified,
101102
such as a `data-test-it`, or a `role` attribute, you might want to implement custom selector aliases
102103
to avoid code duplication.
103104

@@ -192,7 +193,6 @@ await actorCalled('Alice').attemptsTo(
192193
)
193194
```
194195

195-
196196
Note that Serenity/JS expectations are **type-safe** and **portable**.
197197
This means that you're not limited to using just the web-specific expectations in your web tests,
198198
and you can use any other expectations from the [Serenity/JS assertions module](/api/assertions)
@@ -202,7 +202,7 @@ or even [write them yourself](/api/core/class/Expectation).
202202
Learn more about asserting on page elements in chapter "[Web assertions](/handbook/design/assertions#web-assertions)".
203203
:::
204204

205-
### Waiting for condition
205+
### Waiting for conditions
206206

207207
Serenity/JS web module provides [web-specific expectations](/api/core/class/Expectation) you use
208208
to synchronise your test code with the system under test and wait until its state meets your expectations.
@@ -312,6 +312,7 @@ To help you understand how to use this abstraction, remember the shopping basket
312312

313313
Similarly to how you model a [single page element](/handbook/web-testing/page-element-query-language/#working-with-individual-page-elements),
314314
to model a **collection of page elements**:
315+
315316
- Create a function that captures the name of the concept they represent, like `basketItems`.
316317
- Make the function return a [PageElements](/api/web/class/PageElements/#located) object.
317318
- Define a custom description to be used for reporting purposes.
@@ -326,13 +327,13 @@ const basketItems = () =>
326327
.describedAs('basket items') // instead of `PageElement`
327328
```
328329

329-
Note that in the code sample above, selector `By.css('#basket .item')` makes the collection target **both** the `<li class="item" />` elements,
330-
each of which containing two descendant elements: `<span class="name" />` and `<span class="price" />` .
330+
Note that in the code sample above, the selector `By.css('#basket .item')` makes the collection target **both** the `<li class="item" />` elements,
331+
each of which contains two descendant elements: `<span class="name" />` and `<span class="price" />` .
331332

332333
In a moment, I'll show you [how to make your queries more precise](/handbook/web-testing/page-element-query-language/#querying-page-elements)
333334
and retrieve only those elements you need.
334335

335-
### Retrieving element from a collection
336+
### Retrieving an element from a collection
336337

337338
If you need to retrieve a specific element from a collection, and you know what position it occupies, you can use
338339
[`PageElements#first()`](/api/web/class/PageElements#first),
@@ -355,11 +356,11 @@ const lastItem = () =>
355356
.last()
356357
```
357358

358-
Above APIs are particularly useful when you need to retrieve elements from a sorted collection,
359+
The above APIs are particularly useful when you need to retrieve elements from a sorted collection,
359360
such as the most recent comment under an article, the last customer order in a CRM system,
360361
nth position from a league table, and so on.
361362

362-
### Retrieving text of multiple elements
363+
### Retrieving the text of multiple elements
363364

364365
Similarly to [`PageElement`](/api/web/class/PageElement), [`PageElements`](/api/web/class/PageElements) can be composed with other questions,
365366
like [`Text.ofAll`](/api/web/class/Text):
@@ -375,7 +376,7 @@ const basketItemNameElementNames = () =>
375376
Text.ofAll(basketItemNameElements())
376377
```
377378

378-
[`Text.ofAll`](/api/web/class/Text) API is useful when you need to retrieve text content of multiple elements and assert on it all at once:
379+
The [`Text.ofAll`](/api/web/class/Text) API is useful when you need to retrieve text content of multiple elements and assert on it all at once:
379380

380381
```typescript
381382
import { actorCalled } from '@serenity-js/core'
@@ -423,10 +424,10 @@ to fix multiple selectors in your test automation code. Not to mention the issue
423424

424425
Serenity/JS [meta-questions](/api/core/interface/MetaQuestion) are "questions about questions",
425426
so questions that can be composed with other questions and answered in their context.
426-
In short, any Serenity/JS question that has an [`question.of(anotherQuestion)`](/api/core/interface/MetaQuestion/#of) API is
427+
In short, any Serenity/JS question that has a [`question.of(anotherQuestion)`](/api/core/interface/MetaQuestion/#of) API is
427428
a meta-question.
428429

429-
Conveniently, [`PageElement`](/api/web/class/PageElement/) is a meta-question that can be
430+
Conveniently, [`PageElement`](/api/web/class/PageElement/) is a meta-question that can be
430431
composed with another `PageElement` using a declarative [`childElement.of(parentElement)`](/api/web/class/PageElement/#of) API
431432
to dynamically model a descendant/ancestor (a.k.a. child/parent) relationship between the elements.
432433

@@ -488,13 +489,13 @@ await actorCalled('Alice').attemptsTo(
488489
```
489490

490491
Serenity/JS lets you compose not just the page elements, but also their **descriptions**.
491-
In our example, description of `Text.of(itemName().of(basketItem()))` will be **derived from individual descriptions** of
492-
questions in the chain and reported as `text of name of basket item`.
492+
In our example, the description of `Text.of(itemName().of(basketItem()))` will be **derived from individual descriptions** of
493+
questions in the chain and reported as `text of name of basket item`.
493494
Of course, you can set your own description if you prefer using `.describedAs()`, too.
494495

495-
You might have also noticed that [`childElement.of(parentElement)`](/api/web/class/PageElement/#of) API
496+
You might have also noticed that the [`childElement.of(parentElement)`](/api/web/class/PageElement/#of) API
496497
works only with **individual elements**.
497-
To map **multiple elements** we need to use `PageElements` [mapping API](/handbook/web-testing/page-element-query-language/#mapping-page-elements-in-a-collection) we'll talk about next.
498+
To map **multiple elements** we need to use the `PageElements` [mapping API](/handbook/web-testing/page-element-query-language/#mapping-page-elements-in-a-collection), which we'll talk about next.
498499

499500
:::tip Serenity/JS PEQL helps you avoid code duplication
500501
Serenity/JS PEQL lets you **compose** and **reuse** page element definitions,
@@ -508,10 +509,10 @@ especially when the system under test uses a consistent convention to name eleme
508509

509510
Similarly to how you [transform answers to individual questions](/handbook/web-testing/page-element-query-language/#transforming-answers-to-questions),
510511
you can also transform each element in a collection
511-
using [`PageElements#eachMappedTo`](/api/web/class/PageElements#eachMappedTo) API
512+
using the [`PageElements#eachMappedTo`](/api/web/class/PageElements#eachMappedTo) API
512513
and providing a [meta-question](/api/core/interface/MetaQuestion) to be used for the mapping.
513514

514-
For example, just how you'd use the meta-question about [`Text`](/api/web/class/Text/) to retrieve the text
515+
For example, the same way you'd use the meta-question about [`Text`](/api/web/class/Text/) to retrieve the text
515516
value of an **individual page element**:
516517

517518
```typescript
@@ -528,7 +529,7 @@ await actorCalled('Alice').attemptsTo(
528529
)
529530
```
530531

531-
you could also use it to extract the text value of each element in a collection:
532+
you could also use it to extract the text value of each element in a collection:
532533

533534
```typescript
534535
import { actorCalled } from '@serenity-js/core'
@@ -551,7 +552,7 @@ await actorCalled('Alice').attemptsTo(
551552
```
552553

553554
Where this pattern becomes indispensable is when you start **reusing** and **composing several meta-questions together**.
554-
For example, you could map each of `basketItems()` to retrieve their name or price:
555+
For example, you could map each of the `basketItems()` to retrieve their name or price:
555556

556557
```typescript
557558
import { actorCalled } from '@serenity-js/core'
@@ -590,15 +591,16 @@ await actorCalled('Alice').attemptsTo(
590591

591592
### Creating custom meta-questions
592593

593-
Serenity/JS provides a number of meta-questions, like [`Text`](/api/web/class/Text),
594-
[`CssClasses`](/api/web/class/CssClasses), or [`Attribute`](/api/web/class/Attribute),
594+
Serenity/JS provides a number of meta-questions, like [`Text`](/api/web/class/Text),
595+
[`CssClasses`](/api/web/class/CssClasses), or [`Attribute`](/api/web/class/Attribute),
595596
and you can always write your own if needed.
596597

597-
For example, if you're dealing with a web interface that presents tabular data,
598-
you might want to fetch the table row, perform some transformation on each cell,
598+
For example, if you're dealing with a web interface that presents tabular data,
599+
you might want to fetch a table row, perform some transformation on each cell,
599600
then return the result as a JSON object so that it's easier to work with.
600601

601602
An equivalent of doing that in our example would be to:
603+
602604
- retrieve the name and price of each basket item,
603605
- clean up the data,
604606
- transform it into a JSON object,
@@ -618,7 +620,7 @@ const basketItems = () => // Locate basket item contai
618620

619621
const BasketItemDetails: MetaQuestion<PageElement, Question<Promise<{ name: string, price: number }>>> = {
620622

621-
of: (element: PageElement) => // A meta-qustion must provide a method called `of`
623+
of: (element: PageElement) => // A meta-question must provide a method called `of`
622624
623625
Question.about('basket item details', async actor => { // Create a question
624626

@@ -652,7 +654,7 @@ await actorCalled('Alice').attemptsTo(
652654

653655
Alternatively, when you want to create a question that returns a JSON object, instead of using [`Question.about`](/api/core/class/Question/#about)
654656
you can also use [`Question.fromObject`](/api/core/class/Question/#fromObject), which will make your implementation
655-
more concise:
657+
more concise:
656658

657659
```typescript
658660
const BasketItemDetails: MetaQuestion<PageElement, Question<Promise<{ name: string, price: number }>>> = {
@@ -675,10 +677,11 @@ const BasketItemDetails: MetaQuestion<PageElement, Question<Promise<{ name: stri
675677

676678
## Querying page elements
677679

678-
While Serenity/JS [expectations](/api/core/class/Expectation) are most commonly used with [assertion](#performing-assertions) and [synchronisation](#waiting-for-condition) statements,
679-
when used with [`PageElements#where`](/api/web/class/PageElements#where) API they offer a great and reusable alternative to complex CSS selectors and XPath expressions.
680+
While Serenity/JS [expectations](/api/core/class/Expectation) are most commonly used with [assertion](#performing-assertions) and [synchronisation](#waiting-for-conditions) statements,
681+
when used with the [`PageElements#where`](/api/web/class/PageElements#where) API they offer a great and reusable alternative to complex CSS selectors and XPath expressions.
680682

681683
In this section, I'll show you how to:
684+
682685
- query page elements to find those that meet your expectations,
683686
- find an interactive element based on some property of its sibling,
684687
- iterate over selected elements to perform a common task.
@@ -736,7 +739,7 @@ const destroyButton = () => // Destroy button
736739
### Filtering page elements
737740

738741
Serenity/JS [`PageElements`](/api/web/class/PageElements/) are a [`List`](/api/core/class/List/), which means they offer a filtering API
739-
[`list.where(metaQuestion, expectation)`](/api/core/class/List/#where) and methods like
742+
([`list.where(metaQuestion, expectation)`](/api/core/class/List/#where)) and methods like
740743
[`first()`](/api/web/class/PageElements/#first),
741744
[`last()`](/api/web/class/PageElements/#last),
742745
or [`count()`](/api/web/class/PageElements/#count).
@@ -757,7 +760,7 @@ await actorCalled('Alice').attemptsTo(
757760
)
758761
```
759762

760-
Furthermore, you can compose the result of your query with another question, like `label().of(...)`:
763+
Furthermore, you can compose the result of your query with another question, like `label().of(...)`:
761764

762765
```typescript
763766
import { actorCalled } from '@serenity-js/core'
@@ -797,7 +800,7 @@ await actorCalled('Alice').attemptsTo(
797800
```
798801

799802
You can also define a chain of filtering calls to **resolve it dynamically**
800-
in the context of a root element at runtime, improving reusability of your code:
803+
in the context of a root element at runtime, improving the reusability of your code:
801804

802805
```typescript
803806
import { actorCalled } from '@serenity-js/core'
@@ -820,8 +823,9 @@ await actorCalled('Alice').attemptsTo(
820823

821824
### Finding a sibling element
822825

823-
To find a sibling element, e.g. find a destroy button for an item which label contains a certain text:
824-
- find the container element which descendant element meets your conditions,
826+
To find a sibling element, e.g. to find a destroy button for an item whose label contains a certain text:
827+
828+
- find the container element whose descendant element meets your conditions,
825829
- locate the sibling element within that container element.
826830

827831
```typescript
@@ -849,7 +853,8 @@ use the [`List#forEach`](/api/core/class/List#forEach) API to
849853
perform a sequence of interactions with each element of the collection.
850854

851855
For example, to toggle every item that hasn't been bought yet:
852-
- filter the list find elements that meet the expectation,
856+
857+
- filter the list to find elements that meet the expectation,
853858
- iterate over the found elements to click on the toggle button of each element.
854859

855860
```typescript

0 commit comments

Comments
 (0)