Testing Library - About Queries

About Queries

概述

Queries 是测试库提供给你的在页面上查找元素的方法。有几种类型的查询("get"、"find"、"query");它们之间的区别在于,如果找不到元素,查询是否会抛出错误,或者是否将返回一个Promise 并重试。根据你选择的页面内容,不同的查询可能更合适或不太合适。请参阅优先级指南,了解如何使用语义查询以最可访问的方式测试你的页面。

选择元素后,你可以使用事件API或用户事件来触发事件并模拟用户与页面的交互,或者使用Jest和jest-dom对元素进行断言。

有一些与查询一起工作的测试库辅助方法。随着元素的出现和消失,可以使用像 waitFor 或 findBy 这样的异步API来等待DOM中的更改。要仅查找特定元素的子元素,可以使用within。如有必要,你还可以配置一些选项,如重试的超时时间和默认的 testID 属性。

举个简单的例子:

javascript 复制代码
import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)

test('should show login form', () => {
  render(<Login />)
  const input = screen.getByLabelText('Username')
  // Events and assertions...
})

查询类型

1、单个元素
  • getBy...: 查询返回匹配的节点,如果没有匹配项或找到多个匹配项,则抛出一个描述性的错误(如果期望有多个元素,则使用getAllBy)。
  • queryBy...:返回与查询匹配的节点,如果没有匹配的元素则返回null。这对于断言不存在的元素很有用。如果找到多个匹配项,则会抛出错误(如果期望有多个元素,请使用queryAllBy)。
  • findBy...: 返回一个 Promise,当找到与给定查询匹配的元素时,该 Promise 会解决。如果在默认的 1000 毫秒超时后未找到任何元素或找到多个元素,则 Promise 会被拒绝。如果需要找到多个元素,请使用 findAllBy。
2、多个元素
  • getAllBy...: 返回查询到的所有匹配的节点数组,如果没有匹配的元素,则抛出错误。
  • queryAllBy...: 对于给定的查询,返回所有匹配节点的数组。如果没有匹配的元素,则返回一个空数组([])。
  • findAllBy...: 当找到与给定查询匹配的任何元素时,返回一个解析为元素数组的 Promise。如果在默认的 1000ms 超时后仍未找到任何元素,则 Promise 将被拒绝。

findBy方法是getBy*查询和waitFor的组合。它们将waitFor选项作为最后一个参数(即await screen.findByText('text', queryOptions, waitForOptions))接受。

优先级

基于指导原则,您的测试应该尽可能地模拟用户与您的代码(组件、页面等)的交互。考虑到这一点,我们推荐这种优先顺序:

1、每个人都可以访问的查询反映以下体验的查询 视觉/鼠标用户以及使用辅助技术的用户。

  1. getByRole:这可以用于查询在可访问性树中暴露的每个元素。使用 name 选项,您可以根据可访问名称筛选返回的元素。对于几乎所有内容,这应该是您的首选。您几乎可以用这个获取所有内容(如果您不能,那么可能是您的用户界面无法访问)。大多数情况下,这将与name选项一起使用,如下所示:getByRole('button', {name: /submit/i})。
  2. getByLabelText:此方法对于表单字段来说真的非常有用。当用户浏览网站表单时,他们会使用标签文本来查找元素。此方法模拟了这种行为,因此它应该是您的首选。
  3. getByPlaceholderText:占位符不能代替标签。但如果你只有这个,那么它比其他选项要好。
  4. getByText:在表单之外,文本内容是用户查找元素的主要方式。此方法可用于查找非交互式元素(如 divs、spans 和paragraphs)。
  5. getByDisplayValue:当使用已填写的值浏览页面时,表单元素的当前值会非常有用。

2、语义查询HTML5和ARIA兼容选择器。请注意,使用这些属性进行交互的用户体验在浏览器和辅助技术之间存在很大差异。

  1. getByAltText:如果你的元素是支持alt文本(img``areainput, 和任何自定义元素),然后您可以使用它来查找 那个元素。
  2. getByTitle:屏幕阅读器不能始终读取标题属性,且视力正常的用户默认看不到它
  3. getByTestId:用户无法看到(或听到)这些内容,因此只有在无法通过角色或文本进行匹配或这样做没有意义的情况下(例如文本是动态的)才推荐使用。

使用查询

DOM Testing Library 的基础查询需要你传递一个容器作为第一个参数。当你使用 Testing Library 的大多数框架实现来渲染你的组件时,它们会提供这些查询的预绑定版本,这意味着你不需要提供一个容器。此外,如果你只想查询 document.body,那么你可以使用下面演示的 screen 导出(推荐使用 screen)。

给定以下 DOM 元素(可以是 React、Vue、Angular、 或纯 HTML 代码):

html 复制代码
<body>
  <div id="app">
    <label for="username-input">Username</label>
    <input id="username-input" />
  </div>
</body>

您可以使用 query 来查找元素(在本例中为 byLabelText):

javascript 复制代码
import {screen, getByLabelText} from '@testing-library/dom'

// With screen:
const inputNode1 = screen.getByLabelText('Username')

// Without screen, you need to provide a container:
const container = document.querySelector('#app')
const inputNode2 = getByLabelText(container, 'Username')
查询选项

可以传递具有查询类型的 queryOptions 以查看可用选项,例如 byRole API 对象。请参阅每个文档 。

screen

由 DOM 测试库导出的所有查询都将容器作为第一个参数。由于查询整个 document.body 非常常见,因此 DOM 测试库还导出了一个 screen 对象,该对象具有预绑定到 document.body 的每个查询(使用 within 功能)。React 测试库等包装器会重新导出 screen,以便您可以以相同的方式使用它。

以下是您的使用方法:

javascript 复制代码
// 原生

import {screen} from '@testing-library/dom'

document.body.innerHTML = `
  <label for="example">Example</label>
  <input id="example" />
`

const exampleInput = screen.getByLabelText('Example')


// React

import {render, screen} from '@testing-library/react'

render(
  <div>
    <label htmlFor="example">Example</label>
    <input id="example" />
  </div>,
)

const exampleInput = screen.getByLabelText('Example')


// Angular
import {render, screen} from '@testing-library/angular'

await render(`
  <div>
    <label for="example">Example</label>
    <input id="example" />
  </div>
`)

const exampleInput = screen.getByLabelText('Example')

注意:使用 screen 需要一个全局 DOM 环境。如果使用的是 jest,需要把 testEnvironment 设置为 jsdom。

TextMatch

大多数查询API都将TextMatch作为参数,这意味着该参数可以是字符串、正则表达式,或者是具有签名(content?: string,element?: Element | null)=> boolean的函数,其中,如果匹配则返回true,如果不匹配则返回false。

TextMatch示例

给定以下 HTML:

javascript 复制代码
<div>Hello World</div>

如何找到 div:

javascript 复制代码
// Matching a string:
screen.getByText('Hello World') // full string match
screen.getByText('llo Worl', {exact: false}) // substring match
screen.getByText('hello world', {exact: false}) // ignore case

// Matching a regex:
screen.getByText(/World/) // substring match
screen.getByText(/world/i) // substring match, ignore case
screen.getByText(/^hello world$/i) // full string match, ignore case
screen.getByText(/Hello W?oRlD/i) // substring match, ignore case, searches for "hello world" or "hello orld"

// Matching with a custom function:
screen.getByText((content, element) => content.startsWith('Hello'))

找不到 div:

javascript 复制代码
// full string does not match
screen.getByText('Goodbye World')

// case-sensitive regex with different case
screen.getByText(/hello world/)

// function looking for a span when it's actually a div:
screen.getByText((content, element) => {
  return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})
精度

采用 TextMatch 的查询也接受一个对象作为最终参数,该对象可以包含影响字符串匹配精度的选项:

  • exact:默认为 true;匹配完整字符串,区分大小写。当 false 时, 匹配子字符串,并且不区分大小写。
    • exactregexfunction参数没有影响。
    • exact 对使用 name``*byRole 选项指定的辅助名称查询没有影响。您可以使用正则表达式在可访问的设备上进行模糊匹配 名字。
    • 在大多数情况下,使用正则表达式代替字符串可以让你更好地控制模糊匹配,并应优先于{exact: false}。
  • normalizer:一个可选的函数,用于覆盖规范化行为。请参阅规范化
规范化

在针对 DOM 中的文本运行任何匹配逻辑之前,DOM Testing Library 会自动规范化该文本。默认情况下,规范化包括从文本的开始和结束处修剪空白字符,并将字符串中的多个相邻空白字符合并为一个空格。

如果你想要阻止这种规范化,或者提供替代的规范化(例如,移除 Unicode 控制字符),你可以在选项对象中提供一个规范化函数。这个函数将接收一个字符串,并期望返回该字符串的规范化版本。

注意:为标准化器指定一个值将替换内置的标准化,但您可以调用 getDefaultNormalizer 来获取一个内置的标准化器,以便调整该标准化或从其自身的标准化器中调用它。

getDefaultNormalizer 接受一个选项对象,允许选择行为:

  • trim:默认为 true。修剪前导和尾随空格
  • collapseWhitespace:默认为 true。折叠内部空格 (换行符、制表符、重复空格)合并为单个空格。

要在不修剪的情况下对文本执行匹配:

javascript 复制代码
screen.getByText('text', {
  normalizer: getDefaultNormalizer({trim: false}),
})

重写规范化以删除某些 Unicode 字符,同时保留一些 Unicode 字符 (但不是全部)内置规范化行为:

javascript 复制代码
screen.getByText('text', {
  normalizer: str =>
    getDefaultNormalizer({trim: false})(str).replace(/[\u200E-\u200F]*/g, ''),
})

手动查询

除了测试库提供的查询之外,你还可以使用常规的 querySelector DOM API 来查询元素。请注意,不建议使用此方法通过类或 ID 查询作为逃避手段,因为它们对用户是不可见的。如果你必须使用它们,请使用 testid 来明确你的意图,并退回到非语义查询,同时在 HTML 中建立稳定的 API 合约。

javascript 复制代码
// @testing-library/react
const {container} = render(<MyComponent />)
const foo = container.querySelector('[data-foo="bar"]')

浏览器扩展

在知道如何使用测试库查询方面,您仍然有问题吗?

Chrome浏览器有一款非常棒的扩展程序,名为Testing Playground,能帮助您找到选择元素的最佳查询。它允许您检查浏览器开发工具中的元素层次结构,并提供有关如何选择它们的建议,同时鼓励良好的测试实践。

如果你想对这些查询更熟悉一些,可以在 testing-playground.com 上尝试一下。Testing Playground是一个交互式沙箱,你可以在这里对自己的html运行不同的查询,并得到与上述规则相匹配的视觉反馈。

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax