React Component Testing with Vitest's Browser Mode and Playwright


Vitest and React Testing Library are great for component testing. We all love to see those console messages with our tests passing so we can confidently kick off that deployment. However, unit testing front-end components was always a bit tricky for two reasons:
We didn’t actually see the components that were rendered during testing
We didn’t know how they would behave in a real browser since we’d be using JSDom or HappyDOM to simulate a browser for our tests. These are far less complex than real browsers, so the tests aren’t that reliable after all.
Vitest Browser Mode was created to address the above problems.
Although you’ll see Playwright here, this is not end-to-end testing. That requires a lot more setup compared to Browser Mode, which can be accomplished by only a few lines.
Let’s see how.
Project Setup
If you have an existing project, skip to Vitest Setup.
React Router with a simple Search component
To kick things off, I’m creating a React Router project:
npx create-react-router@latest
This will set you up with a simple React application. Just make sure it runs as expected by opening http://localhost:5173/
.
$ npm run dev
> dev
> react-router dev
Re-optimizing dependencies because lockfile has changed
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
Let’s create a new folder components
with one file inside Search.tsx
. This will be our simple search component, styled with TailwindCSS:
import React, { useState } from 'react';
const Search: React.FC = () => {
const [query, setQuery] = useState('');
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value);
};
const handleSearch = () => {
console.log('Searching for:', query);
// Add your search logic here
};
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<div className="w-full max-w-md">
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="Search..."
className="w-full p-2 border border-gray-300 rounded mb-4"
/>
<button
onClick={handleSearch}
className="w-full p-2 bg-blue-500 text-white rounded"
>
Search
</button>
</div>
</div>
);
};
export default Search;
Now open app/welcome/welcome.tsx
and replace the contents with the following:
import Search from "~/components/Search";
export function Welcome() {
return (
<main className="flex items-center justify-center">
<Search />
</main>
);
}
After this, just reload the app, and you should see something like this:
Don’t see this input? Leave a comment or contact me on hello at akoskm dot com.
Vitest Setup
Vitest Browser Mode
Once you verify that the new React Router project is running, in the root of your project folder, run the command npx vitest init browser
.
This will ask you some basic questions about your project and will generate you a couple of files to get started:
workspace files
a HelloWorld component and a HelloWorld browser test - remove these
There are a couple of things you still need to change to make browser tests work. Let’s go through these.
Add some tests
First, let’s create a simple test next to our Search.tsx
component app/components/Search.test.tsx
:
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-react'
import Search from './Search'
test('renders search', async () => {
const { getByText } = render(<Search />)
await expect.element(getByText('Search')).toBeInTheDocument()
})
React Router created a TypeScript project for you, so you’ll get errors whenever you try to use a library whose types are unknown to TypeScript:
Luckily, this is something we can easily fix.
Types Configuration
Add @vitest/browser/matchers
to compilerOptions.types
inside tsconfig.json
:
{
// ...
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client", "@vitest/browser/matchers"],
// ...
}
}
Vitest Configuration
Right now, the CLI tool creates an invalid configuration object, and if you try to run your test, you’d get this error:
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.
There’s an issue and a PR for this already, so let’s hope I can remove this step soon 🤞
While this is an issue, you can just fix the configuration for yourself by opening vitest.workspace.ts
and replacing configs
with instances
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
// If you want to keep running your existing tests in Node.js, uncomment the next line.
// 'vite.config.ts',
{
extends: 'vite.config.ts',
test: {
browser: {
enabled: true,
provider: 'playwright',
// https://vitest.dev/guide/browser/playwright
// before ❌
// configs: [
// ],
// after ✅
instances: [
{ browser: 'chromium' },
],
},
},
},
])
There’s one more error we’ll run into, so let’s fix that.
Vite Setup
Now you could run the tests using the npm run test:browser
command that the CLI tool suggested, and your selected browser would show up, but you’d still be seeing an error: React Router Vite plugin can't detect preamble.
app/components/Search.test.tsx
Error: React Router Vite plugin can't detect preamble. Something is wrong.
- /app/components/Search.tsx:1:466
You can see how to fix this in my other blog post about Vitest and React Router, but I’ll also provide the configuration for this case.
To fix this error, you simply have to disable React Router when the tests are running using !process.env.VITEST && reactRouter()
. Open vite.config.ts
and make the following change:
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
// before
// plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
// after 👇
plugins: [tailwindcss(), !process.env.VITEST && reactRouter(), tsconfigPaths()],
});
That’s it! Now you can run your first test in Browser Mode!
Running The Tests
You can do this by using npx vitest
or with npm run test:browser
that’s a command vitest init
injected into your package.json
file. The browser should show up immediately, and you should see the test for your Search component passing:
You’ll also see the test passing in the terminal:
Conclusions
You just learned how to set up Vitest Browser Mode in a React Router project.
As a quick reminder, here’s a checklist for your next project:
Vitest Initialization: Run
npx vitest init browser
in the root of your project to set up Vitest Browser Mode.Types Configuration: Add
@vitest/browser/matchers
tocompilerOptions.types
intsconfig.json
to fix TypeScript errors.Fix Vitest Configuration: Open
vitest.workspace.ts
and replaceconfigs
withinstances
.Vite Configuration: Modify
vite.config.ts
to disable React Router during tests using!process.env.VITEST && reactRouter()
.
GitHub Repo
Subscribe to my newsletter
Read articles from Ákos Kőműves directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ákos Kőműves
Ákos Kőműves
I build web apps and make educational content to help web developers.