互動 API
Vitest 實作了 @testing-library/user-event
API 的子集,並利用 Chrome DevTools Protocol 或 webdriver 來模擬事件行為,而非直接觸發事件。這種方式能讓瀏覽器行為更可靠,並與使用者實際互動的方式更一致。
import { userEvent } from '@vitest/browser/context';
await userEvent.click(document.querySelector('.button'));
幾乎所有 userEvent
方法都會繼承其提供者的選項。若要在您的 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 event detail(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:透過設定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/conteneditable 元素中。它支援 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。
參考資料: