diff --git a/docs/guide/browser/assertion-api.md b/docs/guide/browser/assertion-api.md
index c0a422a0b..e7cb87845 100644
--- a/docs/guide/browser/assertion-api.md
+++ b/docs/guide/browser/assertion-api.md
@@ -68,14 +68,14 @@ Tests in the browser might fail inconsistently due to their asynchronous nature.
```ts
import { expect, test } from 'vitest'
-import { screen } from '@testing-library/dom'
+import { page } from '@vitest/browser/context'
test('error banner is rendered', async () => {
triggerError()
// @testing-library provides queries with built-in retry-ability
// It will try to find the banner until it's rendered
- const banner = await screen.findByRole('alert', {
+ const banner = page.getByRole('alert', {
name: /error/i,
})
diff --git a/docs/guide/browser/index.md b/docs/guide/browser/index.md
index ff3124abe..2ffec7108 100644
--- a/docs/guide/browser/index.md
+++ b/docs/guide/browser/index.md
@@ -341,110 +341,107 @@ Headless mode is not available by default. You need to use either [`playwright`]
## Examples
-Browser Mode is framework agnostic so it doesn't provide any method to render your components. However, you should be able to use your framework's test utils packages.
+Vitest provides packages to render components for several popular frameworks out of the box:
-We recommend using `testing-library` packages depending on your framework:
+- [`vitest-browser-vue`](https://github.com/vitest-dev/vitest-browser-vue) to render [vue](https://vuejs.org) components
+- [`vitest-browser-svelte`](https://github.com/vitest-dev/vitest-browser-svelte) to render [svelte](https://svelte.dev) components
+- [`vitest-browser-react`](https://github.com/vitest-dev/vitest-browser-react) to render [react](https://react.dev) components
-- [`@testing-library/dom`](https://testing-library.com/docs/dom-testing-library/intro) if you don't use a framework
-- [`@testing-library/vue`](https://testing-library.com/docs/vue-testing-library/intro) to render [vue](https://vuejs.org) components
-- [`@testing-library/svelte`](https://testing-library.com/docs/svelte-testing-library/intro) to render [svelte](https://svelte.dev) components
-- [`@testing-library/react`](https://testing-library.com/docs/react-testing-library/intro) to render [react](https://react.dev) components
-- [`@testing-library/preact`](https://testing-library.com/docs/preact-testing-library/intro) to render [preact](https://preactjs.com) components
-- [`solid-testing-library`](https://testing-library.com/docs/solid-testing-library/intro) to render [solid](https://www.solidjs.com) components
-- [`@marko/testing-library`](https://testing-library.com/docs/marko-testing-library/intro) to render [marko](https://markojs.com) components
+If your framework is not represented, feel free to create your own package - it is a simple wrapper around the framework renderer and `page.elementLocator` API. We will add a link to it on this page. Make sure it has a name starting with `vitest-browser-`.
-Besides rendering components and querying elements using `@testing-library/your-framework`, you will also need to make assertions. Vitest bundles the [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. Read more at the [Assertions API](/guide/browser/assertion-api).
+Besides rendering components and locating elements, you will also need to make assertions. Vitest bundles the [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. Read more at the [Assertions API](/guide/browser/assertion-api).
```ts
import { expect } from 'vitest'
+import { page } from '@vitest/browser/context'
// element is rendered correctly
-await expect.element(screen.getByText('Hello World')).toBeInTheDocument()
+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).
```ts
-import { userEvent } from '@vitest/browser/context'
-await userEvent.type(screen.getByLabelText(/username/i), 'Alice')
+import { page, userEvent } from '@vitest/browser/context'
+await userEvent.fill(page.getByLabelText(/username/i), 'Alice')
+// or just locator.fill
+await page.getByLabelText(/username/i).fill('Alice')
```
-::: 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`](#interactivity-api) imported from `@vitest/browser/context` that uses Chrome DevTools Protocol or Webdriver (depending on the provider) under the hood.
-:::
-
::: code-group
```ts [vue]
-// based on @testing-library/vue example
-// https://testing-library.com/docs/vue-testing-library/examples
-
-import { userEvent } from '@vitest/browser/context'
-import { render, screen } from '@testing-library/vue'
+import { render } from 'vitest-browser-vue'
import Component from './Component.vue'
test('properly handles v-model', async () => {
- render(Component)
+ const screen = render(Component)
// Asserts initial state.
- expect(screen.getByText('Hi, my name is Alice')).toBeInTheDocument()
+ await expect.element(screen.getByText('Hi, my name is Alice')).toBeInTheDocument()
// Get the input DOM node by querying the associated label.
- const usernameInput = await screen.findByLabelText(/username/i)
+ const usernameInput = screen.getByLabelText(/username/i)
// Type the name into the input. This already validates that the input
// is filled correctly, no need to check the value manually.
- await userEvent.fill(usernameInput, 'Bob')
+ await usernameInput.fill('Bob')
- expect(screen.getByText('Hi, my name is Bob')).toBeInTheDocument()
+ await expect.element(screen.getByText('Hi, my name is Bob')).toBeInTheDocument()
})
```
```ts [svelte]
-// based on @testing-library/svelte
-// https://testing-library.com/docs/svelte-testing-library/example
-
-import { render, screen } from '@testing-library/svelte'
-import { userEvent } from '@vitest/browser/context'
+import { render } from 'vitest-browser-svelte'
import { expect, test } from 'vitest'
import Greeter from './greeter.svelte'
test('greeting appears on click', async () => {
- const user = userEvent.setup()
- render(Greeter, { name: 'World' })
+ const screen = render(Greeter, { name: 'World' })
const button = screen.getByRole('button')
- await user.click(button)
- const greeting = await screen.findByText(/hello world/iu)
+ await button.click()
+ const greeting = screen.getByText(/hello world/iu)
- expect(greeting).toBeInTheDocument()
+ await expect.element(greeting).toBeInTheDocument()
})
```
```tsx [react]
-// based on @testing-library/react example
-// https://testing-library.com/docs/react-testing-library/example-intro
-
-import { userEvent } from '@vitest/browser/context'
-import { render, screen } from '@testing-library/react'
+import { render } from 'vitest-browser-react'
import Fetch from './fetch'
test('loads and displays greeting', async () => {
// Render a React element into the DOM
- render()
+ const screen = render()
- await userEvent.click(screen.getByText('Load Greeting'))
+ await screen.getByText('Load Greeting').click()
// wait before throwing an error if it cannot find an element
- const heading = await screen.findByRole('heading')
+ const heading = screen.getByRole('heading')
// assert that the alert message is correct
- expect(heading).toHaveTextContent('hello there')
- expect(screen.getByRole('button')).toBeDisabled()
+ await expect.element(heading).toHaveTextContent('hello there')
+ await expect.element(screen.getByRole('button')).toBeDisabled()
})
```
+:::
+
+Vitest doesn't support all frameworks out of the box, but you can use external tools to run tests with these frameworks. We also encourage the community to create their own `vitest-browser` wrappers - if you have one, feel free to add it to the examples above.
+
+For unsupported frameworks, we recommend using `testing-library` packages:
+
+- [`@testing-library/preact`](https://testing-library.com/docs/preact-testing-library/intro) to render [preact](https://preactjs.com) components
+- [`solid-testing-library`](https://testing-library.com/docs/solid-testing-library/intro) to render [solid](https://www.solidjs.com) components
+- [`@marko/testing-library`](https://testing-library.com/docs/marko-testing-library/intro) to render [marko](https://markojs.com) components
+
+::: 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.
+:::
+
+::: code-block
```tsx [preact]
// based on @testing-library/preact example
// https://testing-library.com/docs/preact-testing-library/example
import { h } from 'preact'
-import { userEvent } from '@vitest/browser/context'
+import { page } from '@vitest/browser/context'
import { render } from '@testing-library/preact'
import HiddenMessage from '../hidden-message'
@@ -452,19 +449,21 @@ import HiddenMessage from '../hidden-message'
test('shows the children when the checkbox is checked', async () => {
const testMessage = 'Test Message'
- const { queryByText, getByLabelText, getByText } = render(
+ const { baseElement } = render(
{testMessage},
)
- // query* functions will return the element or null if it cannot be found.
- // get* functions will return the element or throw an error if it cannot be found.
- expect(queryByText(testMessage)).not.toBeInTheDocument()
+ const screen = page.elementLocator(baseElement)
+
+ // .query() will return the element or null if it cannot be found.
+ // .element() will return the element or throw an error if it cannot be found.
+ expect(screen.getByText(testMessage).query()).not.toBeInTheDocument()
// The queries can accept a regex to make your selectors more
// resilient to content tweaks and changes.
- await userEvent.click(getByLabelText(/show/i))
+ await screen.getByLabelText(/show/i).click()
- expect(getByText(testMessage)).toBeInTheDocument()
+ await expect.element(screen.getByText(testMessage)).toBeInTheDocument()
})
```
```tsx [solid]
@@ -488,8 +487,10 @@ it('uses params', async () => {
Start
} />
>
)
- const { findByText } = render(() => , { location: 'ids/1234' })
- expect(await findByText('Id: 1234')).toBeInTheDocument()
+ const { baseElement } = render(() => , { location: 'ids/1234' })
+ const screen = page.elementLocator(baseElement)
+
+ await expect.screen(screen.getByText('Id: 1234')).toBeInTheDocument()
})
```
```ts [marko]
@@ -500,9 +501,10 @@ import { render, screen } from '@marko/testing-library'
import Greeting from './greeting.marko'
test('renders a message', async () => {
- const { container } = await render(Greeting, { name: 'Marko' })
- expect(screen.getByText(/Marko/)).toBeInTheDocument()
- expect(container.firstChild).toMatchInlineSnapshot(`
+ const { baseElement } = await render(Greeting, { name: 'Marko' })
+ const screen = page.elementLocator(baseElement)
+ await expect.element(screen.getByText(/Marko/)).toBeInTheDocument()
+ await expect.element(container.firstChild).toMatchInlineSnapshot(`
Hello, Marko!
`)
})
diff --git a/docs/guide/browser/interactivity-api.md b/docs/guide/browser/interactivity-api.md
index 66bd614ba..537abc23d 100644
--- a/docs/guide/browser/interactivity-api.md
+++ b/docs/guide/browser/interactivity-api.md
@@ -35,10 +35,6 @@ Almost every `userEvent` method inherits its provider options. To see all availa
```
:::
-::: warning
-This page uses `@testing-library/dom` in examples to query elements. If you are using a framework like Vue, React or any other, use `@testing-library/{framework-name}` instead. Simple examples are available on the [Browser Mode page](/guide/browser/#examples).
-:::
-
## userEvent.setup
- **Type:** `() => UserEvent`
@@ -69,13 +65,14 @@ This behaviour is more useful because we do not emulate the keyboard, we actuall
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 { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('clicks on an element', async () => {
- const logo = screen.getByRole('img', { name: /logo/ })
+ const logo = page.getByRole('img', { name: /logo/ })
await userEvent.click(logo)
+ // or you can access it directly on the locator
+ await logo.click()
})
```
@@ -94,13 +91,14 @@ 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 { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('triggers a double click on an element', async () => {
- const logo = screen.getByRole('img', { name: /logo/ })
+ const logo = page.getByRole('img', { name: /logo/ })
await userEvent.dblClick(logo)
+ // or you can access it directly on the locator
+ await logo.dblClick()
})
```
@@ -119,11 +117,10 @@ 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 { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('triggers a triple click on an element', async () => {
- const logo = screen.getByRole('img', { name: /logo/ })
+ const logo = page.getByRole('img', { name: /logo/ })
let tripleClickFired = false
logo.addEventListener('click', (evt) => {
if (evt.detail === 3) {
@@ -132,6 +129,9 @@ test('triggers a triple click on an element', async () => {
})
await userEvent.tripleClick(logo)
+ // or you can access it directly on the locator
+ await logo.tripleClick()
+
expect(tripleClickFired).toBe(true)
})
```
@@ -149,15 +149,17 @@ References:
Set a value to the `input/textarea/conteneditable` field. This will remove any existing text in the input before setting the new value.
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('update input', async () => {
- const input = screen.getByRole('input')
+ const input = page.getByRole('input')
await userEvent.fill(input, 'foo') // input.value == foo
await userEvent.fill(input, '{{a[[') // input.value == {{a[[
await userEvent.fill(input, '{Shift}') // input.value == {Shift}
+
+ // or you can access it directly on the locator
+ await input.fill('foo') // input.value == foo
})
```
@@ -208,11 +210,10 @@ References:
Sends a `Tab` key event. This is a shorthand for `userEvent.keyboard('{tab}')`.
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('tab works', async () => {
- const [input1, input2] = screen.getAllByRole('input')
+ const [input1, input2] = page.getByRole('input').elements()
expect(input1).toHaveFocus()
@@ -247,11 +248,10 @@ This function allows you to type characters into an input/textarea/conteneditabl
If you just need to press characters without an input, use [`userEvent.keyboard`](#userevent-keyboard) API.
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('update input', async () => {
- const input = screen.getByRole('input')
+ const input = page.getByRole('input')
await userEvent.type(input, 'foo') // input.value == foo
await userEvent.type(input, '{{a[[') // input.value == foo{a[
@@ -259,6 +259,10 @@ test('update input', async () => {
})
```
+::: note
+Vitest doesn't expose `.type` method on the locator like `input.type` because it exists only for compatiblity with the `userEvent` library. Consider using `.fill` instead as it is faster.
+:::
+
References:
- [Playwright `locator.press` API](https://playwright.dev/docs/api/class-locator#locator-press)
@@ -272,16 +276,18 @@ References:
This method clears the input element content.
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('clears input', async () => {
- const input = screen.getByRole('input')
+ const input = page.getByRole('input')
await userEvent.fill(input, 'foo')
expect(input).toHaveValue('foo')
await userEvent.clear(input)
+ // or you can access it directly on the locator
+ await input.clear()
+
expect(input).toHaveValue('')
})
```
@@ -305,21 +311,23 @@ Unlike `@testing-library`, Vitest doesn't support [listbox](https://developer.mo
:::
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('clears input', async () => {
- const select = screen.getByRole('select')
+ const select = page.getByRole('select')
await userEvent.selectOptions(select, 'Option 1')
+ // or you can access it directly on the locator
+ await select.selectOptions('Option 1')
+
expect(select).toHaveValue('option-1')
await userEvent.selectOptions(select, 'option-1')
expect(select).toHaveValue('option-1')
await userEvent.selectOptions(select, [
- screen.getByRole('option', { name: 'Option 1' }),
- screen.getByRole('option', { name: 'Option 2' }),
+ page.getByRole('option', { name: 'Option 1' }),
+ page.getByRole('option', { name: 'Option 2' }),
])
expect(select).toHaveValue(['option-1', 'option-2'])
})
@@ -348,13 +356,14 @@ If you are using `playwright` provider, the cursor moves to "some" visible point
:::
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('hovers logo element', async () => {
- const logo = screen.getByRole('img', { name: /logo/ })
+ const logo = page.getByRole('img', { name: /logo/ })
await userEvent.hover(logo)
+ // or you can access it directly on the locator
+ await page.hover()
})
```
@@ -375,13 +384,14 @@ By default, the cursor position is in "some" visible place (in `playwright` prov
:::
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('unhover logo element', async () => {
- const logo = screen.getByRole('img', { name: /logo/ })
+ const logo = page.getByRole('img', { name: /logo/ })
await userEvent.unhover(logo)
+ // or you can access it directly on the locator
+ await page.unhover()
})
```
@@ -398,14 +408,15 @@ References:
Drags the source element on top of the target element. Don't forget that the `source` element has to have the `draggable` attribute set to `true`.
```ts
-import { userEvent } from '@vitest/browser/context'
-import { screen } from '@testing-library/dom'
+import { page, userEvent } from '@vitest/browser/context'
test('drag and drop works', async () => {
- const source = screen.getByRole('img', { name: /logo/ })
- const target = screen.getByTestId('logo-target')
+ const source = page.getByRole('img', { name: /logo/ })
+ const target = page.getByTestId('logo-target')
await userEvent.dragAndDrop(source, target)
+ // or you can access it directly on the locator
+ await source.dropTo(target)
await expect.element(target).toHaveTextContent('Logo is processed')
})
diff --git a/packages/browser/src/node/plugin.ts b/packages/browser/src/node/plugin.ts
index 38b72b87a..749442364 100644
--- a/packages/browser/src/node/plugin.ts
+++ b/packages/browser/src/node/plugin.ts
@@ -205,6 +205,52 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
}
}
+ const include = [
+ 'vitest > @vitest/snapshot > magic-string',
+ 'vitest > chai',
+ 'vitest > chai > loupe',
+ 'vitest > @vitest/utils > loupe',
+ '@vitest/browser > @testing-library/user-event',
+ '@vitest/browser > @testing-library/dom',
+ ]
+
+ const react = tryResolve('vitest-browser-react', [project.ctx.config.root])
+ if (react) {
+ include.push(react)
+ }
+ const vue = tryResolve('vitest-browser-react', [project.ctx.config.root])
+ if (vue) {
+ include.push(vue)
+ }
+
+ const exclude = [
+ 'vitest',
+ 'vitest/utils',
+ 'vitest/browser',
+ 'vitest/runners',
+ '@vitest/browser',
+ '@vitest/browser/client',
+ '@vitest/utils',
+ '@vitest/utils/source-map',
+ '@vitest/runner',
+ '@vitest/spy',
+ '@vitest/utils/error',
+ '@vitest/snapshot',
+ '@vitest/expect',
+ 'std-env',
+ 'tinybench',
+ 'tinyspy',
+ 'tinyrainbow',
+ 'pathe',
+ 'msw',
+ 'msw/browser',
+ ]
+
+ const svelte = tryResolve('vitest-browser-svelte', [project.ctx.config.root])
+ if (svelte) {
+ exclude.push(svelte)
+ }
+
return {
define,
resolve: {
@@ -212,36 +258,8 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
},
optimizeDeps: {
entries,
- exclude: [
- 'vitest',
- 'vitest/utils',
- 'vitest/browser',
- 'vitest/runners',
- '@vitest/browser',
- '@vitest/browser/client',
- '@vitest/utils',
- '@vitest/utils/source-map',
- '@vitest/runner',
- '@vitest/spy',
- '@vitest/utils/error',
- '@vitest/snapshot',
- '@vitest/expect',
- 'std-env',
- 'tinybench',
- 'tinyspy',
- 'tinyrainbow',
- 'pathe',
- 'msw',
- 'msw/browser',
- ],
- include: [
- 'vitest > @vitest/snapshot > magic-string',
- 'vitest > chai',
- 'vitest > chai > loupe',
- 'vitest > @vitest/utils > loupe',
- '@vitest/browser > @testing-library/user-event',
- '@vitest/browser > @testing-library/dom',
- ],
+ exclude,
+ include,
},
}
},
diff --git a/packages/vitest/src/create/browser/creator.ts b/packages/vitest/src/create/browser/creator.ts
index 7de4fb638..df5903f16 100644
--- a/packages/vitest/src/create/browser/creator.ts
+++ b/packages/vitest/src/create/browser/creator.ts
@@ -105,13 +105,13 @@ function getFramework(): prompt.Choice[] {
function getFrameworkTestPackage(framework: string) {
switch (framework) {
case 'vanilla':
- return '@testing-library/dom'
+ return null
case 'vue':
- return '@testing-library/vue'
+ return 'vitest-browser-vue'
case 'svelte':
- return '@testing-library/svelte'
+ return 'vitest-browser-svelte'
case 'react':
- return '@testing-library/react'
+ return 'vitest-browser-react'
case 'preact':
return '@testing-library/preact'
case 'solid':
@@ -431,9 +431,13 @@ export async function create() {
const dependenciesToInstall = [
'@vitest/browser',
- getFrameworkTestPackage(framework),
]
+ const frameworkPackage = getFrameworkTestPackage(framework)
+ if (frameworkPackage) {
+ dependenciesToInstall.push(frameworkPackage)
+ }
+
const providerPkg = getProviderPackageNames(provider)
if (providerPkg.pkg) {
dependenciesToInstall.push(providerPkg.pkg)
diff --git a/packages/vitest/src/create/browser/examples.ts b/packages/vitest/src/create/browser/examples.ts
index b492b8915..6be47158c 100644
--- a/packages/vitest/src/create/browser/examples.ts
+++ b/packages/vitest/src/create/browser/examples.ts
@@ -29,8 +29,7 @@ import HelloWorld from './HelloWorld.jsx'
test('renders name', () => {
const { getByText } = render()
- const element = getByText('Hello Vitest!')
- expect(element).toBeInTheDocument()
+ await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`,
}
@@ -65,15 +64,14 @@ defineProps<{
`,
test: `
import { expect, test } from 'vitest'
-import { render } from '@testing-library/vue'
+import { render } from 'vitest-browser-vue'
import HelloWorld from './HelloWorld.vue'
test('renders name', () => {
const { getByText } = render(HelloWorld, {
props: { name: 'Vitest' },
})
- const element = getByText('Hello Vitest!')
- expect(element).toBeInTheDocument()
+ await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`,
}
@@ -96,15 +94,12 @@ const svelteExample = {
`,
test: `
import { expect, test } from 'vitest'
-import { render } from '@testing-library/svelte'
+import { render } from 'vitest-browser-svelte'
import HelloWorld from './HelloWorld.svelte'
test('renders name', () => {
- const { getByText } = render(HelloWorld, {
- props: { name: 'Vitest' },
- })
- const element = getByText('Hello Vitest!')
- expect(element).toBeInTheDocument()
+ const { getByText } = render(HelloWorld, { name: 'Vitest' })
+ await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`,
}
@@ -183,11 +178,15 @@ function getExampleTest(framework: string) {
switch (framework) {
case 'solid':
case 'preact':
- case 'react':
return {
...jsxExample,
test: jsxExample.test.replace('@testing-library/jsx', `@testing-library/${framework}`),
}
+ case 'react':
+ return {
+ ...jsxExample,
+ test: jsxExample.test.replace('@testing-library/jsx', 'vitest-browser-react'),
+ }
case 'vue':
return vueExample
case 'svelte':
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2c7af7727..78c6cce8e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1108,6 +1108,9 @@ importers:
vitest:
specifier: workspace:*
version: link:../../packages/vitest
+ vitest-browser-react:
+ specifier: ^0.0.1
+ version: 0.0.1(@types/react-dom@18.2.14)(@types/react@18.2.79)(@vitest/browser@packages+browser)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vitest@packages+vitest)
webdriverio:
specifier: ^8.32.2
version: 8.32.2(typescript@5.5.4)
@@ -1287,7 +1290,7 @@ importers:
version: 3.4.37(typescript@5.5.4)
webdriverio:
specifier: latest
- version: 8.40.0
+ version: 8.40.2
test/global-setup:
devDependencies:
@@ -1402,7 +1405,7 @@ importers:
version: link:../../packages/vitest
webdriverio:
specifier: latest
- version: 8.40.0
+ version: 8.40.2
test/workspaces:
devDependencies:
@@ -4201,8 +4204,8 @@ packages:
resolution: {integrity: sha512-yNuGPMPibY91s936gnJCHWlStvIyDrwLwGfLC/NCdTin4F7HL4Gp5iJnHWkJFty1/DfFi8jjoIUBNLM8HEez+A==}
engines: {node: ^16.13 || >=18}
- '@wdio/config@8.40.0':
- resolution: {integrity: sha512-sE+sBXUz4ZggS253hLNVu64ZCpm5ZidQ/IJNeM9Exh5OcsuZEnSeuqCZnd4ytK68A2heyZk8r2OjYZriA4l/Sg==}
+ '@wdio/config@8.40.2':
+ resolution: {integrity: sha512-RED2vcdX5Zdd6r+K+aWcjK4douxjJY4LP/8YvvavgqM0TURd5PDI0Y7IEz7+BIJOT4Uh+3atZawIN9/3yWFeag==}
engines: {node: ^16.13 || >=18}
'@wdio/logger@8.28.0':
@@ -4239,8 +4242,8 @@ packages:
resolution: {integrity: sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==}
engines: {node: ^16.13 || >=18}
- '@wdio/utils@8.40.0':
- resolution: {integrity: sha512-P9b6XbRDRfCsZvdA70VYQrnsbkDVwEXlAGe4v4hcdgFxz81w+k4IX5bFUb7IB33E+3EZ/GhJWVU3QHgI9Y0u6w==}
+ '@wdio/utils@8.40.2':
+ resolution: {integrity: sha512-leYcCUSaAdLUCVKqRKNgMCASPOUo/VvOTKETiZ/qpdY2azCBt/KnLugtiycCzakeYg6Kp+VIjx5fkm0M7y4qhA==}
engines: {node: ^16.13 || >=18}
'@yeger/debounce@2.0.9':
@@ -8892,6 +8895,22 @@ packages:
postcss:
optional: true
+ vitest-browser-react@0.0.1:
+ resolution: {integrity: sha512-075hxhouuuMeCqHRyX4POzZWEDD3aPxFxDcwENN+T2ZlJ4lh5vUlgbkV4C4A0KZWinHb/Wsj3pYEJlDgIO23CQ==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ peerDependencies:
+ '@types/react': '>18.0.0'
+ '@types/react-dom': '>18.0.0'
+ '@vitest/browser': ^2.1.0-beta.4
+ react: '>18.0.0'
+ react-dom: '>18.0.0'
+ vitest: workspace:*
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
vitest-environment-custom@file:test/core/vitest-environment-custom:
resolution: {directory: test/core/vitest-environment-custom, type: directory}
@@ -8996,8 +9015,8 @@ packages:
resolution: {integrity: sha512-Kc3+SfiH4ufyrIht683VT2vnJocx0pfH8rYdyPvEh1b2OYewtFTHK36k9rBDHZiBmk6jcSXs4M2xeFgOuon9Lg==}
engines: {node: ^16.13 || >=18}
- webdriver@8.40.0:
- resolution: {integrity: sha512-pAuU8FbFXox837UgxjC2yT4s+goLBcqohdCSZJ1f1wG/XMsgjDHmouU6+f1SHHG7/I0IDGEZIsRD01RM57F3OA==}
+ webdriver@8.40.2:
+ resolution: {integrity: sha512-GoRR94m3yL8tWC9Myf+xIBSdVK8fi1ilZgEZZaYT8+XIWewR02dvrC6rml+/2ZjXUQzeee0RFGDwk9IC7cyYrg==}
engines: {node: ^16.13 || >=18}
webdriverio@8.32.2:
@@ -9018,8 +9037,8 @@ packages:
devtools:
optional: true
- webdriverio@8.40.0:
- resolution: {integrity: sha512-AYFLdfVt3wcDdnyxRDBlysOgB3XryLZrZdmtjUU842IyMcoV4Cq3SdVgz9aj9tskSeIJ3G37KgDEf5znnd5f3Q==}
+ webdriverio@8.40.2:
+ resolution: {integrity: sha512-6yuzUlE064qNuMy98Du1+8QHbXk0st8qTWF7MDZRgYK19FGoy+KhQbaUv1wlFJuFHM0PiAYuduTURL4ub6HvzQ==}
engines: {node: ^16.13 || >=18}
peerDependencies:
devtools: ^8.14.0
@@ -12597,11 +12616,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@wdio/config@8.40.0':
+ '@wdio/config@8.40.2':
dependencies:
'@wdio/logger': 8.38.0
'@wdio/types': 8.39.0
- '@wdio/utils': 8.40.0
+ '@wdio/utils': 8.40.2
decamelize: 6.0.0
deepmerge-ts: 5.1.0
glob: 10.4.1
@@ -12675,9 +12694,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@wdio/utils@8.40.0':
+ '@wdio/utils@8.40.2':
dependencies:
- '@puppeteer/browsers': 1.7.0
+ '@puppeteer/browsers': 1.9.1
'@wdio/logger': 8.38.0
'@wdio/types': 8.39.0
decamelize: 6.0.0
@@ -18071,6 +18090,16 @@ snapshots:
- typescript
- universal-cookie
+ vitest-browser-react@0.0.1(@types/react-dom@18.2.14)(@types/react@18.2.79)(@vitest/browser@packages+browser)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vitest@packages+vitest):
+ dependencies:
+ '@vitest/browser': link:packages/browser
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ vitest: link:packages/vitest
+ optionalDependencies:
+ '@types/react': 18.2.79
+ '@types/react-dom': 18.2.14
+
vitest-environment-custom@file:test/core/vitest-environment-custom: {}
vitest-sonar-reporter@0.4.1(vitest@packages+vitest):
@@ -18200,15 +18229,15 @@ snapshots:
- supports-color
- utf-8-validate
- webdriver@8.40.0:
+ webdriver@8.40.2:
dependencies:
'@types/node': 20.14.14
'@types/ws': 8.5.12
- '@wdio/config': 8.40.0
+ '@wdio/config': 8.40.2
'@wdio/logger': 8.38.0
'@wdio/protocols': 8.38.0
'@wdio/types': 8.39.0
- '@wdio/utils': 8.40.0
+ '@wdio/utils': 8.40.2
deepmerge-ts: 5.1.0
got: 12.6.1
ky: 0.33.3
@@ -18285,15 +18314,15 @@ snapshots:
- typescript
- utf-8-validate
- webdriverio@8.40.0:
+ webdriverio@8.40.2:
dependencies:
'@types/node': 20.14.13
- '@wdio/config': 8.40.0
+ '@wdio/config': 8.40.2
'@wdio/logger': 8.38.0
'@wdio/protocols': 8.38.0
'@wdio/repl': 8.24.12
'@wdio/types': 8.39.0
- '@wdio/utils': 8.40.0
+ '@wdio/utils': 8.40.2
archiver: 7.0.1
aria-query: 5.3.0
css-shorthand-properties: 1.1.1
@@ -18311,7 +18340,7 @@ snapshots:
resq: 1.11.0
rgb2hex: 0.2.5
serialize-error: 11.0.2
- webdriver: 8.40.0
+ webdriver: 8.40.2
transitivePeerDependencies:
- bufferutil
- encoding
diff --git a/test/browser/fixtures/locators/blog.test.tsx b/test/browser/fixtures/locators/blog.test.tsx
index 1a50c29e2..fc54b916e 100644
--- a/test/browser/fixtures/locators/blog.test.tsx
+++ b/test/browser/fixtures/locators/blog.test.tsx
@@ -1,12 +1,9 @@
import { expect, test } from 'vitest'
-import { render } from '@testing-library/react'
import { page } from '@vitest/browser/context'
import Blog from '../../src/blog-app/blog'
test('renders blog posts', async () => {
- const { container } = render()
-
- const screen = page.elementLocator(container)
+ const screen = page.render()
await expect.element(screen.getByRole('heading', { name: 'Blog' })).toBeInTheDocument()
diff --git a/test/browser/fixtures/locators/vitest.config.ts b/test/browser/fixtures/locators/vitest.config.ts
index 8edb8f6cf..e32545f4a 100644
--- a/test/browser/fixtures/locators/vitest.config.ts
+++ b/test/browser/fixtures/locators/vitest.config.ts
@@ -11,6 +11,7 @@ export default defineConfig({
},
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
test: {
+ setupFiles: ['vitest-browser-react'],
browser: {
enabled: true,
provider,
diff --git a/test/browser/package.json b/test/browser/package.json
index 834f456b9..9d7384c67 100644
--- a/test/browser/package.json
+++ b/test/browser/package.json
@@ -31,6 +31,7 @@
"strip-ansi": "^7.1.0",
"url": "^0.11.3",
"vitest": "workspace:*",
+ "vitest-browser-react": "^0.0.1",
"webdriverio": "^8.32.2"
}
}
diff --git a/test/browser/tsconfig.json b/test/browser/tsconfig.json
index 06bca217a..0468f44e9 100644
--- a/test/browser/tsconfig.json
+++ b/test/browser/tsconfig.json
@@ -8,7 +8,8 @@
},
"types": [
"vite/client",
- "@vitest/browser/providers/playwright"
+ "@vitest/browser/providers/playwright",
+ "vitest-browser-react"
],
"esModuleInterop": true
}