Setting the file. One moment.
Skill 16 · Vue Testing Best Practices
Subchapter 16.4
reference/testing-browser-vs-node-runners.mdMarkdown5 KBView on GitHub
Impact: MEDIUM - Node-based test runners (Vitest with jsdom/happy-dom) simulate the DOM but cannot test real CSS rendering, native browser events, cookies, computed styles, or cross-browser behavior. Use browser-based runners when these matter.
Also bundled
LICENSEUse Vitest for most component tests (fast), but use Vitest Browser Mode when testing visual/DOM-dependent features.
Best for:
// vitest.config.js
export default defineConfig({
test: {
environment: 'happy-dom', // or 'jsdom'
}
})// Fast but limited - fine for most tests
test('button emits click event', async () => {
const wrapper = mount(Button)
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})Required for:
npm install -D @vitest/browser playwright// vitest.config.js
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
browser: {
enabled: true,
name: 'chromium',
provider: 'playwright',
},
},
})// Button.browser.test.js
import { render } from 'vitest-browser-vue'
import Button from './Button.vue'
test('has correct hover styling', async () => {
const { getByRole } = render(Button, { props: { label: 'Click me' } })
const button = getByRole('button')
// Check initial style
await expect.element(button).toHaveStyle({
backgroundColor: 'rgb(59, 130, 246)' // blue
})
})
test('maintains focus after click', async () => {
const { getByRole } = render(Button)
const button = getByRole('button')
await button.click()
await expect.element(button).toHaveFocus()
})// Node runner: CANNOT verify actual CSS
test('danger button has red background', () => {
const wrapper = mount(Button, { props: { variant: 'danger' } })
// This only checks class exists, not actual color
expect(wrapper.classes()).toContain('bg-red-500')
})
// Vitest Browser Mode: CAN verify computed styles
test('danger button renders red', async () => {
const { getByRole } = render(Button, { props: { variant: 'danger' } })
await expect.element(getByRole('button')).toHaveStyle({
backgroundColor: 'rgb(239, 68, 68)'
})
})// Node runner: CANNOT get real computed styles
test('button has correct padding', () => {
const wrapper = mount(Button)
// getComputedStyle returns empty/default values in jsdom
const style = window.getComputedStyle(wrapper.element)
// style.padding will be empty string, not actual computed value
})
// Vitest Browser Mode: Real computed styles
test('button has correct padding', async () => {
const { getByRole } = render(Button)
const button = getByRole('button')
await expect.element(button).toHaveStyle({
padding: '12px 24px'
})
})// Node runner: Synthetic events only
test('handles drag and drop', async () => {
const wrapper = mount(DraggableList)
// trigger('dragstart') is synthetic - may not work as expected
await wrapper.find('.item').trigger('dragstart')
})
// Vitest Browser Mode: Real native events via userEvent
import { userEvent } from '@vitest/browser/context'
test('reorders items on drag', async () => {
const { getByTestId } = render(DraggableList)
const item = getByTestId('item-1')
const target = getByTestId('item-3')
await userEvent.dragAndDrop(item, target)
// Assert reordering
})// vitest.config.js - Separate test configurations
export default defineConfig({
test: {
// Default: Node environment for speed
environment: 'happy-dom',
// Browser tests in separate directory
include: ['src/**/*.test.{js,ts}'],
},
})
// Run browser tests separately
// npx vitest --browser.enabledtests/
├── unit/ # Fast node-based tests
│ ├── Button.test.js
│ └── useCounter.test.js
├── component/ # Slower browser-based tests
│ ├── Button.browser.test.js
│ └── DragDrop.browser.test.js
└── e2e/ # Full E2E tests (Playwright)
└── user-flow.spec.ts