Interactivity API
Vitest implements a subset of @testing-library/user-event
APIs using Chrome DevTools Protocol or webdriver instead of faking events which makes the browser behaviour more reliable and consistent with how users interact with a page.
import { userEvent } from '@vitest/browser/context'
await userEvent.click(document.querySelector('.button'))
Almost every userEvent
method inherits its provider options. To see all available options in your IDE, add webdriver
or playwright
types (depending on your provider) to your setup file or a config file (depending on what is in included
in your tsconfig.json
):
/// <reference types="@vitest/browser/providers/playwright" />
/// <reference types="@vitest/browser/providers/webdriverio" />
userEvent.setup
function setup(): UserEvent
Creates a new user event instance. This is useful if you need to keep the state of keyboard to press and release buttons correctly.
WARNING
Unlike @testing-library/user-event
, the default userEvent
instance from @vitest/browser/context
is created once, not every time its methods are called! You can see the difference in how it works in this snippet:
import { userEvent as vitestUserEvent } from '@vitest/browser/context'
import { userEvent as originalUserEvent } from '@testing-library/user-event'
await vitestUserEvent.keyboard('{Shift}') // press shift without releasing
await vitestUserEvent.keyboard('{/Shift}') // releases shift
await originalUserEvent.keyboard('{Shift}') // press shift without releasing
await originalUserEvent.keyboard('{/Shift}') // DID NOT release shift because the state is different
This behaviour is more useful because we do not emulate the keyboard, we actually press the Shift, so keeping the original behaviour would cause unexpected issues when typing in the field.
userEvent.click
function click(
element: Element | Locator,
options?: UserEventClickOptions,
): Promise<void>
Click on an element. Inherits provider's options. Please refer to your provider's documentation for detailed explanation about how this method works.
import { page, userEvent } from '@vitest/browser/context'
test('clicks on an element', async () => {
const logo = page.getByRole('img', { name: /logo/ })
await userEvent.click(logo)
// or you can access it directly on the locator
await logo.click()
})
References:
userEvent.dblClick
function dblClick(
element: Element | Locator,
options?: UserEventDoubleClickOptions,
): Promise<void>
Triggers a double click event on an element.
Please refer to your provider's documentation for detailed explanation about how this method works.
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)
// or you can access it directly on the locator
await logo.dblClick()
})
References:
userEvent.tripleClick
function tripleClick(
element: Element | Locator,
options?: UserEventTripleClickOptions,
): Promise<void>
Triggers a triple click event on an element. Since there is no tripleclick
in browser api, this method will fire three click events in a row, and so you must check click event detail to filter the event: evt.detail === 3
.
Please refer to your provider's documentation for detailed explanation about how this method works.
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)
// or you can access it directly on the locator
await logo.tripleClick()
expect(tripleClickFired).toBe(true)
})
References:
- Playwright
locator.click
API: implemented viaclick
withclickCount: 3
. - WebdriverIO
browser.action
API: implemented via actions api withmove
plus threedown + up + pause
events in a row - testing-library
tripleClick
API
userEvent.fill
function fill(
element: Element | Locator,
text: string,
): Promise<void>
Set a value to the input/textarea/conteneditable
field. This will remove any existing text in the input before setting the new value.
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}
// or you can access it directly on the locator
await input.fill('foo') // input.value == foo
})
This methods focuses the element, fills it and triggers an input
event after filling. You can use an empty string to clear the field.
TIP
This API is faster than using userEvent.type
or userEvent.keyboard
, but it doesn't support user-event keyboard
syntax (e.g., {Shift}{selectall}
).
We recommend using this API over userEvent.type
in situations when you don't need to enter special characters or have granular control over keypress events.
References:
userEvent.keyboard
function keyboard(text: string): Promise<void>
The userEvent.keyboard
allows you to trigger keyboard strokes. If any input has a focus, it will type characters into that input. Otherwise, it will trigger keyboard events on the currently focused element (document.body
if there are no focused elements).
This API supports user-event keyboard
syntax.
import { userEvent } from '@vitest/browser/context'
test('trigger keystrokes', async () => {
await userEvent.keyboard('foo') // translates to: f, o, o
await userEvent.keyboard('{{a[[') // translates to: {, a, [
await userEvent.keyboard('{Shift}{f}{o}{o}') // translates to: Shift, f, o, o
await userEvent.keyboard('{a>5}') // press a without releasing it and trigger 5 keydown
await userEvent.keyboard('{a>5/}') // press a for 5 keydown and then release it
})
References:
userEvent.tab
function tab(options?: UserEventTabOptions): Promise<void>
Sends a Tab
key event. This is a shorthand for 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()
})
References:
userEvent.type
function type(
element: Element | Locator,
text: string,
options?: UserEventTypeOptions,
): Promise<void>
WARNING
If you don't rely on special characters (e.g., {shift}
or {selectall}
), it is recommended to use userEvent.fill
instead for better performance.
The type
method implements @testing-library/user-event
's type
utility built on top of keyboard
API.
This function allows you to type characters into an input/textarea/conteneditable element. It supports user-event keyboard
syntax.
If you just need to press characters without an input, use 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 doesn't expose .type
method on the locator like input.type
because it exists only for compatiblity with the userEvent
library. Consider using .fill
instead as it is faster.
References:
userEvent.clear
function clear(element: Element | Locator): Promise<void>
This method clears the input element content.
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)
// or you can access it directly on the locator
await input.clear()
expect(input).toHaveValue('')
})
References:
userEvent.selectOptions
function selectOptions(
element: Element | Locator,
values:
| HTMLElement
| HTMLElement[]
| Locator
| Locator[]
| string
| string[],
options?: UserEventSelectOptions,
): Promise<void>
The userEvent.selectOptions
allows selecting a value in a <select>
element.
WARNING
If select element doesn't have multiple
attribute, Vitest will select only the first element in the array.
Unlike @testing-library
, Vitest doesn't support listbox at the moment, but we plan to add support for it in the future.
import { page, userEvent } from '@vitest/browser/context'
test('clears input', async () => {
const select = page.getByRole('select')
await userEvent.selectOptions(select, 'Option 1')
// or you can access it directly on the locator
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
provider doesn't support selecting multiple elements because it doesn't provide API to do so.
References:
- Playwright
locator.selectOption
API - WebdriverIO
element.selectByIndex
API - testing-library
selectOptions
API
userEvent.hover
function hover(
element: Element | Locator,
options?: UserEventHoverOptions,
): Promise<void>
This method moves the cursor position to the selected element. Please refer to your provider's documentation for detailed explanation about how this method works.
WARNING
If you are using webdriverio
provider, the cursor will move to the center of the element by default.
If you are using playwright
provider, the cursor moves to "some" visible point of the element.
import { page, userEvent } from '@vitest/browser/context'
test('hovers logo element', async () => {
const logo = page.getByRole('img', { name: /logo/ })
await userEvent.hover(logo)
// or you can access it directly on the locator
await logo.hover()
})
References:
userEvent.unhover
function unhover(
element: Element | Locator,
options?: UserEventHoverOptions,
): Promise<void>
This works the same as userEvent.hover
, but moves the cursor to the document.body
element instead.
WARNING
By default, the cursor position is in "some" visible place (in playwright
provider) or in the center (in webdriverio
provider) of the body element, so if the currently hovered element is already in the same position, this method will have no effect.
import { page, userEvent } from '@vitest/browser/context'
test('unhover logo element', async () => {
const logo = page.getByRole('img', { name: /logo/ })
await userEvent.unhover(logo)
// or you can access it directly on the locator
await logo.unhover()
})
References:
userEvent.upload
function upload(
element: Element | Locator,
files: string[] | string | File[] | File,
): Promise<void>
Change a file input element to have the specified files.
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)
// or you can access it directly on the locator
await input.upload(file)
// you can also use file paths relative to the test file
await userEvent.upload(input, '../fixtures/file.png')
})
WARNING
webdriverio
provider supports this command only in chrome
and edge
browsers. It also only supports string types at the moment.
References:
userEvent.dragAndDrop
function dragAndDrop(
source: Element | Locator,
target: Element | Locator,
options?: UserEventDragAndDropOptions,
): Promise<void>
Drags the source element on top of the target element. Don't forget that the source
element has to have the draggable
attribute set to 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)
// or you can access it directly on the locator
await source.dropTo(target)
await expect.element(target).toHaveTextContent('Logo is processed')
})
WARNING
This API is not supported by the default preview
provider.
References: