diff --git a/.gitignore b/.gitignore index 02fe571b8450..2d3ca233f013 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ docs/public/sponsors .eslintcache docs/.vitepress/cache/ !test/cli/fixtures/dotted-files/**/.cache -.vitest-reports \ No newline at end of file +test/browser/test/__screenshots__/**/* +.vitest-reports diff --git a/docs/config/index.md b/docs/config/index.md index 8bf98b9796b7..a092e3a28853 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -923,7 +923,7 @@ Minimum number of workers to run tests in. `poolOptions.{threads,vmThreads}.minT ### testTimeout - **Type:** `number` -- **Default:** `5000` +- **Default:** `5_000` in Node.js, `15_000` if `browser.enabled` is `true` - **CLI:** `--test-timeout=5000`, `--testTimeout=5000` Default timeout of a test in milliseconds @@ -931,7 +931,7 @@ Default timeout of a test in milliseconds ### hookTimeout - **Type:** `number` -- **Default:** `10000` +- **Default:** `10_000` in Node.js, `30_000` if `browser.enabled` is `true` - **CLI:** `--hook-timeout=10000`, `--hookTimeout=10000` Default timeout of a hook in milliseconds diff --git a/docs/guide/browser.md b/docs/guide/browser.md index 998f9a865c1d..098759b91672 100644 --- a/docs/guide/browser.md +++ b/docs/guide/browser.md @@ -212,6 +212,7 @@ export const server: { * @experimental */ export const userEvent: { + setup: () => UserEvent /** * Click on an element. Uses provider's API under the hood and supports all its options. * @see {@link https://playwright.dev/docs/api/class-locator#locator-click} Playwright API @@ -219,6 +220,99 @@ export const userEvent: { * @see {@link https://testing-library.com/docs/user-event/convenience/#click} testing-library API */ click: (element: Element, options?: UserEventClickOptions) => Promise + /** + * Triggers a double click event on an element. Uses provider's API under the hood. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-dblclick} Playwright API + * @see {@link https://webdriver.io/docs/api/element/doubleClick/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#dblClick} testing-library API + */ + dblClick: (element: Element, options?: UserEventDoubleClickOptions) => Promise + /** + * Choose one or more values from a select element. Uses provider's API under the hood. + * If select doesn't have `multiple` attribute, only the first value will be selected. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-select-option} Playwright API + * @see {@link https://webdriver.io/docs/api/element/doubleClick/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/utility/#-selectoptions-deselectoptions} testing-library API + */ + selectOptions: ( + element: Element, + values: HTMLElement | HTMLElement[] | string | string[], + options?: UserEventSelectOptions, + ) => Promise + /** + * Type text on the keyboard. If any input is focused, it will receive the text, + * otherwise it will be typed on the document. Uses provider's API under the hood. + * **Supports** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}`) even with `playwright` and `webdriverio` providers. + * @example + * 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 + * @see {@link https://playwright.dev/docs/api/class-locator#locator-press} Playwright API + * @see {@link https://webdriver.io/docs/api/browser/action#key-input-source} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/keyboard} testing-library API + */ + keyboard: (text: string) => Promise + /** + * Types text into an element. Uses provider's API under the hood. + * **Supports** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}`) even with `playwright` and `webdriverio` providers. + * @example + * await userEvent.type(input, 'foo') // translates to: f, o, o + * await userEvent.type(input, '{{a[[') // translates to: {, a, [ + * await userEvent.type(input, '{Shift}{f}{o}{o}') // translates to: Shift, f, o, o + * @see {@link https://playwright.dev/docs/api/class-locator#locator-press} Playwright API + * @see {@link https://webdriver.io/docs/api/browser/action#key-input-source} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/utility/#type} testing-library API + */ + type: (element: Element, text: string, options?: UserEventTypeOptions) => Promise + /** + * Removes all text from an element. Uses provider's API under the hood. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-clear} Playwright API + * @see {@link https://webdriver.io/docs/api/element/clearValue} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/utility/#clear} testing-library API + */ + clear: (element: Element) => Promise + /** + * Sends a `Tab` key event. Uses provider's API under the hood. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-press} Playwright API + * @see {@link https://webdriver.io/docs/api/element/keys} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#tab} testing-library API + */ + tab: (options?: UserEventTabOptions) => Promise + /** + * Hovers over an element. Uses provider's API under the hood. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API + * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API + */ + hover: (element: Element, options?: UserEventHoverOptions) => Promise + /** + * Moves cursor position to the body element. Uses provider's API under the hood. + * By default, the cursor position is in the center (in webdriverio) or in some visible place (in playwright) + * of the body element, so if the current element is already there, this will have no effect. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API + * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API + */ + unhover: (element: Element, options?: UserEventHoverOptions) => Promise + /** + * Fills an input element with text. This will remove any existing text in the input before typing the new value. + * Uses provider's API under the hood. + * This API is faster than using `userEvent.type` or `userEvent.keyboard`, but it **doesn't support** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}`). + * @example + * await userEvent.fill(input, 'foo') // translates to: f, o, o + * await userEvent.fill(input, '{{a[[') // translates to: {, {, a, [, [ + * await userEvent.fill(input, '{Shift}') // translates to: {, S, h, i, f, t, } + * @see {@link https://playwright.dev/docs/api/class-locator#locator-fill} Playwright API + * @see {@link https://webdriver.io/docs/api/element/setValue} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/utility/#type} testing-library API + */ + fill: (element: Element, text: string, options?: UserEventFillOptions) => Promise + /** + * Drags a source element on top of the target element. This API is not supported by "preview" provider. + * @see {@link https://playwright.dev/docs/api/class-frame#frame-drag-and-drop} Playwright API + * @see {@link https://webdriver.io/docs/api/element/dragAndDrop/} WebdriverIO API + */ + dragAndDrop: (source: Element, target: Element, options?: UserEventDragAndDropOptions) => Promise } /** @@ -244,6 +338,362 @@ export const page: { } ``` +## Interactivity API + +Vitest implements a subset of [`@testing-library/user-event`](https://testing-library.com/docs/user-event) APIs using [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) or [webdriver](https://www.w3.org/TR/webdriver/) APIs instead of faking events which makes the browser behaviour more reliable and consistent. + +Almost every `userEvent` method inherits its provider options. To see all available options in your IDE, add `webdriver` or `playwright` types to your `tsconfig.json` file: + +::: code-group +```json [playwright] +{ + "compilerOptions": { + "types": [ + "@vitest/browser/providers/playwright" + ] + } +} +``` +```json [webdriverio] +{ + "compilerOptions": { + "types": [ + "@vitest/browser/providers/webdriverio" + ] + } +} +``` +::: + +### userEvent.click + +- **Type:** `(element: Element, options?: UserEventClickOptions) => Promise` + +Clicks on an element. Inherits provider's options. Please refer to your provider's documentation for detailed explanaition about how this method works. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('clicks on an element', () => { + const logo = screen.getByRole('img', { name: /logo/ }) + + await userEvent.click(logo) +}) +``` + +References: + +- [Playwright `locator.click` API](https://playwright.dev/docs/api/class-locator#locator-click) +- [WebdriverIO `element.click` API](https://webdriver.io/docs/api/element/click/) +- [testing-library `click` API](https://testing-library.com/docs/user-event/convenience/#click) + +### userEvent.dblClick + +- **Type:** `(element: Element, options?: UserEventDoubleClickOptions) => Promise` + +Triggers a double click event on an element + +Please refer to your provider's documentation for detailed explanaition about how this method works. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('triggers a double click on an element', () => { + const logo = screen.getByRole('img', { name: /logo/ }) + + await userEvent.dblClick(logo) +}) +``` + +References: + +- [Playwright `locator.dblclick` API](https://playwright.dev/docs/api/class-locator#locator-dblclick) +- [WebdriverIO `element.doubleClick` API](https://webdriver.io/docs/api/element/doubleClick/) +- [testing-library `dblClick` API](https://testing-library.com/docs/user-event/convenience/#dblClick) + +### userEvent.fill + +- **Type:** `(element: Element, text: string) => Promise` + +Fills an input/textarea/conteneditable element with text. This will remove any existing text in the input before typing the new value. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('update input', () => { + const input = screen.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} +}) +``` + +::: tip +This API is faster than using [`userEvent.type`](#userevent-type) or [`userEvent.keyboard`](#userevent-keyboard), but it **doesn't support** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}{selectall}`). + +We recommend using this API over [`userEvent.type`](#userevent-type) in situations when you don't need to enter special characters. +::: + +References: + +- [Playwright `locator.fill` API](https://playwright.dev/docs/api/class-locator#locator-fill) +- [WebdriverIO `element.setValue` API](https://webdriver.io/docs/api/element/setValue) +- [testing-library `type` API](https://testing-library.com/docs/user-event/utility/#type) + +### userEvent.keyboard + +- **Type:** `(text: string) => Promise` + +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](https://testing-library.com/docs/user-event/keyboard). + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('trigger keystrokes', () => { + 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: + +- [Playwright `locator.press` API](https://playwright.dev/docs/api/class-locator#locator-press) +- [WebdriverIO `action('key')` API](https://webdriver.io/docs/api/browser/action#key-input-source) +- [testing-library `type` API](https://testing-library.com/docs/user-event/utility/#type) + +### userEvent.tab + +- **Type:** `(options?: UserEventTabOptions) => Promise` + +Sends a `Tab` key event. This is a shorthand for `userEvent.keyboard('{tab}')`. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' +import '@testing-library/jest-dom' // adds support for "toHaveFocus" + +test('tab works', () => { + const [input1, input2] = screen.getAllByRole('input') + + expect(input1).toHaveFocus() + + await userEvent.tab() + + expect(input2).toHaveFocus() + + await userEvent.tab({ shift: true }) + + expect(input1).toHaveFocus() +}) +``` + +References: + +- [Playwright `locator.press` API](https://playwright.dev/docs/api/class-locator#locator-press) +- [WebdriverIO `action('key')` API](https://webdriver.io/docs/api/browser/action#key-input-source) +- [testing-library `tab` API](https://testing-library.com/docs/user-event/convenience/#tab) + +### userEvent.type + +- **Type:** `(element: Element, text: string, options?: UserEventTypeOptions) => Promise` + +::: warning +If you don't rely on [special characters](https://testing-library.com/docs/user-event/keyboard) (e.g., `{shift}` or `{selectall}`), it is recommended to use [`userEvent.fill`](#userevent-fill) instead. +::: + +The `type` method implements `@testing-library/user-event`'s [`type`](https://testing-library.com/docs/user-event/utility/#type) utility built on top of [`keyboard`](https://testing-library.com/docs/user-event/keyboard) API. + +This function allows you to type characters into an input/textarea/conteneditable element. It supports [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard). + +If you just need to press characters without an input, use [`userEvent.keyboard`](#userevent-keyboard) API. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('update input', () => { + const input = screen.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[ +}) +``` + +References: + +- [Playwright `locator.press` API](https://playwright.dev/docs/api/class-locator#locator-press) +- [WebdriverIO `action('key')` API](https://webdriver.io/docs/api/browser/action#key-input-source) +- [testing-library `type` API](https://testing-library.com/docs/user-event/utility/#type) + +### userEvent.clear + +- **Type:** `(element: Element) => Promise` + +This method clear the input element content. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' +import '@testing-library/jest-dom' // adds support for "toHaveValue" + +test('clears input', () => { + const input = screen.getByRole('input') + + await userEvent.fill(input, 'foo') + expect(input).toHaveValue('foo') + + await userEvent.clear(input) + expect(input).toHaveValue('') +}) +``` + +References: + +- [Playwright `locator.clear` API](https://playwright.dev/docs/api/class-locator#locator-clear) +- [WebdriverIO `element.clearValue` API](https://webdriver.io/docs/api/element/clearValue) +- [testing-library `clear` API](https://testing-library.com/docs/user-event/utility/#clear) + +### userEvent.selectOptions + +- **Type:** `(element: Element, values: HTMLElement | HTMLElement[] | string | string[], options?: UserEventSelectOptions) => Promise` + +The `userEvent.selectOptions` allows selecting a value in a `