互動 API
Vitest 實作了 @testing-library/user-event
API 的子集。它透過使用 Chrome 開發者工具協定 (Chrome DevTools Protocol) 或 webdriver,而非模擬事件,使得瀏覽器行為更為可靠,並與使用者在頁面上的實際互動方式保持一致。
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>;
點擊一個元素。此方法繼承提供者的選項。有關此方法的詳細運作說明,請參閱您的提供者文件。
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
事件,此方法將連續觸發三次點擊事件。因此,您必須檢查 點擊事件詳細資訊 以過濾事件: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 將只選擇陣列中的第一個元素。
與 @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
。
參考資料: