Files
agent-skills/skills/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

5.5 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Choose Browser-Based Runner for Style and DOM Event Testing MEDIUM Node-based runners cannot test real CSS behavior, native DOM events, cookies, or computed styles capability
vue3
testing
component-testing
vitest
browser
jsdom

Choose Browser-Based Runner for Style and DOM Event Testing

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.

Use Vitest for most component tests (fast), but use Vitest Browser Mode when testing visual/DOM-dependent features.

Task Checklist

  • Use Vitest (node) for logic-focused component tests
  • Use Vitest Browser Mode for style-dependent tests
  • Use Vitest Browser Mode for native events (focus, drag, resize)
  • Use Vitest Browser Mode for cookies and computed CSS styles
  • Accept slower speed tradeoff for browser accuracy

When to Use Each Approach

Node-Based Runner (Vitest + happy-dom/jsdom)

Best for:

  • Pure logic testing
  • State management
  • Event emission
  • Props/slots behavior
  • Most component interactions
  • Fast CI/CD pipelines
// 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()
})

Vitest Browser Mode

Required for:

  • CSS computed styles verification
  • CSS transitions/animations
  • Real focus/blur behavior
  • Drag and drop
  • Cookie operations
  • Viewport-dependent behavior
  • Cross-browser validation

Vitest Browser Mode Setup

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()
})

Examples: What Each Runner Can/Cannot Test

Styles - Browser Required

// 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)'
  })
})

Computed CSS Styles - Browser Required

// 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'
  })
})

Native Events - Browser Required

// 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.enabled

Directory Structure

tests/
├── 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

Reference