Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/src/api/class-locator.md
Original file line number Diff line number Diff line change
Expand Up @@ -3056,3 +3056,44 @@ Optional argument to pass to [`param: expression`].

### option: Locator.waitForFunction.signal = %%-input-signal-%%
* since: v1.62

## method: Locator.within
* since: v1.64
- returns: <[Locator]>

Returns a locator that matches this locator's elements inside each element matched by [`param: locator`]. This is the same as calling [`method: Locator.locator`] on [`param: locator`] with this locator as an argument, but reads in the natural order.

Note that relative locators, such as [`method: Locator.nth`] or [`method: Locator.first`], are resolved separately inside each matched parent. In the example below, `page.getByRole('cell').nth(2)` picks the third cell of every row, not the third cell in the whole table.

**Usage**

```js
const thirdColumn = page.getByRole('cell').nth(2).within(page.getByRole('row'));
await expect(thirdColumn).toHaveText(['Apple', 'Banana', 'Cherry']);
```

```python async
third_column = page.get_by_role("cell").nth(2).within(page.get_by_role("row"))
await expect(third_column).to_have_text(["Apple", "Banana", "Cherry"])
```

```python sync
third_column = page.get_by_role("cell").nth(2).within(page.get_by_role("row"))
expect(third_column).to_have_text(["Apple", "Banana", "Cherry"])
```

```java
Locator thirdColumn = page.getByRole(AriaRole.CELL).nth(2).within(page.getByRole(AriaRole.ROW));
assertThat(thirdColumn).hasText(new String[] {"Apple", "Banana", "Cherry"});
```

```csharp
var thirdColumn = page.GetByRole(AriaRole.Cell).Nth(2).Within(page.GetByRole(AriaRole.Row));
await Expect(thirdColumn).ToHaveTextAsync(new[] { "Apple", "Banana", "Cherry" });
```

### param: Locator.within.locator
* since: v1.64
- `locator` <[Locator]>

Locator matching the parent elements to search within.
25 changes: 25 additions & 0 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17368,6 +17368,31 @@ export interface Locator {
*/
timeout?: number;
}): Promise<void>;

/**
* Returns a locator that matches this locator's elements inside each element matched by
* [`locator`](https://playwright.dev/docs/api/class-locator#locator-within-option-locator). This is the same as
* calling
* [locator.locator(selectorOrLocator[, options])](https://playwright.dev/docs/api/class-locator#locator-locator) on
* [`locator`](https://playwright.dev/docs/api/class-locator#locator-within-option-locator) with this locator as an
* argument, but reads in the natural order.
*
* Note that relative locators, such as
* [locator.nth(index)](https://playwright.dev/docs/api/class-locator#locator-nth) or
* [locator.first()](https://playwright.dev/docs/api/class-locator#locator-first), are resolved separately inside each
* matched parent. In the example below, `page.getByRole('cell').nth(2)` picks the third cell of every row, not the
* third cell in the whole table.
*
* **Usage**
*
* ```js
* const thirdColumn = page.getByRole('cell').nth(2).within(page.getByRole('row'));
* await expect(thirdColumn).toHaveText(['Apple', 'Banana', 'Cherry']);
* ```
*
* @param locator Locator matching the parent elements to search within.
*/
within(locator: Locator): Locator;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/client/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ export class Locator implements api.Locator {
return new Locator(this._frame, this._selector + ' >> internal:chain=' + JSON.stringify(selectorOrLocator._selector), options);
}

within(locator: Locator): Locator {
return locator.locator(this);
}

getByTestId(testId: string | RegExp): Locator {
return this.locator(getByTestIdSelector(testIdAttributeName(), testId));
}
Expand Down
25 changes: 25 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17368,6 +17368,31 @@ export interface Locator {
*/
timeout?: number;
}): Promise<void>;

/**
* Returns a locator that matches this locator's elements inside each element matched by
* [`locator`](https://playwright.dev/docs/api/class-locator#locator-within-option-locator). This is the same as
* calling
* [locator.locator(selectorOrLocator[, options])](https://playwright.dev/docs/api/class-locator#locator-locator) on
* [`locator`](https://playwright.dev/docs/api/class-locator#locator-within-option-locator) with this locator as an
* argument, but reads in the natural order.
*
* Note that relative locators, such as
* [locator.nth(index)](https://playwright.dev/docs/api/class-locator#locator-nth) or
* [locator.first()](https://playwright.dev/docs/api/class-locator#locator-first), are resolved separately inside each
* matched parent. In the example below, `page.getByRole('cell').nth(2)` picks the third cell of every row, not the
* third cell in the whole table.
*
* **Usage**
*
* ```js
* const thirdColumn = page.getByRole('cell').nth(2).within(page.getByRole('row'));
* await expect(thirdColumn).toHaveText(['Apple', 'Banana', 'Cherry']);
* ```
*
* @param locator Locator matching the parent elements to search within.
*/
within(locator: Locator): Locator;
}

/**
Expand Down
18 changes: 18 additions & 0 deletions tests/page/locator-query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,24 @@ it('should support locator.locator with and/or', async ({ page }) => {
await expect(page.locator('button').and(page.getByRole('button'))).toHaveText(['three', 'five']);
});

it('should support locator.within', async ({ page }) => {
await page.setContent(`
<table>
<tr><td>a1</td><td>a2</td><td>a3</td></tr>
<tr><td>b1</td><td>b2</td><td>b3</td></tr>
<tr><td>c1</td><td>c2</td><td>c3</td></tr>
</table>
<span>outside</span>
`);

await expect(page.getByRole('cell').within(page.getByRole('row'))).toHaveText(['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']);
await expect(page.getByRole('cell').nth(1).within(page.getByRole('row'))).toHaveText(['a2', 'b2', 'c2']);
await expect(page.getByRole('cell').last().within(page.getByRole('row'))).toHaveText(['a3', 'b3', 'c3']);
await expect(page.getByRole('cell').nth(1).within(page.getByRole('row').nth(2))).toHaveText(['c2']);
await expect(page.locator('span').within(page.getByRole('row'))).toHaveCount(0);
await expect(page.getByRole('cell').nth(1).within(page.getByRole('row')).nth(1)).toHaveText('b2');
});

it('should allow some, but not all nested frameLocators', async ({ page }) => {
await page.setContent(`<iframe srcdoc="<span id=target>world</span>"></iframe><span>hello</span>`);
await expect(page.frameLocator('iframe').locator('span').or(page.frameLocator('iframe').locator('article'))).toHaveText('world');
Expand Down
Loading