交互 API
Vitest 利用 Chrome DevTools Protocol 或 Webdriver 协议,实现了 @testing-library/user-event
的部分 API。与模拟事件不同,这种实现方式能更可靠地模拟浏览器行为,使其与用户实际操作页面的方式保持一致。
import { userEvent } from '@vitest/browser/context';
await userEvent.click(document.querySelector('.button'));
几乎所有 userEvent
方法都支持其底层提供者的选项。若要在 IDE 中查看所有可用选项,请根据您使用的提供者(webdriver
或 playwright
),将相应的类型定义添加到您的设置文件或配置文件中(具体取决于 tsconfig.json
中 included
的配置):
/// <reference types="@vitest/browser/providers/playwright" />
/// <reference 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>;
点击元素。此方法支持提供者(provider)的选项。有关此方法如何工作的详细说明,请参阅您的提供者文档。
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,
options?: UserEventClearOptions
): 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 将只选择传入 values
数组中的第一个元素。
与 @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,
options?: UserEventUploadOptions
): 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。
参考:
userEvent.copy
function copy(): Promise<void>;
将已选中的文本复制到剪贴板。
import { page, userEvent } from '@vitest/browser/context';
test('copy and paste', async () => {
// 写入 'source'
await userEvent.click(page.getByPlaceholder('source'));
await userEvent.keyboard('hello');
// 选择并复制 'source'
await userEvent.dblClick(page.getByPlaceholder('source'));
await userEvent.copy();
// 粘贴到 'target'
await userEvent.click(page.getByPlaceholder('target'));
await userEvent.paste();
await expect
.element(page.getByPlaceholder('source'))
.toHaveTextContent('hello');
await expect
.element(page.getByPlaceholder('target'))
.toHaveTextContent('hello');
});
参考:
userEvent.cut
function cut(): Promise<void>;
将已选中的文本剪切到剪贴板。
import { page, userEvent } from '@vitest/browser/context';
test('copy and paste', async () => {
// 写入 'source'
await userEvent.click(page.getByPlaceholder('source'));
await userEvent.keyboard('hello');
// 选择并剪切 'source'
await userEvent.dblClick(page.getByPlaceholder('source'));
await userEvent.cut();
// 粘贴到 'target'
await userEvent.click(page.getByPlaceholder('target'));
await userEvent.paste();
await expect.element(page.getByPlaceholder('source')).toHaveTextContent('');
await expect
.element(page.getByPlaceholder('target'))
.toHaveTextContent('hello');
});
参考:
userEvent.paste
function paste(): Promise<void>;
从剪贴板粘贴文本。有关使用示例,请参阅 userEvent.copy
和 userEvent.cut
。
参考: