定位
定位器是元素或多个元素的表示。每个定位器由一个称为选择器的字符串定义。Vitest 通过提供方便的方法在后台生成这些选择器,从而抽象了选择器。
定位器 API 使用了 Playwright 的定位器 的一个分支,称为 Ivya。然而,Vitest 将此 API 提供给每个 provider。
getByRole
function getByRole(
role: ARIARole | string,
options?: LocatorByRoleOptions,
): Locator
通过元素的 ARIA 角色、ARIA 属性 和 可访问名称 创建一种定位元素的方式。
TIP
如果你只查询单个元素(例如使用 getByText('The name')
),通常更好的做法是使用 getByRole(expectedRole, { name: 'The name' })
。可访问名称查询并不会替代其他查询,例如 *ByAltText
或 *ByTitle
。虽然可访问名称可以等于这些属性的值,但它并不能替代这些属性的功能。
考虑以下 DOM 结构。
<h3>Sign up</h3>
<label>
Login
<input type="text" />
</label>
<label>
Password
<input type="password" />
</label>
<br/>
<button>Submit</button>
你可以通过每个元素的隐式角色来定位它们:
await expect.element(
page.getByRole('heading', { name: 'Sign up' })
).toBeVisible()
await page.getByRole('textbox', { name: 'Login' }).fill('admin')
await page.getByRole('textbox', { name: 'Password' }).fill('admin')
await page.getByRole('button', { name: /submit/i }).click()
WARNING
角色通过字符串相等性进行匹配,不会继承自 ARIA 角色层次结构。因此,查询像 checkbox
这样的超类角色时,不会包含具有子类角色(如 switch
)的元素。
默认情况下,许多语义化的 HTML 元素都有一个角色;例如,<input type="radio">
具有 "radio" 角色。非语义化的 HTML 元素没有角色;没有添加语义的 <div>
和 <span>
返回 null
。role
属性可以提供语义。
根据 ARIA 指南,强烈不建议 通过 role
或 aria-*
属性为已经具有隐式角色的内置元素提供角色。
Options
exact: boolean
name
是否精确匹配:区分大小写且完全匹配字符串。默认情况下禁用此选项。如果name
是正则表达式,则忽略此选项。请注意,精确匹配仍然会修剪空白字符。tsx<button>Hello World</button> 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
或<input type="checkbox"/>
设置)。默认情况下,不会应用此过滤器。更多信息请参阅
aria-checked
。tsx<> <button role="checkbox" aria-checked="true" /> <input type="checkbox" checked /> </> page.getByRole('checkbox', { checked: true }) // ✅ page.getByRole('checkbox', { checked: false }) // ❌
disabled: boolean
是否应包含已禁用的元素。默认情况下,不会应用此过滤器。请注意,与其他属性不同,
disable
状态是可继承的。更多信息请参阅
aria-disabled
。tsx<input type="text" disabled /> page.getByRole('textbox', { disabled: true }) // ✅ page.getByRole('textbox', { disabled: false }) // ❌
expanded: boolean
是否应包含展开的元素。默认情况下,不会应用此过滤器。
更多信息请参阅
aria-expanded
。tsx<a aria-expanded="true" href="example.com">Link</a> page.getByRole('link', { expanded: true }) // ✅ page.getByRole('link', { expanded: false }) // ❌
includeHidden: boolean
是否应查询那些通常从可访问性树中排除的元素。默认情况下,只有非隐藏元素会匹配角色选择器。更多信息请参阅 可访问性树中的排除规则。
请注意,角色
none
和presentation
始终会被包含。tsx<button style="display: none" /> page.getByRole('button') // ❌ page.getByRole('button', { includeHidden: false }) // ❌ page.getByRole('button', { includeHidden: true }) // ✅
level: number
一个数字属性,通常存在于
heading
、listitem
、row
、treeitem
角色中,并且对于<h1>-<h6>
元素有默认值。默认情况下,不会应用此过滤器。更多信息请参阅
aria-level
。tsx<> <h1>Heading Level One</h1> <div role="heading" aria-level="1">Second Heading Level One</div> </> page.getByRole('heading', { level: 1 }) // ✅ page.getByRole('heading', { level: 2 }) // ❌
name: string | RegExp
可访问名称。默认情况下,匹配是不区分大小写的,并且会搜索子字符串。使用
exact
选项来控制此行为。tsx<button>Click Me!</button> page.getByRole('button', { name: 'Click Me!' }) // ✅ page.getByRole('button', { name: 'click me!' }) // ✅ page.getByRole('button', { name: 'Click Me?' }) // ❌
pressed: boolean
是否应包含被按下的元素。默认情况下,不会应用此过滤器。
更多信息请参阅
aria-pressed
。tsx<button aria-pressed="true">👍</button> page.getByRole('button', { pressed: true }) // ✅ page.getByRole('button', { pressed: false }) // ❌
selected: boolean
是否应包含被选中的元素。默认情况下,不会应用此过滤器。
更多信息请参阅
aria-selected
。tsx<button role="tab" aria-selected="true">Vue</button> page.getByRole('button', { selected: true }) // ✅ page.getByRole('button', { selected: false }) // ❌
See also
getByAltText
function getByAltText(
text: string | RegExp,
options?: LocatorOptions,
): Locator
创建一个能够找到具有匹配 alt
属性的元素的定位器。与 testing-library 的实现不同,Vitest 会匹配任何具有匹配 alt
属性的元素。
<img alt="Incredibles 2 Poster" src="/incredibles-2.png" />
page.getByAltText(/incredibles.*? poster/i) // ✅
page.getByAltText('non existing alt text') // ❌
Options
exact: boolean
text
是否精确匹配:区分大小写且完全匹配字符串。默认情况下禁用此选项。如果text
是正则表达式,则忽略此选项。请注意,精确匹配仍然会修剪空白字符。
See also
getByLabelText
function getByLabelText(
text: string | RegExp,
options?: LocatorOptions,
): Locator
创建一个能够找到具有关联标签的元素的定位器。
page.getByLabelText('Username')
定位器将在以下示例中找到所有的输入元素:
// for/htmlFor relationship between label and form element id
<label for="username-input">Username</label>
<input id="username-input" />
// The aria-labelledby attribute with form elements
<label id="username-label">Username</label>
<input aria-labelledby="username-label" />
// Wrapper labels
<label>Username <input /></label>
// Wrapper labels where the label text is in another child element
<label>
<span>Username</span>
<input />
</label>
// aria-label attributes
// Take care because this is not a label that users can see on the page,
// so the purpose of your input must be obvious to visual users.
<input aria-label="Username" />
Options
exact: boolean
text
是否精确匹配:区分大小写且完全匹配字符串。默认情况下禁用此选项。如果text
是正则表达式,则忽略此选项。请注意,精确匹配仍然会修剪空白字符。
See also
getByPlaceholder
function getByPlaceholder(
text: string | RegExp,
options?: LocatorOptions,
): Locator
创建一个能够找到具有指定 placeholder
属性的元素的定位器。Vitest 会匹配任何具有匹配 placeholder
属性的元素,而不仅仅是 input
元素。
<input placeholder="Username" />
page.getByPlaceholder('Username') // ✅
page.getByPlaceholder('not found') // ❌
WARNING
通常情况下,使用 getByLabelText
依赖标签比依赖占位符更好。
Options
exact: boolean
text
是否精确匹配:区分大小写且完全匹配字符串。默认情况下禁用此选项。如果text
是正则表达式,则忽略此选项。请注意,精确匹配仍然会修剪空白字符。
See also
getByText
function getByText(
text: string | RegExp,
options?: LocatorOptions,
): Locator
创建一个能够找到包含指定文本的元素的定位器。文本将与 TextNode 的 nodeValue
或类型为 button
或 reset
的输入值进行匹配。通过文本匹配时总是会规范化空白字符,即使在精确匹配的情况下也是如此。例如,它会将多个空格转换为一个空格,将换行符转换为空格,并忽略前导和尾随的空白字符。
<a href="/about">About ℹ️</a>
page.getByText(/about/i) // ✅
page.getByText('about', { exact: true }) // ❌
TIP
此定位器适用于定位非交互式元素。如果你需要定位交互式元素,比如按钮或输入框,建议使用 getByRole
。
Options
exact: boolean
text
是否精确匹配:区分大小写且完全匹配字符串。默认情况下禁用此选项。如果text
是正则表达式,则忽略此选项。请注意,精确匹配仍然会修剪空白字符。
See also
getByTitle
function getByTitle(
text: string | RegExp,
options?: LocatorOptions,
): Locator
创建一个能够找到具有指定 title
属性的元素的定位器。与 testing-library 的 getByTitle
不同,Vitest 无法在 SVG 中找到 title
元素。
<span title="Delete" id="2"></span>
page.getByTitle('Delete') // ✅
page.getByTitle('Create') // ❌
Options
exact: boolean
text
是否精确匹配:区分大小写且完全匹配字符串。默认情况下禁用此选项。如果text
是正则表达式,则忽略此选项。请注意,精确匹配仍然会修剪空白字符。
See also
getByTestId
function getByTestId(text: string | RegExp): Locator
创建一个能够找到与指定测试 ID 属性匹配的元素的定位器。你可以通过 browser.locators.testIdAttribute
配置属性名称。
<div data-testid="custom-element" />
page.getByTestId('custom-element') // ✅
page.getByTestId('non-existing-element') // ❌
WARNING
建议仅在其他定位器不适用于你的使用场景时才使用此方法。使用 data-testid
属性并不符合用户实际使用软件的方式,因此如果可能应避免使用。
Options
exact: boolean
text
是否精确匹配:区分大小写和整个字符串。默认情况下禁用此选项。如果text
是正则表达式,则忽略此选项。请注意,精确匹配仍然会修剪空格。
See also
nth
function nth(index: number): Locator
此方法返回一个新的定位器,仅匹配多元素查询结果中的特定索引。与 elements()[n]
不同,nth
定位器会重试,直到元素出现为止。
<div aria-label="one"><input/><input/><input/></div>
<div aria-label="two"><input/></div>
page.getByRole('textbox').nth(0) // ✅
page.getByRole('textbox').nth(4) // ❌
TIP
在使用 nth
之前,你可能会发现使用链式定位器来缩小搜索范围会更有帮助。 有时没有比通过元素位置更好的区分方式;虽然这可能导致测试不稳定,但总比没有强。
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
function first(): Locator
此方法返回一个新的定位器,仅匹配多元素查询结果中的第一个索引。 它是 nth(0)
的语法糖。
<input/> <input/> <input/>
page.getByRole('textbox').first() // ✅
last
function last(): Locator
此方法返回一个新的定位器,仅匹配多元素查询结果中的最后一个索引。 它是 nth(-1)
的语法糖。
<input/> <input/> <input/>
page.getByRole('textbox').last() // ✅
and
function and(locator: Locator): Locator
This method creates a new locator that matches both the parent and provided locator. The following example finds a button with a specific title:
page.getByRole('button').and(page.getByTitle('Subscribe'))
or
function or(locator: Locator): Locator
This method creates a new locator that matches either one or both locators.
WARNING
Note that if locator matches more than a single element, calling another method might throw an error if it expects a single element:
<>
<button>Click me</button>
<a href="https://vitest.dev">Error happened!</a>
</>
page.getByRole('button')
.or(page.getByRole('link'))
.click() // ❌ matches multiple elements
filter
function filter(options: LocatorOptions): Locator
This methods narrows down the locator according to the options, such as filtering by text. It can be chained to apply multiple filters.
has
- Type:
Locator
This options narrows down the selector to match elements that contain other elements matching provided locator. For example, with this HTML:
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
We can narrow down the locator to only find the article
with Vitest
text inside:
page.getByRole('article').filter({ has: page.getByText('Vitest') }) // ✅
WARNING
Provided locator (page.getByText('Vitest')
in the example) must be relative to the parent locator (page.getByRole('article')
in the example). It will be queried starting with the parent locator, not the document root.
Meaning, you cannot pass down a locator that queries the element outside of the parent locator:
page.getByText('Vitest').filter({ has: page.getByRole('article') }) // ❌
This example will fail because the article
element is outside the element with Vitest
text.
TIP
This method can be chained to narrow down the element even further:
page.getByRole('article')
.filter({ has: page.getByRole('button', { name: 'delete row' }) })
.filter({ has: page.getByText('Vitest') })
hasNot
- Type:
Locator
This option narrows down the selector to match elements that do not contain other elements matching provided locator. For example, with this HTML:
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
We can narrow down the locator to only find the article
that doesn't have Rolldown
inside.
page.getByRole('article')
.filter({ hasNot: page.getByText('Rolldown') }) // ✅
page.getByRole('article')
.filter({ hasNot: page.getByText('Vitest') }) // ❌
WARNING
Note that provided locator is queried against the parent, not the document root, just like has
option.
hasText
- Type:
string | RegExp
This options narrows down the selector to only match elements that contain provided text somewhere inside. When the string
is passed, matching is case-insensitive and searches for a substring.
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
Both locators will find the same element because the search is case-insensitive:
page.getByRole('article').filter({ hasText: 'Vitest' }) // ✅
page.getByRole('article').filter({ hasText: 'Vite' }) // ✅
hasNotText
- Type:
string | RegExp
This options narrows down the selector to only match elements that do not contain provided text somewhere inside. When the string
is passed, matching is case-insensitive and searches for a substring.
Methods
所有方法都是异步的,必须使用 await
。自 Vitest 3 起,如果方法没有被 await
,测试将会失败。
click
function click(options?: UserEventClickOptions): Promise<void>
点击一个元素。你可以使用选项来设置光标位置。
import { page } from '@vitest/browser/context'
await page.getByRole('img', { name: 'Rose' }).click()
dblClick
function dblClick(options?: UserEventDoubleClickOptions): Promise<void>
在元素上触发双击事件。你可以使用选项来设置光标位置。
import { page } from '@vitest/browser/context'
await page.getByRole('img', { name: 'Rose' }).dblClick()
tripleClick
function tripleClick(options?: UserEventTripleClickOptions): Promise<void>
在元素上触发三连击事件。由于浏览器 API 中没有 tripleclick
,此方法会连续触发三次点击事件。
import { page } from '@vitest/browser/context'
await page.getByRole('img', { name: 'Rose' }).tripleClick()
clear
function clear(options?: UserEventClearOptions): Promise<void>
清除输入元素的内容。
import { page } from '@vitest/browser/context'
await page.getByRole('textbox', { name: 'Full Name' }).clear()
hover
function hover(options?: UserEventHoverOptions): Promise<void>
将光标位置移动到选中的元素。
import { page } from '@vitest/browser/context'
await page.getByRole('img', { name: 'Rose' }).hover()
unhover
function unhover(options?: UserEventHoverOptions): Promise<void>
这与 locator.hover
的工作方式相同,但将光标移动到 document.body
元素。
import { page } from '@vitest/browser/context'
await page.getByRole('img', { name: 'Rose' }).unhover()
fill
function fill(text: string, options?: UserEventFillOptions): Promise<void>
设置当前 input
、textarea
或 contenteditable
元素的值。
import { page } from '@vitest/browser/context'
await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean')
dropTo
function dropTo(
target: Locator,
options?: UserEventDragAndDropOptions,
): Promise<void>
将当前元素拖动到目标位置。
import { page } from '@vitest/browser/context'
const paris = page.getByText('Paris')
const france = page.getByText('France')
await paris.dropTo(france)
selectOptions
function selectOptions(
values:
| HTMLElement
| HTMLElement[]
| Locator
| Locator[]
| string
| string[],
options?: UserEventSelectOptions,
): Promise<void>
从 <select>
元素中选择一个或多个值。
import { page } from '@vitest/browser/context'
const languages = page.getByRole('select', { name: 'Languages' })
await languages.selectOptions('EN')
await languages.selectOptions(['ES', 'FR'])
await languages.selectOptions([
languages.getByRole('option', { name: 'Spanish' }),
languages.getByRole('option', { name: 'French' }),
])
screenshot
function screenshot(options: LocatorScreenshotOptions & { base64: true }): Promise<{
path: string
base64: string
}>
function screenshot(options?: LocatorScreenshotOptions & { base64?: false }): Promise<string>
创建与定位器选择器匹配的元素的屏幕截图。
你可以使用 path
选项指定屏幕截图的保存位置,该选项相对于当前测试文件。如果未设置 path
选项,Vitest 将默认使用 browser.screenshotDirectory
(默认为 __screenshot__
),并结合文件名和测试名来确定屏幕截图的文件路径。
如果你还需要屏幕截图的内容,可以指定 base64: true
以返回屏幕截图的 base64 编码内容以及保存路径。
import { page } from '@vitest/browser/context'
const button = page.getByRole('button', { name: 'Click Me!' })
const path = await button.screenshot()
const { path, base64 } = await button.screenshot({
path: './button-click-me.png',
base64: true, // also return base64 string
})
// path - fullpath to the screenshot
// bas64 - base64 encoded string of the screenshot
query
function query(): Element | null
此方法返回与定位器选择器匹配的单个元素,如果没有找到元素则返回 null
。
如果多个元素匹配该选择器,此方法将抛出错误。如果你需要所有匹配的 DOM 元素,可以使用 .elements()
;如果你需要匹配选择器的定位器数组,可以使用 .all()
。
考虑以下 DOM 结构:
<div>Hello <span>World</span></div>
<div>Hello</div>
这些定位器将不会抛出错误:
page.getByText('Hello World').query() // ✅ HTMLDivElement
page.getByText('Hello Germany').query() // ✅ null
page.getByText('World').query() // ✅ HTMLSpanElement
page.getByText('Hello', { exact: true }).query() // ✅ HTMLSpanElement
这些定位器将抛出错误:
// returns multiple elements
page.getByText('Hello').query() // ❌
page.getByText(/^Hello/).query() // ❌
element
function element(): Element
此方法返回与定位器选择器匹配的单个元素。
如果 没有元素 匹配该选择器,则会抛出错误。如果你只需要检查元素是否存在,可以考虑使用 .query()
。
如果 多个元素 匹配该选择器,则会抛出错误。如果你需要所有匹配的 DOM 元素,可以使用 .elements()
;如果你需要匹配选择器的定位器数组,可以使用 .all()
。
TIP
此方法在需要将其传递给外部库时非常有用。当定位器与 expect.element
一起使用时,每次断言 重试 时都会自动调用此方法:
await expect.element(page.getByRole('button')).toBeDisabled()
考虑以下 DOM 结构:
<div>Hello <span>World</span></div>
<div>Hello Germany</div>
<div>Hello</div>
这些定位器将不会抛出错误:
page.getByText('Hello World').element() // ✅
page.getByText('Hello Germany').element() // ✅
page.getByText('World').element() // ✅
page.getByText('Hello', { exact: true }).element() // ✅
这些定位器将抛出错误:
// returns multiple elements
page.getByText('Hello').element() // ❌
page.getByText(/^Hello/).element() // ❌
// returns no elements
page.getByText('Hello USA').element() // ❌
elements
function elements(): Element[]
此方法返回与定位器选择器匹配的元素数组。
此函数不会抛出错误。如果没有元素匹配该选择器,此方法将返回一个空数组。
考虑以下 DOM 结构:
<div>Hello <span>World</span></div>
<div>Hello</div>
这些定位器将始终成功:
page.getByText('Hello World').elements() // ✅ [HTMLElement]
page.getByText('World').elements() // ✅ [HTMLElement]
page.getByText('Hello', { exact: true }).elements() // ✅ [HTMLElement]
page.getByText('Hello').element() // ✅ [HTMLElement, HTMLElement]
page.getByText('Hello USA').elements() // ✅ []
all
function all(): Locator[]
此方法返回一个与选择器匹配的新定位器数组。
在内部,此方法调用 .elements
并使用 page.elementLocator
包装每个元素。
Properties
selector
selector
是一个字符串,将由浏览器提供程序用于定位元素。Playwright 将使用 playwright
定位器语法,而 preview
和 webdriverio
将使用 CSS。
DANGER
你不应在测试代码中使用此字符串。selector
字符串仅应在使用 Commands API 时使用:
import type { BrowserCommand } from 'vitest/node'
const test: BrowserCommand<string> = function test(context, selector) {
// playwright
await context.iframe.locator(selector).click()
// webdriverio
await context.browser.$(selector).click()
}
import { commands, page } from '@vitest/browser/context'
import { test } from 'vitest'
test('works correctly', async () => {
await commands.test(page.getByText('Hello').selector) // ✅
// vitest will automatically unwrap it to a string
await commands.test(page.getByText('Hello')) // ✅
})