Skip to content

Fix HeadlessUI/JSDOM compatibility issues in component tests #35

Description

@ErnieAtLYD

Problem

Component tests that use HeadlessUI components are failing in CI/CD due to JSDOM compatibility issues:

  1. HeadlessUI focus management errors: Cannot set properties of undefined (setting 'headlessuiFocusVisible')
  2. 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

  1. Install Playwright CT:

    pnpm add -D @playwright/experimental-ct-react
  2. 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,
      },
    })
  3. 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()
    })
  4. 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

  1. Package.json: Add Playwright CT dependency and scripts
  2. Component tests: Convert to Playwright CT format
  3. CI/CD: Update to run Playwright CT tests
  4. 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

  • Playwright Component Testing is set up and working
  • All HeadlessUI component tests pass in real browsers
  • Accessibility testing works with real axe-core
  • CI/CD runs both E2E and component tests with Playwright
  • No JSDOM compatibility issues
  • Faster and more reliable component testing

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions