If your SvelteKit project uses @testing-library/svelte with JSDOM,
it’s time to consider migrating to vitest-browser-svelte. Here’s how
to do it systematically.
Why Migrate?
@testing-library/svelte served us well, but:
- JSDOM doesn’t support Svelte 5’s runes properly
- Real browser testing catches bugs JSDOM misses
- The ecosystem is moving to browser-based testing
- Better debugging with Playwright’s tools
Prerequisites
You’ll need:
- Vitest 3.x or higher
- Node.js 18+
- Playwright installed
Step 1: Install Dependencies
1pnpm add -D vitest-browser-svelte @vitest/browser playwrightStep 2: Update Vitest Config
1// vitest.config.ts
2import { sveltekit } from '@sveltejs/kit/vite';
3import { defineConfig } from 'vitest/config';
4
5export default defineConfig({
6 plugins: [sveltekit()],
7 test: {
8 include: ['src/**/*.{test,spec}.{js,ts}'],
9 browser: {
10 enabled: true,
11 provider: 'playwright',
12 instances: [{ browser: 'chromium' }],
13 },
14 },
15});Step 3: Update Imports
The biggest change is the import path:
1// Before
2import { render, screen } from '@testing-library/svelte';
3
4// After
5import { render, screen } from 'vitest-browser-svelte';Step 4: Update Assertions
Assertions become async with expect.element():
1// Before
2expect(screen.getByRole('button')).toHaveTextContent('Click me');
3
4// After
5await expect
6 .element(screen.getByRole('button'))
7 .toHaveTextContent('Click me');Step 5: Update Event Handling
Events use locator methods directly:
1// Before
2import { fireEvent } from '@testing-library/svelte';
3fireEvent.click(screen.getByRole('button'));
4
5// After
6await screen.getByRole('button').click();Step 6: Handle userEvent Changes
If you used @testing-library/user-event:
1// Before
2import userEvent from '@testing-library/user-event';
3await userEvent.type(screen.getByRole('textbox'), 'hello');
4
5// After
6await screen.getByRole('textbox').fill('hello');Common Migration Patterns
Container Queries
1// Before
2const { container } = render(Component);
3const element = container.querySelector('.my-class');
4
5// After
6const { container } = render(Component);
7const element = container.locator('.my-class');
8await expect.element(element).toBeVisible();Waiting for Elements
1// Before
2await waitFor(() => {
3 expect(screen.getByText('Loaded')).toBeInTheDocument();
4});
5
6// After
7await expect.element(screen.getByText('Loaded')).toBeVisible();Component Props Updates
1// Before
2const { rerender } = render(Component, { props: { count: 0 } });
3await rerender({ count: 1 });
4
5// After
6const { rerender } = render(Component, { props: { count: 0 } });
7await rerender({ props: { count: 1 } });Debugging Tips
Run Tests in Headed Mode
1pnpm vitest --browser.headless=falseUse Playwright’s UI Mode
1pnpm vitest --uiInspect Failing Tests
1test('debug example', async () => {
2 render(Component);
3 await page.pause(); // Stops execution, opens inspector
4});CI Configuration
For GitHub Actions:
1- name: Install Playwright
2 run: pnpm exec playwright install chromium
3
4- name: Run tests
5 run: pnpm testMigration Checklist
- Install new dependencies
- Update vitest.config.ts
- Replace imports file by file
- Update assertions to async
- Replace fireEvent with locator methods
- Update userEvent to fill/click/etc
- Test in CI with Playwright installed
- Remove old @testing-library dependencies
Getting Stuck?
Migration can surface hidden issues in your test suite. If you need help modernizing your SvelteKit testing infrastructure - let’s talk.