Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 testing best practices with Vitest, Vue Test Utils, component testing, mocking, and Playwright E2E.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/testing-browser-vs-node-runners.md
1---2title: Choose Browser-Based Runner for Style and DOM Event Testing3impact: MEDIUM4impactDescription: Node-based runners cannot test real CSS behavior, native DOM events, cookies, or computed styles5type: capability6tags: [vue3, testing, component-testing, vitest, browser, jsdom]7---89# Choose Browser-Based Runner for Style and DOM Event Testing1011**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.1213Use Vitest for most component tests (fast), but use Vitest Browser Mode when testing visual/DOM-dependent features.1415## Task Checklist1617- [ ] Use Vitest (node) for logic-focused component tests18- [ ] Use Vitest Browser Mode for style-dependent tests19- [ ] Use Vitest Browser Mode for native events (focus, drag, resize)20- [ ] Use Vitest Browser Mode for cookies and computed CSS styles21- [ ] Accept slower speed tradeoff for browser accuracy2223## When to Use Each Approach2425### Node-Based Runner (Vitest + happy-dom/jsdom)26Best for:27- Pure logic testing28- State management29- Event emission30- Props/slots behavior31- Most component interactions32- Fast CI/CD pipelines3334```javascript35// vitest.config.js36export default defineConfig({37test: {38environment: 'happy-dom', // or 'jsdom'39}40})41```4243```javascript44// Fast but limited - fine for most tests45test('button emits click event', async () => {46const wrapper = mount(Button)47await wrapper.trigger('click')48expect(wrapper.emitted('click')).toBeTruthy()49})50```5152### Vitest Browser Mode53Required for:54- CSS computed styles verification55- CSS transitions/animations56- Real focus/blur behavior57- Drag and drop58- Cookie operations59- Viewport-dependent behavior60- Cross-browser validation6162## Vitest Browser Mode Setup6364```bash65npm install -D @vitest/browser playwright66```6768```javascript69// vitest.config.js70import { defineConfig } from 'vitest/config'7172export default defineConfig({73test: {74browser: {75enabled: true,76name: 'chromium',77provider: 'playwright',78},79},80})81```8283```javascript84// Button.browser.test.js85import { render } from 'vitest-browser-vue'86import Button from './Button.vue'8788test('has correct hover styling', async () => {89const { getByRole } = render(Button, { props: { label: 'Click me' } })9091const button = getByRole('button')9293// Check initial style94await expect.element(button).toHaveStyle({95backgroundColor: 'rgb(59, 130, 246)' // blue96})97})9899test('maintains focus after click', async () => {100const { getByRole } = render(Button)101102const button = getByRole('button')103await button.click()104105await expect.element(button).toHaveFocus()106})107```108109## Examples: What Each Runner Can/Cannot Test110111### Styles - Browser Required112```javascript113// Node runner: CANNOT verify actual CSS114test('danger button has red background', () => {115const wrapper = mount(Button, { props: { variant: 'danger' } })116// This only checks class exists, not actual color117expect(wrapper.classes()).toContain('bg-red-500')118})119120// Vitest Browser Mode: CAN verify computed styles121test('danger button renders red', async () => {122const { getByRole } = render(Button, { props: { variant: 'danger' } })123await expect.element(getByRole('button')).toHaveStyle({124backgroundColor: 'rgb(239, 68, 68)'125})126})127```128129### Computed CSS Styles - Browser Required130```javascript131// Node runner: CANNOT get real computed styles132test('button has correct padding', () => {133const wrapper = mount(Button)134// getComputedStyle returns empty/default values in jsdom135const style = window.getComputedStyle(wrapper.element)136// style.padding will be empty string, not actual computed value137})138139// Vitest Browser Mode: Real computed styles140test('button has correct padding', async () => {141const { getByRole } = render(Button)142const button = getByRole('button')143144await expect.element(button).toHaveStyle({145padding: '12px 24px'146})147})148```149150### Native Events - Browser Required151```javascript152// Node runner: Synthetic events only153test('handles drag and drop', async () => {154const wrapper = mount(DraggableList)155// trigger('dragstart') is synthetic - may not work as expected156await wrapper.find('.item').trigger('dragstart')157})158159// Vitest Browser Mode: Real native events via userEvent160import { userEvent } from '@vitest/browser/context'161162test('reorders items on drag', async () => {163const { getByTestId } = render(DraggableList)164165const item = getByTestId('item-1')166const target = getByTestId('item-3')167168await userEvent.dragAndDrop(item, target)169170// Assert reordering171})172```173174## Recommended Testing Strategy175176```javascript177// vitest.config.js - Separate test configurations178179export default defineConfig({180test: {181// Default: Node environment for speed182environment: 'happy-dom',183184// Browser tests in separate directory185include: ['src/**/*.test.{js,ts}'],186},187})188189// Run browser tests separately190// npx vitest --browser.enabled191```192193### Directory Structure194```195tests/196├── unit/ # Fast node-based tests197│ ├── Button.test.js198│ └── useCounter.test.js199├── component/ # Slower browser-based tests200│ ├── Button.browser.test.js201│ └── DragDrop.browser.test.js202└── e2e/ # Full E2E tests (Playwright)203└── user-flow.spec.ts204```205206## Reference207- [Vue.js Testing - Component Testing](https://vuejs.org/guide/scaling-up/testing#component-testing)208- [Vitest Browser Mode](https://vitest.dev/guide/browser.html)209