All Posts

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

sveltesveltekittestingmigrationvitest

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

pnpm add -D vitest-browser-svelte @vitest/browser playwright

Step 2: Update Vitest Config

// vitest.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';

export default defineConfig({
	plugins: [sveltekit()],
	test: {
		include: ['src/**/*.{test,spec}.{js,ts}'],
		browser: {
			enabled: true,
			provider: 'playwright',
			instances: [{ browser: 'chromium' }],
		},
	},
});

Step 3: Update Imports

The biggest change is the import path:

// Before
import { render, screen } from '@testing-library/svelte';

// After
import { render, screen } from 'vitest-browser-svelte';

Step 4: Update Assertions

Assertions become async with expect.element():

// Before
expect(screen.getByRole('button')).toHaveTextContent('Click me');

// After
await expect.element(screen.getByRole('button')).toHaveTextContent(
	'Click me',
);

Step 5: Update Event Handling

Events use locator methods directly:

// Before
import { fireEvent } from '@testing-library/svelte';
fireEvent.click(screen.getByRole('button'));

// After
await screen.getByRole('button').click();

Step 6: Handle userEvent Changes

If you used @testing-library/user-event:

// Before
import userEvent from '@testing-library/user-event';
await userEvent.type(screen.getByRole('textbox'), 'hello');

// After
await screen.getByRole('textbox').fill('hello');

Common Migration Patterns

Container Queries

// Before
const { container } = render(Component);
const element = container.querySelector('.my-class');

// After
const { container } = render(Component);
const element = container.locator('.my-class');
await expect.element(element).toBeVisible();

Waiting for Elements

// Before
await waitFor(() => {
	expect(screen.getByText('Loaded')).toBeInTheDocument();
});

// After
await expect.element(screen.getByText('Loaded')).toBeVisible();

Component Props Updates

// Before
const { rerender } = render(Component, { props: { count: 0 } });
await rerender({ count: 1 });

// After
const { rerender } = render(Component, { props: { count: 0 } });
await rerender({ props: { count: 1 } });

Debugging Tips

Run Tests in Headed Mode

pnpm vitest --browser.headless=false

Use Playwright’s UI Mode

pnpm vitest --ui

Inspect Failing Tests

test('debug example', async () => {
	render(Component);
	await page.pause(); // Stops execution, opens inspector
});

CI Configuration

For GitHub Actions:

- name: Install Playwright
  run: pnpm exec playwright install chromium

- name: Run tests
  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.