--- url: /api/browser/locators.md --- # 定位器 定位器是元素或多个元素的表示。每个定位器都由一个称为选择器的字符串定义。Vitest 通过提供在幕后生成这些选择器的便捷方法,对此选择器进行了抽象。 定位器 API 使用了 [Playwright 的定位器](https://playwright.dev/docs/api/class-locator) 的一个分支,称为 [Ivya](https://npmx.dev/ivya)。然而,Vitest 向每个 [提供者](/config/browser/provider) 提供此 API,不仅仅是 playwright。 ::: tip 本页涵盖 API 用法。为了更好地理解定位器及其用法,请阅读 [Playwright 的“定位器”文档](https://playwright.dev/docs/locators)。 ::: ::: tip 与 testing-library 的区别 Vitest 的 `page.getBy*` 方法返回一个定位器对象,而不是 DOM 元素。这使得定位器查询可组合,并允许 Vitest 在需要时重试交互和断言。 与 testing-library 查询相比: * 使用定位器链式调用(`.getBy*`、`.filter`、`.nth`)而不是 `within(...)`。 * 保留定位器并在稍后与它们交互(`await locator.click()`),而不是预先解析元素。 * 单元素逃生舱口如 `.element()` 和 `.query()` 是严格的,如果匹配多个元素则抛出错误。 ```ts import { expect } from 'vitest' import { page } from 'vitest/browser' const deleteButton = page .getByRole('row') .filter({ hasText: 'Vitest' }) .getByRole('button', { name: /delete/i }) await deleteButton.click() await expect.element(deleteButton).toBeEnabled() ``` ::: ## getByRole ```ts function getByRole( role: ARIARole | string, options?: LocatorByRoleOptions, ): Locator ``` 创建一种通过其 [ARIA 角色](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles)、[ARIA 属性](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes) 和 [可访问名称](https://developer.mozilla.org/en-US/docs/Glossary/Accessible_name) 来定位元素的方法。 ::: tip 如果你只查询单个元素 `getByText('The name')`,通常最好使用 `getByRole(expectedRole, { name: 'The name' })`。可访问名称查询不会替换其他查询,如 `*ByAltText` 或 `*ByTitle`。虽然可访问名称可以等于这些属性,但它不会替换这些属性的功能。 ::: 考虑以下 DOM 结构。 ```html

注册


``` 你可以通过其隐式角色定位每个元素: ```ts await expect.element( page.getByRole('heading', { name: '注册' }) ).toBeVisible() await page.getByRole('textbox', { name: '登录' }).fill('admin') await page.getByRole('textbox', { name: '密码' }).fill('admin') await page.getByRole('button', { name: /提交/i }).click() ``` ::: warning 角色通过字符串相等性匹配,不从 ARIA 角色层次结构继承。因此,查询超类角色(如 `checkbox`)不会包含具有子类角色(如 `switch`)的元素。 默认情况下,HTML 中的许多语义元素都有角色;例如,`` 具有 "radio" 角色。HTML 中的非语义元素没有角色;没有添加语义的 `
` 和 `` 返回 `null`。`role` 属性可以提供语义。 根据 ARIA 指南,通过 `role` 或 `aria-*` 属性为已经具有隐式角色的内置元素提供角色是 **强烈不推荐** 的。 ::: **选项** * `exact: boolean` `name` 是否精确匹配(区分大小写且匹配整个字符串)。默认采用 [`browser.locators.exact`](/config/browser/locators#browser-locators-exact) 的值,该项默认值为 `true`。如果 `name` 是正则表达式,则忽略此选项。注意,精确匹配仍会修剪空白字符。 ```tsx page.getByRole('button', { name: 'hello world' }) // ✅ page.getByRole('button', { name: 'hello world', exact: true }) // ❌ page.getByRole('button', { name: 'Hello World', exact: true }) // ✅ ``` * `checked: boolean` 是否应包含已选中的元素(由 `aria-checked` 或 `` 设置)。默认情况下,不应用过滤器。 参见 [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked) 获取更多信息 ```tsx <> page.getByRole('button', { name: '点击我!' }) // ✅ page.getByRole('button', { name: '点击我!' }) // ✅ page.getByRole('button', { name: '点击我?' }) // ❌ ``` * `pressed: boolean` 是否应包含按下的元素。默认情况下,不应用过滤器。 参见 [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed) 获取更多信息 ```tsx page.getByRole('button', { pressed: true }) // ✅ page.getByRole('button', { pressed: false }) // ❌ ``` * `selected: boolean` 是否应包含选中的元素。默认情况下,不应用过滤器。 参见 [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected) 获取更多信息 ```tsx page.getByRole('button', { selected: true }) // ✅ page.getByRole('button', { selected: false }) // ❌ ``` **另见** * [MDN 上的 ARIA 角色列表](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) * [w3.org 上的 ARIA 角色列表](https://www.w3.org/TR/wai-aria-1.2/#role_definitions) * [testing-library 的 `ByRole`](https://testing-library.com/docs/queries/byrole/) ## getByAltText ```ts function getByAltText( text: string | RegExp, options?: LocatorOptions, ): Locator ``` 创建能够查找具有匹配 `alt` 属性的元素的定位器。与 testing-library 的实现不同,Vitest 将匹配任何具有匹配 `alt` 属性的元素。 ```tsx Incredibles 2 Poster page.getByAltText(/incredibles.*? poster/i) // ✅ page.getByAltText('non existing alt text') // ❌ ``` **选项** * `exact: boolean` `text` 是否精确匹配(区分大小写且匹配整个字符串)。默认采用 [`browser.locators.exact`](/config/browser/locators#browser-locators-exact) 的值,该项默认值为 `true`。如果 `text` 是正则表达式,则忽略此选项。注意,精确匹配仍会修剪空白字符。 **另见** * [testing-library 的 `ByAltText`](https://testing-library.com/docs/queries/byalttext/) ## getByLabelText ```ts function getByLabelText( text: string | RegExp, options?: LocatorOptions, ): Locator ``` 创建能够查找具有关联标签的元素的定位器。 `page.getByLabelText('Username')` 定位器将找到下面示例中的每个输入: ```html // label 和表单元素 id 之间的 for/htmlFor 关系 // 带有表单元素的 aria-labelledby 属性 // 包装标签 // 标签文本在另一个子元素中的包装标签 // aria-label 属性 // 请注意,因为这不是用户在页面上可以看到的标签, // 所以你的输入目的对于视觉用户来说必须是显而易见的。 ``` **选项** * `exact: boolean` `text` 是否精确匹配(区分大小写且匹配整个字符串)。默认采用 [`browser.locators.exact`](/config/browser/locators#browser-locators-exact) 的值,该项默认值为 `true`。如果 `text` 是正则表达式,则忽略此选项。注意,精确匹配仍会修剪空白字符。 **另见** * [testing-library 的 `ByLabelText`](https://testing-library.com/docs/queries/bylabeltext/) ## getByPlaceholder ```ts function getByPlaceholder( text: string | RegExp, options?: LocatorOptions, ): Locator ``` 创建能够查找具有指定 `placeholder` 属性的元素的定位器。Vitest 将匹配任何具有匹配 `placeholder` 属性的元素,不仅仅是 `input`。 ```tsx page.getByPlaceholder('Username') // ✅ page.getByPlaceholder('not found') // ❌ ``` ::: warning 通常最好依赖使用 [`getByLabelText`](#getbylabeltext) 的标签而不是占位符。 ::: **选项** * `exact: boolean` `text` 是否精确匹配(区分大小写且匹配整个字符串)。默认采用 [`browser.locators.exact`](/config/browser/locators#browser-locators-exact) 的值,该项默认值为 `true`。如果 `text` 是正则表达式,则忽略此选项。注意,精确匹配仍会修剪空白字符。 **另见** * [testing-library 的 `ByPlaceholderText`](https://testing-library.com/docs/queries/byplaceholdertext/) ## getByText ```ts function getByText( text: string | RegExp, options?: LocatorOptions, ): Locator ``` 创建一个能够查找包含指定文本的元素的定位器。文本将与 TextNode 的 [`nodeValue`](https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeValue) 匹配,如果类型是 `button` 或 `reset`,则与 input 的 value 匹配。文本匹配总是会规范化空白字符,即使是精确匹配。例如,它将多个空格变为一个,将换行符变为空格,并忽略前导和尾随空白。 ```tsx 关于 ℹ️ page.getByText(/about/i) // ✅ page.getByText('about', { exact: true }) // ❌ ``` ::: tip 此定位器适用于查找非交互元素。如果你需要查找交互元素,如按钮或输入框,建议使用 [`getByRole`](#getbyrole)。 ::: **选项** * `exact: boolean` `text` 是否精确匹配(区分大小写且匹配整个字符串)。默认采用 [`browser.locators.exact`](/config/browser/locators#browser-locators-exact) 的值,该项默认值为 `true`。如果 `text` 是正则表达式,则忽略此选项。注意,精确匹配仍会修剪空白字符。 **另见** * [testing-library 的 `ByText`](https://testing-library.com/docs/queries/bytext/) ## getByTitle ```ts function getByTitle( text: string | RegExp, options?: LocatorOptions, ): Locator ``` 创建一个能够查找具有指定 `title` 属性的元素的定位器。与 testing-library 的 `getByTitle` 不同,Vitest 无法在 SVG 内查找 `title` 元素。 ```tsx page.getByTitle('Delete') // ✅ page.getByTitle('Create') // ❌ ``` **选项** * `exact: boolean` `text` 是否精确匹配(区分大小写且匹配整个字符串)。默认采用 [`browser.locators.exact`](/config/browser/locators#browser-locators-exact) 的值,该项默认值为 `true`。如果 `text` 是正则表达式,则忽略此选项。注意,精确匹配仍会修剪空白字符。 **另见** * [testing-library 的 `ByTitle`](https://testing-library.com/docs/queries/bytitle/) ## getByTestId ```ts function getByTestId(text: string | RegExp): Locator ``` 创建一个能够查找匹配指定测试 id 属性的元素的定位器。你可以使用 [`browser.locators.testIdAttribute`](/config/browser/locators#testidattribute) 配置属性名称。 ```tsx
page.getByTestId('custom-element') // ✅ page.getByTestId('non-existing-element') // ❌ ``` ::: warning 建议仅在其他定位器不适用于你的用例时使用此方法。使用 `data-testid` 属性并不像你的软件被使用的方式,如果可能应避免使用。 ::: **另见** * [testing-library 的 `ByTestId`](https://testing-library.com/docs/queries/bytestid/) ## nth ```ts function nth(index: number): Locator ``` 此方法返回一个新的定位器,仅匹配多元素查询结果中的特定索引。它是从零开始的,`nth(0)` 选择第一个元素。与 `elements()[n]` 不同,`nth` 定位器会重试直到元素出现。 ```html
``` ```tsx page.getByRole('textbox').nth(0) // ✅ page.getByRole('textbox').nth(4) // ❌ ``` ::: tip 在使用 `nth` 之前,你可能会发现使用链式定位器来缩小搜索范围很有用。 有时除了元素位置外没有更好的区分方法;虽然这可能导致不稳定,但总比没有好。 ::: ```tsx page.getByLabel('two').getByRole('input') // ✅ page.getByRole('textbox').nth(3) 的更好替代方案 page.getByLabel('one').getByRole('input') // ❌ 太模糊 page.getByLabel('one').getByRole('input').nth(1) // ✅ 务实的妥协 ``` ## first ```ts function first(): Locator ``` 此方法返回一个新的定位器,仅匹配多元素查询结果的第一个索引。 它是 `nth(0)` 的语法糖。 ```html ``` ```tsx page.getByRole('textbox').first() // ✅ ``` ## last ```ts function last(): Locator ``` 此方法返回一个新的定位器,仅匹配多元素查询结果的最后一个索引。 它是 `nth(-1)` 的语法糖。 ```html ``` ```tsx page.getByRole('textbox').last() // ✅ ``` ## and ```ts function and(locator: Locator): Locator ``` 此方法创建一个新的定位器,同时匹配父级和提供的定位器。以下示例查找具有特定标题的按钮: ```ts page.getByRole('button').and(page.getByTitle('Subscribe')) ``` ## or ```ts function or(locator: Locator): Locator ``` 此方法创建一个新的定位器,匹配其中一个或两个定位器。 ::: warning 注意,如果定位器匹配多个元素,调用另一个方法可能会抛出错误,如果它期望单个元素: ```tsx <> 发生错误! page.getByRole('button') .or(page.getByRole('link')) .click() // ❌ 匹配多个元素 ``` ::: ## filter ```ts function filter(options: LocatorFilterOptions): Locator ``` 此方法根据选项缩小定位器范围,例如按文本过滤。它可以链式调用以应用多个过滤器。 ### has * **类型:** `Locator` 此选项缩小选择器以匹配包含其他匹配所提供定位器的元素的元素。例如,使用此 HTML: ```html{1,3}
Vitest
Rolldown
``` 我们可以缩小定位器范围以仅查找内部包含 `Vitest` 文本的 `article`: ```ts page.getByRole('article').filter({ has: page.getByText('Vitest') }) // ✅ ``` ::: warning 提供的定位器(示例中的 `page.getByText('Vitest')`)必须相对于父级定位器(示例中的 `page.getByRole('article')`)。它将从父级定位器开始查询,而不是文档根节点。 意味着,你不能传递一个查询父级定位器外部元素的定位器: ```ts page.getByText('Vitest').filter({ has: page.getByRole('article') }) // ❌ ``` 此示例将失败,因为 `article` 元素在包含 `Vitest` 文本的元素外部。 ::: ::: tip 此方法可以链式调用以进一步缩小元素范围: ```ts page.getByRole('article') .filter({ has: page.getByRole('button', { name: 'delete row' }) }) .filter({ has: page.getByText('Vitest') }) ``` ::: ### hasNot * **类型:** `Locator` 此选项缩小选择器以匹配不包含其他匹配所提供定位器的元素的元素。例如,使用此 HTML: ```html{1,3}
Vitest
Rolldown
``` 我们可以缩小定位器范围以仅查找内部不包含 `Rolldown` 的 `article`。 ```ts page.getByRole('article') .filter({ hasNot: page.getByText('Rolldown') }) // ✅ page.getByRole('article') .filter({ hasNot: page.getByText('Vitest') }) // ❌ ``` ::: warning 注意,提供的定位器是针对父级查询的,而不是文档根节点,就像 [`has`](#has) 选项一样。 ::: ### hasText * **类型:** `string | RegExp` 此选项缩小选择器以仅匹配内部某处包含所提供文本的元素。当传递 `string` 时,匹配是不区分大小写的,并搜索子字符串。 ```html{1,3}
Vitest
Rolldown
``` 两个定位器都会找到相同的元素,因为搜索是不区分大小写的: ```ts page.getByRole('article').filter({ hasText: 'Vitest' }) // ✅ page.getByRole('article').filter({ hasText: 'Vite' }) // ✅ ``` ### hasNotText * **类型:** `string | RegExp` 此选项缩小选择器以仅匹配内部某处不包含所提供文本的元素。当传递 `string` 时,匹配是不区分大小写的,并搜索子字符串。 ## 方法 所有方法都是异步的,必须被等待。自 Vitest 3 起,如果方法未被等待,测试将失败。 ### click ```ts function click(options?: UserEventClickOptions): Promise ``` 点击一个元素。你可以使用选项来设置光标位置。 ```ts import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).click() ``` * [查看更多 `userEvent.click`](/api/browser/interactivity#userevent-click) ### dblClick ```ts function dblClick(options?: UserEventDoubleClickOptions): Promise ``` 在元素上触发双击事件。你可以使用选项来设置光标位置。 ```ts import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).dblClick() ``` * [查看更多 `userEvent.dblClick`](/api/browser/interactivity#userevent-dblclick) ### tripleClick ```ts function tripleClick(options?: UserEventTripleClickOptions): Promise ``` 在元素上触发三击事件。由于浏览器 API 中没有 `tripleclick`,此方法将连续触发三次点击事件。 ```ts import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).tripleClick() ``` * [查看更多 `userEvent.tripleClick`](/api/browser/interactivity#userevent-tripleclick) ### wheel 4.1.0 {#wheel} ```ts function wheel(options: UserEventWheelOptions): Promise ``` 在元素上触发 [`wheel` 事件](https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event)。你可以使用选项来选择一般的滚动 `direction` 或精确的 `delta` 值。 ```ts import { page } from 'vitest/browser' // 向右滚动 await page.getByRole('tablist').wheel({ direction: 'right' }) ``` * [查看更多 `userEvent.wheel`](/api/browser/interactivity#userevent-wheel) ### clear ```ts function clear(options?: UserEventClearOptions): Promise ``` 清除输入元素的内容。 ```ts import { page } from 'vitest/browser' await page.getByRole('textbox', { name: 'Full Name' }).clear() ``` * [查看更多 `userEvent.clear`](/api/browser/interactivity#userevent-clear) ### hover ```ts function hover(options?: UserEventHoverOptions): Promise ``` 将光标位置移动到选定的元素。 ```ts import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).hover() ``` * [查看更多 `userEvent.hover`](/api/browser/interactivity#userevent-hover) ### unhover ```ts function unhover(options?: UserEventHoverOptions): Promise ``` 这与 [`locator.hover`](#hover) 工作原理相同,但将光标移动到 `document.body` 元素。 ```ts import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).unhover() ``` * [查看更多 `userEvent.unhover`](/api/browser/interactivity#userevent-unhover) ### fill ```ts function fill(text: string, options?: UserEventFillOptions): Promise ``` 设置当前 `input`、`textarea` 或 `contenteditable` 元素的值。 ```ts import { page } from 'vitest/browser' await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean') ``` * [查看更多 `userEvent.fill`](/api/browser/interactivity#userevent-fill) ### dropTo ```ts function dropTo( target: Locator, options?: UserEventDragAndDropOptions, ): Promise ``` 将当前元素拖动到目标位置。 ```ts import { page } from 'vitest/browser' const paris = page.getByText('Paris') const france = page.getByText('France') await paris.dropTo(france) ``` * [查看更多 `userEvent.dragAndDrop`](/api/browser/interactivity#userevent-draganddrop) ### selectOptions ```ts function selectOptions( values: | HTMLElement | HTMLElement[] | Locator | Locator[] | string | string[], options?: UserEventSelectOptions, ): Promise ``` 从 `