From 19c97cda6761cd60ffae67ea0d917be9f960dd1a Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 1 Oct 2025 15:58:13 +0200 Subject: [PATCH] feat: introduce separate packages for browser mode providers (#8629) --- .vitepress/config.ts | 5 +++ guide/browser/assertion-api.md | 6 ++-- guide/browser/commands.md | 12 ++++---- guide/browser/component-testing.md | 4 +-- guide/browser/config.md | 27 ++++++---------- guide/browser/context.md | 2 +- guide/browser/index.md | 28 ++++++++--------- guide/browser/interactivity-api.md | 36 +++++++++++----------- guide/browser/locators.md | 26 ++++++++-------- guide/browser/multiple-setups.md | 27 +++------------- guide/browser/playwright.md | 14 ++++++--- guide/browser/preview.md | 32 +++++++++++++++++++ guide/browser/trace-viewer.md | 4 +-- guide/browser/visual-regression-testing.md | 6 ++-- guide/browser/webdriverio.md | 20 ++++++------ guide/debugging.md | 2 +- guide/migration.md | 33 +++++++++++++++++--- 17 files changed, 164 insertions(+), 120 deletions(-) create mode 100644 guide/browser/preview.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 72b358a9..7cc9ba86 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -253,6 +253,11 @@ export default ({ mode }: { mode: string }) => { link: '/guide/browser/webdriverio', docFooterText: 'Configuring WebdriverIO | Browser Mode', }, + { + text: 'Configuring Preview', + link: '/guide/browser/preview', + docFooterText: 'Configuring Preview | Browser Mode', + }, ], }, { diff --git a/guide/browser/assertion-api.md b/guide/browser/assertion-api.md index b10901a5..4c8e4b44 100644 --- a/guide/browser/assertion-api.md +++ b/guide/browser/assertion-api.md @@ -7,10 +7,10 @@ title: Assertion API | Browser Mode Vitest provides a wide range of DOM assertions out of the box forked from [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library with the added support for locators and built-in retry-ability. ::: tip TypeScript Support -If you are using [TypeScript](/guide/browser/#typescript) or want to have correct type hints in `expect`, make sure you have `@vitest/browser/context` referenced somewhere. If you never imported from there, you can add a `reference` comment in any file that's covered by your `tsconfig.json`: +If you are using [TypeScript](/guide/browser/#typescript) or want to have correct type hints in `expect`, make sure you have `vitest/browser` referenced somewhere. If you never imported from there, you can add a `reference` comment in any file that's covered by your `tsconfig.json`: ```ts -/// +/// ``` ::: @@ -18,7 +18,7 @@ Tests in the browser might fail inconsistently due to their asynchronous nature. ```ts import { expect, test } from 'vitest' -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' test('error banner is rendered', async () => { triggerError() diff --git a/guide/browser/commands.md b/guide/browser/commands.md index 0b7bc125..8e370443 100644 --- a/guide/browser/commands.md +++ b/guide/browser/commands.md @@ -20,7 +20,7 @@ This API follows [`server.fs`](https://vitejs.dev/config/server-options.html#ser ::: ```ts -import { server } from '@vitest/browser/context' +import { server } from 'vitest/browser' const { readFile, writeFile, removeFile } = server.commands @@ -38,10 +38,10 @@ it('handles files', async () => { ## CDP Session -Vitest exposes access to raw Chrome Devtools Protocol via the `cdp` method exported from `@vitest/browser/context`. It is mostly useful to library authors to build tools on top of it. +Vitest exposes access to raw Chrome Devtools Protocol via the `cdp` method exported from `vitest/browser`. It is mostly useful to library authors to build tools on top of it. ```ts -import { cdp } from '@vitest/browser/context' +import { cdp } from 'vitest/browser' const input = document.createElement('input') document.body.appendChild(input) @@ -97,10 +97,10 @@ export default function BrowserCommands(): Plugin { } ``` -Then you can call it inside your test by importing it from `@vitest/browser/context`: +Then you can call it inside your test by importing it from `vitest/browser`: ```ts -import { commands } from '@vitest/browser/context' +import { commands } from 'vitest/browser' import { expect, test } from 'vitest' test('custom command works correctly', async () => { @@ -109,7 +109,7 @@ test('custom command works correctly', async () => { }) // if you are using TypeScript, you can augment the module -declare module '@vitest/browser/context' { +declare module 'vitest/browser' { interface BrowserCommands { myCustomCommand: (arg1: string, arg2: string) => Promise<{ someValue: true diff --git a/guide/browser/component-testing.md b/guide/browser/component-testing.md index 1f7d7a38..fe6e80f4 100644 --- a/guide/browser/component-testing.md +++ b/guide/browser/component-testing.md @@ -138,7 +138,7 @@ The key is using `page.elementLocator()` to bridge Testing Library's DOM output ```jsx // For Solid.js components import { render } from '@testing-library/solid' -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' test('Solid component handles user interaction', async () => { // Use Testing Library to render the component @@ -564,7 +564,7 @@ import { render } from 'vitest-browser-react' // [!code ++] ### Key Differences - Use `await expect.element()` instead of `expect()` for DOM assertions -- Use `@vitest/browser/context` for user interactions instead of `@testing-library/user-event` +- Use `vitest/browser` for user interactions instead of `@testing-library/user-event` - Browser Mode provides real browser environment for accurate testing ## Learn More diff --git a/guide/browser/config.md b/guide/browser/config.md index 8db0cb7e..c1f8c724 100644 --- a/guide/browser/config.md +++ b/guide/browser/config.md @@ -4,7 +4,7 @@ You can change the browser configuration by updating the `test.browser` field in ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ test: { @@ -47,14 +47,11 @@ Run all tests inside a browser by default. Note that `--browser` only works if y ## browser.instances - **Type:** `BrowserConfig` -- **Default:** `[{ browser: name }]` - -Defines multiple browser setups. Every config has to have at least a `browser` field. The config supports your providers configurations: +- **Default:** `[]` -- [Configuring Playwright](/guide/browser/playwright) -- [Configuring WebdriverIO](/guide/browser/webdriverio) +Defines multiple browser setups. Every config has to have at least a `browser` field. -In addition to that, you can also specify most of the [project options](/config/) (not marked with a icon) and some of the `browser` options like `browser.testerHtmlPath`. +You can specify most of the [project options](/config/) (not marked with a icon) and some of the `browser` options like `browser.testerHtmlPath`. ::: warning Every browser config inherits options from the root config: @@ -79,8 +76,6 @@ export default defineConfig({ }) ``` -During development, Vitest supports only one [non-headless](#browser-headless) configuration. You can limit the headed project yourself by specifying `headless: false` in the config, or by providing the `--browser.headless=false` flag, or by filtering projects with `--project=chromium` flag. - For more examples, refer to the ["Multiple Setups" guide](/guide/browser/multiple-setups). ::: @@ -94,8 +89,6 @@ List of available `browser` options: - [`browser.screenshotFailures`](#browser-screenshotfailures) - [`browser.provider`](#browser-provider) -By default, Vitest creates an array with a single element which uses the [`browser.name`](#browser-name) field as a `browser`. Note that this behaviour will be removed with Vitest 4. - Under the hood, Vitest transforms these instances into separate [test projects](/advanced/api/test-project) sharing a single Vite server for better caching performance. ## browser.headless @@ -134,12 +127,12 @@ Configure options for Vite server that serves code in the browser. Does not affe - **Default:** `'preview'` - **CLI:** `--browser.provider=playwright` -The return value of the provider factory. You can import the factory from `@vitest/browser/providers/` or make your own provider: +The return value of the provider factory. You can import the factory from `@vitest/browser-` or make your own provider: ```ts{8-10} -import { playwright } from '@vitest/browser/providers/playwright' -import { webdriverio } from '@vitest/browser/providers/webdriverio' -import { preview } from '@vitest/browser/providers/preview' +import { playwright } from '@vitest/browser-playwright' +import { webdriverio } from '@vitest/browser-webdriverio' +import { preview } from '@vitest/browser-preview' export default defineConfig({ test: { @@ -155,7 +148,7 @@ export default defineConfig({ To configure how provider initializes the browser, you can pass down options to the factory function: ```ts{7-13,20-26} -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ test: { @@ -294,7 +287,7 @@ export interface BrowserScript { - **Type:** `Record` - **Default:** `{ readFile, writeFile, ... }` -Custom [commands](/guide/browser/commands) that can be imported during browser tests from `@vitest/browser/commands`. +Custom [commands](/guide/browser/commands) that can be imported during browser tests from `vitest/browser`. ## browser.connectTimeout diff --git a/guide/browser/context.md b/guide/browser/context.md index d8710a0c..8bedda3b 100644 --- a/guide/browser/context.md +++ b/guide/browser/context.md @@ -4,7 +4,7 @@ title: Context API | Browser Mode # Context API -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. +Vitest exposes a context module via `vitest/browser` entry point. As of 2.0, it exposes a small set of utilities that might be useful to you in tests. ## `userEvent` diff --git a/guide/browser/index.md b/guide/browser/index.md index adb54322..29e19a2c 100644 --- a/guide/browser/index.md +++ b/guide/browser/index.md @@ -99,7 +99,7 @@ To activate browser mode in your Vitest configuration, set the `browser.enabled` ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ test: { @@ -127,7 +127,7 @@ If you have not used Vite before, make sure you have your framework's plugin ins ```ts [react] import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ plugins: [react()], @@ -144,7 +144,7 @@ export default defineConfig({ ``` ```ts [vue] import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' import vue from '@vitejs/plugin-vue' export default defineConfig({ @@ -163,7 +163,7 @@ export default defineConfig({ ```ts [svelte] import { defineConfig } from 'vitest/config' import { svelte } from '@sveltejs/vite-plugin-svelte' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ plugins: [svelte()], @@ -181,7 +181,7 @@ export default defineConfig({ ```ts [solid] import { defineConfig } from 'vitest/config' import solidPlugin from 'vite-plugin-solid' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ plugins: [solidPlugin()], @@ -199,7 +199,7 @@ export default defineConfig({ ```ts [marko] import { defineConfig } from 'vitest/config' import marko from '@marko/vite' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ plugins: [marko()], @@ -217,7 +217,7 @@ export default defineConfig({ ```ts [qwik] import { defineConfig } from 'vitest/config' import { qwikVite } from '@builder.io/qwik/optimizer' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' // optional, run the tests in SSR mode import { testSSR } from 'vitest-browser-qwik/ssr-plugin' @@ -241,7 +241,7 @@ If you need to run some tests using Node-based runner, you can define a [`projec ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ test: { @@ -338,7 +338,7 @@ Here's an example configuration enabling headless mode: ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser/providers/playwright' +import { playwright } from '@vitest/browser-playwright' export default defineConfig({ test: { @@ -369,7 +369,7 @@ By default, you don't need any external packages to work with the Browser Mode: ```js [example.test.js] import { expect, test } from 'vitest' -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' import { render } from './my-render-function.js' test('properly handles form inputs', async () => { @@ -407,15 +407,15 @@ Besides rendering components and locating elements, you will also need to make a ```ts import { expect } from 'vitest' -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' // element is rendered correctly await expect.element(page.getByText('Hello World')).toBeInTheDocument() ``` -Vitest exposes a [Context API](/guide/browser/context) with a small set of utilities that might be useful to you in tests. For example, if you need to make an interaction, like clicking an element or typing text into an input, you can use `userEvent` from `@vitest/browser/context`. Read more at the [Interactivity API](/guide/browser/interactivity-api). +Vitest exposes a [Context API](/guide/browser/context) with a small set of utilities that might be useful to you in tests. For example, if you need to make an interaction, like clicking an element or typing text into an input, you can use `userEvent` from `vitest/browser`. Read more at the [Interactivity API](/guide/browser/interactivity-api). ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' await userEvent.fill(page.getByLabelText(/username/i), 'Alice') // or just locator.fill await page.getByLabelText(/username/i).fill('Alice') @@ -532,7 +532,7 @@ For unsupported frameworks, we recommend using `testing-library` packages: You can also see more examples in [`browser-examples`](https://github.com/vitest-tests/browser-examples) repository. ::: warning -`testing-library` provides a package `@testing-library/user-event`. We do not recommend using it directly because it simulates events instead of actually triggering them - instead, use [`userEvent`](/guide/browser/interactivity-api) imported from `@vitest/browser/context` that uses Chrome DevTools Protocol or Webdriver (depending on the provider) under the hood. +`testing-library` provides a package `@testing-library/user-event`. We do not recommend using it directly because it simulates events instead of actually triggering them - instead, use [`userEvent`](/guide/browser/interactivity-api) imported from `vitest/browser` that uses Chrome DevTools Protocol or Webdriver (depending on the provider) under the hood. ::: ::: code-group diff --git a/guide/browser/interactivity-api.md b/guide/browser/interactivity-api.md index b9a6c2de..301ade07 100644 --- a/guide/browser/interactivity-api.md +++ b/guide/browser/interactivity-api.md @@ -7,7 +7,7 @@ title: Interactivity API | Browser Mode Vitest implements a subset of [`@testing-library/user-event`](https://testing-library.com/docs/user-event/intro) APIs using [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) or [webdriver](https://www.w3.org/TR/webdriver/) instead of faking events which makes the browser behaviour more reliable and consistent with how users interact with a page. ```ts -import { userEvent } from '@vitest/browser/context' +import { userEvent } from 'vitest/browser' await userEvent.click(document.querySelector('.button')) ``` @@ -23,10 +23,10 @@ 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: +Unlike `@testing-library/user-event`, the default `userEvent` instance from `vitest/browser` is created once, not every time its methods are called! You can see the difference in how it works in this snippet: ```ts -import { userEvent as vitestUserEvent } from '@vitest/browser/context' +import { userEvent as vitestUserEvent } from 'vitest/browser' import { userEvent as originalUserEvent } from '@testing-library/user-event' await vitestUserEvent.keyboard('{Shift}') // press shift without releasing @@ -51,7 +51,7 @@ function click( Click on an element. Inherits provider's options. Please refer to your provider's documentation for detailed explanation about how this method works. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('clicks on an element', async () => { const logo = page.getByRole('img', { name: /logo/ }) @@ -82,7 +82,7 @@ Triggers a double click event on an element. Please refer to your provider's documentation for detailed explanation about how this method works. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('triggers a double click on an element', async () => { const logo = page.getByRole('img', { name: /logo/ }) @@ -113,7 +113,7 @@ Triggers a triple click event on an element. Since there is no `tripleclick` in Please refer to your provider's documentation for detailed explanation about how this method works. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('triggers a triple click on an element', async () => { const logo = page.getByRole('img', { name: /logo/ }) @@ -150,7 +150,7 @@ function fill( Set a value to the `input`/`textarea`/`contenteditable` field. This will remove any existing text in the input before setting the new value. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('update input', async () => { const input = page.getByRole('input') @@ -189,7 +189,7 @@ The `userEvent.keyboard` allows you to trigger keyboard strokes. If any input ha This API supports [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard). ```ts -import { userEvent } from '@vitest/browser/context' +import { userEvent } from 'vitest/browser' test('trigger keystrokes', async () => { await userEvent.keyboard('foo') // translates to: f, o, o @@ -215,7 +215,7 @@ function tab(options?: UserEventTabOptions): Promise Sends a `Tab` key event. This is a shorthand for `userEvent.keyboard('{tab}')`. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('tab works', async () => { const [input1, input2] = page.getByRole('input').elements() @@ -259,7 +259,7 @@ This function allows you to type characters into an `input`/`textarea`/`contente If you just need to press characters without an input, use [`userEvent.keyboard`](#userevent-keyboard) API. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('update input', async () => { const input = page.getByRole('input') @@ -289,7 +289,7 @@ function clear(element: Element | Locator, options?: UserEventClearOptions): Pro This method clears the input element content. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('clears input', async () => { const input = page.getByRole('input') @@ -336,7 +336,7 @@ Unlike `@testing-library`, Vitest doesn't support [listbox](https://developer.mo ::: ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('clears input', async () => { const select = page.getByRole('select') @@ -386,7 +386,7 @@ If you are using `playwright` provider, the cursor moves to "some" visible point ::: ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('hovers logo element', async () => { const logo = page.getByRole('img', { name: /logo/ }) @@ -419,7 +419,7 @@ By default, the cursor position is in "some" visible place (in `playwright` prov ::: ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('unhover logo element', async () => { const logo = page.getByRole('img', { name: /logo/ }) @@ -449,7 +449,7 @@ function upload( Change a file input element to have the specified files. ```ts -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('can upload a file', async () => { const input = page.getByRole('button', { name: /Upload files/ }) @@ -488,7 +488,7 @@ function dragAndDrop( 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 { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('drag and drop works', async () => { const source = page.getByRole('img', { name: /logo/ }) @@ -520,7 +520,7 @@ function copy(): Promise Copy the selected text to the clipboard. ```js -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('copy and paste', async () => { // write to 'source' @@ -553,7 +553,7 @@ function cut(): Promise Cut the selected text to the clipboard. ```js -import { page, userEvent } from '@vitest/browser/context' +import { page, userEvent } from 'vitest/browser' test('copy and paste', async () => { // write to 'source' diff --git a/guide/browser/locators.md b/guide/browser/locators.md index e442e9dc..f3d93f34 100644 --- a/guide/browser/locators.md +++ b/guide/browser/locators.md @@ -609,7 +609,7 @@ function click(options?: UserEventClickOptions): Promise Click on an element. You can use the options to set the cursor position. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).click() ``` @@ -625,7 +625,7 @@ function dblClick(options?: UserEventDoubleClickOptions): Promise Triggers a double click event on an element. You can use the options to set the cursor position. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).dblClick() ``` @@ -641,7 +641,7 @@ function tripleClick(options?: UserEventTripleClickOptions): Promise 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. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).tripleClick() ``` @@ -657,7 +657,7 @@ function clear(options?: UserEventClearOptions): Promise Clears the input element content. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' await page.getByRole('textbox', { name: 'Full Name' }).clear() ``` @@ -673,7 +673,7 @@ function hover(options?: UserEventHoverOptions): Promise Moves the cursor position to the selected element. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).hover() ``` @@ -689,7 +689,7 @@ function unhover(options?: UserEventHoverOptions): Promise This works the same as [`locator.hover`](#hover), but moves the cursor to the `document.body` element instead. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' await page.getByRole('img', { name: 'Rose' }).unhover() ``` @@ -705,7 +705,7 @@ function fill(text: string, options?: UserEventFillOptions): Promise Sets the value of the current `input`, `textarea` or `contenteditable` element. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean') ``` @@ -724,7 +724,7 @@ function dropTo( Drags the current element to the target location. ```ts -import { page } from '@vitest/browser/context' +import { page } from 'vitest/browser' const paris = page.getByText('Paris') const france = page.getByText('France') @@ -752,7 +752,7 @@ function selectOptions( Choose one or more values from a `