Skip to content

Commit

Permalink
feat(browser): implement locator.nth() (#7137)
Browse files Browse the repository at this point in the history
Co-authored-by: Vladimir <[email protected]>
  • Loading branch information
xeger and sheremet-va authored Jan 4, 2025
1 parent 1d45895 commit 38458ea
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 0 deletions.
63 changes: 63 additions & 0 deletions docs/guide/browser/locators.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,69 @@ It is recommended to use this only after the other locators don't work for your

- [testing-library's `ByTestId`](https://testing-library.com/docs/queries/bytestid/)

## nth

```ts
function nth(index: number): Locator
```

This method returns a new locator that matches only a specific index within a multi-element query result. Unlike `elements()[n]`, the `nth` locator will be retried until the element is present.

```html
<div aria-label="one"><input/><input/><input/></div>
<div aria-label="two"><input/></div>
```

```tsx
page.getByRole('textbox').nth(0) // ✅
page.getByRole('textbox').nth(4) // ❌
```

::: tip
Before resorting to `nth`, you may find it useful to use chained locators to narrow down your search.
Sometimes there is no better way to distinguish than by element position; although this can lead to flake, it's better than nothing.
:::

```tsx
page.getByLabel('two').getByRole('input') // ✅ better alternative to page.getByRole('textbox').nth(3)
page.getByLabel('one').getByRole('input') // ❌ too ambiguous
page.getByLabel('one').getByRole('input').nth(1) // ✅ pragmatic compromise
```

## first

```ts
function first(): Locator
```

This method returns a new locator that matches only the first index of a multi-element query result.
It is sugar for `nth(0)`.

```html
<input/> <input/> <input/>
```

```tsx
page.getByRole('textbox').first() // ✅
```

## last

```ts
function last(): Locator
```

This method returns a new locator that matches only the last index of a multi-element query result.
It is sugar for `nth(-1)`.

```html
<input/> <input/> <input/>
```

```tsx
page.getByRole('textbox').last() // ✅
```

## Methods

All methods are asynchronous and must be awaited. Since Vitest 3, tests will fail if a method is not awaited.
Expand Down
12 changes: 12 additions & 0 deletions packages/browser/src/client/tester/locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ export abstract class Locator {
return this.elements().map(element => this.elementLocator(element))
}

public nth(index: number): Locator {
return this.locator(`nth=${index}`)
}

public first(): Locator {
return this.nth(0)
}

public last(): Locator {
return this.nth(-1)
}

public toString(): string {
return this.selector
}
Expand Down
5 changes: 5 additions & 0 deletions test/browser/fixtures/locators/blog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ test('renders blog posts', async () => {

expect(screen.getByRole('listitem').all()).toHaveLength(3)

expect(screen.getByRole('listitem').nth(0).element()).toHaveTextContent(/molestiae ut ut quas/)
await expect.element(screen.getByRole('listitem').nth(666)).not.toBeInTheDocument()
expect(screen.getByRole('listitem').first().element()).toHaveTextContent(/molestiae ut ut quas/)
expect(screen.getByRole('listitem').last().element()).toHaveTextContent(/eum et est/)

expect(screen.getByPlaceholder('non-existing').query()).not.toBeInTheDocument()
})

0 comments on commit 38458ea

Please sign in to comment.