概述
查询是 Testing Library 提供的一种方法,用于在页面上查找元素。有几种查询类型("get"、"find"、"query"),它们的区别在于如果找不到元素,查询是否会抛出错误或返回一个 Promise 并重试。根据所选择的页面内容,不同的查询可能更或少合适。有关如何以最可访问的方式测试页面的建议,请参阅优先级指南。
在选择元素之后,你可以使用 Events API 或 user-event 触发事件并模拟用户与页面的交互,或使用 Jest 和 jest-dom 对元素进行断言。
Testing Library 提供了一些与查询一起使用的辅助方法。当元素在响应操作时出现和消失时,可以使用 Async APIs(如 waitFor 或 findBy 查询)来等待 DOM 中的变化。要仅查找特定元素的子元素,可以使用 within。如果需要,还可以配置一些选项,例如重试的超时时间和默认的 testID 属性。
示例
import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)
test('应显示登录表单', () => {
render(<Login />)
const input = screen.getByLabelText('Username')
// 事件和断言...
})
查询类型
单个元素
getBy...
:返回与查询匹配的节点,如果没有匹配的元素或找到多个匹配项,则抛出错误(如果预期有多个元素,请改用getAllBy
)。queryBy...
:返回与查询匹配的节点,如果没有匹配的元素,则返回null
。这对于断言不出现的元素很有用。如果找到多个匹配项,则抛出错误(如果这个结果可以接受,请改用queryAllBy
)。findBy...
:返回一个 Promise,当找到与给定查询匹配的元素时解析。如果没有找到元素或在默认超时时间(1000ms)后找到多个元素,则 Promise 被拒绝。如果需要查找多个元素,请使用findAllBy
。
多个元素
getAllBy...
:返回查询匹配的所有节点的数组,如果没有匹配的元素,则抛出错误。queryAllBy...
:返回查询匹配的所有节点的数组,如果没有匹配的元素,则返回一个空数组([]
)。findAllBy...
:返回一个 Promise,当找到与给定查询匹配的任何元素时解析为元素数组。如果在默认超时时间(1000ms)后没有找到元素,则 Promise 被拒绝。
findBy
方法是getBy*
查询和 waitFor 的组合。它们接受最后一个参数作为waitFor
的选项(例如:await screen.findByText('text', queryOptions, waitForOptions)
)。
统计表
查询类型 | 0 个匹配 | 1 个匹配 | 多于 1 个匹配 | 重试(异步/等待) |
---|---|---|---|---|
单个元素 | ||||
getBy... | 抛出错误 | 返回元素 | 抛出错误 | 否 |
queryBy... | 返回 null | 返回元素 | 抛出错误 | 否 |
findBy... | 抛出错误 | 返回元素 | 抛出错误 | 是 |
多个元素 | ||||
getAllBy... | 抛出错误 | 返回数组 | 返回数组 | 否 |
queryAllBy... | 返回 [] | 返回数组 | 返回数组 | 否 |
findAllBy... | 抛出错误 | 返回数组 | 返回数组 | 是 |
优先级
根据指导原则,你的测试应尽可能地反映用户与你的代码(组件、页面等)的交互方式。基于此,我们建议按以下顺序设置优先级:
适用于所有用户的查询:反映视觉/鼠标用户以及使用辅助技术的用户的体验。
getByRole
:可以用于查询所有在可访问性树中公开的元素。通过name
选项,你可以根据它们的可访问名称对返回的元素进行过滤。这应该是你的首选方法。使用此方法几乎可以获得所有内容(如果不能,则可能是你的用户界面不可访问)。最常见的用法是将name
选项与正则表达式一起使用,如getByRole('button', {name: /submit/i})
。请查看角色列表。getByLabelText
:对于表单字段来说,这个方法非常好用。在浏览网站表单时,用户使用标签文本查找元素。该方法模拟了这种行为,因此应该是你的首选方法。getByPlaceholderText
:占位符不是标签的替代品。但是如果你只有占位符,那么它比其他选择要好。getByText
:除了表单之外,文本内容是用户查找元素的主要方式。该方法可用于查找非交互式元素(如 div、span 和段落)。getByDisplayValue
:当浏览包含已填写值的页面时,表单元素的当前值可能很有用。
语义查询:符合 HTML5 和 ARIA 规范的选择器。请注意,通过这些属性进行交互的用户体验在不同的浏览器和辅助技术中差异很大。
getByAltText
:如果你的元素支持alt
文本(img
、area
、input
和任何自定义元素),那么可以使用此方法查找该元素。getByTitle
:title
属性在屏幕阅读器中的读取方式不一致,并且对于有视觉感知能力的用户,默认情况下是不可见的。
测试 ID
getByTestId
:用户无法看到(或听到)这些 ID,因此仅建议在无法通过角色或文本匹配或没有意义时使用(例如,文本是动态的)。
使用查询
DOM Testing Library 的基本查询需要你将 container
作为第一个参数传递。大多数测试库的框架实现在渲染组件时会提供预绑定版本的这些查询,这意味着你不需要提供容器。此外,如果你只想查询 document.body
,则可以使用下面演示的 screen 导出(推荐使用 screen
)。
查询的主要参数可以是字符串、正则表达式或函数。还有一些选项可以调整节点文本的解析方式。有关可以传递给查询的内容,请参阅TextMatch 文档。
给定以下 DOM 元素(可以由 React、Vue、Angular 或普通 HTML 代码渲染):
<body>
<div id="app">
<label for="username-input">Username</label>
<input id="username-input" />
</div>
</body>
你可以使用查询来查找元素(在此示例中使用 byLabelText
):
import { screen, getByLabelText } from '@testing-library/dom'
// 使用 screen:
const inputNode1 = screen.getByLabelText('Username')
// 不使用 screen,你需要提供一个容器:
const container = document.querySelector('#app')
const inputNode2 = getByLabelText(container, 'Username')
queryOptions
你可以传一个包含查询类型的 queryOptions
对象。请参阅每种查询类型的文档以查看可用的选项,例如 byRole API。
screen
DOM Testing Library 导出的所有查询都接受一个 container
作为第一个参数。由于查询整个 document.body
非常常见,DOM Testing Library 还导出了一个 screen
对象,该对象具有预绑定到 document.body
的每个查询(使用 within 功能)。像 React Testing Library 这样的包装器会重新导出 screen
,因此你可以以相同的方式使用它。
以下是使用方法:
import { screen } from '@testing-library/dom'
document.body.innerHTML = `
<label for="example">Example</label>
<input id="example" />
`
const exampleInput = screen.getByLabelText('Example')
{/tabs-pane}
{tabs-pane label="React"}
import { render, screen } from '@testing-library/react'
render(
<div>
<label htmlFor="example">Example</label>
<input id="example" />
</div>,
)
const exampleInput = screen.getByLabelText('Example')
{/tabs-pane}
{tabs-pane label="Cypress"}
cy.findByLabelText('Example').should('exist')
{/tabs-pane}
注意
你需要一个全局的 DOM 环境来使用
screen
。如果你使用的是 jest,并且将 testEnvironment 设置为jsdom
,则会为你提供全局的 DOM 环境。如果你使用
script
标签加载测试,请确保它位于body
之后。可以参考此处的示例。
TextMatch
大多数查询 API 都接受 TextMatch
作为参数,这意味着参数可以是 字符串、正则表达式 或一个签名为 (content?: string, element?: Element | null) => boolean
的 函数,该函数返回 true
表示匹配,返回 false
表示不匹配。
TextMatch 示例
给定以下 HTML:
<div>Hello World</div>
将找到 div 元素:
// 使用字符串匹配:
screen.getByText('Hello World') // 完全字符串匹配
screen.getByText('llo Worl', {exact: false}) // 子字符串匹配
screen.getByText('hello world', {exact: false}) // 忽略大小写
// 使用正则表达式匹配:
screen.getByText(/World/) // 子字符串匹配
screen.getByText(/world/i) // 子字符串匹配,忽略大小写
screen.getByText(/^hello world$/i) // 完全字符串匹配,忽略大小写
screen.getByText(/Hello W?oRlD/i) // 子字符串匹配,忽略大小写,匹配 "hello world" 或 "hello orld"
// 使用自定义函数匹配:
screen.getByText((content, element) => content.startsWith('Hello'))
将不会找到 div 元素:
// 完全字符串不匹配
screen.getByText('Goodbye World')
// 大小写敏感的正则表达式与不同大小写不匹配
screen.getByText(/hello world/)
// 函数在实际上是 div 元素时查找 span 元素:
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})
精确度
接受 TextMatch
的查询还可以接受一个对象作为最后一个参数,该对象可以包含影响字符串匹配精确度的选项:
exact
:默认为true
;匹配完整字符串,区分大小写。当为false
时,匹配子字符串且不区分大小写。exact
对regex
或function
参数无效。exact
对*byRole
查询的name
选项指定的可访问名称无效。你可以使用正则表达式对可访问名称进行模糊匹配。- 在大多数情况下,使用正则表达式而不是字符串可以更好地控制模糊匹配,并且应优先选择
{ exact: false }
。
normalizer
:可选函数,用于覆盖标准化行为。详见 Normalization。
标准化
在对 DOM 中的文本运行任何匹配逻辑之前,DOM Testing Library
会自动对文本进行标准化。默认情况下,标准化包括从文本的开头和结尾删除空格,并将字符串中的多个相邻空格字符折叠为单个空格。
如果要阻止标准化,或提供替代标准化(例如删除 Unicode 控制字符),可以在选项对象中提供一个 normalizer
函数。该函数将接收一个字符串,并返回该字符串的标准化版本。
注意
为
normalizer
指定一个值会替换内置标准化,但是你可以调用getDefaultNormalizer
来获取内置的标准化函数,以便调整该标准化或从自己的标准化函数中调用它。
getDefaultNormalizer
接受一个选项对象,允许选择行为:
trim
:默认为true
。修剪前导和尾随空格。collapseWhitespace
:默认为true
。将内部空格(换行符、制表符、连续的空格)折叠为单个空格。
标准化示例
执行不修剪文本的匹配:
screen.getByText('text', {
normalizer: getDefaultNormalizer({trim: false}),
})
覆盖标准化以删除一些 Unicode 字符,同时保留一些(但不是全部)内置标准化行为:
screen.getByText('text', {
normalizer: str =>
getDefaultNormalizer({trim: false})(str).replace(/[\u200E-\u200F]*/g, ''),
})
测试
screen.debug()
为了方便起见,screen
除了查询方法外还提供了一个 debug
方法。该方法实质上是 console.log(prettyDOM())
的快捷方式。它支持调试整个文档、单个元素或元素数组。
import {screen} from '@testing-library/dom'
document.body.innerHTML = `
<button>test</button>
<span>multi-test</span>
<div>multi-test</div>
`
// 调试整个文档
screen.debug()
// 调试单个元素
screen.debug(screen.getByText('test'))
// 调试多个元素
screen.debug(screen.getAllByText('multi-test'))
screen.logTestingPlaygroundURL()
为了使用 testing-playground 进行调试,screen
提供了这个方便的方法,它会记录并返回一个可以在浏览器中打开的 URL。
import {screen} from '@testing-library/dom'
document.body.innerHTML = `
<button>test</button>
<span>multi-test</span>
<div>multi-test</div>
`
// 将整个文档记录到 testing-playground
screen.logTestingPlaygroundURL()
// 记录单个元素
screen.logTestingPlaygroundURL(screen.getByText('test'))
手动查询
除了测试库提供的查询方法之外,你还可以使用常规的 querySelector DOM API 来查询元素。请注意,不建议使用此方法通过类名或 id 进行查询,因为这些对用户是不可见的。如果必须这样做,请使用 testid
,以明确你回退到非语义查询的意图,并在 HTML 中建立稳定的 API 契约。
// @testing-library/react
const {container} = render(<MyComponent />)
const foo = container.querySelector('[data-foo="bar"]')
浏览器扩展
如果你仍然不知道如何使用 Testing Library 查询,有一个非常酷的 Chrome 浏览器扩展程序叫做 Testing Playground。它可以帮助你找到最佳的查询方式来选择元素。它允许你检查浏览器开发者工具中的元素层次结构,并为你提供选择它们的建议,同时鼓励良好的测试实践。
Playground
如果你想对这些查询更加熟悉,你可以在 testing-playground.com 上尝试它们。Testing Playground 是一个交互式沙箱,你可以针对自己的 HTML 运行不同的查询,并获得与上述规则匹配的可视化反馈。
评论 (0)