All Posts

Why Your Svelte 5 Tests Should Run in Real Browsers

sveltesveltekittestingvitestteams

I first heard about vitest-browser-svelte at Svelte Summit from Dominik G, one of the core maintainers of SvelteKit and Vite. The pitch was simple: test Svelte components in actual browsers instead of simulated environments like JSDOM.

After migrating several production codebases, I’m convinced this is how all Svelte teams should test.

The Problem with JSDOM

JSDOM simulates a browser environment in Node.js. It’s fast and convenient, but:

  • No real layout - getBoundingClientRect() returns zeros
  • No real events - Synthetic events don’t bubble like real ones
  • No real CSS - Media queries, animations, transitions don’t work
  • Missing APIs - ResizeObserver, IntersectionObserver need polyfills

Your tests pass, but they’re testing a simulation, not reality.

What Changes with Browser Mode

Vitest’s browser mode runs your tests in actual Chromium, Firefox, or WebKit via Playwright:

// vitest.config.ts
export default defineConfig({
	plugins: [sveltekit()],
	test: {
		browser: {
			enabled: true,
			provider: 'playwright',
			instances: [{ browser: 'chromium' }],
		},
	},
});

Your component renders in a real browser. Events fire through the real DOM. CSS actually applies.

The vitest-browser-svelte API

The API is clean and familiar:

import { render, screen } from 'vitest-browser-svelte';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';

test('increments count on click', async () => {
	render(Counter, { props: { initial: 0 } });

	const button = screen.getByRole('button');
	await button.click();

	await expect.element(button).toHaveTextContent('Count: 1');
});

Key differences from @testing-library/svelte:

  • Locators, not elements - screen.getByRole() returns a locator
  • Async assertions - expect.element() with built-in waiting
  • Real clicks - await button.click() fires real browser events

Migration Path

For teams on @testing-library/svelte:

  1. Install: vitest-browser-svelte, @vitest/browser, playwright
  2. Update vitest config for browser mode
  3. Replace imports: vitest-browser-svelte instead of @testing-library/svelte
  4. Update assertions to async pattern

Most tests migrate with minimal changes. The ones that break usually reveal real bugs your JSDOM tests were missing.

When to Use This

Browser mode is ideal for:

  • Component libraries - Test real rendering and interactions
  • Complex forms - Validation, focus management, accessibility
  • Animations/transitions - Actually test CSS behaviors
  • Cross-browser testing - Run same tests in multiple browsers

Keep unit tests in Node for pure logic. Use browser mode for anything touching the DOM.

Team Benefits

For development teams:

  • Confidence - Tests verify real browser behavior
  • Fewer production bugs - Catch issues JSDOM misses
  • Better DX - Playwright’s debugging tools are excellent
  • Future-proof - This is where the ecosystem is heading

Need Help Migrating?

I’ve set up Vitest browser mode testing for multiple SvelteKit teams. If you’re stuck on legacy testing patterns or want to modernize your test infrastructure - let’s talk.