DEV Community

Olivier Colas
Olivier Colas

Posted on

Testing svelte5 with vitest and playwright for non svelte-kit projects

I will preface that I'm using Svelte through a non svelte-kit framework called Primate.run, if you're using svelte-kit, you don't need to do any of this, it is already setup out of the box.

With that out of the way, you may be using @testing-library/svelte along with @testing-library/user-event to fire events in a jsdom or happydom environment. This probably will work fine for you. So why consider using playwright in the first place? The "why" is covered extensively here so feel free to give that a quick glance.

The first thing you're going to need is to install a couple packages

The quick way is (although I haven't tested this):

npx vitest init browser
Enter fullscreen mode Exit fullscreen mode

Or you can manually add the packages:

# @vitest/ui is optional
pnpm add -D vitest @vitest/browser vitest-browser-svelte @vitest/browser-playwright @sveltejs/vite-plugin-svelte @vitest/ui
Enter fullscreen mode Exit fullscreen mode

Create a vitest.config.ts

import { svelte } from "@sveltejs/vite-plugin-svelte";
import { playwright } from "@vitest/browser-playwright";
import { defineConfig } from "vitest/config";

export default defineConfig({
    plugins: [
        svelte({
            compilerOptions: {
                dev: true,
            },
        }),
    ],
    test: {
        include: [
            "**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
        ],
        browser: {
            enabled: true,
            provider: playwright(),
            instances: [{ browser: "chromium" }],
        },
        setupFiles: ["./vitest-setup-client.ts"],
    },
});

Enter fullscreen mode Exit fullscreen mode

Create the setup file to get matchers in your test files
vitest-setup-client.ts

/// <reference types="@vitest/browser/matchers" />
Enter fullscreen mode Exit fullscreen mode

Add the following scripts to your package.json scripts property:

    "scripts": {
        "test": "vitest run",
        "test:ui": "vitest --ui", //if you added @vitest/ui
        "test:watch": "vitest",
        // ... other scripts
     }
Enter fullscreen mode Exit fullscreen mode

And create your first test file that tests a svelte component, here's an example of a simple component I have called step-header.svelte

<script lang="ts">
    let { children }: { children: any } = $props();
</script>

<div
    class="flex items-center justify-between relative bg-gray-100 dark:bg-[#242424] py-8"
>
    {@render children?.()}
</div>
Enter fullscreen mode Exit fullscreen mode

This is an arbitrary demonstration simply to show you how to add children as its not immediately obvious:

step-header.svelte.test.js

import { expect, test } from "vitest";
import { render } from "vitest-browser-svelte";
import { createChildrenSnippet } from "../../../../lib/test/utils.ts";
import StepHeader from "./step-header.svelte";

test("should display the step header", async () => {
    const screen = render(StepHeader, {
        children: createRawSnippet(() => ({
            render: () => "Step Header",
            setup: () => {},
        }));
    });

    const element = screen.getByText("Step Header");

    await expect.element(element).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

Obviously its quite verbose to have to use createRawSnippet each time so you can extract it to a separate createChildrenSnippet function like so:

utils.ts

import { createRawSnippet } from "svelte";

export const createChildrenSnippet = (content: any) =>
    createRawSnippet(() => ({
        render: () => content,
        setup: () => {},
    }));
Enter fullscreen mode Exit fullscreen mode

And here's the final test file:

import { expect, test } from "vitest";
import { render } from "vitest-browser-svelte";
import { createChildrenSnippet } from "../../../../lib/test/utils.ts";
import StepHeader from "./step-header.svelte";

test("should display the step header", async () => {
    const screen = render(StepHeader, {
        children: createChildrenSnippet("Step Header"),
    });

    const element = screen.getByText("Step Header");

    await expect.element(element).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

And that's it! Hope this helps someone.

Sources:

Top comments (0)