close

Assertion

expect.element 是 Browser Mode 中用于 Locator 断言的 API。它接收 Locator,返回可链式调用的断言对象。与 expect(value) 主要比较 JS 值不同,expect.element 面向页面上的真实元素状态,适合验证可见性、文本、表单值、数量等用户可观察结果。

自动重试

使用 Playwright provider 时,所有 expect.element matcher 都会自动重试,直到断言通过或超时。你不需要手写 waitFor 或轮询逻辑——只需断言期望状态,框架会自动处理等待。

当你在 Browser Mode 测试里导入 @rstest/browser(例如导入 page)时,expect 会自动具备 element 能力。这让查询、交互、断言围绕同一 Locator 组织,失败信息更聚焦于元素状态。

下面这个最小示例展示了常见用法:先断言可见,再验证勾选状态变化,最后校验元素属性。

src/assertion.test.ts
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';

test('asserts element states in browser mode', async () => {
  await expect
    .element(page.getByRole('button', { name: 'Save' }))
    .toBeVisible();

  await expect.element(page.getByLabel('Agree')).toBeUnchecked();
  await page.getByLabel('Agree').check();
  await expect.element(page.getByLabel('Agree')).toBeChecked();

  await expect
    .element(page.getByRole('button', { name: 'Save' }))
    .toHaveAttribute('id', 'save-btn');
});

类型签名

expect.element(locator: Locator): BrowserElementExpect

自动重试与超时

expect.element 的断言行为依赖具体 provider。使用 Playwright provider 时,expect.element 的 matcher 是 web-first assertion:它们会在超时时间内持续重试,直到断言通过或超时失败。你不需要手写 waitFor 或轮询逻辑。

超时优先级

断言的超时按以下优先级决定:

  1. Per-assertion timeout:直接传给 matcher 的 timeout 参数,优先级最高
  2. 全局 testTimeout 配置:来自 rstest.config.ts 中的 testTimeout(默认 5000ms)
  3. RPC 兜底超时:30000ms(仅作为通信层保底,通常不会触及)
// 使用全局 testTimeout(默认 5000ms)
await expect.element(page.getByText('Done')).toBeVisible();

// 覆盖为更长的超时
await expect.element(page.getByText('Done')).toBeVisible({ timeout: 10000 });

失败输出

当断言超时失败时,错误信息会包含:期望的元素状态、实际状态、以及超时时间。例如:

Error: Expected element to be visible
  Locator: getByText('Loading complete')
  Timeout: 5000ms

适用场景

自动重试特别适合处理异步渲染场景——例如等待 loading 消失、等待数据加载完成后的 UI 变化:

// 点击后等待异步结果出现
await page.getByRole('button', { name: 'Submit' }).click();
await expect.element(page.getByText('Submitted!')).toBeVisible();

// 等待 loading 消失
await expect.element(page.getByTestId('spinner')).toBeHidden();

输入约束

  • locator 必须是 @rstest/browser 返回的 Locator。这样运行时才能识别并回放完整查询链路;手写对象或其他库的 locator 无法被这套机制解析。
  • 在非 Browser Mode 中,expect.element 不可用。因为它依赖 Browser Mode 的浏览器运行时与通信通道来执行元素断言;纯 Node 测试环境只支持普通 expect(value) 断言。

元素断言

以下列出的 matcher 是当前支持的子集,未列出的 matcher 暂不可用。

not

  • 类型: BrowserElementExpect

对后续断言取反。

await expect
  .element(page.getByRole('button', { name: 'Submit' }))
  .not.toBeDisabled();

以下 matcher 都支持可选参数 options?: { timeout?: number }(除非类型签名里另有说明)。

toBeVisible

  • 类型: (options?: { timeout?: number }) => Promise<void>

Locatortimeout 内解析到已挂载且 visible 的元素。

visible 的判定:元素具有非空 bounding box,且 visibility 不是 hidden。例如 display: none 或尺寸为 0 不算 visible;opacity: 0 仍算 visible。

await expect.element(page.getByRole('button', { name: 'Save' })).toBeVisible();

toBeHidden

  • 类型: (options?: { timeout?: number }) => Promise<void>

Locatortimeout 内满足以下任一条件:不匹配任何 DOM 节点,或匹配到 non-visible 节点。

可把它理解为 toBeVisible 的反面条件;例如仅设置 opacity: 0 通常不会让 toBeHidden 通过。

await expect.element(page.getByTestId('loading')).toBeHidden();

toBeEnabled

  • 类型: (options?: { timeout?: number }) => Promise<void>

元素不处于 disabled 状态。

disabled 判定:原生表单控件带 disabled、处于 disabledfieldset 中,或处于 aria-disabled=true 的语义禁用上下文,都可能被视为 disabled。

await expect
  .element(page.getByRole('button', { name: 'Submit' }))
  .toBeEnabled();

toBeDisabled

  • 类型: (options?: { timeout?: number }) => Promise<void>

元素被判定为 disabled。

判定规则与 toBeEnabled 相同,只是结果相反;推荐在按钮提交、防重复点击等场景里使用。

await expect
  .element(page.getByRole('button', { name: 'Submit' }))
  .toBeDisabled();

toBeChecked

  • 类型: (options?: { timeout?: number }) => Promise<void>

checkbox 或 radio 的 checked 状态为 true

常用于验证用户勾选行为或默认选中状态是否生效。

await expect.element(page.getByLabel('Agree')).toBeChecked();

toBeUnchecked

  • 类型: (options?: { timeout?: number }) => Promise<void>

checkbox 或 radio 的 checked 状态为 false

适合与 check / uncheck 连用,验证交互前后状态变化。

await expect.element(page.getByLabel('Agree')).toBeUnchecked();

toBeAttached

  • 类型: (options?: { timeout?: number }) => Promise<void>

Locator 指向的节点与 DocumentShadowRoot 保持连接(可理解为 Node.isConnected === true)。

这个断言只关注“是否在 DOM 树中”,不要求元素可见。

await expect.element(page.locator('#toast')).toBeAttached();

toBeDetached

  • 类型: (options?: { timeout?: number }) => Promise<void>

Locator 不再指向已连接的 DOM 节点。

常见于条件渲染、异步卸载或删除操作后的校验。

await expect.element(page.locator('#toast')).toBeDetached();

toBeEditable

  • 类型: (options?: { timeout?: number }) => Promise<void>

元素同时满足 enabled 且非 readonly。

readonly 的判断既包括原生 readonly,也包括支持该语义的 aria-readonly=true 场景。

await expect.element(page.getByLabel('Bio')).toBeEditable();

toBeFocused

  • 类型: (options?: { timeout?: number }) => Promise<void>

该元素是当前文档的焦点目标(active element)。

适合验证键盘导航、自动聚焦或表单切换焦点行为。

await expect.element(page.getByLabel('Username')).toBeFocused();

toBeEmpty

  • 类型: (options?: { timeout?: number }) => Promise<void>

可编辑元素内容为空,或普通 DOM 节点没有文本内容。

它关注“内容是否为空”,而不是“元素是否存在”或“是否可见”。

await expect.element(page.locator('#empty-state')).toBeEmpty();

toBeInViewport

  • 类型: (options?: { timeout?: number; ratio?: number }) => Promise<void>

元素与 viewport 有交集(基于 Intersection Observer 语义)。

ratio 表示最小交集比例;例如 ratio: 0.5 表示至少一半区域进入视口。

await expect.element(page.locator('#hero')).toBeInViewport({ ratio: 0.5 });

toHaveText

  • 类型: (text: string | RegExp, options?: { timeout?: number }) => Promise<void>

元素文本与期望值完整匹配(支持 string / RegExp)。

文本计算会包含嵌套子元素内容;当期望值是 string 时,会对空白与换行做归一化后再匹配。

await expect.element(page.getByRole('status')).toHaveText('Saved');

toContainText

  • 类型: (text: string | RegExp, options?: { timeout?: number }) => Promise<void>

元素文本包含期望子串,或匹配给定正则。

toHaveText 的区别是:toContainText 做子串匹配,toHaveText 做完整匹配。

await expect.element(page.getByRole('status')).toContainText('Save');

toHaveValue

  • 类型: (value: string | RegExp, options?: { timeout?: number }) => Promise<void>

表单控件的当前 value 与期望值匹配(支持 string / RegExp)。

适用于 inputtextareaselect 等可取值元素。

await expect.element(page.getByLabel('Email')).toHaveValue('[email protected]');

toHaveId

  • 类型: (value: string | RegExp, options?: { timeout?: number }) => Promise<void>

元素的 id 与期望值匹配(支持 string / RegExp)。

适合校验动态生成 ID 或组件挂载后注入的固定 ID。

await expect
  .element(page.getByRole('button', { name: 'Save' }))
  .toHaveId('save-btn');

toHaveClass

  • 类型: (value: string | RegExp, options?: { timeout?: number }) => Promise<void>

元素 class 属性与期望值匹配(支持 string / RegExp)。

当传入 string 时,按整体 class 字符串匹配;若只关心某个 class,建议使用更有针对性的正则。

await expect.element(page.getByRole('alert')).toHaveClass(/error/);

toHaveAttribute

  • 类型:
    • (name: string, options?: { timeout?: number }) => Promise<void>
    • (name: string, value: string | RegExp, options?: { timeout?: number }) => Promise<void>

只传 name 时,断言属性存在;传 name + value 时,断言属性值匹配(value 支持 string / RegExp)。

常见用法是校验 typedisabledaria-* 等结构性属性。

await expect
  .element(page.getByRole('button', { name: 'Save' }))
  .toHaveAttribute('type');
await expect
  .element(page.getByRole('button', { name: 'Save' }))
  .toHaveAttribute('type', 'submit');

toHaveCount

  • 类型: (count: number, options?: { timeout?: number }) => Promise<void>

Locator 解析出的元素数量与 count 完全一致。

适合列表渲染、过滤结果和分页条目数等场景。

await expect.element(page.getByRole('listitem')).toHaveCount(3);

toHaveCSS

  • 类型: (name: string, value: string | RegExp, options?: { timeout?: number }) => Promise<void>

元素的 computed style 中,指定 CSS 属性值与期望值匹配。

name 必须是非空字符串;value 支持 string / RegExp

await expect.element(page.getByRole('alert')).toHaveCSS('display', 'block');

toHaveJSProperty

  • 类型: (name: string, value: unknown, options?: { timeout?: number }) => Promise<void>

元素上的 JS property 与期望值匹配。

name 必须是非空字符串;value 需要可被 JSON 序列化(断言参数会通过 Browser Mode 通道传输)。

await expect
  .element(page.getByLabel('Agree'))
  .toHaveJSProperty('checked', true);

相关 API