Search the site
Find pages, services and blog posts
Skip to content
All posts

Migrating from @testing-library/svelte to vitest-browser-svelte

  • svelte
  • sveltekit
  • testing
  • migration
  • vitest

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 playwright

Step 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=false

Use Playwright’s UI Mode

1pnpm vitest --ui

Inspect 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 test

Migration 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.