# Caddy Import Firefox Fix - Investigation & Test Plan **Status**: Investigation & Planning **Priority**: P0 CRITICAL **Issue**: GitHub Issue #567 - Caddyfile import failing in Firefox **Date**: 2026-02-03 **Investigator**: Planning Agent --- ## Executive Summary ### Issue Description User reports that the "Parse and Review" button does not work when clicked in Firefox. Backend logs show `"record not found"` error when checking for import sessions: ``` Path: /app/backend/internal/api/handlers/import_handler.go:61 Query: SELECT * FROM import_sessions WHERE status IN ("pending","reviewing") ``` ### Current Status Assessment Based on code review and git history: - ✅ **Recent fixes applied** (Jan 26 - Feb 1): - Fixed multi-file import API contract mismatch (commit `eb1d710f`) - Added file_server directive warning extraction - Enhanced import feedback and error handling - ⚠️ **Testing gap identified**: No explicit Firefox-specific E2E tests for Caddy import - ❓ **Root cause unclear**: Issue may be fixed by recent commits or may be browser-specific bug --- ## 1. Root Cause Analysis ### 1.1 Code Flow Analysis **Frontend → Backend Flow**: ``` 1. User clicks "Parse and Review" button ├─ File: frontend/src/pages/ImportCaddy.tsx:handleUpload() └─ Calls: uploadCaddyfile(content) → POST /api/v1/import/upload 2. Backend receives request ├─ File: backend/internal/api/handlers/import_handler.go:Upload() ├─ Creates transient session (no DB write initially) └─ Returns: { session, preview, caddyfile_content } 3. Frontend displays review table ├─ File: frontend/src/pages/ImportCaddy.tsx:setShowReview(true) └─ Component: ImportReviewTable 4. User clicks "Commit" button ├─ File: frontend/src/pages/ImportCaddy.tsx:handleCommit() └─ Calls: commitImport() → POST /api/v1/import/commit ``` **Backend Database Query Path**: ```go // File: backend/internal/api/handlers/import_handler.go:61 err := h.db.Where("status IN ?", []string{"pending", "reviewing"}). Order("created_at DESC"). First(&session).Error ``` ### 1.2 Potential Root Causes #### Hypothesis 1: Event Handler Binding Issue (Firefox-specific) **Likelihood**: Medium **Evidence**: - Firefox handles button click events differently than Chromium - Async state updates may cause race conditions - No explicit Firefox testing in CI **Test Criteria**: ```typescript // Verify button is enabled and clickable const parseButton = page.getByRole('button', { name: /parse|review/i }); await expect(parseButton).toBeEnabled(); await expect(parseButton).not.toHaveAttribute('disabled'); // Verify event listener is attached (Firefox-specific check) const hasClickHandler = await parseButton.evaluate( (btn) => !!btn.onclick || !!btn.getAttribute('onclick') ); ``` #### Hypothesis 2: Race Condition in State Management **Likelihood**: High **Evidence**: - Recent fixes addressed API response handling - `useImport` hook manages complex async state - Transient sessions may not be properly handled **Test Criteria**: ```typescript // Register API waiter BEFORE clicking button const uploadPromise = page.waitForResponse( r => r.url().includes('/api/v1/import/upload') ); await parseButton.click(); const response = await uploadPromise; // Verify response structure expect(response.ok()).toBeTruthy(); const body = await response.json(); expect(body.session).toBeDefined(); expect(body.preview).toBeDefined(); ``` #### Hypothesis 3: CORS or Request Header Issue (Firefox-specific) **Likelihood**: Low **Evidence**: - Firefox has stricter CORS enforcement - Axios client configuration may differ between browsers - No CORS errors reported in issue **Test Criteria**: ```typescript // Monitor network requests for CORS failures const failedRequests: string[] = []; page.on('requestfailed', request => { failedRequests.push(request.url()); }); await parseButton.click(); expect(failedRequests).toHaveLength(0); ``` #### Hypothesis 4: Session Storage/Cookie Issue **Likelihood**: Medium **Evidence**: - Backend query returns "record not found" - Firefox may handle session storage differently - Auth cookies must be domain-scoped correctly **Test Criteria**: ```typescript // Verify auth cookies are present and valid const cookies = await context.cookies(); const authCookie = cookies.find(c => c.name.includes('auth')); expect(authCookie).toBeDefined(); // Verify request includes auth headers const request = await uploadPromise; const headers = request.request().headers(); expect(headers['authorization'] || headers['cookie']).toBeDefined(); ``` ### 1.3 Recent Code Changes Analysis **Commit `eb1d710f` (Feb 1, 2026)**: Fixed multi-file import API contract - **Changes**: Updated `ImportSitesModal.tsx` to send `{files: [{filename, content}]}` instead of `{contents}` - **Impact**: May have resolved underlying state management issues - **Testing**: Need to verify fix works in Firefox **Commit `fc2df97f`**: Improved Caddy import with directive detection - **Changes**: Enhanced error messaging for import directives - **Impact**: Better user feedback for edge cases - **Testing**: Verify error messages display correctly in Firefox --- ## 2. E2E Test Coverage Analysis ### 2.1 Existing Test Files | Test File | Purpose | Browser Coverage | Status | |-----------|---------|------------------|--------| | `tests/tasks/import-caddyfile.spec.ts` | Full wizard flow (18 tests) | Chromium only | ✅ Comprehensive | | `tests/tasks/caddy-import-debug.spec.ts` | Diagnostic tests (6 tests) | Chromium only | ✅ Diagnostic | | `tests/tasks/caddy-import-gaps.spec.ts` | Gap coverage (9 tests) | Chromium only | ✅ Edge cases | | `tests/integration/import-to-production.spec.ts` | Integration tests | Chromium only | ✅ Smoke tests | **Key Finding**: ❌ **ZERO Firefox/WebKit-specific Caddy import tests** ### 2.2 Browser Projects Configuration **File**: `playwright.config.js` ```javascript projects: [ { name: 'chromium', use: devices['Desktop Chrome'] }, { name: 'firefox', use: devices['Desktop Firefox'] }, // ← Configured { name: 'webkit', use: devices['Desktop Safari'] }, // ← Configured ] ``` **CI Configuration**: `.github/workflows/e2e-tests.yml` ```yaml matrix: browser: [chromium, firefox, webkit] ``` **Actual Test Execution**: - ✅ Tests run against all 3 browsers in CI - ❌ No browser-specific test filters or tags - ❌ No explicit Firefox validation for Caddy import ### 2.3 Coverage Gaps | Gap | Impact | Priority | |-----|--------|----------| | No Firefox-specific import tests | Cannot detect Firefox-only bugs | P0 | | No cross-browser event handler validation | Click handlers may fail silently | P0 | | No browser-specific network request monitoring | CORS/header issues undetected | P1 | | No explicit WebKit validation | Safari users may experience same issue | P1 | | No mobile browser testing | Responsive issues undetected | P2 | --- ## 3. Reproduction Steps ### 3.1 Manual Reproduction (Firefox Required) **Prerequisites**: 1. Start Charon E2E environment: `.github/skills/scripts/skill-runner.sh docker-rebuild-e2e` 2. Open Firefox browser 3. Navigate to `http://localhost:8080` 4. Log in with admin credentials **Test Steps**: ``` 1. Navigate to /tasks/import/caddyfile 2. Paste valid Caddyfile content: ``` test.example.com { reverse_proxy localhost:3000 } ``` 3. Click "Parse and Review" button 4. EXPECTED: Review table appears with parsed hosts 5. ACTUAL (if bug exists): Button does nothing, no API request sent 6. Open Firefox DevTools → Network tab 7. Repeat steps 2-3 8. EXPECTED: POST /api/v1/import/upload (200 OK) 9. ACTUAL (if bug exists): No request visible, or request fails 10. Check backend logs: ```bash docker logs charon-app 2>&1 | grep -i import | tail -50 ``` 11. EXPECTED: "Import Upload: received upload" 12. ACTUAL (if bug exists): "record not found" error at line 61 ``` ### 3.2 Automated E2E Reproduction Test **File**: `tests/tasks/caddy-import-firefox-specific.spec.ts` (new file) ```typescript import { test, expect } from '@playwright/test'; test.describe('Caddy Import - Firefox Specific @firefox-only', () => { test('should successfully parse Caddyfile in Firefox', async ({ page, browserName }) => { // Skip if not Firefox test.skip(browserName !== 'firefox', 'Firefox-specific test'); await test.step('Navigate to import page', async () => { await page.goto('/tasks/import/caddyfile'); }); const caddyfile = 'test.example.com { reverse_proxy localhost:3000 }'; await test.step('Paste Caddyfile content', async () => { await page.locator('textarea').fill(caddyfile); }); let requestMade = false; await test.step('Monitor network request', async () => { // Register listener BEFORE clicking button (critical for Firefox) const uploadPromise = page.waitForResponse( r => r.url().includes('/api/v1/import/upload'), { timeout: 10000 } ); // Click button await page.getByRole('button', { name: /parse|review/i }).click(); try { const response = await uploadPromise; requestMade = true; // Verify successful response expect(response.ok()).toBeTruthy(); const body = await response.json(); expect(body.session).toBeDefined(); expect(body.preview.hosts).toHaveLength(1); } catch (error) { console.error('❌ API request failed or not sent:', error); } }); await test.step('Verify review table appears', async () => { if (!requestMade) { test.fail('API request was not sent after clicking Parse button'); } const reviewTable = page.getByTestId('import-review-table'); await expect(reviewTable).toBeVisible({ timeout: 5000 }); await expect(reviewTable.getByText('test.example.com')).toBeVisible(); }); }); test('should handle button double-click in Firefox', async ({ page, browserName }) => { test.skip(browserName !== 'firefox', 'Firefox-specific test'); await page.goto('/tasks/import/caddyfile'); const caddyfile = 'test.example.com { reverse_proxy localhost:3000 }'; await page.locator('textarea').fill(caddyfile); // Monitor for duplicate requests const requests: string[] = []; page.on('request', req => { if (req.url().includes('/api/v1/import/upload')) { requests.push(req.method()); } }); const parseButton = page.getByRole('button', { name: /parse|review/i }); // Double-click rapidly (Firefox may handle differently) await parseButton.click(); await parseButton.click(); // Wait for requests to complete await page.waitForTimeout(2000); // Should only send ONE request (button should be disabled after first click) expect(requests.length).toBeLessThanOrEqual(1); }); }); ``` --- ## 4. Comprehensive Test Implementation Plan ### 4.1 Test Strategy **Objective**: Guarantee Caddy import works reliably across all 3 browsers (Chromium, Firefox, WebKit) **Approach**: 1. **Cross-browser baseline tests** - Run existing tests against all browsers 2. **Browser-specific edge case tests** - Target known browser differences 3. **Performance comparison** - Measure timing differences between browsers 4. **Visual regression testing** - Ensure UI renders consistently ### 4.2 Test File Structure ``` tests/ ├── tasks/ │ ├── import-caddyfile.spec.ts # Existing (Chromium) │ ├── caddy-import-debug.spec.ts # Existing (Chromium) │ ├── caddy-import-gaps.spec.ts # Existing (Chromium) │ └── caddy-import-cross-browser.spec.ts # NEW - Cross-browser suite ├── firefox-specific/ # NEW - Firefox-only tests │ ├── caddy-import-firefox.spec.ts │ └── event-handler-regression.spec.ts └── webkit-specific/ # NEW - WebKit-only tests └── caddy-import-webkit.spec.ts ``` ### 4.3 Test Scenarios Matrix | Scenario | Chromium | Firefox | WebKit | Priority | File | |----------|----------|---------|--------|----------|------| | **Parse valid Caddyfile** | ✅ | ❌ | ❌ | P0 | `caddy-import-cross-browser.spec.ts` | | **Handle parse errors** | ✅ | ❌ | ❌ | P0 | `caddy-import-cross-browser.spec.ts` | | **Detect import directives** | ✅ | ❌ | ❌ | P0 | `caddy-import-cross-browser.spec.ts` | | **Show conflict warnings** | ✅ | ❌ | ❌ | P0 | `caddy-import-cross-browser.spec.ts` | | **Commit successful import** | ✅ | ❌ | ❌ | P0 | `caddy-import-cross-browser.spec.ts` | | **Multi-file upload** | ✅ | ❌ | ❌ | P0 | `caddy-import-cross-browser.spec.ts` | | **Button double-click protection** | ❌ | ❌ | ❌ | P1 | `firefox-specific/event-handler-regression.spec.ts` | | **Network request timing** | ❌ | ❌ | ❌ | P1 | `caddy-import-cross-browser.spec.ts` | | **Session persistence** | ❌ | ❌ | ❌ | P1 | `caddy-import-cross-browser.spec.ts` | | **CORS header validation** | ❌ | ❌ | ❌ | P2 | `firefox-specific/caddy-import-firefox.spec.ts` | ### 4.4 New Test Files #### Test 1: `tests/tasks/caddy-import-cross-browser.spec.ts` **Purpose**: Run core Caddy import scenarios against all 3 browsers **Execution**: `npx playwright test caddy-import-cross-browser.spec.ts --project=chromium --project=firefox --project=webkit` **Test Cases**: ```typescript 1. Parse valid Caddyfile (all browsers) ├─ Paste content ├─ Click Parse button ├─ Wait for API response └─ Verify review table appears 2. Handle syntax errors (all browsers) ├─ Paste invalid content ├─ Click Parse button ├─ Expect 400 error └─ Verify error message displayed 3. Multi-file import flow (all browsers) ├─ Click multi-file button ├─ Upload main + site files ├─ Parse └─ Verify imported hosts 4. Conflict resolution (all browsers) ├─ Create existing host via API ├─ Import conflicting host ├─ Verify conflict indicator ├─ Select "Replace" └─ Commit and verify update 5. Session resume (all browsers) ├─ Start import session ├─ Navigate away ├─ Return to import page └─ Verify banner + Review button 6. Cancel import (all browsers) ├─ Parse content ├─ Click back/cancel ├─ Confirm dialog └─ Verify session cleared ``` #### Test 2: `tests/firefox-specific/caddy-import-firefox.spec.ts` **Purpose**: Test Firefox-specific behaviors and edge cases **Execution**: `npx playwright test firefox-specific --project=firefox` **Test Cases**: ```typescript 1. Event listener attachment (Firefox-only) ├─ Verify onclick handler exists ├─ Verify button is not disabled └─ Verify event propagation works 2. Async state update race condition (Firefox-only) ├─ Fill content rapidly ├─ Click parse immediately ├─ Verify request sent despite quick action └─ Verify no "stale" state issues 3. CORS preflight handling (Firefox-only) ├─ Monitor network for OPTIONS request ├─ Verify CORS headers present └─ Verify POST request succeeds 4. Cookie/auth header verification (Firefox-only) ├─ Check cookies sent with request ├─ Verify Authorization header └─ Check session storage state 5. Button double-click protection (Firefox-only) ├─ Double-click Parse button rapidly ├─ Verify only 1 API request sent └─ Verify button disabled after first click 6. Large file handling (Firefox-only) ├─ Paste 10KB+ Caddyfile ├─ Verify no textarea lag └─ Verify upload completes ``` #### Test 3: `tests/webkit-specific/caddy-import-webkit.spec.ts` **Purpose**: Validate Safari/WebKit compatibility **Execution**: `npx playwright test webkit-specific --project=webkit` **Test Cases**: (Same as Firefox test 1-6, adapted for WebKit) ### 4.5 Performance Testing **File**: `tests/performance/caddy-import-perf.spec.ts` **Objective**: Measure and compare browser performance ```typescript test.describe('Caddy Import - Performance Comparison', () => { test('should parse Caddyfile within acceptable time', async ({ page, browserName }) => { const startTime = Date.now(); await page.goto('/tasks/import/caddyfile'); await page.locator('textarea').fill(largeCaddyfile); // 50+ hosts const uploadPromise = page.waitForResponse( r => r.url().includes('/api/v1/import/upload') ); await page.getByRole('button', { name: /parse/i }).click(); await uploadPromise; const endTime = Date.now(); const duration = endTime - startTime; // Log browser-specific performance console.log(`${browserName}: ${duration}ms`); // Acceptable thresholds (adjust based on baseline) expect(duration).toBeLessThan(5000); // 5 seconds max }); }); ``` --- ## 5. Acceptance Criteria ### 5.1 Bug Fix Validation (if bug still exists) - [ ] ✅ Parse button clickable in Firefox - [ ] ✅ API request sent on button click (Firefox DevTools shows POST /api/v1/import/upload) - [ ] ✅ Backend logs show "Import Upload: received upload" (no "record not found") - [ ] ✅ Review table appears with parsed hosts - [ ] ✅ Commit button works correctly - [ ] ✅ No console errors in Firefox DevTools - [ ] ✅ Same behavior in Chromium and Firefox ### 5.2 Test Coverage Requirements - [ ] ✅ All critical scenarios pass in Chromium (baseline validation) - [ ] ✅ All critical scenarios pass in Firefox (regression prevention) - [ ] ✅ All critical scenarios pass in WebKit (Safari compatibility) - [ ] ✅ Cross-browser test file created and integrated into CI - [ ] ✅ Firefox-specific edge case tests passing - [ ] ✅ Performance within acceptable thresholds across all browsers - [ ] ✅ Zero cross-browser test failures in CI for 3 consecutive runs ### 5.3 CI Integration - [ ] ✅ Cross-browser tests run on every PR - [ ] ✅ Browser-specific test results visible in CI summary - [ ] ✅ Failed tests show browser name in error message - [ ] ✅ Codecov reports separate coverage per browser (if applicable) - [ ] ✅ No increase in CI execution time (use sharding if needed) --- ## 6. Implementation Phases ### Phase 1: Investigation & Root Cause Identification (1-2 hours) **Subagent**: Backend_Dev + Frontend_Dev **Tasks**: 1. Manually reproduce issue in Firefox 2. Capture browser DevTools Network + Console logs 3. Capture backend logs showing error 4. Compare request/response between Chromium and Firefox 5. Identify exact line where behavior diverges 6. Document root cause with evidence **Deliverable**: Root cause analysis report with screenshots/logs --- ### Phase 2: Fix Implementation (if bug exists) (2-4 hours) **Subagent**: Frontend_Dev (or Backend_Dev depending on root cause) **Potential Fixes**: **Option A: Frontend Event Handler Fix** (if Hypothesis 1 confirmed) ```typescript // File: frontend/src/pages/ImportCaddy.tsx // BEFORE (potential issue)