交互性 API
Vitest 利用 Chrome DevTools Protocol 或 webdriver 实现了一部分 @testing-library/user-event
API。这种实现方式避免了模拟事件,从而使浏览器行为更可靠,并与用户实际操作页面保持一致。
import { userEvent } from '@vitest/browser/context';
await userEvent.click(document.querySelector('.button'));
几乎所有 userEvent
方法都支持其提供者(provider)的选项。要在 IDE 中查看所有可用选项,请根据您使用的提供者,将 webdriver
或 playwright
类型添加到您的 tsconfig.json
文件中:
{
"compilerOptions": {
"types": ["@vitest/browser/providers/playwright"]
}
}
{
"compilerOptions": {
"types": ["@vitest/browser/providers/webdriverio"]
}
}
userEvent.setup
function setup(): UserEvent;
创建一个新的用户事件实例。如果您需要保持键盘按键的按下和释放状态,这非常有用。
WARNING
与 @testing-library/user-event
不同,来自 @vitest/browser/context
的默认 userEvent
实例只创建一次,而不是在每次调用其方法时都重新创建!以下代码片段展示了它们工作方式的区别:
import { userEvent as vitestUserEvent } from '@vitest/browser/context';
import { userEvent as originalUserEvent } from '@testing-library/user-event';
await vitestUserEvent.keyboard('{Shift}'); // 按下 Shift 键但不释放
await vitestUserEvent.keyboard('{/Shift}'); // 释放 Shift 键
await originalUserEvent.keyboard('{Shift}'); // 按下 Shift 键但不释放
await originalUserEvent.keyboard('{/Shift}'); // 未能释放 Shift 键,因为状态不同
这种行为更有用,因为我们不是模拟键盘,而是实际按下 Shift 键。因此,如果保持原始行为,在字段中输入时可能会导致意外问题。
userEvent.click
function click(
element: Element | Locator,
options?: UserEventClickOptions
): Promise<void>;
对一个元素进行点击。支持提供者的选项。有关此方法工作原理的详细说明,请参阅您的提供者文档。
import { page, userEvent } from '@vitest/browser/context';
test('clicks on an element', async () => {
const logo = page.getByRole('img', { name: /logo/ });
await userEvent.click(logo);
// 或者您可以直接在定位器上调用
await logo.click();
});
参考:
userEvent.dblClick
function dblClick(
element: Element | Locator,
options?: UserEventDoubleClickOptions
): Promise<void>;
触发对元素的双击事件。
有关此方法工作原理的详细说明,请参阅您的提供者文档。
import { page, userEvent } from '@vitest/browser/context';
test('triggers a double click on an element', async () => {
const logo = page.getByRole('img', { name: /logo/ });
await userEvent.dblClick(logo);
// 或者您可以直接在定位器上调用
await logo.dblClick();
});
参考:
userEvent.tripleClick
function tripleClick(
element: Element | Locator,
options?: UserEventTripleClickOptions
): Promise<void>;
触发对元素的三击事件。由于浏览器 API 中没有 tripleclick
事件,此方法将连续触发三次点击事件。因此,您必须检查 click 事件详情 来判断是否为三击事件:evt.detail === 3
。
有关此方法工作原理的详细说明,请参阅您的提供者文档。
import { page, userEvent } from '@vitest/browser/context';
test('triggers a triple click on an element', async () => {
const logo = page.getByRole('img', { name: /logo/ });
let tripleClickFired = false;
logo.addEventListener('click', evt => {
if (evt.detail === 3) {
tripleClickFired = true;
}
});
await userEvent.tripleClick(logo);
// 或者您可以直接在定位器上调用
await logo.tripleClick();
expect(tripleClickFired).toBe(true);
});
参考:
- Playwright
locator.click
API:通过click
实现,clickCount: 3
。 - WebdriverIO
browser.action
API:通过 actions api 实现,连续三次move
加down + up + pause
事件。 - testing-library
tripleClick
API
userEvent.fill
function fill(element: Element | Locator, text: string): Promise<void>;
设置 input/textarea/contenteditable
字段的值。这将在设置新值之前删除输入中任何现有文本。
import { page, userEvent } from '@vitest/browser/context';
test('update input', async () => {
const input = page.getByRole('input');
await userEvent.fill(input, 'foo'); // input.value == foo
await userEvent.fill(input, '{{a[['); // input.value == {{a[[
await userEvent.fill(input, '{Shift}'); // input.value == {Shift}
// 或者您可以直接在定位器上调用
await input.fill('foo'); // input.value == foo
});
此方法会聚焦元素,填充该元素并触发 input
事件。您可以使用空字符串来清除字段。
TIP
此 API 比使用 userEvent.type
或 userEvent.keyboard
更快,但它不支持 user-event keyboard
语法(例如,{Shift}{selectall}
)。
当您不需要输入特殊字符或对按键事件进行精细控制时,我们建议使用此 API 而不是 userEvent.type
。
参考:
userEvent.keyboard
function keyboard(text: string): Promise<void>;
userEvent.keyboard
允许您模拟键盘输入。如果任何输入框具有焦点,它将把字符输入到该输入框中。否则,它将在当前聚焦的元素上触发键盘事件(如果没有聚焦元素,则在 document.body
上触发)。
此 API 支持 user-event keyboard
语法。
import { userEvent } from '@vitest/browser/context';
test('trigger keystrokes', async () => {
await userEvent.keyboard('foo'); // 转换为:f, o, o
await userEvent.keyboard('{{a[['); // 转换为:{, a, [
await userEvent.keyboard('{Shift}{f}{o}{o}'); // 转换为:Shift, f, o, o
await userEvent.keyboard('{a>5}'); // 按下 a 键不释放并触发 5 次 keydown 事件
await userEvent.keyboard('{a>5/}'); // 按下 a 键触发 5 次 keydown 事件然后释放
});
参考:
userEvent.tab
function tab(options?: UserEventTabOptions): Promise<void>;
发送一个 Tab
键事件。这是 userEvent.keyboard('{tab}')
的简写。
import { page, userEvent } from '@vitest/browser/context';
test('tab works', async () => {
const [input1, input2] = page.getByRole('input').elements();
expect(input1).toHaveFocus();
await userEvent.tab();
expect(input2).toHaveFocus();
await userEvent.tab({ shift: true });
expect(input1).toHaveFocus();
});
参考:
userEvent.type
function type(
element: Element | Locator,
text: string,
options?: UserEventTypeOptions
): Promise<void>;
WARNING
如果您不依赖于特殊字符(例如,{shift}
或 {selectall}
),建议使用 userEvent.fill
以获得更好的性能。
type
方法实现了 @testing-library/user-event
的 type
工具,该工具构建在 keyboard
API 之上。
此函数允许您将字符输入到 input/textarea/contenteditable 元素中。它支持 user-event keyboard
语法。
如果您只需要模拟按键操作,而无需向输入框中输入字符,请使用 userEvent.keyboard
API。
import { page, userEvent } from '@vitest/browser/context';
test('update input', async () => {
const input = page.getByRole('input');
await userEvent.type(input, 'foo'); // input.value == foo
await userEvent.type(input, '{{a[['); // input.value == foo{a[
await userEvent.type(input, '{Shift}'); // input.value == foo{a[
});
INFO
Vitest 不会在定位器上暴露 .type
方法,例如 input.type
,因为它仅用于与 userEvent
库兼容。考虑使用 .fill
代替,因为它更快。
参考:
userEvent.clear
function clear(element: Element | Locator): Promise<void>;
此方法清除输入元素的内容。
import { page, userEvent } from '@vitest/browser/context';
test('clears input', async () => {
const input = page.getByRole('input');
await userEvent.fill(input, 'foo');
expect(input).toHaveValue('foo');
await userEvent.clear(input);
// 或者您可以直接在定位器上调用
await input.clear();
expect(input).toHaveValue('');
});
参考:
userEvent.selectOptions
function selectOptions(
element: Element | Locator,
values: HTMLElement | HTMLElement[] | Locator | Locator[] | string | string[],
options?: UserEventSelectOptions
): Promise<void>;
userEvent.selectOptions
允许在 <select>
元素中选择一个或多个值。
WARNING
如果 select 元素没有 multiple
属性,Vitest 将只选择数组中的第一个元素。
与 @testing-library
不同,Vitest 目前不支持 listbox,但我们计划将来添加支持。
import { page, userEvent } from '@vitest/browser/context';
test('clears input', async () => {
const select = page.getByRole('select');
await userEvent.selectOptions(select, 'Option 1');
// 或者您可以直接在定位器上调用
await select.selectOptions('Option 1');
expect(select).toHaveValue('option-1');
await userEvent.selectOptions(select, 'option-1');
expect(select).toHaveValue('option-1');
await userEvent.selectOptions(select, [
page.getByRole('option', { name: 'Option 1' }),
page.getByRole('option', { name: 'Option 2' }),
]);
expect(select).toHaveValue(['option-1', 'option-2']);
});
WARNING
webdriverio
提供者不支持选择多个元素,因为它没有提供相应的 API。
参考:
- Playwright
locator.selectOption
API - WebdriverIO
element.selectByIndex
API - testing-library
selectOptions
API
userEvent.hover
function hover(
element: Element | Locator,
options?: UserEventHoverOptions
): Promise<void>;
此方法将光标移动到所选元素上。有关此方法工作原理的详细说明,请参阅您的提供者文档。
WARNING
如果您使用 webdriverio
提供者,默认情况下光标会移动到元素的中心位置。
如果您使用 playwright
提供者,光标会移动到元素的某个可见位置。
import { page, userEvent } from '@vitest/browser/context';
test('hovers logo element', async () => {
const logo = page.getByRole('img', { name: /logo/ });
await userEvent.hover(logo);
// 或者您可以直接在定位器上调用
await logo.hover();
});
参考:
userEvent.unhover
function unhover(
element: Element | Locator,
options?: UserEventHoverOptions
): Promise<void>;
这与 userEvent.hover
的工作方式类似,但将光标移动到 document.body
元素。
WARNING
默认情况下,光标位置在 body 元素的“某个”可见位置(在 playwright
提供者中)或中心(在 webdriverio
提供者中),因此如果当前悬停的元素已经位于同一位置,此方法可能不会产生明显效果。
import { page, userEvent } from '@vitest/browser/context';
test('unhover logo element', async () => {
const logo = page.getByRole('img', { name: /logo/ });
await userEvent.unhover(logo);
// 或者您可以直接在定位器上调用
await logo.unhover();
});
参考:
userEvent.upload
function upload(
element: Element | Locator,
files: string[] | string | File[] | File
): Promise<void>;
更改文件输入元素以包含指定的文件。
import { page, userEvent } from '@vitest/browser/context';
test('can upload a file', async () => {
const input = page.getByRole('button', { name: /Upload files/ });
const file = new File(['file'], 'file.png', { type: 'image/png' });
await userEvent.upload(input, file);
// 或者您可以直接在定位器上调用
await input.upload(file);
// 您也可以使用相对于测试文件的文件路径
await userEvent.upload(input, '../fixtures/file.png');
});
WARNING
webdriverio
提供者仅在 chrome
和 edge
浏览器中支持此命令。目前也仅支持字符串类型作为文件路径。
参考:
userEvent.dragAndDrop
function dragAndDrop(
source: Element | Locator,
target: Element | Locator,
options?: UserEventDragAndDropOptions
): Promise<void>;
将源元素拖动到目标元素上。请注意,source
元素必须设置 draggable
属性为 true
。
import { page, userEvent } from '@vitest/browser/context';
test('drag and drop works', async () => {
const source = page.getByRole('img', { name: /logo/ });
const target = page.getByTestId('logo-target');
await userEvent.dragAndDrop(source, target);
// 或者您可以直接在定位器上调用
await source.dropTo(target);
await expect.element(target).toHaveTextContent('Logo is processed');
});
WARNING
默认的 preview
提供者不支持此 API。
参考: