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
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() // ✅
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to use this in my project
await expect.element(screen.getByText("Org 1").first()).toBeVisible();

test succeeds but there is a type error

src/pages/Admin/PageAdminIssueReports.spec.ts:178:56 - error TS2339: Property 'first' does not exist on type 'Locator'.

178         await expect.element(screen.getByText("Org 1").first()).toBeVisible();

Do I need to include some special types for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. All public methods need to be defined also here: https://github.com/vitest-dev/vitest/blob/main/packages/browser/context.d.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops; sorry about that. Addressed in #7176

```

## 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()
})
Loading