Problem Summary
The Playwright and Lighthouse CI workflows are failing with timeout errors. After comprehensive investigation using parallel agents, we've identified multiple root causes and optimizations.
What We Tried
✅ Completed: Docker Compose Migration (PR #314)
- Goal: Replace manual process management (PID files, kill commands) with Docker Compose
- Result: Workflow simplified from 368 lines → 147 lines (61% reduction)
- Commits:
02bb93a Fix Playwright and Lighthouse CI workflows using Docker Compose
ec68889 Fix Playwright config to reuse existing server from docker-compose
ee98991 Add VITE_API_URL to docker compose steps for frontend build
e40c888 Optimize Playwright test performance (workers 1→2, retries 2→1, Docker Buildx)
❌ Current Status: Tests Still Timing Out
Despite the improvements, tests fail with 30s timeouts:
Error: locator.click: Test timeout of 30000ms exceeded.
Call log:
- waiting for getByRole('button', { name: 'Need an account? Register' })
Affected tests: 10/20 tests fail (auth registration, import/export, notes CRUD)
Root Causes Identified
🔴 Issue #1: Port Configuration Mismatch
Files involved:
frontend/Dockerfile:44 - Sets ENV PORT=80
docker-compose.yml:67,69 - Port mapping and environment override
.github/workflows/frontend-playwright.yml:59 - Sets FRONTEND_PORT: 3000
The problem:
# Workflow sets FRONTEND_PORT=3000
env:
FRONTEND_PORT: 3000
# This makes docker-compose create:
ports:
- "3000:3000" # Host 3000 → Container 3000
environment:
PORT: 3000 # Container env PORT=3000
# But Dockerfile default is:
ENV PORT=80 # May not be overridden correctly
# Result: Caddy may listen on port 80 while docker maps 3000:3000
# Healthcheck fails → `docker compose up --wait` times out
Port flow confusion:
Dockerfile: PORT=80 → Caddy listens :80
docker-compose env: PORT=3000 → Should override to :3000
Port mapping: 3000:3000 → Host 3000 → Container 3000
Healthcheck: curl localhost:${PORT}/health → Uses $PORT
IF PORT override works: ✅ Caddy on :3000, mapping works
IF PORT override fails: ❌ Caddy on :80, healthcheck on :3000 → timeout
Evidence: Workflow stuck at "Start services with Docker Compose" step
🔴 Issue #2: window.__PLAYWRIGHT_CRYPTO_READY Never Set
File: frontend/e2e/import-export.spec.ts:227
The problem:
// Line 40: Dynamic import fails in production build
import('/src/services/cryptoService.ts').then((module) => {
// ... mocking code ...
window.__PLAYWRIGHT_CRYPTO_READY = true // Never reached
})
// Line 227: Waits forever
await page.waitForFunction(() => !!(window as any).__PLAYWRIGHT_CRYPTO_READY)
Why it fails:
- Production build bundles source files to
/assets/js/crypto-core-[hash].js
- Path
/src/services/cryptoService.ts doesn't exist in built app
- Dynamic import silently fails (no
.catch())
- Flag never set to
true → test times out after 30s
Environment comparison:
| Environment |
Server |
Path Resolution |
Result |
Dev (pnpm run dev) |
Vite dev server |
/src/*.ts works |
✅ Import succeeds |
Docker Compose (preview) |
Static build |
Only /assets/*.js exists |
❌ Import fails |
| CI |
Static build |
Only /assets/*.js exists |
❌ Import fails |
🔴 Issue #3: Libsodium WASM Initialization Race Condition
File: frontend/src/services/cryptoService.ts
The problem:
// Register button calls:
await cryptoService.initSodium() // Dynamic import of libsodium-wrappers
await cryptoService.generateSalt()
await cryptoService.deriveKeyFromPassword()
// In Docker/CI, WASM loading is slower:
// → Button stuck in "Processing..." state
// → Test timeout after 30s waiting for button to become enabled
Root cause: Asynchronous WASM library loading takes longer in containerized environments (no .catch() error handling, no timeout fallback)
🟡 Issue #4: React.StrictMode Causing Double Renders
File: frontend/src/main.tsx:37-39
ReactDOM.createRoot(rootElement).render(
<React.StrictMode> // Intentionally double-invokes effects in dev
<App />
</React.StrictMode>
)
Impact:
useEffect hooks run twice (e.g., initializeApp() in LeafLockApp.tsx:260)
- Race conditions in state updates
- API calls duplicated
- Compounds timing issues in tests
Note: StrictMode is React best practice for development but adds instability to E2E tests
🟡 Issue #5: Potential Infinite useEffect Loop
File: frontend/src/features/app/hooks/useNotes.tsx:136-140
useEffect(() => {
if (notes.length > 0 && !selectedNote) {
setSelectedNote(notes[0]) // Triggers re-render
}
}, [notes, selectedNote]) // notes array reference changes → re-runs
Risk: If setSelectedNote triggers a re-render that modifies notes array, this creates a loop. Combined with StrictMode, this can cause rapid cascading re-renders.
🟢 Issue #6: Sequential Job Execution (Performance)
File: .github/workflows/frontend-playwright.yml:85
lighthouse:
needs: playwright # ← Forces sequential execution
Current timing (sequential):
Playwright job: ~2.5-3 min
↓ (waits)
Lighthouse job: ~2-2.5 min
Total: ~5 min
Optimized timing (parallel):
Playwright job: ~2.5-3 min
Lighthouse job: ~2-2.5 min (simultaneous)
Total: ~3 min (45% faster)
No technical dependency exists - both jobs:
- Spin up independent Docker Compose stacks
- Have separate environment variables
- Don't share artifacts
- Have independent cleanup steps
Proposed Fixes
✅ Fix #1: Simplify Port Configuration
File: .github/workflows/frontend-playwright.yml:59
Remove:
FRONTEND_PORT: 3000 # DELETE THIS LINE
Reason: Use default config → Caddy listens on :80, host maps 3000:80
Impact: Eliminates environment variable override complexity
✅ Fix #2: Remove Broken Crypto Flag Wait
File: frontend/e2e/import-export.spec.ts
Remove:
- Lines 31-73: Broken
addInitScript with dynamic import
- Line 227: Infinite wait for
__PLAYWRIGHT_CRYPTO_READY
Reason:
- Dynamic import fails in production build
- Test already mocks all API endpoints via
page.route()
- Explicit UI element waits (e.g.,
await page.getByLabel('Email')) are sufficient
Impact: Removes 30s timeout, test proceeds with existing mocks
✅ Fix #3: Disable React.StrictMode in Tests
File: frontend/src/main.tsx:36-40
Change:
const isTest = window.location.search.includes('playwright')
const root = ReactDOM.createRoot(rootElement)
if (isTest) {
root.render(<App />)
} else {
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
}
Reason: Eliminate double-renders and race conditions in test environment
Impact: More stable test execution without affecting production
✅ Fix #4: Increase Playwright Timeout for CI
File: frontend/playwright.config.ts:14
Change:
timeout: process.env.CI ? 60_000 : 30_000, // 30s → 60s for CI
Reason: Docker environments are slower than local dev
Impact: Gives async operations more time to complete
✅ Fix #5: Fix useNotes Effect Dependency
File: frontend/src/features/app/hooks/useNotes.tsx:138
Change:
}, [notes.length, selectedNote, loading]) // Use notes.length instead of notes
Reason: Prevent cascading re-renders from array reference changes
Impact: More stable component behavior
✅ Fix #6: Enable Parallel Job Execution
File: .github/workflows/frontend-playwright.yml:85
Remove:
needs: playwright # DELETE THIS LINE
Reason: Jobs are independent and can run simultaneously
Impact: 45% faster CI (5min → 3min)
Implementation Plan
- ✅ Create this GitHub issue (documenting investigation)
- Remove job dependency (parallel execution)
- Fix port configuration
- Fix crypto ready flag
- Disable StrictMode in tests
- Increase timeout
- Fix useNotes hook
- Commit and push
- Monitor workflow run
Expected Outcome
- ✅ Playwright tests pass within 60s timeout
- ✅ Lighthouse tests pass
- ✅ Total workflow time: ~3 minutes (down from ~5 minutes)
- ✅ No test flakiness from re-renders or race conditions
Investigation Methodology
Used 4 parallel agents to investigate simultaneously:
- Workflow structure analysis → Identified sequential execution issue
- Root cause analysis → Found crypto init and re-render issues
- Port configuration analysis → Discovered Caddy port mismatch
- Crypto initialization analysis → Located broken dynamic import
Total investigation time: ~15 minutes with parallel agents vs ~60 minutes sequential
Related Files
Workflow:
.github/workflows/frontend-playwright.yml
Frontend:
frontend/playwright.config.ts
frontend/src/main.tsx
frontend/src/features/app/hooks/useNotes.tsx
frontend/e2e/import-export.spec.ts
Docker:
frontend/Dockerfile
docker-compose.yml
Problem Summary
The Playwright and Lighthouse CI workflows are failing with timeout errors. After comprehensive investigation using parallel agents, we've identified multiple root causes and optimizations.
What We Tried
✅ Completed: Docker Compose Migration (PR #314)
02bb93aFix Playwright and Lighthouse CI workflows using Docker Composeec68889Fix Playwright config to reuse existing server from docker-composeee98991Add VITE_API_URL to docker compose steps for frontend builde40c888Optimize Playwright test performance (workers 1→2, retries 2→1, Docker Buildx)❌ Current Status: Tests Still Timing Out
Despite the improvements, tests fail with 30s timeouts:
Affected tests: 10/20 tests fail (auth registration, import/export, notes CRUD)
Root Causes Identified
🔴 Issue #1: Port Configuration Mismatch
Files involved:
frontend/Dockerfile:44- SetsENV PORT=80docker-compose.yml:67,69- Port mapping and environment override.github/workflows/frontend-playwright.yml:59- SetsFRONTEND_PORT: 3000The problem:
Port flow confusion:
Evidence: Workflow stuck at "Start services with Docker Compose" step
🔴 Issue #2:
window.__PLAYWRIGHT_CRYPTO_READYNever SetFile:
frontend/e2e/import-export.spec.ts:227The problem:
Why it fails:
/assets/js/crypto-core-[hash].js/src/services/cryptoService.tsdoesn't exist in built app.catch())true→ test times out after 30sEnvironment comparison:
pnpm run dev)/src/*.tsworkspreview)/assets/*.jsexists/assets/*.jsexists🔴 Issue #3: Libsodium WASM Initialization Race Condition
File:
frontend/src/services/cryptoService.tsThe problem:
Root cause: Asynchronous WASM library loading takes longer in containerized environments (no
.catch()error handling, no timeout fallback)🟡 Issue #4: React.StrictMode Causing Double Renders
File:
frontend/src/main.tsx:37-39Impact:
useEffecthooks run twice (e.g.,initializeApp()inLeafLockApp.tsx:260)Note: StrictMode is React best practice for development but adds instability to E2E tests
🟡 Issue #5: Potential Infinite useEffect Loop
File:
frontend/src/features/app/hooks/useNotes.tsx:136-140Risk: If
setSelectedNotetriggers a re-render that modifiesnotesarray, this creates a loop. Combined with StrictMode, this can cause rapid cascading re-renders.🟢 Issue #6: Sequential Job Execution (Performance)
File:
.github/workflows/frontend-playwright.yml:85Current timing (sequential):
Optimized timing (parallel):
No technical dependency exists - both jobs:
Proposed Fixes
✅ Fix #1: Simplify Port Configuration
File:
.github/workflows/frontend-playwright.yml:59Remove:
Reason: Use default config → Caddy listens on :80, host maps 3000:80
Impact: Eliminates environment variable override complexity
✅ Fix #2: Remove Broken Crypto Flag Wait
File:
frontend/e2e/import-export.spec.tsRemove:
addInitScriptwith dynamic import__PLAYWRIGHT_CRYPTO_READYReason:
page.route()await page.getByLabel('Email')) are sufficientImpact: Removes 30s timeout, test proceeds with existing mocks
✅ Fix #3: Disable React.StrictMode in Tests
File:
frontend/src/main.tsx:36-40Change:
Reason: Eliminate double-renders and race conditions in test environment
Impact: More stable test execution without affecting production
✅ Fix #4: Increase Playwright Timeout for CI
File:
frontend/playwright.config.ts:14Change:
Reason: Docker environments are slower than local dev
Impact: Gives async operations more time to complete
✅ Fix #5: Fix useNotes Effect Dependency
File:
frontend/src/features/app/hooks/useNotes.tsx:138Change:
Reason: Prevent cascading re-renders from array reference changes
Impact: More stable component behavior
✅ Fix #6: Enable Parallel Job Execution
File:
.github/workflows/frontend-playwright.yml:85Remove:
Reason: Jobs are independent and can run simultaneously
Impact: 45% faster CI (5min → 3min)
Implementation Plan
Expected Outcome
Investigation Methodology
Used 4 parallel agents to investigate simultaneously:
Total investigation time: ~15 minutes with parallel agents vs ~60 minutes sequential
Related Files
Workflow:
.github/workflows/frontend-playwright.ymlFrontend:
frontend/playwright.config.tsfrontend/src/main.tsxfrontend/src/features/app/hooks/useNotes.tsxfrontend/e2e/import-export.spec.tsDocker:
frontend/Dockerfiledocker-compose.yml