···77Vitest 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.
8899::: tip TypeScript Support
1010-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`:
1010+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`:
11111212```ts
1313-/// <reference types="@vitest/browser/context" />
1313+/// <reference types="vitest/browser" />
1414```
1515:::
1616···18181919```ts
2020import { expect, test } from 'vitest'
2121-import { page } from '@vitest/browser/context'
2121+import { page } from 'vitest/browser'
22222323test('error banner is rendered', async () => {
2424 triggerError()
+6-6
docs/guide/browser/commands.md
···2020:::
21212222```ts
2323-import { server } from '@vitest/browser/context'
2323+import { server } from 'vitest/browser'
24242525const { readFile, writeFile, removeFile } = server.commands
2626···38383939## CDP Session
40404141-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.
4141+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.
42424343```ts
4444-import { cdp } from '@vitest/browser/context'
4444+import { cdp } from 'vitest/browser'
45454646const input = document.createElement('input')
4747document.body.appendChild(input)
···9797}
9898```
9999100100-Then you can call it inside your test by importing it from `@vitest/browser/context`:
100100+Then you can call it inside your test by importing it from `vitest/browser`:
101101102102```ts
103103-import { commands } from '@vitest/browser/context'
103103+import { commands } from 'vitest/browser'
104104import { expect, test } from 'vitest'
105105106106test('custom command works correctly', async () => {
···109109})
110110111111// if you are using TypeScript, you can augment the module
112112-declare module '@vitest/browser/context' {
112112+declare module 'vitest/browser' {
113113 interface BrowserCommands {
114114 myCustomCommand: (arg1: string, arg2: string) => Promise<{
115115 someValue: true
+2-2
docs/guide/browser/component-testing.md
···138138```jsx
139139// For Solid.js components
140140import { render } from '@testing-library/solid'
141141-import { page } from '@vitest/browser/context'
141141+import { page } from 'vitest/browser'
142142143143test('Solid component handles user interaction', async () => {
144144 // Use Testing Library to render the component
···564564### Key Differences
565565566566- Use `await expect.element()` instead of `expect()` for DOM assertions
567567-- Use `@vitest/browser/context` for user interactions instead of `@testing-library/user-event`
567567+- Use `vitest/browser` for user interactions instead of `@testing-library/user-event`
568568- Browser Mode provides real browser environment for accurate testing
569569570570## Learn More
+10-17
docs/guide/browser/config.md
···4455```ts [vitest.config.ts]
66import { defineConfig } from 'vitest/config'
77-import { playwright } from '@vitest/browser/providers/playwright'
77+import { playwright } from '@vitest/browser-playwright'
8899export default defineConfig({
1010 test: {
···4747## browser.instances
48484949- **Type:** `BrowserConfig`
5050-- **Default:** `[{ browser: name }]`
5050+- **Default:** `[]`
51515252-Defines multiple browser setups. Every config has to have at least a `browser` field. The config supports your providers configurations:
5353-5454-- [Configuring Playwright](/guide/browser/playwright)
5555-- [Configuring WebdriverIO](/guide/browser/webdriverio)
5252+Defines multiple browser setups. Every config has to have at least a `browser` field.
56535757-In addition to that, you can also specify most of the [project options](/config/) (not marked with a <NonProjectOption /> icon) and some of the `browser` options like `browser.testerHtmlPath`.
5454+You can specify most of the [project options](/config/) (not marked with a <NonProjectOption /> icon) and some of the `browser` options like `browser.testerHtmlPath`.
58555956::: warning
6057Every browser config inherits options from the root config:
···7976})
8077```
81788282-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.
8383-8479For more examples, refer to the ["Multiple Setups" guide](/guide/browser/multiple-setups).
8580:::
8681···9489- [`browser.screenshotFailures`](#browser-screenshotfailures)
9590- [`browser.provider`](#browser-provider)
96919797-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.
9898-9992Under the hood, Vitest transforms these instances into separate [test projects](/advanced/api/test-project) sharing a single Vite server for better caching performance.
1009310194## browser.headless
···134127- **Default:** `'preview'`
135128- **CLI:** `--browser.provider=playwright`
136129137137-The return value of the provider factory. You can import the factory from `@vitest/browser/providers/<provider-name>` or make your own provider:
130130+The return value of the provider factory. You can import the factory from `@vitest/browser-<provider-name>` or make your own provider:
138131139132```ts{8-10}
140140-import { playwright } from '@vitest/browser/providers/playwright'
141141-import { webdriverio } from '@vitest/browser/providers/webdriverio'
142142-import { preview } from '@vitest/browser/providers/preview'
133133+import { playwright } from '@vitest/browser-playwright'
134134+import { webdriverio } from '@vitest/browser-webdriverio'
135135+import { preview } from '@vitest/browser-preview'
143136144137export default defineConfig({
145138 test: {
···155148To configure how provider initializes the browser, you can pass down options to the factory function:
156149157150```ts{7-13,20-26}
158158-import { playwright } from '@vitest/browser/providers/playwright'
151151+import { playwright } from '@vitest/browser-playwright'
159152160153export default defineConfig({
161154 test: {
···294287- **Type:** `Record<string, BrowserCommand>`
295288- **Default:** `{ readFile, writeFile, ... }`
296289297297-Custom [commands](/guide/browser/commands) that can be imported during browser tests from `@vitest/browser/commands`.
290290+Custom [commands](/guide/browser/commands) that can be imported during browser tests from `vitest/browser`.
298291299292## browser.connectTimeout
300293
+1-1
docs/guide/browser/context.md
···4455# Context API
6677-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.
77+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.
8899## `userEvent`
1010
+14-14
docs/guide/browser/index.md
···9999100100```ts [vitest.config.ts]
101101import { defineConfig } from 'vitest/config'
102102-import { playwright } from '@vitest/browser/providers/playwright'
102102+import { playwright } from '@vitest/browser-playwright'
103103104104export default defineConfig({
105105 test: {
···127127```ts [react]
128128import { defineConfig } from 'vitest/config'
129129import react from '@vitejs/plugin-react'
130130-import { playwright } from '@vitest/browser/providers/playwright'
130130+import { playwright } from '@vitest/browser-playwright'
131131132132export default defineConfig({
133133 plugins: [react()],
···144144```
145145```ts [vue]
146146import { defineConfig } from 'vitest/config'
147147-import { playwright } from '@vitest/browser/providers/playwright'
147147+import { playwright } from '@vitest/browser-playwright'
148148import vue from '@vitejs/plugin-vue'
149149150150export default defineConfig({
···163163```ts [svelte]
164164import { defineConfig } from 'vitest/config'
165165import { svelte } from '@sveltejs/vite-plugin-svelte'
166166-import { playwright } from '@vitest/browser/providers/playwright'
166166+import { playwright } from '@vitest/browser-playwright'
167167168168export default defineConfig({
169169 plugins: [svelte()],
···181181```ts [solid]
182182import { defineConfig } from 'vitest/config'
183183import solidPlugin from 'vite-plugin-solid'
184184-import { playwright } from '@vitest/browser/providers/playwright'
184184+import { playwright } from '@vitest/browser-playwright'
185185186186export default defineConfig({
187187 plugins: [solidPlugin()],
···199199```ts [marko]
200200import { defineConfig } from 'vitest/config'
201201import marko from '@marko/vite'
202202-import { playwright } from '@vitest/browser/providers/playwright'
202202+import { playwright } from '@vitest/browser-playwright'
203203204204export default defineConfig({
205205 plugins: [marko()],
···217217```ts [qwik]
218218import { defineConfig } from 'vitest/config'
219219import { qwikVite } from '@builder.io/qwik/optimizer'
220220-import { playwright } from '@vitest/browser/providers/playwright'
220220+import { playwright } from '@vitest/browser-playwright'
221221222222// optional, run the tests in SSR mode
223223import { testSSR } from 'vitest-browser-qwik/ssr-plugin'
···241241242242```ts [vitest.config.ts]
243243import { defineConfig } from 'vitest/config'
244244-import { playwright } from '@vitest/browser/providers/playwright'
244244+import { playwright } from '@vitest/browser-playwright'
245245246246export default defineConfig({
247247 test: {
···338338339339```ts [vitest.config.ts]
340340import { defineConfig } from 'vitest/config'
341341-import { playwright } from '@vitest/browser/providers/playwright'
341341+import { playwright } from '@vitest/browser-playwright'
342342343343export default defineConfig({
344344 test: {
···369369370370```js [example.test.js]
371371import { expect, test } from 'vitest'
372372-import { page } from '@vitest/browser/context'
372372+import { page } from 'vitest/browser'
373373import { render } from './my-render-function.js'
374374375375test('properly handles form inputs', async () => {
···407407408408```ts
409409import { expect } from 'vitest'
410410-import { page } from '@vitest/browser/context'
410410+import { page } from 'vitest/browser'
411411// element is rendered correctly
412412await expect.element(page.getByText('Hello World')).toBeInTheDocument()
413413```
414414415415-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).
415415+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).
416416417417```ts
418418-import { page, userEvent } from '@vitest/browser/context'
418418+import { page, userEvent } from 'vitest/browser'
419419await userEvent.fill(page.getByLabelText(/username/i), 'Alice')
420420// or just locator.fill
421421await page.getByLabelText(/username/i).fill('Alice')
···532532You can also see more examples in [`browser-examples`](https://github.com/vitest-tests/browser-examples) repository.
533533534534::: warning
535535-`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.
535535+`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.
536536:::
537537538538::: code-group
+18-18
docs/guide/browser/interactivity-api.md
···77Vitest 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.
8899```ts
1010-import { userEvent } from '@vitest/browser/context'
1010+import { userEvent } from 'vitest/browser'
11111212await userEvent.click(document.querySelector('.button'))
1313```
···2323Creates a new user event instance. This is useful if you need to keep the state of keyboard to press and release buttons correctly.
24242525::: warning
2626-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:
2626+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:
27272828```ts
2929-import { userEvent as vitestUserEvent } from '@vitest/browser/context'
2929+import { userEvent as vitestUserEvent } from 'vitest/browser'
3030import { userEvent as originalUserEvent } from '@testing-library/user-event'
31313232await vitestUserEvent.keyboard('{Shift}') // press shift without releasing
···5151Click on an element. Inherits provider's options. Please refer to your provider's documentation for detailed explanation about how this method works.
52525353```ts
5454-import { page, userEvent } from '@vitest/browser/context'
5454+import { page, userEvent } from 'vitest/browser'
55555656test('clicks on an element', async () => {
5757 const logo = page.getByRole('img', { name: /logo/ })
···8282Please refer to your provider's documentation for detailed explanation about how this method works.
83838484```ts
8585-import { page, userEvent } from '@vitest/browser/context'
8585+import { page, userEvent } from 'vitest/browser'
86868787test('triggers a double click on an element', async () => {
8888 const logo = page.getByRole('img', { name: /logo/ })
···113113Please refer to your provider's documentation for detailed explanation about how this method works.
114114115115```ts
116116-import { page, userEvent } from '@vitest/browser/context'
116116+import { page, userEvent } from 'vitest/browser'
117117118118test('triggers a triple click on an element', async () => {
119119 const logo = page.getByRole('img', { name: /logo/ })
···150150Set a value to the `input`/`textarea`/`contenteditable` field. This will remove any existing text in the input before setting the new value.
151151152152```ts
153153-import { page, userEvent } from '@vitest/browser/context'
153153+import { page, userEvent } from 'vitest/browser'
154154155155test('update input', async () => {
156156 const input = page.getByRole('input')
···189189This API supports [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard).
190190191191```ts
192192-import { userEvent } from '@vitest/browser/context'
192192+import { userEvent } from 'vitest/browser'
193193194194test('trigger keystrokes', async () => {
195195 await userEvent.keyboard('foo') // translates to: f, o, o
···215215Sends a `Tab` key event. This is a shorthand for `userEvent.keyboard('{tab}')`.
216216217217```ts
218218-import { page, userEvent } from '@vitest/browser/context'
218218+import { page, userEvent } from 'vitest/browser'
219219220220test('tab works', async () => {
221221 const [input1, input2] = page.getByRole('input').elements()
···259259If you just need to press characters without an input, use [`userEvent.keyboard`](#userevent-keyboard) API.
260260261261```ts
262262-import { page, userEvent } from '@vitest/browser/context'
262262+import { page, userEvent } from 'vitest/browser'
263263264264test('update input', async () => {
265265 const input = page.getByRole('input')
···289289This method clears the input element content.
290290291291```ts
292292-import { page, userEvent } from '@vitest/browser/context'
292292+import { page, userEvent } from 'vitest/browser'
293293294294test('clears input', async () => {
295295 const input = page.getByRole('input')
···336336:::
337337338338```ts
339339-import { page, userEvent } from '@vitest/browser/context'
339339+import { page, userEvent } from 'vitest/browser'
340340341341test('clears input', async () => {
342342 const select = page.getByRole('select')
···386386:::
387387388388```ts
389389-import { page, userEvent } from '@vitest/browser/context'
389389+import { page, userEvent } from 'vitest/browser'
390390391391test('hovers logo element', async () => {
392392 const logo = page.getByRole('img', { name: /logo/ })
···419419:::
420420421421```ts
422422-import { page, userEvent } from '@vitest/browser/context'
422422+import { page, userEvent } from 'vitest/browser'
423423424424test('unhover logo element', async () => {
425425 const logo = page.getByRole('img', { name: /logo/ })
···449449Change a file input element to have the specified files.
450450451451```ts
452452-import { page, userEvent } from '@vitest/browser/context'
452452+import { page, userEvent } from 'vitest/browser'
453453454454test('can upload a file', async () => {
455455 const input = page.getByRole('button', { name: /Upload files/ })
···488488Drags 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`.
489489490490```ts
491491-import { page, userEvent } from '@vitest/browser/context'
491491+import { page, userEvent } from 'vitest/browser'
492492493493test('drag and drop works', async () => {
494494 const source = page.getByRole('img', { name: /logo/ })
···520520Copy the selected text to the clipboard.
521521522522```js
523523-import { page, userEvent } from '@vitest/browser/context'
523523+import { page, userEvent } from 'vitest/browser'
524524525525test('copy and paste', async () => {
526526 // write to 'source'
···553553Cut the selected text to the clipboard.
554554555555```js
556556-import { page, userEvent } from '@vitest/browser/context'
556556+import { page, userEvent } from 'vitest/browser'
557557558558test('copy and paste', async () => {
559559 // write to 'source'
+13-13
docs/guide/browser/locators.md
···609609Click on an element. You can use the options to set the cursor position.
610610611611```ts
612612-import { page } from '@vitest/browser/context'
612612+import { page } from 'vitest/browser'
613613614614await page.getByRole('img', { name: 'Rose' }).click()
615615```
···625625Triggers a double click event on an element. You can use the options to set the cursor position.
626626627627```ts
628628-import { page } from '@vitest/browser/context'
628628+import { page } from 'vitest/browser'
629629630630await page.getByRole('img', { name: 'Rose' }).dblClick()
631631```
···641641Triggers 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.
642642643643```ts
644644-import { page } from '@vitest/browser/context'
644644+import { page } from 'vitest/browser'
645645646646await page.getByRole('img', { name: 'Rose' }).tripleClick()
647647```
···657657Clears the input element content.
658658659659```ts
660660-import { page } from '@vitest/browser/context'
660660+import { page } from 'vitest/browser'
661661662662await page.getByRole('textbox', { name: 'Full Name' }).clear()
663663```
···673673Moves the cursor position to the selected element.
674674675675```ts
676676-import { page } from '@vitest/browser/context'
676676+import { page } from 'vitest/browser'
677677678678await page.getByRole('img', { name: 'Rose' }).hover()
679679```
···689689This works the same as [`locator.hover`](#hover), but moves the cursor to the `document.body` element instead.
690690691691```ts
692692-import { page } from '@vitest/browser/context'
692692+import { page } from 'vitest/browser'
693693694694await page.getByRole('img', { name: 'Rose' }).unhover()
695695```
···705705Sets the value of the current `input`, `textarea` or `contenteditable` element.
706706707707```ts
708708-import { page } from '@vitest/browser/context'
708708+import { page } from 'vitest/browser'
709709710710await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean')
711711```
···724724Drags the current element to the target location.
725725726726```ts
727727-import { page } from '@vitest/browser/context'
727727+import { page } from 'vitest/browser'
728728729729const paris = page.getByText('Paris')
730730const france = page.getByText('France')
···752752Choose one or more values from a `<select>` element.
753753754754```ts
755755-import { page } from '@vitest/browser/context'
755755+import { page } from 'vitest/browser'
756756757757const languages = page.getByRole('select', { name: 'Languages' })
758758···784784If you also need the content of the screenshot, you can specify `base64: true` to return it alongside the filepath where the screenshot is saved.
785785786786```ts
787787-import { page } from '@vitest/browser/context'
787787+import { page } from 'vitest/browser'
788788789789const button = page.getByRole('button', { name: 'Click Me!' })
790790···946946947947```ts [example.test.ts]
948948import { test } from 'vitest'
949949-import { commands, page } from '@vitest/browser/context'
949949+import { commands, page } from 'vitest/browser'
950950951951test('works correctly', async () => {
952952 await commands.test(page.getByText('Hello').selector) // ✅
···988988:::
989989990990```ts
991991-import { locators } from '@vitest/browser/context'
991991+import { locators } from 'vitest/browser'
992992993993locators.extend({
994994 getByArticleTitle(title) {
···1010101010111011// if you are using typescript, you can extend LocatorSelectors interface
10121012// to have the autocompletion in locators.extend, page.* and locator.* methods
10131013-declare module '@vitest/browser/context' {
10131013+declare module 'vitest/browser' {
10141014 interface LocatorSelectors {
10151015 // if the custom method returns a string, it will be converted into a locator
10161016 // if it returns anything else, then it will be returned as usual
+5-22
docs/guide/browser/multiple-setups.md
···11# Multiple Setups
2233-Since Vitest 3, you can specify several different browser setups using the new [`browser.instances`](/guide/browser/config#browser-instances) option.
33+You can specify several different browser setups using the [`browser.instances`](/guide/browser/config#browser-instances) option.
4455The main advantage of using the `browser.instances` over the [test projects](/guide/projects) is improved caching. Every project will use the same Vite server meaning the file transform and [dependency pre-bundling](https://vite.dev/guide/dep-pre-bundling.html) has to happen only once.
66···10101111```ts [vitest.config.ts]
1212import { defineConfig } from 'vitest/config'
1313-import { playwright } from '@vitest/browser/providers/playwright'
1313+import { playwright } from '@vitest/browser-playwright'
14141515export default defineConfig({
1616 test: {
···3535::: code-group
3636```ts [vitest.config.ts]
3737import { defineConfig } from 'vitest/config'
3838-import { playwright } from '@vitest/browser/providers/playwright'
3838+import { playwright } from '@vitest/browser-playwright'
39394040export default defineConfig({
4141 test: {
···5050 setupFiles: ['./ratio-setup.ts'],
5151 provide: {
5252 ratio: 1,
5353- }
5353+ },
5454 },
5555 {
5656 browser: 'chromium',
5757 name: 'chromium-2',
5858 provide: {
5959 ratio: 2,
6060- }
6060+ },
6161 },
6262 ],
6363 },
···119119})
120120```
121121:::
122122-123123-::: warning
124124-Vitest cannot run multiple instances that have `headless` mode set to `false` (the default behaviour). During development, you can select what project to run in your terminal:
125125-126126-```shell
127127-? Found multiple projects that run browser tests in headed mode: "chromium", "firefox".
128128-Vitest cannot run multiple headed browsers at the same time. Select a single project
129129-to run or cancel and run tests with "headless: true" option. Note that you can also
130130-start tests with --browser=name or --project=name flag. › - Use arrow-keys. Return to submit.
131131-❯ chromium
132132- firefox
133133-```
134134-135135-If you have several non-headless projects in CI (i.e. the `headless: false` is set manually in the config and not overridden in CI env), Vitest will fail the run and won't start any tests.
136136-137137-The ability to run tests in headless mode is not affected by this. You can still run all instances in parallel as long as they don't have `headless: false`.
138138-:::
+9-5
docs/guide/browser/playwright.md
···11# Configuring Playwright
2233-To run tests using playwright, you need to specify it in the `test.browser.provider` property in your config:
33+To run tests using playwright, you need to install the [`@vitest/browser-playwright`](https://www.npmjs.com/package/@vitest/browser-playwright) npm package and specify its `playwright` export in the `test.browser.provider` property of your config:
4455```ts [vitest.config.js]
66-import { playwright } from '@vitest/browser/providers/playwright'
66+import { playwright } from '@vitest/browser-playwright'
77import { defineConfig } from 'vitest/config'
8899export default defineConfig({
···1616})
1717```
18181919-Vitest opens a single page to run all tests in the same file. You can configure the `launch`, `connect` and `context` when calling `playwright` at the top level or inside instances:
1919+You can configure the [`launchOptions`](https://playwright.dev/docs/api/class-browsertype#browser-type-launch), [`connectOptions`](https://playwright.dev/docs/api/class-browsertype#browser-type-connect) and [`contextOptions`](https://playwright.dev/docs/api/class-browser#browser-new-context) when calling `playwright` at the top level or inside instances:
20202121```ts{7-14,21-26} [vitest.config.js]
2222-import { playwright } from '@vitest/browser/providers/playwright'
2222+import { playwright } from '@vitest/browser-playwright'
2323import { defineConfig } from 'vitest/config'
24242525export default defineConfig({
···5252 },
5353})
5454```
5555+5656+::: warning
5757+Unlike Playwright test runner, Vitest opens a _single_ page to run all tests that are defined in the same file. This means that isolation is restricted to a single test file, not to every individual test.
5858+:::
55595660## launchOptions
5761···9498You can also configure the action timeout per-action:
959996100```ts
9797-import { page, userEvent } from '@vitest/browser/context'
101101+import { page, userEvent } from 'vitest/browser'
9810299103await userEvent.click(page.getByRole('button'), {
100104 timeout: 1_000,
+32
docs/guide/browser/preview.md
···11+# Configuring Preview
22+33+::: warning
44+The `preview` provider's main functionality is to show tests in a real browser environment. However, it does not support advanced browser automation features like multiple browser instances or headless mode. For more complex scenarios, consider using [Playwright](/guide/browser/playwright) or [WebdriverIO](/guide/browser/webdriverio).
55+:::
66+77+To see your tests running in a real browser, you need to install the [`@vitest/browser-preview`](https://www.npmjs.com/package/@vitest/browser-preview) npm package and specify its `preview` export in the `test.browser.provider` property of your config:
88+99+```ts [vitest.config.js]
1010+import { preview } from '@vitest/browser-preview'
1111+import { defineConfig } from 'vitest/config'
1212+1313+export default defineConfig({
1414+ test: {
1515+ browser: {
1616+ provider: preview(),
1717+ instances: [{ browser: 'chromium' }]
1818+ },
1919+ },
2020+})
2121+```
2222+2323+This will open a new browser window using your default browser to run the tests. You can configure which browser to use by setting the `browser` property in the `instances` array. Vitest will try to open that browser automatically, but it might not work in some environments. In that case, you can manually open the provided URL in your desired browser.
2424+2525+## Differences with Other Providers
2626+2727+The preview provider has some limitations compared to other providers like [Playwright](/guide/browser/playwright) or [WebdriverIO](/guide/browser/webdriverio):
2828+2929+- It does not support headless mode; the browser window will always be visible.
3030+- It does not support multiple instances of the same browser; each instance must use a different browser.
3131+- It does not support advanced browser capabilities or options; you can only specify the browser name.
3232+- It does not support CDP (Chrome DevTools Protocol) commands or other low-level browser interactions. Unlike Playwright or WebdriverIO, the [`userEvent`](/guide/browser/interactivity-api) API is just re-exported from [`@testing-library/user-event`](https://www.npmjs.com/package/@testing-library/user-event) and does not have any special integration with the browser.
+2-2
docs/guide/browser/trace-viewer.md
···99::: code-group
1010```ts [vitest.config.js]
1111import { defineConfig } from 'vitest/config'
1212-import { playwright } from '@vitest/browser/providers/playwright'
1212+import { playwright } from '@vitest/browser-playwright'
13131414export default defineConfig({
1515 test: {
···39394040```ts [vitest.config.js]
4141import { defineConfig } from 'vitest/config'
4242-import { playwright } from '@vitest/browser/providers/playwright'
4242+import { playwright } from '@vitest/browser-playwright'
43434444export default defineConfig({
4545 test: {
+3-3
docs/guide/browser/visual-regression-testing.md
···54545555```ts
5656import { expect, test } from 'vitest'
5757-import { page } from '@vitest/browser/context'
5757+import { page } from 'vitest/browser'
58585959test('hero section looks correct', async () => {
6060 // ...the rest of the test
···239239```
240240241241```ts [vitest.config.ts]
242242-import { playwright } from '@vitest/browser/providers/playwright'
242242+import { playwright } from '@vitest/browser-playwright'
243243import { defineConfig } from 'vitest/config'
244244245245export default defineConfig({
···592592```ts [vitest.config.ts]
593593import { env } from 'node:process'
594594import { defineConfig } from 'vitest/config'
595595-import { playwright } from '@vitest/browser/providers/playwright'
595595+import { playwright } from '@vitest/browser-playwright'
596596597597export default defineConfig({
598598 // ...global Vite config
+11-9
docs/guide/browser/webdriverio.md
···44If you do not already use WebdriverIO in your project, we recommend starting with [Playwright](/guide/browser/playwright) as it is easier to configure and has more flexible API.
55:::
6677-To run tests using WebdriverIO, you need to specify it in the `test.browser.provider` property in your config:
77+To run tests using WebdriverIO, you need to install the [`@vitest/browser-webdriverio`](https://www.npmjs.com/package/@vitest/browser-webdriverio) npm package and specify its `webdriverio` export in the `test.browser.provider` property of your config:
8899```ts [vitest.config.js]
1010-import { webdriverio } from '@vitest/browser/providers/webdriverio'
1010+import { webdriverio } from '@vitest/browser-webdriverio'
1111import { defineConfig } from 'vitest/config'
12121313export default defineConfig({
···2020})
2121```
22222323-Vitest opens a single page to run all tests in the same file. You can configure all the parameters that [`remote`](https://webdriver.io/docs/api/modules/#remoteoptions-modifier) function accepts:
2323+You can configure all the parameters that [`remote`](https://webdriver.io/docs/api/modules/#remoteoptions-modifier) function accepts:
24242525-```ts{8-12,19-23} [vitest.config.js]
2626-import { webdriverio } from '@vitest/browser/providers/webdriverio'
2525+```ts{8-12,19-25} [vitest.config.js]
2626+import { webdriverio } from '@vitest/browser-webdriverio'
2727import { defineConfig } from 'vitest/config'
28282929export default defineConfig({
···4242 // overriding options only for a single instance
4343 // this will NOT merge options with the parent one
4444 provider: webdriverio({
4545- 'moz:firefoxOptions': {
4646- args: ['--disable-gpu'],
4545+ capabilities: {
4646+ 'moz:firefoxOptions': {
4747+ args: ['--disable-gpu'],
4848+ },
4749 },
4850 })
4949- }
5151+ },
5052 ],
5153 },
5254 },
···5860::: tip
5961Most useful options are located on `capabilities` object. WebdriverIO allows nested capabilities, but Vitest will ignore those options because we rely on a different mechanism to spawn several browsers.
60626161-Note that Vitest will ignore `capabilities.browserName`. Use [`test.browser.instances.browser`](/guide/browser/config#browser-capabilities-name) instead.
6363+Note that Vitest will ignore `capabilities.browserName` — use [`test.browser.instances.browser`](/guide/browser/config#browser-capabilities-name) instead.
6264:::
+1-1
docs/guide/debugging.md
···5252```
5353```ts [vitest.config.js]
5454import { defineConfig } from 'vitest/config'
5555-import { playwright } from '@vitest/browser/providers/playwright'
5555+import { playwright } from '@vitest/browser-playwright'
56565757export default defineConfig({
5858 test: {
+29-4
docs/guide/migration.md
···215215```
216216:::
217217218218-### Browser Provider Accepts an Object
218218+### Browser Provider Rework
219219220220-In Vitest 4.0, the browser provider now accepts an object instead of a string (`'playwright'`, `'webdriverio'`). This makes it simpler to work with custom options and doesn't require adding `/// <reference` comments anymore.
220220+In Vitest 4.0, the browser provider now accepts an object instead of a string (`'playwright'`, `'webdriverio'`). The `preview` is no longer a default. This makes it simpler to work with custom options and doesn't require adding `/// <reference` comments anymore.
221221222222```ts
223223-import { playwright } from '@vitest/browser/providers/playwright' // [!code ++]
223223+import { playwright } from '@vitest/browser-playwright' // [!code ++]
224224225225export default defineConfig({
226226 test: {
···246246247247The naming of properties in `playwright` factory now also aligns with [Playwright documentation](https://playwright.dev/docs/api/class-testoptions#test-options-launch-options) making it easier to find.
248248249249+With this change, the `@vitest/browser` package is no longer needed, and you can remove it from your dependencies. To support the context import, you should update the `@vitest/browser/context` to `vitest/browser`:
250250+251251+```ts
252252+import { page } from '@vitest/browser/context' // [!code --]
253253+import { page } from 'vitest/browser' // [!code ++]
254254+255255+test('example', async () => {
256256+ await page.getByRole('button').click()
257257+})
258258+```
259259+260260+The modules are identical, so doing a simple "Find and Replace" should be sufficient.
261261+262262+If you were using the `@vitest/browser/utils` module, you can now import those utilities from `vitest/browser` as well:
263263+264264+```ts
265265+import { getElementError } from '@vitest/browser/utils' // [!code --]
266266+import { utils } from 'vitest/browser' // [!code ++]
267267+const { getElementError } = utils // [!code ++]
268268+```
269269+270270+::: warning
271271+Both `@vitest/browser/context` and `@vitest/browser/utils` work at runtime during the transition period, but they will be removed in a future release.
272272+:::
273273+249274### Reporter Updates
250275251276Reporter APIs `onCollected`, `onSpecsCollected`, `onPathsCollected`, `onTaskUpdate` and `onFinished` were removed. See [`Reporters API`](/advanced/api/reporters) for new alternatives. The new APIs were introduced in Vitest `v3.0.0`.
···277302278303In Vitest 4.0 snapshots that include custom elements will print the shadow root contents. To restore the previous behavior, set the [`printShadowRoot` option](/config/#snapshotformat) to `false`.
279304280280-```js
305305+```js{15-22}
281306// before Vite 4.0
282307exports[`custom element with shadow root 1`] = `
283308"<body>
···11+# @vitest/browser-playwright
22+33+[](https://www.npmjs.com/package/@vitest/browser-playwright)
44+55+Run your Vitest [browser tests](https://vitest.dev/guide/browser/) using [playwright](https://playwright.dev/docs/api/class-playwright) API. Note that Vitest does not use playwright as a test runner, but only as a browser provider.
66+77+We recommend using this package if you are already using playwright in your project or if you do not have any E2E tests yet.
88+99+## Installation
1010+1111+Install the package with your favorite package manager:
1212+1313+```sh
1414+npm install -D @vitest/browser-playwright
1515+# or
1616+yarn add -D @vitest/browser-playwright
1717+# or
1818+pnpm add -D @vitest/browser-playwright
1919+```
2020+2121+Then specify it in the `browser.provider` field of your Vitest configuration:
2222+2323+```ts
2424+// vitest.config.ts
2525+import { defineConfig } from 'vitest/config'
2626+import { playwright } from '@vitest/browser-playwright'
2727+2828+export default defineConfig({
2929+ test: {
3030+ browser: {
3131+ provider: playwright({
3232+ // ...custom playwright options
3333+ }),
3434+ instances: [
3535+ { name: 'chromium' },
3636+ ],
3737+ },
3838+ },
3939+})
4040+```
4141+4242+Then run Vitest in the browser mode:
4343+4444+```sh
4545+npx vitest --browser
4646+```
4747+4848+[GitHub](https://github.com/vitest-dev/vitest/tree/main/packages/browser-playwright) | [Documentation](https://vitest.dev/guide/browser/playwright)
···11+import type { ScreenshotOptions } from 'vitest/browser'
22+import type { BrowserCommandContext } from 'vitest/node'
33+import { mkdir } from 'node:fs/promises'
44+import { resolveScreenshotPath } from '@vitest/browser'
55+import { dirname, normalize } from 'pathe'
66+77+interface ScreenshotCommandOptions extends Omit<ScreenshotOptions, 'element' | 'mask'> {
88+ element?: string
99+ mask?: readonly string[]
1010+}
1111+/**
1212+ * Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path.
1313+ *
1414+ * **Note**: the returned `path` indicates where the screenshot *might* be found.
1515+ * It is not guaranteed to exist, especially if `options.save` is `false`.
1616+ *
1717+ * @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots.
1818+ */
1919+export async function takeScreenshot(
2020+ context: BrowserCommandContext,
2121+ name: string,
2222+ options: Omit<ScreenshotCommandOptions, 'base64'>,
2323+): Promise<{ buffer: Buffer<ArrayBufferLike>; path: string }> {
2424+ if (!context.testPath) {
2525+ throw new Error(`Cannot take a screenshot without a test path`)
2626+ }
2727+2828+ const path = resolveScreenshotPath(
2929+ context.testPath,
3030+ name,
3131+ context.project.config,
3232+ options.path,
3333+ )
3434+3535+ // playwright does not need a screenshot path if we don't intend to save it
3636+ let savePath: string | undefined
3737+3838+ if (options.save) {
3939+ savePath = normalize(path)
4040+4141+ await mkdir(dirname(savePath), { recursive: true })
4242+ }
4343+4444+ const mask = options.mask?.map(selector => context.iframe.locator(selector))
4545+4646+ if (options.element) {
4747+ const { element: selector, ...config } = options
4848+ const element = context.iframe.locator(selector)
4949+ const buffer = await element.screenshot({
5050+ ...config,
5151+ mask,
5252+ path: savePath,
5353+ })
5454+ return { buffer, path }
5555+ }
5656+5757+ const buffer = await context.iframe.locator('body').screenshot({
5858+ ...options,
5959+ mask,
6060+ path: savePath,
6161+ })
6262+ return { buffer, path }
6363+}
···11+# @vitest/browser-preview
22+33+[](https://www.npmjs.com/package/@vitest/browser-preview)
44+55+See how your tests look like in a real browser. For proper and stable browser testing, we recommend running tests in a headless browser in your CI instead. For this, you should use either:
66+77+- [@vitest/browser-playwright](https://www.npmjs.com/package/@vitest/browser-playwright) - run tests using [playwright](https://playwright.dev/)
88+- [@vitest/browser-webdriverio](https://www.npmjs.com/package/@vitest/browser-webdriverio) - run tests using [webdriverio](https://webdriver.io/)
99+1010+## Installation
1111+1212+Install the package with your favorite package manager:
1313+1414+```sh
1515+npm install -D @vitest/browser-preview
1616+# or
1717+yarn add -D @vitest/browser-preview
1818+# or
1919+pnpm add -D @vitest/browser-preview
2020+```
2121+2222+Then specify it in the `browser.provider` field of your Vitest configuration:
2323+2424+```ts
2525+// vitest.config.ts
2626+import { defineConfig } from 'vitest/config'
2727+import { preview } from '@vitest/browser-preview'
2828+2929+export default defineConfig({
3030+ test: {
3131+ browser: {
3232+ provider: preview(),
3333+ instances: [
3434+ { name: 'chromium' },
3535+ ],
3636+ },
3737+ },
3838+})
3939+```
4040+4141+Then run Vitest in the browser mode:
4242+4343+```sh
4444+npx vitest --browser
4545+```
4646+4747+If browser didn't open automatically, follow the link in the terminal to open the browser preview.
4848+4949+[GitHub](https://github.com/vitest-dev/vitest/tree/main/packages/browser-preview) | [Documentation](https://vitest.dev/guide/browser/)
···11+# @vitest/browser-webdriverio
22+33+[](https://www.npmjs.com/package/@vitest/browser-webdriverio)
44+55+Run your Vitest [browser tests](https://vitest.dev/guide/browser/) using [webdriverio](https://webdriver.io/docs/api/browser) API. Note that Vitest does not use webdriverio as a test runner, but only as a browser provider.
66+77+We recommend using this package if you are already using webdriverio in your project.
88+99+## Installation
1010+1111+Install the package with your favorite package manager:
1212+1313+```sh
1414+npm install -D @vitest/browser-webdriverio
1515+# or
1616+yarn add -D @vitest/browser-webdriverio
1717+# or
1818+pnpm add -D @vitest/browser-webdriverio
1919+```
2020+2121+Then specify it in the `browser.provider` field of your Vitest configuration:
2222+2323+```ts
2424+// vitest.config.ts
2525+import { defineConfig } from 'vitest/config'
2626+import { webdriverio } from '@vitest/browser-webdriverio'
2727+2828+export default defineConfig({
2929+ test: {
3030+ browser: {
3131+ provider: webdriverio({
3232+ // ...custom webdriverio options
3333+ }),
3434+ instances: [
3535+ { name: 'chromium' },
3636+ ],
3737+ },
3838+ },
3939+})
4040+```
4141+4242+Then run Vitest in the browser mode:
4343+4444+```sh
4545+npx vitest --browser
4646+```
4747+4848+[GitHub](https://github.com/vitest-dev/vitest/tree/main/packages/browser-webdriverio) | [Documentation](https://vitest.dev/guide/browser/webdriverio)
···11+import type { ScreenshotOptions } from 'vitest/browser'
22+import type { BrowserCommandContext } from 'vitest/node'
33+import crypto from 'node:crypto'
44+import { mkdir, rm } from 'node:fs/promises'
55+import { normalize as platformNormalize } from 'node:path'
66+import { resolveScreenshotPath } from '@vitest/browser'
77+import { dirname, normalize, resolve } from 'pathe'
88+99+interface ScreenshotCommandOptions extends Omit<ScreenshotOptions, 'element' | 'mask'> {
1010+ element?: string
1111+ mask?: readonly string[]
1212+}
1313+1414+/**
1515+ * Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path.
1616+ *
1717+ * **Note**: the returned `path` indicates where the screenshot *might* be found.
1818+ * It is not guaranteed to exist, especially if `options.save` is `false`.
1919+ *
2020+ * @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots.
2121+ */
2222+export async function takeScreenshot(
2323+ context: BrowserCommandContext,
2424+ name: string,
2525+ options: Omit<ScreenshotCommandOptions, 'base64'>,
2626+): Promise<{ buffer: Buffer<ArrayBufferLike>; path: string }> {
2727+ if (!context.testPath) {
2828+ throw new Error(`Cannot take a screenshot without a test path`)
2929+ }
3030+3131+ const path = resolveScreenshotPath(
3232+ context.testPath,
3333+ name,
3434+ context.project.config,
3535+ options.path,
3636+ )
3737+3838+ // playwright does not need a screenshot path if we don't intend to save it
3939+ let savePath: string | undefined
4040+4141+ if (options.save) {
4242+ savePath = normalize(path)
4343+4444+ await mkdir(dirname(savePath), { recursive: true })
4545+ }
4646+4747+ // webdriverio needs a path, so if one is not already set we create a temporary one
4848+ if (savePath === undefined) {
4949+ savePath = resolve(context.project.tmpDir, crypto.randomUUID())
5050+5151+ await mkdir(context.project.tmpDir, { recursive: true })
5252+ }
5353+5454+ const page = context.browser
5555+ const element = !options.element
5656+ ? await page.$('body')
5757+ : await page.$(`${options.element}`)
5858+5959+ // webdriverio expects the path to contain the extension and only works with PNG files
6060+ const savePathWithExtension = savePath.endsWith('.png') ? savePath : `${savePath}.png`
6161+6262+ // there seems to be a bug in webdriverio, `X:/` gets appended to cwd, so we convert to `X:\`
6363+ const buffer = await element.saveScreenshot(
6464+ platformNormalize(savePathWithExtension),
6565+ )
6666+ if (!options.save) {
6767+ await rm(savePathWithExtension, { force: true })
6868+ }
6969+ return { buffer, path }
7070+}
···11import { SerializedConfig } from 'vitest'
22+import { StringifyOptions } from 'vitest/internal/browser'
23import { ARIARole } from './aria-role.js'
34import {} from './matchers.js'
45···186187 * state of keyboard to press and release buttons correctly.
187188 *
188189 * **Note:** Unlike `@testing-library/user-event`, the default `userEvent` instance
189189- * from `@vitest/browser/context` is created once, not every time its methods are called!
190190+ * from `vitest/browser` is created once, not every time its methods are called!
190191 * @see {@link https://vitest.dev/guide/browser/interactivity-api.html#userevent-setup}
191192 */
192193 setup: () => UserEvent
···361362 * regular expression. Note that exact match still trims whitespace.
362363 */
363364 exact?: boolean
365365+ hasText?: string | RegExp
366366+ hasNotText?: string | RegExp
367367+ has?: Locator
368368+ hasNot?: Locator
364369}
365370366371export interface LocatorByRoleOptions extends LocatorOptions {
···660665 config: SerializedConfig
661666}
662667663663-export interface LocatorOptions {
664664- hasText?: string | RegExp
665665- hasNotText?: string | RegExp
666666- has?: Locator
667667- hasNot?: Locator
668668-}
669669-670668/**
671669 * Handler for user interactions. The support is provided by the browser provider (`playwright` or `webdriverio`).
672670 * If used with `preview` provider, fallbacks to simulated events via `@testing-library/user-event`.
···732730733731export interface BrowserLocators {
734732 createElementLocators(element: Element): LocatorSelectors
733733+ // TODO: enhance docs
734734+ /**
735735+ * Extends `page.*` and `locator.*` interfaces.
736736+ * @see {@link}
737737+ *
738738+ * @example
739739+ * ```ts
740740+ * import { locators } from 'vitest/browser'
741741+ *
742742+ * declare module 'vitest/browser' {
743743+ * interface LocatorSelectors {
744744+ * getByCSS(css: string): Locator
745745+ * }
746746+ * }
747747+ *
748748+ * locators.extend({
749749+ * getByCSS(css: string) {
750750+ * return `css=${css}`
751751+ * }
752752+ * })
753753+ * ```
754754+ */
735755 extend(methods: {
736736- [K in keyof LocatorSelectors]?: (this: BrowserPage | Locator, ...args: Parameters<LocatorSelectors[K]>) => ReturnType<LocatorSelectors[K]> | string
756756+ [K in keyof LocatorSelectors]?: (
757757+ this: BrowserPage | Locator,
758758+ ...args: Parameters<LocatorSelectors[K]>
759759+ ) => ReturnType<LocatorSelectors[K]> | string
737760 }): void
761761+}
762762+763763+764764+export type PrettyDOMOptions = Omit<StringifyOptions, 'maxLength'>
765765+766766+export const utils: {
767767+ getElementLocatorSelectors(element: Element): LocatorSelectors
768768+ debug(
769769+ el?: Element | Locator | null | (Element | Locator)[],
770770+ maxLength?: number,
771771+ options?: PrettyDOMOptions,
772772+ ): void
773773+ prettyDOM(
774774+ dom?: Element | Locator | undefined | null,
775775+ maxLength?: number,
776776+ prettyFormatOptions?: PrettyDOMOptions,
777777+ ): string
778778+ getElementError(selector: string, container?: Element): Error
738779}
739780740781export const locators: BrowserLocators
+3-2
packages/browser/context.js
···11-// Vitest resolves "@vitest/browser/context" as a virtual module instead
11+// Vitest resolves "vitest/browser" as a virtual module instead
2233// fake exports for static analysis
44export const page = null
···77export const cdp = null
88export const commands = null
99export const locators = null
1010+export const utils = null
10111112const pool = globalThis.__vitest_worker__?.ctx?.pool
12131314throw new Error(
1415 // eslint-disable-next-line prefer-template
1515- '@vitest/browser/context can be imported only inside the Browser Mode. '
1616+ 'vitest/browser can be imported only inside the Browser Mode. '
1617 + (pool
1718 ? `Your test is running in ${pool} pool. Make sure your regular tests are excluded from the "test.include" glob pattern.`
1819 : 'Instead, it was imported outside of Vitest.'),
+5-5
packages/browser/jest-dom.d.ts
···2323 * This matcher calculates the intersection ratio between the element and the viewport, similar to the
2424 * IntersectionObserver API.
2525 *
2626- * The element must be in the document and have visible dimensions. Elements with display: none or
2626+ * The element must be in the document and have visible dimensions. Elements with display: none or
2727 * visibility: hidden are considered not in viewport.
2828 * @example
2929 * <div
···3434 * </div>
3535 *
3636 * <div
3737- * data-testid="hidden-element"
3737+ * data-testid="hidden-element"
3838 * style="position: fixed; top: -100px; left: 10px; width: 50px; height: 50px;"
3939 * >
4040 * Hidden Element
···4949 *
5050 * // Check if any part of element is in viewport
5151 * await expect.element(page.getByTestId('visible-element')).toBeInViewport()
5252- *
5252+ *
5353 * // Check if element is outside viewport
5454 * await expect.element(page.getByTestId('hidden-element')).not.toBeInViewport()
5555- *
5555+ *
5656 * // Check if at least 50% of element is visible
5757 * await expect.element(page.getByTestId('large-element')).toBeInViewport({ ratio: 0.5 })
5858- *
5858+ *
5959 * // Check if element is completely visible
6060 * await expect.element(page.getByTestId('visible-element')).toBeInViewport({ ratio: 1 })
6161 * @see https://vitest.dev/guide/browser/assertion-api#tobeinviewport
+1-1
packages/browser/matchers.d.ts
···11-import type { Locator } from '@vitest/browser/context'
11+import type { Locator } from './context.js'
22import type { TestingLibraryMatchers } from './jest-dom.js'
33import type { Assertion, ExpectPollOptions } from 'vitest'
44
···15151616import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717import type { Locator } from '../locators'
1818-import { server } from '@vitest/browser/context'
1918import { beginAriaCaches, endAriaCaches, isElementVisible as ivyaIsVisible } from 'ivya/utils'
1919+import { server } from 'vitest/browser'
2020import { getElementFromUserInput } from './utils'
21212222export default function toBeVisible(
···11import type { ExpectationResult, MatcherState } from '@vitest/expect'
22import type { Locator } from '../locators'
33-import { server } from '@vitest/browser/context'
33+import { server } from 'vitest/browser'
44import { getElementFromUserInput } from './utils'
5566const browser = server.config.browser.name
···33import type { ScreenshotMatcherArguments, ScreenshotMatcherOutput } from '../../../shared/screenshotMatcher/types'
44import type { Locator } from '../locators'
55import { getBrowserState, getWorkerState } from '../../utils'
66-import { convertToSelector } from '../utils'
66+import { convertToSelector } from '../tester-utils'
7788const counters = new Map<string, { current: number }>([])
99
···11-import type { Locator } from '@vitest/browser/context'
11+import type { Locator } from 'vitest/browser'
22import type { BrowserRPC } from '../client'
33import { getBrowserState, getWorkerState } from '../utils'
44···206206 if (elementOrLocator instanceof Element) {
207207 return convertElementToCssSelector(elementOrLocator)
208208 }
209209- if ('selector' in elementOrLocator) {
210210- return (elementOrLocator as any).selector
209209+ if (isLocator(elementOrLocator)) {
210210+ return elementOrLocator.selector
211211 }
212212 throw new Error('Expected element or locator to be an instance of Element or Locator.')
213213}
214214+215215+const kLocator = Symbol.for('$$vitest:locator')
216216+217217+export function isLocator(element: unknown): element is Locator {
218218+ return (!!element && typeof element === 'object' && kLocator in element)
219219+}
+1-1
packages/browser/src/client/utils.ts
···11import type { VitestRunner } from '@vitest/runner'
22import type { EvaluatedModules, SerializedConfig, WorkerGlobalState } from 'vitest'
33import type { IframeOrchestrator } from './orchestrator'
44-import type { CommandsManager } from './tester/utils'
44+import type { CommandsManager } from './tester/tester-utils'
5566export async function importId(id: string): Promise<any> {
77 const name = `/@id/${id}`.replace(/\\/g, '/')
···11-import type { Locator } from '@vitest/browser/context'
11+import type { Locator } from 'vitest/browser'
22import type { BrowserCommand } from 'vitest/node'
3344export type UserEventCommand<T extends (...args: any) => any> = BrowserCommand<
-14
packages/browser/src/node/commands/viewport.ts
···11-import type { UserEventCommand } from './utils'
22-import { WebdriverBrowserProvider } from '../providers/webdriverio'
33-44-export const viewport: UserEventCommand<(options: {
55- width: number
66- height: number
77-}) => void> = async (context, options) => {
88- if (context.provider instanceof WebdriverBrowserProvider) {
99- await context.provider.setViewport(options)
1010- }
1111- else {
1212- throw new TypeError(`Provider ${context.provider.name} doesn't support "viewport" command`)
1313- }
1414-}
+41-13
packages/browser/src/node/index.ts
···11-import type { Plugin } from 'vitest/config'
22-import type { TestProject } from 'vitest/node'
11+import type { BrowserCommand, BrowserProviderOption, BrowserServerFactory } from 'vitest/node'
32import { MockerRegistry } from '@vitest/mocker'
43import { interceptorPlugin } from '@vitest/mocker/node'
54import c from 'tinyrainbow'
65import { createViteLogger, createViteServer } from 'vitest/node'
76import { version } from '../../package.json'
77+import { distRoot } from './constants'
88import BrowserPlugin from './plugin'
99import { ParentBrowserProject } from './projectParent'
1010import { setupBrowserRpc } from './rpc'
11111212-export { distRoot } from './constants'
1313-export { createBrowserPool } from './pool'
1212+export function defineBrowserCommand<T extends unknown[]>(
1313+ fn: BrowserCommand<T>,
1414+): BrowserCommand<T> {
1515+ return fn
1616+}
1717+1818+// export type { ProjectBrowser } from './project'
1919+export { parseKeyDef, resolveScreenshotPath } from './utils'
14201515-export type { ProjectBrowser } from './project'
2121+export const createBrowserServer: BrowserServerFactory = async (options) => {
2222+ const project = options.project
2323+ const configFile = project.vite.config.configFile
16241717-export async function createBrowserServer(
1818- project: TestProject,
1919- configFile: string | undefined,
2020- prePlugins: Plugin[] = [],
2121- postPlugins: Plugin[] = [],
2222-): Promise<ParentBrowserProject> {
2325 if (project.vitest.version !== version) {
2426 project.vitest.logger.warn(
2527 c.yellow(
···42444345 const mockerRegistry = new MockerRegistry()
44464747+ let cacheDir: string
4548 const vite = await createViteServer({
4649 ...project.options, // spread project config inlined in root workspace config
4750 base: '/',
···7174 },
7275 cacheDir: project.vite.config.cacheDir,
7376 plugins: [
7474- ...prePlugins,
7777+ {
7878+ name: 'vitest-internal:browser-cacheDir',
7979+ configResolved(config) {
8080+ cacheDir = config.cacheDir
8181+ },
8282+ },
8383+ ...options.mocksPlugins({
8484+ filter(id) {
8585+ if (id.includes(distRoot) || id.includes(cacheDir)) {
8686+ return false
8787+ }
8888+ return true
8989+ },
9090+ }),
9191+ options.metaEnvReplacer(),
7592 ...(project.options?.plugins || []),
7693 BrowserPlugin(server),
7794 interceptorPlugin({ registry: mockerRegistry }),
7878- ...postPlugins,
9595+ options.coveragePlugin(),
7996 ],
8097 })
8198···8510286103 return server
87104}
105105+106106+export function defineBrowserProvider<T extends object = object>(options: Omit<
107107+ BrowserProviderOption<T>,
108108+ 'serverFactory' | 'options'
109109+> & { options?: T }): BrowserProviderOption {
110110+ return {
111111+ ...options,
112112+ options: options.options || {},
113113+ serverFactory: createBrowserServer,
114114+ }
115115+}
···11import type { DeferPromise } from '@vitest/utils/helpers'
22-import type {
33- BrowserProvider,
44- ProcessPool,
55- TestProject,
66- TestSpecification,
77- Vitest,
88-} from 'vitest/node'
22+import type { Vitest } from '../core'
33+import type { ProcessPool } from '../pool'
44+import type { TestProject } from '../project'
55+import type { TestSpecification } from '../spec'
66+import type { BrowserProvider } from '../types/browser'
97import crypto from 'node:crypto'
108import * as nodeos from 'node:os'
1111-import { performance } from 'node:perf_hooks'
129import { createDefer } from '@vitest/utils/helpers'
1310import { stringify } from 'flatted'
1414-import { createDebugger } from 'vitest/node'
1111+import { createDebugger } from '../../utils/debugger'
15121613const debug = createDebugger('vitest:browser:pool')
1714···310307 if (!this._promise) {
311308 throw new Error(`Unexpected empty queue`)
312309 }
313313- const startTime = performance.now()
314310315311 const orchestrator = this.getOrchestrator(sessionId)
316312 debug?.('[%s] run test %s', sessionId, file)
···324320 // this will be parsed by the test iframe, not the orchestrator
325321 // so we need to stringify it first to avoid double serialization
326322 providedContext: this._providedContext || '[{}]',
327327- startTime,
328323 },
329324 )
330325 .then(() => {
+44
packages/browser/src/node/project.ts
···22import type { ViteDevServer } from 'vite'
33import type { ParsedStack, SerializedConfig, TestError } from 'vitest'
44import type {
55+ BrowserCommand,
66+ BrowserCommandContext,
57 BrowserProvider,
68 ProjectBrowser as IProjectBrowser,
79 ResolvedConfig,
810 TestProject,
911 Vitest,
1012} from 'vitest/node'
1313+import type { BrowserCommands } from '../../context'
1114import type { ParentBrowserProject } from './projectParent'
1215import { existsSync } from 'node:fs'
1316import { readFile } from 'node:fs/promises'
···5760 return this.parent.vite
5861 }
59626363+ private commands = {} as Record<string, BrowserCommand<any, any>>
6464+6565+ public registerCommand<K extends keyof BrowserCommands>(
6666+ name: K,
6767+ cb: BrowserCommand<
6868+ Parameters<BrowserCommands[K]>,
6969+ ReturnType<BrowserCommands[K]>
7070+ >,
7171+ ): void {
7272+ if (!/^[a-z_$][\w$]*$/i.test(name)) {
7373+ throw new Error(
7474+ `Invalid command name "${name}". Only alphanumeric characters, $ and _ are allowed.`,
7575+ )
7676+ }
7777+ this.commands[name] = cb
7878+ }
7979+8080+ public triggerCommand = (<K extends keyof BrowserCommand>(
8181+ name: K,
8282+ context: BrowserCommandContext,
8383+ ...args: Parameters<BrowserCommands[K]>
8484+ ): ReturnType<BrowserCommands[K]> => {
8585+ if (name in this.commands) {
8686+ return this.commands[name](context, ...args)
8787+ }
8888+ if (name in this.parent.commands) {
8989+ return this.parent.commands[name](context, ...args)
9090+ }
9191+ throw new Error(`Provider ${this.provider.name} does not support command "${name}".`)
9292+ }) as any
9393+6094 wrapSerializedConfig(): SerializedConfig {
6195 const config = wrapConfig(this.project.serializedConfig)
6296 config.env ??= {}
···69103 return
70104 }
71105 this.provider = await getBrowserProvider(project.config.browser, project)
106106+ if (this.provider.initScripts) {
107107+ this.parent.initScripts = this.provider.initScripts
108108+ // make sure the script can be imported
109109+ this.provider.initScripts.forEach((script) => {
110110+ const allow = this.parent.vite.config.server.fs.allow
111111+ if (!allow.includes(script)) {
112112+ allow.push(script)
113113+ }
114114+ })
115115+ }
72116 }
7311774118 public parseErrorStacktrace(
···11+// @ts-ignore -- @vitest/browser-playwright might not be installed
22+export * from '@vitest/browser-playwright/context'
33+// @ts-ignore -- @vitest/browser-webdriverio might not be installed
44+export * from '@vitest/browser-webdriverio/context'
55+// @ts-ignore -- @vitest/browser-preview might not be installed
66+export * from '@vitest/browser-preview/context'
+20
packages/vitest/browser/context.js
···11+// Vitest resolves "vitest/browser" as a virtual module instead
22+33+// fake exports for static analysis
44+export const page = null
55+export const server = null
66+export const userEvent = null
77+export const cdp = null
88+export const commands = null
99+export const locators = null
1010+export const utils = null
1111+1212+const pool = globalThis.__vitest_worker__?.ctx?.pool
1313+1414+throw new Error(
1515+ // eslint-disable-next-line prefer-template
1616+ 'vitest/browser can be imported only inside the Browser Mode. '
1717+ + (pool
1818+ ? `Your test is running in ${pool} pool. Make sure your regular tests are excluded from the "test.include" glob pattern.`
1919+ : 'Instead, it was imported outside of Vitest.'),
2020+)
···253253 const browser = resolved.browser
254254255255 if (browser.enabled) {
256256- if (!browser.name && !browser.instances) {
257257- throw new Error(`Vitest Browser Mode requires "browser.name" (deprecated) or "browser.instances" options, none were set.`)
256256+ const instances = browser.instances
257257+ if (!browser.instances) {
258258+ browser.instances = []
259259+ }
260260+261261+ // use `chromium` by default when the preview provider is specified
262262+ // for a smoother experience. if chromium is not available, it will
263263+ // open the default browser anyway
264264+ if (!browser.instances.length && browser.provider?.name === 'preview') {
265265+ browser.instances = [{ browser: 'chromium' }]
258266 }
259267260260- const instances = browser.instances
261261- if (browser.name && browser.instances) {
268268+ if (browser.name && instances?.length) {
262269 // --browser=chromium filters configs to a single one
263270 browser.instances = browser.instances.filter(instance => instance.browser === browser.name)
264264- }
265271266266- if (browser.instances && !browser.instances.length) {
267267- throw new Error([
268268- `"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`,
269269- browser.name && instances?.length ? ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map(c => c.browser).join(', ')}). Did you mean to use another name?` : '',
270270- ].join(''))
272272+ // if `instances` were defined, but now they are empty,
273273+ // let's throw an error because the filter is invalid
274274+ if (!browser.instances.length) {
275275+ throw new Error([
276276+ `"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`,
277277+ ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map(c => c.browser).join(', ')}). Did you mean to use another name?`,
278278+ ].join(''))
279279+ }
271280 }
272281 }
273282···750759 resolved.browser.locators ??= {} as any
751760 resolved.browser.locators.testIdAttribute ??= 'data-testid'
752761753753- if (resolved.browser.enabled && stdProvider === 'stackblitz') {
754754- resolved.browser.provider = undefined // reset to "preview"
755755- }
756756-757762 if (typeof resolved.browser.provider === 'string') {
758758- const source = `@vitest/browser/providers/${resolved.browser.provider}`
763763+ const source = `@vitest/browser-${resolved.browser.provider}`
759764 throw new TypeError(
760765 'The `browser.provider` configuration was changed to accept a factory instead of a string. '
761766 + `Add an import of "${resolved.browser.provider}" from "${source}" instead. See: https://vitest.dev/guide/browser/config#provider`,
···763768 }
764769765770 const isPreview = resolved.browser.provider?.name === 'preview'
771771+772772+ if (!isPreview && resolved.browser.enabled && stdProvider === 'stackblitz') {
773773+ throw new Error(`stackblitz environment does not support the ${resolved.browser.provider?.name} provider. Please, use "@vitest/browser-preview" instead.`)
774774+ }
766775 if (isPreview && resolved.browser.screenshotFailures === true) {
767776 console.warn(c.yellow(
768777 [
+5-1
packages/vitest/src/node/core.ts
···273273 throw new Error(`No projects matched the filter "${filter}".`)
274274 }
275275 else {
276276- throw new Error(`Vitest wasn't able to resolve any project.`)
276276+ let error = `Vitest wasn't able to resolve any project.`
277277+ if (this.config.browser.enabled && !this.config.browser.instances?.length) {
278278+ error += ` Please, check that you specified the "browser.instances" option.`
279279+ }
280280+ throw new Error(error)
277281 }
278282 }
279283
···11-import { userEvent } from '@vitest/browser/context';
11+import { userEvent } from 'vitest/browser';
22import { test } from 'vitest';
3344test('submitting a form reloads the iframe with "?" query', async () => {
···11import { afterEach, describe, expect, test } from 'vitest'
22import { extractToMatchScreenshotPaths, render } from './utils'
33-import { page, server } from '@vitest/browser/context'
33+import { page, server } from 'vitest/browser'
44import { join } from 'pathe'
5566const blockSize = 19
+1-1
test/browser/fixtures/failing/failing.test.ts
···11-import { page } from '@vitest/browser/context'
11+import { page } from 'vitest/browser'
22import { index } from '@vitest/bundled-lib'
33import { expect, it } from 'vitest'
44import { throwError } from './src/error'
···11-import { type Locator, locators, page } from '@vitest/browser/context';
11+import type { BrowserPage, Locator } from 'vitest/browser';
22+import { locators, page, utils } from 'vitest/browser';
23import { beforeEach, expect, test } from 'vitest';
33-import { getElementLocatorSelectors } from '@vitest/browser/utils'
4455-declare module '@vitest/browser/context' {
55+declare module 'vitest/browser' {
66 interface LocatorSelectors {
77 getByCustomTitle: (title: string) => Locator
88 getByNestedTitle: (title: string) => Locator
···8787})
88888989test('locators are available from getElementLocatorSelectors', () => {
9090- const locators = getElementLocatorSelectors(document.body)
9090+ const locators = utils.getElementLocatorSelectors(document.body)
91919292 expect(locators.updateHtml).toBeTypeOf('function')
9393 expect(locators.getByCustomTitle).toBeTypeOf('function')
+1-1
test/browser/fixtures/locators/blog.test.tsx
···11import { expect, test } from 'vitest'
22-import { page, userEvent } from '@vitest/browser/context'
22+import { page, userEvent } from 'vitest/browser'
33import Blog from '../../src/blog-app/blog'
4455test('renders blog posts', async () => {
+1-1
test/browser/fixtures/locators/query.test.ts
···11-import { page } from '@vitest/browser/context';
11+import { page } from 'vitest/browser';
22import { afterEach, describe, expect, test } from 'vitest';
3344afterEach(() => {
···11import { test as baseTest, expect, inject } from 'vitest';
22-import { server } from '@vitest/browser/context'
22+import { server } from 'vitest/browser'
3344const test = baseTest.extend({
55 // chromium should inject the value as "true"
···11import { expect, onTestFinished, test } from 'vitest'
22-import { userEvent } from '@vitest/browser/context'
22+import { userEvent } from 'vitest/browser'
3344test('cleanup retry', { retry: 1 }, async (ctx) => {
55 let logs: any[] = [];
+1-1
test/browser/fixtures/user-event/cleanup1.test.ts
···11import { expect, onTestFinished, test } from 'vitest'
22-import { userEvent } from '@vitest/browser/context'
22+import { userEvent } from 'vitest/browser'
3344test('cleanup1', async () => {
55 let logs: any[] = [];
+1-1
test/browser/fixtures/user-event/cleanup2.test.ts
···11import { expect, onTestFinished, test } from 'vitest'
22-import { userEvent } from '@vitest/browser/context'
22+import { userEvent } from 'vitest/browser'
3344// test per-test-file cleanup just in case
55
···11import { expect, test } from 'vitest';
22-import { page, userEvent } from '@vitest/browser/context';
22+import { page, userEvent } from 'vitest/browser';
3344test('clipboard', async () => {
55 // make it smaller since webdriverio fails when scaled
+1-1
test/browser/fixtures/user-event/keyboard.test.ts
···11import { expect, test } from 'vitest'
22-import { userEvent, page, server } from '@vitest/browser/context'
22+import { userEvent, page, server } from 'vitest/browser'
3344test('non US keys', async () => {
55 document.body.innerHTML = `
+1-1
test/browser/fixtures/viewport/basic.test.ts
···11-import { page, userEvent, server } from "@vitest/browser/context";
11+import { page, userEvent, server } from "vitest/browser";
22import { expect, test } from "vitest";
3344test("drag and drop over large viewport", async () => {
···11-import { expect, test } from 'vitest'
11+import { expect, test, vi } from 'vitest'
22import { instances, runBrowserTests } from './utils'
3344test('locators work correctly', async () => {
55+ const log = vi.fn()
56 const { stderr, stdout } = await runBrowserTests({
67 root: './fixtures/locators',
77- reporters: [['verbose', { isTTY: false }]],
88+ reporters: [
99+ ['verbose', { isTTY: false }],
1010+ {
1111+ onInit(vitest) {
1212+ vitest.logger.deprecate = log
1313+ },
1414+ },
1515+ ],
816 })
9171018 expect(stderr).toReportNoErrors()
1919+ expect(log).toHaveBeenCalledWith(
2020+ expect.stringContaining(
2121+ `tries to load a deprecated "@vitest/browser/context" module. `
2222+ + `This import will stop working in the next major version. Please, use "vitest/browser" instead.`,
2323+ ),
2424+ )
11251226 instances.forEach(({ browser }) => {
1327 expect(stdout).toReportPassedTest('blog.test.tsx', browser)
+3-3
test/browser/specs/playwright-connect.test.ts
···11-import { playwright } from '@vitest/browser/providers/playwright'
11+import { playwright } from '@vitest/browser-playwright'
22import { chromium } from 'playwright'
33import { expect, test } from 'vitest'
44import { provider } from '../settings'
···27272828 await browserServer.close()
29293030+ expect(stderr).toBe('')
3031 expect(stdout).toContain('Tests 2 passed')
3132 expect(exitCode).toBe(0)
3232- expect(stderr).toBe('')
3333})
34343535test.runIf(provider.name === 'playwright')('[playwright] warns if both connect and launch mode are configured', async () => {
···56565757 await browserServer.close()
58585959+ expect(stderr).toContain('Found both connect and launch options in browser instance configuration.')
5960 expect(stdout).toContain('Tests 2 passed')
6061 expect(exitCode).toBe(0)
6161- expect(stderr).toContain('Found both connect and launch options in browser instance configuration.')
6262})
+9-8
test/browser/specs/to-match-screenshot.test.ts
···11-import type { ViteUserConfig } from 'vitest/config.js'
11+import type { ViteUserConfig } from 'vitest/config'
22import type { TestFsStructure } from '../../test-utils'
33import { platform } from 'node:os'
44import { resolve } from 'node:path'
55-import { playwright } from '@vitest/browser/providers/playwright'
65import { describe, expect, test } from 'vitest'
76import { runVitestCli, useFS } from '../../test-utils'
87import { extractToMatchScreenshotPaths } from '../fixtures/expect-dom/utils'
···1312const bgColor = '#fff'
14131514const testContent = /* ts */`
1616-import { page, server } from '@vitest/browser/context'
1515+import { page, server } from 'vitest/browser'
1716import { describe, test } from 'vitest'
1817import { render } from './utils'
1918···30293130export async function runInlineTests(
3231 structure: TestFsStructure,
3333- config?: ViteUserConfig['test'],
3232+ config: ViteUserConfig['test'] = {},
3433) {
3534 const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
36353736 const fs = useFS(root, {
3837 ...structure,
3939- 'vitest.config.ts': {
3838+ 'vitest.config.ts': `
3939+ import { playwright } from '@vitest/browser-playwright'
4040+ export default {
4041 test: {
4142 browser: {
4243 enabled: true,
4344 screenshotFailures: false,
4445 provider: playwright(),
4546 headless: true,
4646- instances: [{ browser }],
4747+ instances: [{ browser: ${JSON.stringify(browser)} }],
4748 },
4849 reporters: ['verbose'],
4949- ...config,
5050+ ...${JSON.stringify(config)},
5051 },
5151- },
5252+ }`,
5253 })
53545455 const vitest = await runVitestCli({
+1-1
test/browser/test/cdp.test.ts
···11-import { cdp, server } from '@vitest/browser/context'
21import { describe, expect, it, onTestFinished, vi } from 'vitest'
22+import { cdp, server } from 'vitest/browser'
3344describe.runIf(
55 server.provider === 'playwright' && server.browser === 'chromium',
+2-2
test/browser/test/commands.test.ts
···11-import { server } from '@vitest/browser/context'
21import { expect, it } from 'vitest'
22+import { server } from 'vitest/browser'
3344const { readFile, writeFile, removeFile, myCustomCommand } = server.commands
55···5151 })
5252})
53535454-declare module '@vitest/browser/context' {
5454+declare module 'vitest/browser' {
5555 interface BrowserCommands {
5656 myCustomCommand: (arg1: string, arg2: string) => Promise<{
5757 testPath: string
+1-1
test/browser/test/dom.test.ts
···11import { createNode } from '#src/createNode'
22-import { page, server } from '@vitest/browser/context'
32import { afterAll, beforeEach, describe, expect, test } from 'vitest'
33+import { page, server } from 'vitest/browser'
44import '../src/button.css'
5566afterAll(() => {
+1-1
test/browser/test/expect-element.test.ts
···11-import { page } from '@vitest/browser/context'
21import { expect, test, vi } from 'vitest'
22+import { page } from 'vitest/browser'
3344// element selector uses prettyDOM under the hood, which is an expensive call
55// that should not be called on each failed locator attempt to avoid memory leak:
+1-1
test/browser/test/iframe.test.ts
···11-import { page, server } from '@vitest/browser/context'
21import { expect, test } from 'vitest'
22+import { page, server } from 'vitest/browser'
3344test.runIf(server.provider === 'playwright')('locates an iframe', async () => {
55 const iframe = document.createElement('iframe')
+2-2
test/browser/test/userEvent.test.ts
···11-import { userEvent as _uE, server } from '@vitest/browser/context'
21import { beforeEach, describe, expect, test, vi } from 'vitest'
22+import { userEvent as _uE, server } from 'vitest/browser'
33import '../src/button.css'
4455beforeEach(() => {
···615615 expect(value()).toBe('Another Value')
616616 })
617617618618- test('fill input in shadow root', async () => {
618618+ test.skipIf(server.provider === 'preview')('fill input in shadow root', async () => {
619619 const input = getInput()
620620 const shadowRoot = createShadowRoot()
621621 shadowRoot.appendChild(input)
+8-9
test/browser/test/utils.test.ts
···11-import { commands } from '@vitest/browser/context'
22-import { prettyDOM } from '@vitest/browser/utils'
31import { afterEach, expect, it, test } from 'vitest'
22+import { commands, utils } from 'vitest/browser'
4354import { inspect } from 'vitest/internal/browser'
65···1312})
14131514test('prints default document', async () => {
1616- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
1515+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
17161817 const div = document.createElement('div')
1918 div.innerHTML = '<span>hello</span>'
2019 document.body.append(div)
21202222- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
2121+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
2322})
24232524test('prints the element', async () => {
···2726 div.innerHTML = '<span>hello</span>'
2827 document.body.append(div)
29283030- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
2929+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
3130})
32313332test('prints the element with attributes', async () => {
···3534 div.innerHTML = '<span class="some-name" data-test-id="33" id="5">hello</span>'
3635 document.body.append(div)
37363838- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
3737+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
3938})
40394140test('should handle DOM content bigger than maxLength', async () => {
···5049 parentDiv.innerHTML = domString
51505251 document.body.appendChild(parentDiv)
5353- expect(await commands.stripVTControlCharacters(prettyDOM(undefined, maxContent))).toMatchSnapshot()
5252+ expect(await commands.stripVTControlCharacters(utils.prettyDOM(undefined, maxContent))).toMatchSnapshot()
5453})
55545655test('should handle shadow DOM content', async () => {
···7170 div.innerHTML = '<custom-element></custom-element>'
7271 document.body.append(div)
73727474- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
7373+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
7574})
76757776test('should be able to opt out of shadow DOM content', async () => {
···9291 div.innerHTML = '<no-shadow-root></no-shadow-root>'
9392 document.body.append(div)
94939595- expect(await commands.stripVTControlCharacters(prettyDOM(undefined, undefined, { printShadowRoot: false }))).toMatchSnapshot()
9494+ expect(await commands.stripVTControlCharacters(utils.prettyDOM(undefined, undefined, { printShadowRoot: false }))).toMatchSnapshot()
9695})
+1-1
test/browser/test/viewport.test.ts
···11-import { server } from '@vitest/browser/context'
21import { describe, expect, it } from 'vitest'
22+import { server } from 'vitest/browser'
3344describe.skipIf(
55 // preview cannot control viewport
···61616262exports[`should fail no-assertions.test.ts 1`] = `"Error: expected any number of assertion, but got none"`;
63636464-exports[`should fail node-browser-context.test.ts 1`] = `"Error: @vitest/browser/context can be imported only inside the Browser Mode. Your test is running in forks pool. Make sure your regular tests are excluded from the "test.include" glob pattern."`;
6464+exports[`should fail node-browser-context.test.ts 1`] = `"Error: vitest/browser can be imported only inside the Browser Mode. Your test is running in forks pool. Make sure your regular tests are excluded from the "test.include" glob pattern."`;
65656666exports[`should fail poll-no-awaited.test.ts 1`] = `
6767"Error: expect.poll(assertion).toBe() was not awaited. This assertion is asynchronous and must be awaited; otherwise, it is not executed to avoid unhandled rejections:
+2-2
test/cli/test/annotations.test.ts
···11import type { TestAnnotation } from 'vitest'
22-import { playwright } from '@vitest/browser/providers/playwright'
22+import { playwright } from '@vitest/browser-playwright'
33import { describe, expect, test } from 'vitest'
44import { runInlineTests } from '../../test-utils'
55···4040 provider: playwright(),
4141 headless: true,
4242 instances: [
4343- { browser: 'chromium' },
4343+ { browser: 'chromium' as const },
4444 ],
4545 },
4646 },
+10-11
test/cli/test/fails.test.ts
···11import type { TestCase } from 'vitest/node'
22-import { playwright } from '@vitest/browser/providers/playwright'
22+import { playwright } from '@vitest/browser-playwright'
3344import { resolve } from 'pathe'
55import { glob } from 'tinyglobby'
···109109110110it('prints a warning if the assertion is not awaited in the browser mode', async () => {
111111 const { stderr } = await runInlineTests({
112112- './vitest.config.js': {
113113- test: {
114114- browser: {
115115- enabled: true,
116116- instances: [{ browser: 'chromium' }],
117117- provider: playwright(),
118118- headless: true,
119119- },
120120- },
121121- },
122112 'base.test.js': ts`
123113 import { expect, test } from 'vitest';
124114···126116 expect(Promise.resolve(1)).resolves.toBe(1)
127117 })
128118 `,
119119+ }, {}, {}, {
120120+ test: {
121121+ browser: {
122122+ enabled: true,
123123+ instances: [{ browser: 'chromium' }],
124124+ provider: playwright(),
125125+ headless: true,
126126+ },
127127+ },
129128 })
130129 expect(stderr).toContain('Promise returned by \`expect(actual).resolves.toBe(expected)\` was not awaited')
131130 expect(stderr).toContain('base.test.js:5:33')
···11import type { TestUserConfig } from 'vitest/node'
2233-import { playwright } from '@vitest/browser/providers/playwright'
33+import { playwright } from '@vitest/browser-playwright'
44import { expect, test } from 'vitest'
55import { runVitest } from '../../test-utils'
66
···11import type { UserConfig as ViteUserConfig } from 'vite'
22import type { TestUserConfig } from 'vitest/node'
33import type { VitestRunnerCLIOptions } from '../../test-utils'
44-import { playwright } from '@vitest/browser/providers/playwright'
55-import { preview } from '@vitest/browser/providers/preview'
66-import { webdriverio } from '@vitest/browser/providers/webdriverio'
44+import { playwright } from '@vitest/browser-playwright'
55+import { preview } from '@vitest/browser-preview'
66+import { webdriverio } from '@vitest/browser-webdriverio'
77import { normalize, resolve } from 'pathe'
88import { beforeEach, expect, test } from 'vitest'
99import { version } from 'vitest/package.json'
···405405 },
406406 },
407407 })
408408- expect(stderr).toMatch('"browser.instances" was set in the config, but the array is empty. Define at least one browser config.')
408408+ expect(stderr).toMatch(`Vitest wasn't able to resolve any project. Please, check that you specified the "browser.instances" option.`)
409409})
410410411411test('browser.name or browser.instances are required', async () => {
412412 const { stderr, exitCode } = await runVitestCli('--browser.enabled', '--root=./fixtures/browser-no-config')
413413 expect(exitCode).toBe(1)
414414- expect(stderr).toMatch('Vitest Browser Mode requires "browser.name" (deprecated) or "browser.instances" options, none were set.')
414414+ expect(stderr).toMatch('Vitest received --browser flag, but no project had a browser configuration.')
415415})
416416417417test('--browser flag without browser configuration throws an error', async () => {
···495495 },
496496 })
497497 expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "1 (firefox)" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects should have unique names. Make sure your configuration is correct.')
498498-})
499499-500500-test('throws an error if several browsers are headed in nonTTY mode', async () => {
501501- const { stderr } = await runVitest({}, {
502502- test: {
503503- browser: {
504504- enabled: true,
505505- provider: playwright(),
506506- headless: false,
507507- instances: [
508508- { browser: 'chromium' },
509509- { browser: 'firefox' },
510510- ],
511511- },
512512- },
513513- })
514514- expect(stderr).toContain('Found multiple projects that run browser tests in headed mode: "chromium", "firefox"')
515515- expect(stderr).toContain('Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option')
516498})
517499518500test('non existing project name will throw', async () => {
···77import { resolve } from 'node:path'
88import { fileURLToPath } from 'node:url'
99import { stripVTControlCharacters } from 'node:util'
1010-import { playwright } from '@vitest/browser/providers/playwright'
1010+import { playwright } from '@vitest/browser-playwright'
1111import libCoverage from 'istanbul-lib-coverage'
1212import { normalize } from 'pathe'
1313import { vi, describe as vitestDescribe, test as vitestTest } from 'vitest'