diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 590704c1eeeb..9dc63ea9e0d7 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -241,7 +241,33 @@ export default ({ mode }: { mode: string }) => { }, { text: 'Browser Mode', - link: '/guide/browser', + link: '/guide/browser/', + collapsed: true, + items: [{ + text: 'Assertion API', + link: '/guide/browser/assertion-api', + docFooterText: 'Assertion API | Browser Mode', + }, { + text: 'Retry-ability', + link: '/guide/browser/retry-ability', + docFooterText: 'Retry-ability | Browser Mode', + }, { + text: 'Context', + link: '/guide/browser/context', + docFooterText: 'Context | Browser Mode', + }, { + text: 'Interactivity API', + link: '/guide/browser/interactivity-api', + docFooterText: 'Interactivity API | Browser Mode', + }, { + text: 'Commands', + link: '/guide/browser/commands', + docFooterText: 'Commands | Browser Mode', + }, { + text: 'Examples', + link: '/guide/browser/examples', + docFooterText: 'Examples | Browser Mode', + }], }, { text: 'In-Source Testing', diff --git a/docs/config/index.md b/docs/config/index.md index 42e9b8085628..fb5e08a45fdb 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -412,7 +412,7 @@ or [`happy-dom`](https://github.com/capricorn86/happy-dom) instead. If you are building edge functions, you can use [`edge-runtime`](https://edge-runtime.vercel.app/packages/vm) environment ::: tip -You can also use [Browser Mode](/guide/browser) to run integration or unit tests in the browser without mocking the environment. +You can also use [Browser Mode](/guide/browser/) to run integration or unit tests in the browser without mocking the environment. ::: By adding a `@vitest-environment` docblock or comment at the top of the file, @@ -1507,7 +1507,7 @@ Listen to port and serve API. When set to true, the default port is 51204 Run Vitest tests in a browser. We use [WebdriverIO](https://webdriver.io/) for running tests by default, but it can be configured with [browser.provider](#browser-provider) option. ::: tip NOTE -Read more about testing in a real browser in the [guide page](/guide/browser). +Read more about testing in a real browser in the [guide page](/guide/browser/). ::: ::: warning @@ -1697,7 +1697,7 @@ The script `src` and `content` will be processed by Vite plugins. - **Type:** `Record` - **Default:** `{ readFile, writeFile, ... }` -Custom [commands](/guide/browser#commands) that can be import during browser tests from `@vitest/browser/commands`. +Custom [commands](/guide/browser/commands) that can be import during browser tests from `@vitest/browser/commands`. ### clearMocks @@ -2308,7 +2308,7 @@ The `location` property has `column` and `line` values that correspond to the `t This option will be auto-enabled if you don't disable it explicitly, and you are running Vitest with: - [Vitest UI](/guide/ui) -- or using the [Browser Mode](/guide/browser) without [headless](/guide/browser#headless) mode +- or using the [Browser Mode](/guide/browser/) without [headless](/guide/browser/#headless) mode - or using [HTML Reporter](/guide/reporters#html-reporter) ::: tip diff --git a/docs/guide/browser.md b/docs/guide/browser.md deleted file mode 100644 index 7dc57bf9c04c..000000000000 --- a/docs/guide/browser.md +++ /dev/null @@ -1,1199 +0,0 @@ ---- -title: Browser Mode | Guide ---- - -# Browser Mode Experimental {#browser-mode} - -This page provides information about the experimental browser mode feature in the Vitest API, which allows you to run your tests in the browser natively, providing access to browser globals like window and document. This feature is currently under development, and APIs may change in the future. - -## Installation - -For easier setup, you can use `vitest init browser` command to install required dependencies and create browser configuration. - -::: code-group -```bash [npm] -npx vitest init browser -``` -```bash [yarn] -yarn exec vitest init browser -``` -```bash [pnpm] -pnpx vitest init browser -``` -```bash [bun] -bunx vitest init browser -``` -::: - -### Manual Installation - -You can also install packages manually. By default, Browser Mode doesn't require any additional E2E provider to run tests locally because it reuses your existing browser. - -::: code-group -```bash [npm] -npm install -D vitest @vitest/browser -``` -```bash [yarn] -yarn add -D vitest @vitest/browser -``` -```bash [pnpm] -pnpm add -D vitest @vitest/browser -``` -```bash [bun] -bun add -D vitest @vitest/browser -``` -::: - -::: warning -However, to run tests in CI you need to install either [`playwright`](https://npmjs.com/package/playwright) or [`webdriverio`](https://www.npmjs.com/package/webdriverio). We also recommend switching to either one of them for testing locally instead of using the default `preview` provider since it relies on simulating events instead of using Chrome DevTools Protocol. -::: - -### Using Playwright - -::: code-group -```bash [npm] -npm install -D vitest @vitest/browser playwright -``` -```bash [yarn] -yarn add -D vitest @vitest/browser playwright -``` -```bash [pnpm] -pnpm add -D vitest @vitest/browser playwright -``` -```bash [bun] -bun add -D vitest @vitest/browser playwright -``` -::: - -### Using Webdriverio - -::: code-group -```bash [npm] -npm install -D vitest @vitest/browser webdriverio -``` -```bash [yarn] -yarn add -D vitest @vitest/browser webdriverio -``` -```bash [pnpm] -pnpm add -D vitest @vitest/browser webdriverio -``` -```bash [bun] -bun add -D vitest @vitest/browser webdriverio -``` -::: - -## Configuration - -To activate browser mode in your Vitest configuration, you can use the `--browser` flag or set the `browser.enabled` field to `true` in your Vitest configuration file. Here is an example configuration using the browser field: - -```ts -export default defineConfig({ - test: { - browser: { - provider: 'playwright', // or 'webdriverio' - enabled: true, - name: 'chrome', // browser name is required - }, - } -}) -``` - -If you have not used Vite before, make sure you have your framework's plugin installed and specified in the config. Some frameworks might require extra configuration to work - check their Vite related documentation to be sure. - -::: code-group -```ts [vue] -import { defineConfig } from 'vitest/config' -import vue from '@vitejs/plugin-vue' - -export default defineConfig({ - plugins: [vue()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -```ts [svelte] -import { defineConfig } from 'vitest/config' -import { svelte } from '@sveltejs/vite-plugin-svelte' - -export default defineConfig({ - plugins: [svelte()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -```ts [solid] -import { defineConfig } from 'vitest/config' -import solidPlugin from 'vite-plugin-solid' - -export default defineConfig({ - plugins: [solidPlugin()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -```ts [marko] -import { defineConfig } from 'vitest/config' -import marko from '@marko/vite' - -export default defineConfig({ - plugins: [marko()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -::: - -::: tip -`react` doesn't require a plugin to work, but `preact` requires [extra configuration](https://preactjs.com/guide/v10/getting-started/#create-a-vite-powered-preact-app) to make aliases work. -::: - -If you need to run some tests using Node-based runner, you can define a [workspace](/guide/workspace) file with separate configurations for different testing strategies: - -```ts -// vitest.workspace.ts -import { defineWorkspace } from 'vitest/config' - -export default defineWorkspace([ - { - test: { - // an example of file based convention, - // you don't have to follow it - include: [ - 'tests/unit/**/*.{test,spec}.ts', - 'tests/**/*.unit.{test,spec}.ts', - ], - name: 'unit', - environment: 'node', - }, - }, - { - test: { - // an example of file based convention, - // you don't have to follow it - include: [ - 'tests/browser/**/*.{test,spec}.ts', - 'tests/**/*.browser.{test,spec}.ts', - ], - name: 'browser', - browser: { - enabled: true, - name: 'chrome', - }, - }, - }, -]) -``` - -## Browser Option Types - -The browser option in Vitest depends on the provider. Vitest will fail, if you pass `--browser` and don't specify its name in the config file. Available options: - -- `webdriverio` supports these browsers: - - `firefox` - - `chrome` - - `edge` - - `safari` -- `playwright` supports these browsers: - - `firefox` - - `webkit` - - `chromium` - -## Browser Compatibility - -Vitest uses [Vite dev server](https://vitejs.dev/guide/#browser-support) to run your tests, so we only support features specified in the [`esbuild.target`](https://vitejs.dev/config/shared-options.html#esbuild) option (`esnext` by default). - -By default, Vite targets browsers which support the native [ES Modules](https://caniuse.com/es6-module), native [ESM dynamic import](https://caniuse.com/es6-module-dynamic-import), and [`import.meta`](https://caniuse.com/mdn-javascript_operators_import_meta). On top of that, we utilize [`BroadcastChannel`](https://caniuse.com/?search=BroadcastChannel) to communicate between iframes: - -- Chrome >=87 -- Firefox >=78 -- Safari >=15.4 -- Edge >=88 - -## Motivation - -We developed the Vitest browser mode feature to help improve testing workflows and achieve more accurate and reliable test results. This experimental addition to our testing API allows developers to run tests in a native browser environment. In this section, we'll explore the motivations behind this feature and its benefits for testing. - -### Different Ways of Testing - -There are different ways to test JavaScript code. Some testing frameworks simulate browser environments in Node.js, while others run tests in real browsers. In this context, [jsdom](https://www.npmjs.com/package/jsdom) is an example of a spec implementation that simulates a browser environment by being used with a test runner like Jest or Vitest, while other testing tools such as [WebdriverIO](https://webdriver.io/) or [Cypress](https://www.cypress.io/) allow developers to test their applications in a real browser or in case of [Playwright](https://playwright.dev/) provide you a browser engine. - -### The Simulation Caveat - -Testing JavaScript programs in simulated environments such as jsdom or happy-dom has simplified the test setup and provided an easy-to-use API, making them suitable for many projects and increasing confidence in test results. However, it is crucial to keep in mind that these tools only simulate a browser environment and not an actual browser, which may result in some discrepancies between the simulated environment and the real environment. Therefore, false positives or negatives in test results may occur. - -To achieve the highest level of confidence in our tests, it's crucial to test in a real browser environment. This is why we developed the browser mode feature in Vitest, allowing developers to run tests natively in a browser and gain more accurate and reliable test results. With browser-level testing, developers can be more confident that their application will work as intended in a real-world scenario. - -## Drawbacks - -When using Vitest browser, it is important to consider the following drawbacks: - -### Early Development - -The browser mode feature of Vitest is still in its early stages of development. As such, it may not yet be fully optimized, and there may be some bugs or issues that have not yet been ironed out. It is recommended that users augment their Vitest browser experience with a standalone browser-side test runner like WebdriverIO, Cypress or Playwright. - -### Longer Initialization - -Vitest browser requires spinning up the provider and the browser during the initialization process, which can take some time. This can result in longer initialization times compared to other testing patterns. - -## Cross-Browser Testing - -When you specify a browser name in the browser option, Vitest will try to run the specified browser using [WebdriverIO](https://webdriver.io/) by default, and then run the tests there. This feature makes cross-browser testing easy to use and configure in environments like a CI. If you don't want to use WebdriverIO, you can configure the custom browser provider by using `browser.provider` option. - -To specify a browser using the CLI, use the `--browser` flag followed by the browser name, like this: - -```sh -npx vitest --browser=chrome -``` - -Or you can provide browser options to CLI with dot notation: - -```sh -npx vitest --browser.name=chrome --browser.headless -``` - -## Headless - -Headless mode is another option available in the browser mode. In headless mode, the browser runs in the background without a user interface, which makes it useful for running automated tests. The headless option in Vitest can be set to a boolean value to enable or disable headless mode. - -Here's an example configuration enabling headless mode: - -```ts -export default defineConfig({ - test: { - browser: { - provider: 'playwright', - enabled: true, - headless: true, - }, - } -}) -``` - -You can also set headless mode using the `--browser.headless` flag in the CLI, like this: - -```sh -npx vitest --browser.name=chrome --browser.headless -``` - -In this case, Vitest will run in headless mode using the Chrome browser. - -::: warning -Headless mode is not available by default. You need to use either [`playwright`](https://npmjs.com/package/playwright) or [`webdriverio`](https://www.npmjs.com/package/webdriverio) providers to enable this feature. -::: - -## Assertion API - -Vitest bundles [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. For detailed documentation, you can read the `jest-dom` readme: - -- [`toBeDisabled`](https://github.com/testing-library/jest-dom#toBeDisabled) -- [`toBeEnabled`](https://github.com/testing-library/jest-dom#toBeEnabled) -- [`toBeEmptyDOMElement`](https://github.com/testing-library/jest-dom#toBeEmptyDOMElement) -- [`toBeInTheDocument`](https://github.com/testing-library/jest-dom#toBeInTheDocument) -- [`toBeInvalid`](https://github.com/testing-library/jest-dom#toBeInvalid) -- [`toBeRequired`](https://github.com/testing-library/jest-dom#toBeRequired) -- [`toBeValid`](https://github.com/testing-library/jest-dom#toBeValid) -- [`toBeVisible`](https://github.com/testing-library/jest-dom#toBeVisible) -- [`toContainElement`](https://github.com/testing-library/jest-dom#toContainElement) -- [`toContainHTML`](https://github.com/testing-library/jest-dom#toContainHTML) -- [`toHaveAccessibleDescription`](https://github.com/testing-library/jest-dom#toHaveAccessibleDescription) -- [`toHaveAccessibleErrorMessage`](https://github.com/testing-library/jest-dom#toHaveAccessibleErrorMessage) -- [`toHaveAccessibleName`](https://github.com/testing-library/jest-dom#toHaveAccessibleName) -- [`toHaveAttribute`](https://github.com/testing-library/jest-dom#toHaveAttribute) -- [`toHaveClass`](https://github.com/testing-library/jest-dom#toHaveClass) -- [`toHaveFocus`](https://github.com/testing-library/jest-dom#toHaveFocus) -- [`toHaveFormValues`](https://github.com/testing-library/jest-dom#toHaveFormValues) -- [`toHaveStyle`](https://github.com/testing-library/jest-dom#toHaveStyle) -- [`toHaveTextContent`](https://github.com/testing-library/jest-dom#toHaveTextContent) -- [`toHaveValue`](https://github.com/testing-library/jest-dom#toHaveValue) -- [`toHaveDisplayValue`](https://github.com/testing-library/jest-dom#toHaveDisplayValue) -- [`toBeChecked`](https://github.com/testing-library/jest-dom#toBeChecked) -- [`toBePartiallyChecked`](https://github.com/testing-library/jest-dom#toBePartiallyChecked) -- [`toHaveRole`](https://github.com/testing-library/jest-dom#toHaveRole) -- [`toHaveErrorMessage`](https://github.com/testing-library/jest-dom#toHaveErrorMessage) - -If you are using TypeScript or want to have correct type hints in `expect`, make sure you have either `@vitest/browser/providers/playwright` or `@vitest/browser/providers/webdriverio` specified in your `tsconfig` depending on the provider you use. If you use the default `preview` provider, you can specify `@vitest/browser/matchers` instead. - -::: code-group -```json [preview] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/matchers" - ] - } -} -``` -```json [playwright] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/playwright" - ] - } -} -``` -```json [webdriverio] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/webdriverio" - ] - } -} -``` -::: - -## Retry-ability - -Tests in the browser might fail inconsistently due to their asynchronous nature. Because of this, it is important to have a way to guarantee that assertions succeed even if the condition is delayed (by a timeout, network request, or animation, for example). For this purpose, Vitest provides retriable assertions out of the box via the [`expect.poll`](/api/expect#poll) and `expect.element` APIs: - -```ts -import { expect, test } from 'vitest' -import { screen } from '@testing-library/dom' - -test('error banner is rendered', async () => { - triggerError() - - // @testing-library provides queries with built-in retry-ability - // It will try to find the banner until it's rendered - const banner = await screen.findByRole('alert', { - name: /error/i, - }) - - // Vitest provides `expect.element` with built-in retry-ability - // It will check `element.textContent` until it's equal to "Error!" - await expect.element(banner).toHaveTextContent('Error!') -}) -``` - -::: tip -`expect.element` is a shorthand for `expect.poll(() => element)` and works in exactly the same way. - -`toHaveTextContent` and all other [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) assertions are still available on a regular `expect` without a built-in retry-ability mechanism: - -```ts -// will fail immediately if .textContent is not `'Error!'` -expect(banner).toHaveTextContent('Error!') -``` -::: - -## Context - -Vitest exposes a context module via `@vitest/browser/context` entry point. As of 2.0, it exposes a small set of utilities that might be useful to you in tests. - -```ts -export const server: { - /** - * Platform the Vitest server is running on. - * The same as calling `process.platform` on the server. - */ - platform: Platform - /** - * Runtime version of the Vitest server. - * The same as calling `process.version` on the server. - */ - version: string - /** - * Name of the browser provider. - */ - provider: string - /** - * Name of the current browser. - */ - browser: string - /** - * Available commands for the browser. - */ - commands: BrowserCommands -} - -/** - * Handler for user interactions. The support is implemented by the browser provider (`playwright` or `webdriverio`). - * If used with `preview` provider, fallbacks to simulated events via `@testing-library/user-event`. - * @experimental - */ -export const userEvent: { - setup: () => UserEvent - click: (element: Element, options?: UserEventClickOptions) => Promise - dblClick: (element: Element, options?: UserEventDoubleClickOptions) => Promise - selectOptions: ( - element: Element, - values: HTMLElement | HTMLElement[] | string | string[], - options?: UserEventSelectOptions, - ) => Promise - keyboard: (text: string) => Promise - type: (element: Element, text: string, options?: UserEventTypeOptions) => Promise - clear: (element: Element) => Promise - tab: (options?: UserEventTabOptions) => Promise - hover: (element: Element, options?: UserEventHoverOptions) => Promise - unhover: (element: Element, options?: UserEventHoverOptions) => Promise - fill: (element: Element, text: string, options?: UserEventFillOptions) => Promise - dragAndDrop: (source: Element, target: Element, options?: UserEventDragAndDropOptions) => Promise -} - -/** - * Available commands for the browser. - * A shortcut to `server.commands`. - */ -export const commands: BrowserCommands - -export const page: { - /** - * Serialized test config. - */ - config: ResolvedConfig - /** - * Change the size of iframe's viewport. - */ - viewport(width: number | string, height: number | string): Promise - /** - * Make a screenshot of the test iframe or a specific element. - * @returns Path to the screenshot file or path and base64. - */ - screenshot(options: Omit & { base64: true }): Promise<{ - path: string - base64: string - }> - screenshot(options?: ScreenshotOptions): Promise -} - -export const cdp: () => CDPSession -``` - -## 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.tripleClick - -- **Type:** `(element: Element, options?: UserEventTripleClickOptions) => Promise` - -Triggers a triple click event on an element - -Please refer to your provider's documentation for detailed explanation about how this method works. - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('triggers a triple click on an element', async () => { - const logo = screen.getByRole('img', { name: /logo/ }) - - await userEvent.tripleClick(logo) -}) -``` - -References: - -- [Playwright `locator.click` API](https://playwright.dev/docs/api/class-locator#locator-click) -- [WebdriverIO `browser.action` API](https://webdriver.io/docs/api/browser/action/) -- [testing-library `tripleClick` API](https://testing-library.com/docs/user-event/convenience/#tripleClick) - -### 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' - -test('tab works', async () => { - 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' - -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 `` element. + +::: warning +If select element doesn't have [`multiple`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple) attribute, Vitest will select only the first element in the array. + +Unlike `@testing-library`, Vitest doesn't support [listbox](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role) at the moment, but we plan to add support for it in the future. +::: + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('clears input', async () => { + const select = screen.getByRole('select') + + await userEvent.selectOptions(select, 'Option 1') + expect(select).toHaveValue('option-1') + + await userEvent.selectOptions(select, 'option-1') + expect(select).toHaveValue('option-1') + + await userEvent.selectOptions(select, [ + screen.getByRole('option', { name: 'Option 1' }), + screen.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](https://playwright.dev/docs/api/class-locator#locator-select-option) +- [WebdriverIO `element.selectByIndex` API](https://webdriver.io/docs/api/element/selectByIndex) +- [testing-library `selectOptions` API](https://testing-library.com/docs/user-event/utility/#-selectoptions-deselectoptions) + +## userEvent.hover + +- **Type:** `(element: Element, options?: UserEventHoverOptions) => Promise` + +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. +::: + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('hovers logo element', async () => { + const logo = screen.getByRole('img', { name: /logo/ }) + + await userEvent.hover(logo) +}) +``` + +References: + +- [Playwright `locator.hover` API](https://playwright.dev/docs/api/class-locator#locator-hover) +- [WebdriverIO `element.moveTo` API](https://webdriver.io/docs/api/element/moveTo/) +- [testing-library `hover` API](https://testing-library.com/docs/user-event/convenience/#hover) + +## userEvent.unhover + +- **Type:** `(element: Element, options?: UserEventHoverOptions) => Promise` + +This works the same as [`userEvent.hover`](#userevent-hover), but moves the cursor to the `document.body` element instead. + +::: warning +By default, the cursor position is in the center (in `webdriverio` provider) or in "some" visible place (in `playwright` provider) of the body element, so if the currently hovered element is already in the same position, this method will have no effect. +::: + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' + +test('unhover logo element', async () => { + const logo = screen.getByRole('img', { name: /logo/ }) + + await userEvent.unhover(logo) +}) +``` + +References: + +- [Playwright `locator.hover` API](https://playwright.dev/docs/api/class-locator#locator-hover) +- [WebdriverIO `element.moveTo` API](https://webdriver.io/docs/api/element/moveTo/) +- [testing-library `hover` API](https://testing-library.com/docs/user-event/convenience/#hover) + +## userEvent.dragAndDrop + +- **Type:** `(source: Element, target: Element, options?: UserEventDragAndDropOptions) => Promise` + +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`. + +```ts +import { userEvent } from '@vitest/browser/context' +import { screen } from '@testing-library/dom' +import '@testing-library/jest-dom' // adds support for "toHaveTextContent" + +test('drag and drop works', async () => { + const source = screen.getByRole('img', { name: /logo/ }) + const target = screen.getByTestId('logo-target') + + await userEvent.dragAndDrop(source, target) + + expect(target).toHaveTextContent('Logo is processed') +}) +``` + +::: warning +This API is not supported by the default `preview` provider. +::: + +References: + +- [Playwright `frame.dragAndDrop` API](https://playwright.dev/docs/api/class-frame#frame-drag-and-drop) +- [WebdriverIO `element.dragAndDrop` API](https://webdriver.io/docs/api/element/dragAndDrop/) diff --git a/docs/guide/browser/retry-ability.md b/docs/guide/browser/retry-ability.md new file mode 100644 index 000000000000..65e4180eba54 --- /dev/null +++ b/docs/guide/browser/retry-ability.md @@ -0,0 +1,37 @@ +--- +title: Retry-ability | Browser Mode +--- + +# Retry-ability + +Tests in the browser might fail inconsistently due to their asynchronous nature. Because of this, it is important to have a way to guarantee that assertions succeed even if the condition is delayed (by a timeout, network request, or animation, for example). For this purpose, Vitest provides retriable assertions out of the box via the [`expect.poll`](/api/expect#poll) and `expect.element` APIs: + +```ts +import { expect, test } from 'vitest' +import { screen } from '@testing-library/dom' + +test('error banner is rendered', async () => { + triggerError() + + // @testing-library provides queries with built-in retry-ability + // It will try to find the banner until it's rendered + const banner = await screen.findByRole('alert', { + name: /error/i, + }) + + // Vitest provides `expect.element` with built-in retry-ability + // It will check `element.textContent` until it's equal to "Error!" + await expect.element(banner).toHaveTextContent('Error!') +}) +``` + +::: tip +`expect.element` is a shorthand for `expect.poll(() => element)` and works in exactly the same way. + +`toHaveTextContent` and all other [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) assertions are still available on a regular `expect` without a built-in retry-ability mechanism: + +```ts +// will fail immediately if .textContent is not `'Error!'` +expect(banner).toHaveTextContent('Error!') +``` +::: diff --git a/packages/browser/context.d.ts b/packages/browser/context.d.ts index b6da5aa83c3d..167af84420ab 100644 --- a/packages/browser/context.d.ts +++ b/packages/browser/context.d.ts @@ -67,7 +67,7 @@ export interface UserEvent { /** * Triggers a triple click event on an element. Uses provider's API under the hood. * @see {@link https://playwright.dev/docs/api/class-locator#locator-click} Playwright API: using `click` with `clickCount: 3` - * @see {@link https://webdriver.io/docs/api/browser/actions/} WebdriverIO API: using actions with `move` and 3 `down + up + down` events in a row + * @see {@link https://webdriver.io/docs/api/browser/actions/} WebdriverIO API: using actions api with `move` plus three `down + up + pause` events in a row * @see {@link https://testing-library.com/docs/user-event/convenience/#tripleclick} testing-library API */ tripleClick: (element: Element, options?: UserEventTripleClickOptions) => Promise @@ -222,7 +222,7 @@ export const server: { browser: string /** * Available commands for the browser. - * @see {@link https://vitest.dev/guide/browser#commands} + * @see {@link https://vitest.dev/guide/browser/commands} */ commands: BrowserCommands } @@ -237,7 +237,7 @@ export const userEvent: UserEvent /** * Available commands for the browser. * A shortcut to `server.commands`. - * @see {@link https://vitest.dev/guide/browser#commands} + * @see {@link https://vitest.dev/guide/browser/commands} */ export const commands: BrowserCommands diff --git a/packages/browser/src/client/tester/dialog.ts b/packages/browser/src/client/tester/dialog.ts index 7c807dc642a5..d921c44b4e69 100644 --- a/packages/browser/src/client/tester/dialog.ts +++ b/packages/browser/src/client/tester/dialog.ts @@ -2,7 +2,7 @@ function showPopupWarning(name: string, value: T, defaultValue?: T) { return (...params: any[]) => { const formatedParams = params.map(p => JSON.stringify(p)).join(', ') - console.warn(`Vitest encountered a \`${name}(${formatedParams})\` call that it cannot handle by default, so it returned \`${value}\`. Read more in https://vitest.dev/guide/browser#thread-blocking-dialogs. + console.warn(`Vitest encountered a \`${name}(${formatedParams})\` call that it cannot handle by default, so it returned \`${value}\`. Read more in https://vitest.dev/guide/browser/#thread-blocking-dialogs. If needed, mock the \`${name}\` call manually like: \`\`\` diff --git a/packages/browser/src/node/providers/preview.ts b/packages/browser/src/node/providers/preview.ts index 9b412ca961a5..8693f661d6fa 100644 --- a/packages/browser/src/node/providers/preview.ts +++ b/packages/browser/src/node/providers/preview.ts @@ -24,7 +24,7 @@ export class PreviewBrowserProvider implements BrowserProvider { this.open = false if (ctx.config.browser.headless) { throw new Error( - 'You\'ve enabled headless mode for "preview" provider but it doesn\'t support it. Use "playwright" or "webdriverio" instead: https://vitest.dev/guide/browser#configuration', + 'You\'ve enabled headless mode for "preview" provider but it doesn\'t support it. Use "playwright" or "webdriverio" instead: https://vitest.dev/guide/browser/#configuration', ) } } diff --git a/packages/vitest/src/types/browser.ts b/packages/vitest/src/types/browser.ts index 1b747077454a..a6de0e7595ff 100644 --- a/packages/vitest/src/types/browser.ts +++ b/packages/vitest/src/types/browser.ts @@ -156,7 +156,7 @@ export interface BrowserConfigOptions { /** * Commands that will be executed on the server * via the browser `import("@vitest/browser/context").commands` API. - * @see {@link https://vitest.dev/guide/browser#commands} + * @see {@link https://vitest.dev/guide/browser/commands} */ commands?: Record> }