diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 6a0fb6af80..ec3e28b50c 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -6,6 +6,40 @@ a way to find element(s) on the page at any moment. Locator can be created with [Learn more about locators](../locators.md). +## async method: Locator.all +* since: v1.14 +- returns: <[Array]<[Locator]>> + +When locator points to a list of elements, returns array of locators, pointing +to respective elements. + +**Usage** + +```js +for (const li of await page.getByRole('listitem').all()) + await li.click(); +``` + +```python async +for li in await page.get_by_role('listitem').all(): + await li.click(); +``` + +```python sync +for li in page.get_by_role('listitem').all(): + li.click(); +``` + +```java +for (Locator li : page.getByRole('listitem').all()) + li.click(); +``` + +```csharp +foreach (var li in await page.GetByRole('listitem').AllAsync()) + await li.ClickAsync(); +``` + ## async method: Locator.allInnerTexts * since: v1.14 - returns: <[Array]<[string]>> @@ -424,6 +458,36 @@ Resolves given locator to the first matching DOM element. If no elements matchin Resolves given locator to all matching DOM elements. +## async method: Locator.enumerate +* since: v1.14 +* langs: js, python, csharp +- returns: <[Array]<[Tuple]<[Locator],[int]>>> + +When locator points to a list of elements, returns array of (locator, index) pairs, +pointing to respective elements. + +**Usage** + +```js +for (const [li, i] of await page.getByRole('listitem').enumerate()) + await li.click(); +``` + +```python async +for (li, index) in await page.get_by_role('listitem').enumerate(): + await li.click(); +``` + +```python sync +for (li, index) in page.get_by_role('listitem').enumerate(): + li.click(); +``` + +```csharp +foreach (var (li, index) in await page.GetByRole('listitem').AllAsync()) + await li.ClickAsync(); +``` + ## async method: Locator.evaluate * since: v1.14 - returns: <[Serializable]> diff --git a/docs/src/locators.md b/docs/src/locators.md index edbe49da97..7fac77b9c9 100644 --- a/docs/src/locators.md +++ b/docs/src/locators.md @@ -1360,34 +1360,58 @@ You should now have a "screenshot.png" file in your project's root directory. ### Rare use cases -#### Get All text contents +#### Do something with each element in the list + +Iterate elements: ```js -const rows = page.getByRole('listitem'); -const texts = await rows.allTextContents(); +for (const row of await page.getByRole('listitem').all()) + console.log(await row.textContent()); ``` ```python async -rows = page.get_by_role("listitem") -texts = await rows.all_text_contents() +for row in await page.get_by_role("listitem").all(): + print(await row.text_content()) ``` ```python sync -rows = page.get_by_role("listitem") -texts = rows.all_text_contents() +for row in page.get_by_role("listitem").all(): + print(row.text_content()) ``` ```java -Locator rows = page.getByRole(AriaRole.LISTITEM); -List texts = rows.allTextContents(); +for (Locator row : page.getByRole(AriaRole.LISTITEM).all()) + System.out.println(row.textContent()); ``` ```csharp -var rows = page.GetByRole(AriaRole.Listitem); -var texts = await rows.AllTextContentsAsync(); +foreach (var row in await page.GetByRole(AriaRole.Listitem).AllAsync()) + Console.WriteLine(await row.TextContentAsync()); ``` -#### Do something with each element in the list +Iterate elements with their respective indexes: + +```js +for (const [row, index] of await page.getByRole('listitem').enumerate()) + console.log(index, await row.textContent()); +``` + +```python async +for (row, index) in await page.get_by_role('listitem').enumerate(): + print(index, await row.text_content()) +``` + +```python sync +for (row, index) in page.get_by_role('listitem').enumerate(): + print(index, row.text_content()) +``` + +```csharp +foreach (var (row, index) in await page.GetByRole('listitem').AllAsync()) + Console.WriteLine(index + ' ' + await row.TextContentAsync()); +``` + +Iterate using regular for loop: ```js const rows = page.getByRole('listitem'); diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 999b3961ad..94e79190d1 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -292,6 +292,14 @@ export class Locator implements api.Locator { return this._frame.uncheck(this._selector, { strict: true, ...options }); } + async all(): Promise { + return new Array(await this.count()).fill(0).map((e, i) => this.nth(i)); + } + + async enumerate(): Promise<[Locator, number][]> { + return new Array(await this.count()).fill(0).map((e, i) => [this.nth(i), i]); + } + async allInnerTexts(): Promise { return this._frame.$$eval(this._selector, ee => ee.map(e => (e as HTMLElement).innerText)); } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 7de1eeaf2b..46a87b8639 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -21,6 +21,8 @@ import { ReadStream } from 'fs'; import { Protocol } from './protocol'; import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs'; +type Tuple = [A,B]; + type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & { state?: 'visible'|'attached'; }; @@ -9803,6 +9805,19 @@ export interface Locator { elementHandle(options?: { timeout?: number; }): Promise>; + /** + * When locator points to a list of elements, returns array of locators, pointing to respective elements. + * + * **Usage** + * + * ```js + * for (const li of await page.getByRole('listitem').all()) + * await li.click(); + * ``` + * + */ + all(): Promise>; + /** * Returns an array of `node.innerText` values for all matching nodes. */ @@ -10239,6 +10254,20 @@ export interface Locator { */ elementHandles(): Promise>; + /** + * When locator points to a list of elements, returns array of (locator, index) pairs, pointing to respective + * elements. + * + * **Usage** + * + * ```js + * for (const [li, i] of await page.getByRole('listitem').enumerate()) + * await li.click(); + * ``` + * + */ + enumerate(): Promise>>; + /** * Returns the return value of `pageFunction` as a [JSHandle]. * diff --git a/tests/page/locator-list.spec.ts b/tests/page/locator-list.spec.ts new file mode 100644 index 0000000000..b56e729a59 --- /dev/null +++ b/tests/page/locator-list.spec.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test as it, expect } from './pageTest'; + +it('locator.all should work', async ({ page }) => { + await page.setContent(`

A

B

C

`); + const texts = []; + for (const p of await page.locator('div >> p').all()) + texts.push(await p.textContent()); + expect(texts).toEqual(['A', 'B', 'C']); +}); + +it('locator.enumerate should work', async ({ page }) => { + await page.setContent(`

0

1

2

3

`); + let items = 0; + for (const [p, i] of await page.locator('div >> p').enumerate()) { + ++items; + expect(await p.textContent()).toBe(String(i)); + } + expect(items).toBe(4); +}); diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index c85a05234c..448145fecf 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -20,6 +20,8 @@ import { ReadStream } from 'fs'; import { Protocol } from './protocol'; import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs'; +type Tuple = [A,B]; + type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & { state?: 'visible'|'attached'; };