Problem
Component tests that use HeadlessUI components are failing in CI/CD due to JSDOM compatibility issues:
- HeadlessUI focus management errors:
Cannot set properties of undefined (setting 'headlessuiFocusVisible')
- axe-core accessibility test failures:
Cannot read properties of undefined (reading 'toLowerCase')
Affected Components
- Dialog components (using HeadlessUI Dialog)
- Any component with accessibility tests using axe-core
- JournalEntryInput (uses react-textarea-autosize which may interact with HeadlessUI)
Current Temporary Fix
- ✅ Accessibility testing (
testAccessibility function) is temporarily disabled
- ✅ Tests pass but without accessibility validation
Root Cause
HeadlessUI and axe-core expect a real browser DOM environment and don't work properly in JSDOM.
Key Insight: We already have Playwright set up for E2E tests - we should have used Playwright Component Testing from the start instead of JSDOM!
Recommended Solution: Migrate to Playwright Component Testing
Since we already have Playwright infrastructure, the best solution is Playwright Component Testing (@playwright/experimental-ct-react):
Benefits
✅ Real browser environment - Eliminates all JSDOM compatibility issues
✅ HeadlessUI works perfectly - Real DOM with proper focus management
✅ Accessibility testing works - Real axe-core in real browsers
✅ Consistent tooling - Same Playwright setup for E2E and component tests
✅ Team familiarity - We already know Playwright well
✅ Better debugging - Visual debugging, screenshots, traces
Implementation Steps
-
Install Playwright CT:
pnpm add -D @playwright/experimental-ct-react
-
Create Playwright CT config (playwright-ct.config.ts):
import { defineConfig } from '@playwright/experimental-ct-react'
export default defineConfig({
testDir: './src/components',
use: {
ctPort: 3100,
},
webServer: {
command: 'pnpm dev:ct',
port: 3100,
reuseExistingServer: !process.env.CI,
},
})
-
Convert existing component tests:
// Instead of @testing-library/react
import { test, expect } from '@playwright/experimental-ct-react'
test('Dialog renders correctly', async ({ mount }) => {
const component = await mount(<Dialog isOpen={true} title="Test">Content</Dialog>)
await expect(component).toBeVisible()
// Real accessibility testing works!
await expect(component).toPassAccessibilityAudit()
})
-
Keep Vitest for pure logic/utility tests (no DOM needed)
Migration Strategy
Phase 1: Quick Win
- Migrate HeadlessUI component tests to Playwright CT
- Keep simple utility/logic tests in Vitest
Phase 2: Complete Migration
- Move all component tests to Playwright CT
- Remove JSDOM setup entirely
- Unified testing approach
Files to Update
- Package.json: Add Playwright CT dependency and scripts
- Component tests: Convert to Playwright CT format
- CI/CD: Update to run Playwright CT tests
- Remove JSDOM setup: Clean up Vitest config and test setup
Alternative Solutions (Not Recommended)
Option 2: Mock HeadlessUI (More Complex)
- Requires maintaining mocks for every HeadlessUI component
- Loses real component behavior testing
Option 3: Fix JSDOM Compatibility (Most Complex)
- Requires deep JSDOM/HeadlessUI compatibility work
- Ongoing maintenance burden
Acceptance Criteria
Priority
High - This is the cleanest long-term solution that leverages our existing Playwright infrastructure.
Note: Current temporary fix allows shipping while we implement this proper solution.
Problem
Component tests that use HeadlessUI components are failing in CI/CD due to JSDOM compatibility issues:
Cannot set properties of undefined (setting 'headlessuiFocusVisible')Cannot read properties of undefined (reading 'toLowerCase')Affected Components
Current Temporary Fix
testAccessibilityfunction) is temporarily disabledRoot Cause
HeadlessUI and axe-core expect a real browser DOM environment and don't work properly in JSDOM.
Key Insight: We already have Playwright set up for E2E tests - we should have used Playwright Component Testing from the start instead of JSDOM!
Recommended Solution: Migrate to Playwright Component Testing
Since we already have Playwright infrastructure, the best solution is Playwright Component Testing (
@playwright/experimental-ct-react):Benefits
✅ Real browser environment - Eliminates all JSDOM compatibility issues
✅ HeadlessUI works perfectly - Real DOM with proper focus management
✅ Accessibility testing works - Real axe-core in real browsers
✅ Consistent tooling - Same Playwright setup for E2E and component tests
✅ Team familiarity - We already know Playwright well
✅ Better debugging - Visual debugging, screenshots, traces
Implementation Steps
Install Playwright CT:
Create Playwright CT config (
playwright-ct.config.ts):Convert existing component tests:
Keep Vitest for pure logic/utility tests (no DOM needed)
Migration Strategy
Phase 1: Quick Win
Phase 2: Complete Migration
Files to Update
Alternative Solutions (Not Recommended)
Option 2: Mock HeadlessUI (More Complex)
Option 3: Fix JSDOM Compatibility (Most Complex)
Acceptance Criteria
Priority
High - This is the cleanest long-term solution that leverages our existing Playwright infrastructure.
Note: Current temporary fix allows shipping while we implement this proper solution.