vitest-browser-svelte
The community vitest-browser-svelte package renders Svelte components in Browser Mode.
import { render } from 'vitest-browser-svelte'
import { expect, test } from 'vitest'
import Component from './Component.svelte'
test('counter button increments the count', async () => {
const screen = await render(Component, {
initialCount: 1,
})
await screen.getByRole('button', { name: 'Increment' }).click()
await expect.element(screen.getByText('Count is 2')).toBeVisible()
})WARNING
This library takes inspiration from @testing-library/svelte.
If you have used @testing-library/svelte in your tests before, you can keep using it, however the vitest-browser-svelte package provides certain benefits unique to the Browser Mode that @testing-library/svelte lacks:
vitest-browser-svelte returns APIs that interact well with built-in locators, user events and assertions: for example, Vitest will automatically retry the element until the assertion is successful, even if it was rerendered between the assertions.
The package exposes two entry points: vitest-browser-svelte and vitest-browser-svelte/pure. They expose identical API, but the pure entry point doesn't add a handler to remove the component before the next test has started.
render
export function render<C extends Component>(
Component: ComponentImport<C>,
options?: ComponentOptions<C>,
renderOptions?: SetupOptions
): RenderResult<C> & PromiseLike<RenderResult<C>>The render function records a svelte.render trace mark, visible in the Trace View.
WARNING
Synchronous usage of render is deprecated and will be removed in the next major version. Please always await the result:
const screen = render(Component)
const screen = await render(Component) Options
The render function supports either options that you can pass down to mount or props directly:
const screen = await render(Component, {
props: {
initialCount: 1,
},
initialCount: 1,
})props
Component props.
target
By default, Vitest will create a div, append it to document.body, and render your component there. If you provide your own HTMLElement container, it will not be appended automatically — you'll need to call document.body.appendChild(container) before render.
For example, if you are unit testing a tbody element, it cannot be a child of a div. In this case, you can specify a table as the render container.
const table = document.createElement('table')
const screen = await render(TableBody, {
props,
// ⚠️ appending the element to `body` manually before rendering
target: document.body.appendChild(table),
})baseElement
This can be passed down in a third argument. You should rarely, if ever, need to use this option.
If the target is specified, then this defaults to that, otherwise this defaults to document.body. This is used as the base element for the queries as well as what is printed when you use debug().
Render Result
In addition to documented return value, the render function also returns all available locators relative to the baseElement, including custom ones.
const screen = await render(TableBody, props)
await screen.getByRole('link', { name: 'Expand' }).click()container
The containing DOM node where your Svelte component is rendered. This is a regular DOM node, so you technically could call container.querySelector etc. to inspect the children.
DANGER
If you find yourself using container to query for rendered elements then you should reconsider! The locators are designed to be more resilient to changes that will be made to the component you're testing. Avoid using container to query for elements!
component
The mounted Svelte component instance. You can use this to access component methods and properties if needed.
const { component } = await render(Counter, {
initialCount: 0,
})
// Access component exports if neededlocator
The locator of your container. It is useful to use queries scoped only to your component, or pass it down to other assertions:
import { render } from 'vitest-browser-svelte'
const { locator } = await render(NumberDisplay, {
number: 2,
})
await locator.getByRole('button').click()
await expect.element(locator).toHaveTextContent('Hello World')debug
function debug(
el?: HTMLElement | HTMLElement[] | Locator | Locator[],
): voidThis method is a shortcut for console.log(prettyDOM(baseElement)). It will print the DOM content of the container or specified elements to the console.
rerender
function rerender(props: Partial<ComponentProps<T>>): Promise<void>Updates the component's props and waits for Svelte to apply the changes. Use this to test how your component responds to prop changes. Also records a svelte.rerender trace mark in the Trace View.
import { render } from 'vitest-browser-svelte'
const { rerender } = await render(NumberDisplay, {
number: 1,
})
// re-render the same component with different props
await rerender({ number: 2 })unmount
function unmount(): Promise<void>Unmount and destroy the Svelte component. Also records a svelte.unmount trace mark in the Trace View. This is useful for testing what happens when your component is removed from the page (like testing that you don't leave event handlers hanging around causing memory leaks).
WARNING
Synchronous usage of unmount is deprecated and will be removed in the next major version. Please always await the result.
import { render } from 'vitest-browser-svelte'
const { container, unmount } = await render(Component)
await unmount()
// your component has been unmounted and now: container.innerHTML === ''cleanup
export function cleanup(): voidRemove all components rendered with render.
Extend Queries
To extend locator queries, see "Custom Locators". For example, to make render return a new custom locator, define it using the locators.extend API:
import { locators } from 'vitest/browser'
import { render } from 'vitest-browser-svelte'
locators.extend({
getByArticleTitle(title) {
return `[data-title="${title}"]`
},
})
const screen = await render(Component)
await expect.element(
screen.getByArticleTitle('Hello World')
).toBeVisible()Snippets
For simple snippets, you can use a wrapper component and "dummy" children to test them. Setting data-testid attributes can be helpful when testing slots in this manner.
import { render } from 'vitest-browser-svelte'
import { expect, test } from 'vitest'
import SubjectTest from './basic-snippet.test.svelte'
test('basic snippet', async () => {
const screen = await render(SubjectTest)
const heading = screen.getByRole('heading')
const child = heading.getByTestId('child')
await expect.element(child).toBeInTheDocument()
})<script>
let { children } = $props()
</script>
<h1>
{@render children?.()}
</h1><script>
import Subject from './basic-snippet.svelte'
</script>
<Subject>
<span data-testid="child"></span>
</Subject>For more complex snippets, e.g. where you want to check arguments, you can use Svelte's createRawSnippet API.
import { render } from 'vitest-browser-svelte'
import { createRawSnippet } from 'svelte'
import { expect, test } from 'vitest'
import Subject from './complex-snippet.svelte'
test('renders greeting in message snippet', async () => {
const screen = await render(Subject, {
name: 'Alice',
message: createRawSnippet(greeting => ({
render: () => `<span data-testid="message">${greeting()}</span>`,
})),
})
const message = screen.getByTestId('message')
await expect.element(message).toHaveTextContent('Hello, Alice!')
})<script>
let { name, message } = $props()
const greeting = $derived(`Hello, ${name}!`)
</script>
<p>
{@render message?.(greeting)}
</p>