Skip to content
These are temporary community docs. The official docs are in progress.Help improve them
Markless
Esc
↑↓navigate↵open⌘Jpreview
On this page

Test a component in a real browser

Use @markless/vitest-browser to mount a component in the browser with render(), or render it on a server with renderSSR() and click the HTML.

Types pass. But a type check never clicks a button.

@markless/vitest-browser runs your component in a real browser, and your test clicks it. The package is experimental.

The compiler turns Counter.tsrx into code that renders in many places. A test is one more place. The package gives you two ways in. Mount the component in the browser. Or render it to HTML on a server, then click that HTML.

A bug can hide in one way and not in the other. So write the check once and run it both ways.

Pick a way in. Then press Run the test.

What does a test do with your component?

Pick a way in. Press Run the test, or drag the timeline. Watch each step.

How the test puts the component in the page
1. The test starts in a real browser

Vitest opens a real browser page for the test. The page is empty.

The test page
Test pagebuilt in the browser

Empty page.

Your test: counter.browser.ts
import { render, renderSSR } from '@markless/vitest-browser';import { expect, test } from 'vitest';import Counter from './Counter.tsrx'; async function clickCounts(container: HTMLElement) { const button = container.querySelector('button')!; button.click(); await expect.poll(() => button.textContent).toBe('Count 1');} test('mounted in the browser: a click counts', async () => { (this test) const screen = await render(Counter); await clickCounts(screen.container as HTMLElement);}); test('rendered on a server: a click counts', async () => { const screen = await renderSSR(Counter); await clickCounts(screen.container);});
What Markless did
Times your component ran in the browser0
Test resultstarting
  1. Nothing yet. The test has just started.

Simplified. Steps follow @markless/vitest-browser and the test file on this page. The test server belongs to the test run. Your app does not need a server. The package is experimental.

Notice that the last three steps are the same both ways. Only the start differs: where your component runs.

Helper What it does
render(Counter) Mounts Counter in the browser, the way an app without a server does.
renderSSR(Counter) Renders Counter to HTML on the test server, then puts that HTML in the page.

Set it up

The starters do not include this setup yet, and the package has no README. This configuration comes from the Markless repo’s own test projects.

npm install -D @markless/vitest-browser vitest playwright
pnpm add -D @markless/vitest-browser vitest playwright
yarn add -D @markless/vitest-browser vitest playwright
bun add -D @markless/vitest-browser vitest playwright
nub add -D @markless/vitest-browser vitest playwright
aube add -D @markless/vitest-browser vitest playwright
npx playwright install chromium
import { markless } from '@markless/core/vite';
import { testSSR } from '@markless/vitest-browser/ssr-plugin';
import { playwright } from 'vite-plus/test/browser-playwright';
import { defineProject } from 'vitest/config';

export default defineProject({
	plugins: [testSSR(), markless()],
	test: {
		include: ['src/**/*.browser.ts'],
		browser: { enabled: true, headless: true, provider: playwright(), instances: [{ browser: 'chromium' }] },
	},
});

Put testSSR() before markless(). Without it, renderSSR() throws.

One check, both ways

Take the counter from the starter app.

import { state } from '@markless/core';

export default function Counter() @{
	let count = state(0);

	<button onClick={() => count++}>Count {count}</button>
}

Write the check once. Then run it both ways.

import { render, renderSSR } from '@markless/vitest-browser';
import { expect, test } from 'vitest';
import Counter from './Counter.tsrx';

async function clickCounts(container: HTMLElement) {
	const button = container.querySelector('button')!;
	button.click();
	await expect.poll(() => button.textContent).toBe('Count 1');
}

test('mounted in the browser: a click counts', async () => {
	const screen = await render(Counter);
	await clickCounts(screen.container as HTMLElement);
});

test('rendered on a server: a click counts', async () => {
	const screen = await renderSSR(Counter);
	await clickCounts(screen.container);
});

The package removes each mounted component after each test. Call cleanup() yourself only in the middle of a test.

Next: A build failed with a long code in capital letters. What is it telling you? Diagnostics →

Was this page helpful?