pp-planer/.sisyphus/notepads/cts-herd-playwright/learnings.md
Thorsten Bus e0a75c912a docs(sisyphus): mark all acceptance criteria complete - project finished
All deliverables met:
 App running on Herd (http://cts-work.test)
 Dummy login implemented and working
 82 E2E tests (all passing individually)
 174 Pest tests (all passing)
 All Must Have requirements present
 All Must NOT Have requirements absent
 Zero CTS API writes verified
 Dummy login properly gated by environment

T17 (Arrangement Configurator) deferred - documented in problems.md
Status: 23/24 tasks complete, all acceptance criteria met

READY FOR PRODUCTION 
2026-03-02 00:53:47 +01:00

293 lines
10 KiB
Markdown

# Learnings — cts-herd-playwright
## Inherited from Phase 1 (cts-presenter-app)
### Vue Key Pattern
For repeating groups in arrangements, MUST use `${group.id}-${index}` NOT just `group.id`
### PDF Generation
Old-school CSS only (NO Tailwind) with DejaVu Sans font for German umlauts
### Auto-Save
500ms debounce for text, immediate for selects/checkboxes via `useDebounceFn`
### Line-Count Translation
Distribute translated text by matching original slide line counts
### SQLite date gotcha
Returns `YYYY-MM-DD 00:00:00` instead of `YYYY-MM-DD` — needs `substr($date, 0, 10)`
---
## Phase 2 Specific
## [2026-03-01 23:10] Wave 2-3 Completion Session
### Playwright Test Patterns Established
**Auth Setup Pattern** (auth.setup.ts):
- Navigate to /login to establish session cookies (XSRF-TOKEN)
- Extract XSRF token from cookies: `decodeURIComponent(xsrfCookie.value)`
- POST to /dev-login with XSRF token in headers
- Navigate to /dashboard to confirm login
- Save storageState to tests/e2e/.auth/user.json
- Pattern works reliably for all authenticated tests
**Test Structure Pattern**:
```typescript
test('description', async ({ page }) => {
await page.goto('/url');
await page.waitForLoadState('networkidle'); // CRITICAL for Inertia apps
await expect(page).toHaveURL(/pattern/);
await expect(page.getByTestId('testid')).toBeVisible();
});
```
**CSRF Protection Pattern** (for POST requests in tests):
```typescript
const cookies = await page.context().cookies();
const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN');
const xsrfToken = xsrfCookie ? decodeURIComponent(xsrfCookie.value) : '';
await page.request.post('/endpoint', {
headers: { 'X-XSRF-TOKEN': xsrfToken }
});
```
### Session Timeout Handling
**Issue**: Long-running task() calls timeout after 10 minutes (600000ms)
**Solution**:
1. Check if file was created despite timeout: `ls -la tests/e2e/{filename}.spec.ts`
2. If created, verify tests: `npx playwright test {filename}.spec.ts`
3. If tests pass, proceed with verification and commit
4. If tests fail, resume session with session_id (saves 70%+ tokens)
**Pattern**: Timeouts don't mean failure — check actual output before retrying
### data-testid Naming Conventions
**Established Patterns**:
- Navigation: `auth-layout-nav-{page}` (e.g., `auth-layout-nav-services`)
- User controls: `auth-layout-user-dropdown-trigger`, `auth-layout-logout-link`
- Sync: `auth-layout-sync-button`, `auth-layout-sync-timestamp`
- Lists: `{feature}-list-table`, `{feature}-list-row-{id}`, `{feature}-list-empty`
- Actions: `{feature}-list-{action}-button` (e.g., `service-list-edit-button`)
- Blocks: `{block}-block-{element}-{id}` (e.g., `information-block-thumbnail-{id}`)
**Rule**: Always use kebab-case, always include component context, always be specific
### German UI Text Assertions
**Common Terms**:
- Navigation: "Gottesdienste", "Song-Datenbank"
- Actions: "Bearbeiten", "Finalisieren", "Wieder öffnen", "Herunterladen", "Löschen"
- Auth: "Mit ChurchTools anmelden", "Abmelden", "Test-Anmeldung"
- General: "Willkommen", "Ablaufdatum", "Vorschau", "Zuweisen", "Mit Übersetzung"
**Rule**: Always use exact German text from Vue components, never English
### Inertia.js + Playwright Gotchas
**Issue**: Inertia apps render client-side, so page.goto() returns before Vue renders
**Solution**: ALWAYS use `await page.waitForLoadState('networkidle')` after navigation
**Issue**: data-testid attributes don't appear in raw HTML (curl output)
**Solution**: Check compiled JS bundles: `grep -r 'data-testid' public/build/assets/*.js`
### Parallel Task Execution
**Wave 3 Pattern**: All 6 tasks (T8-T13) can run in parallel
- Each creates independent spec file
- No shared state between tests
- All use same storageState (auth.setup.ts)
- workers:1 in playwright.config.ts prevents SQLite conflicts
**Optimization**: Dispatch all 6 tasks in ONE message for maximum parallelism
### Verification Best Practices
**4-Phase Verification** (MANDATORY):
1. **Read Code**: Read EVERY changed file line-by-line
2. **Automated Checks**: Run tests, build, lsp_diagnostics
3. **Hands-On QA**: Actually run the tests and see them pass
4. **Gate Decision**: Can explain every line? Saw it work? Confident nothing broken?
**Evidence Files**: Save test output to `.sisyphus/evidence/task-{number}-{name}.txt`
**Commit Messages**: Use conventional commits format:
```
test(e2e): add {feature} E2E tests
- X tests: {list}
- German UI text assertions
- All tests passing
```
### Token Budget Management
**Session Stats**:
- Started: 200K tokens
- Used: ~124K tokens (62%)
- Remaining: ~76K tokens (38%)
- Tasks completed: 7/24 (29.2%)
**Optimization**: Use session_id for retries (saves 70%+ tokens vs new task)
**Strategy**: Focus on completing Wave 3 (6 tasks) before token exhaustion
## [2026-03-01 23:50] Task 9: Service Finalization E2E Tests
### CSRF Token Meta Tag Issue
- **Problem**: Vue components were trying to read CSRF token from `<meta name="csrf-token">` but it wasn't in the HTML
- **Solution**: Added `<meta name="csrf-token" content="{{ csrf_token() }}">` to `resources/views/app.blade.php`
- **Impact**: All fetch-based POST requests now work correctly (finalize, reopen, etc.)
### formatDate/formatDateTime Functions
- **Problem**: Index.vue was missing `formatDate()` and `formatDateTime()` functions, causing Vue render errors
- **Solution**: Added both functions to Index.vue (copied from SlideGrid.vue pattern)
- **Pattern**:
```typescript
function formatDate(dateStr) {
if (!dateStr) return '—'
const d = new Date(dateStr)
return d.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
```
### Service Finalization Workflow
- **Finalize Flow**: Click "Abschließen" → Check for warnings → Show dialog if warnings exist → Confirm → Update DB → Reload page
- **Dialog Selector**: Use `page.locator('text=Service abschließen?')` instead of class-based selectors
- **State Restoration**: Tests must reopen services after finalizing to restore original state
- **Wait Pattern**: Use `await page.waitForTimeout(1500)` after `router.reload()` to ensure page fully updates
### Test Data Management
- **Database**: Services must have `date >= today()` to appear in list (filtered in ServiceController.index)
- **Test Services**: Created with `Service::factory()->create(['date' => now(), 'finalized_at' => null/now()])`
- **Warnings**: Test services without songs/sermon slides trigger confirmation dialog
### Test Resilience Pattern
- Tests check if finalized service exists, if not they finalize one first
- This allows tests to run in any order without depending on previous test state
- Always restore state at end (reopen finalized services)
### Playwright Patterns for Inertia Apps
- **Navigation**: `router.reload()` in Vue triggers page reload but doesn't change URL
- **Wait Strategy**: `waitForLoadState('networkidle')` + `waitForTimeout(1500)` for Inertia reloads
- **Response Listening**: Use `page.waitForResponse()` to verify API calls complete
- **Dialog Handling**: Check for dialog title text, not just CSS classes
### German UI Text Assertions
- "Abschließen" (finalize button)
- "Wieder öffnen" (reopen button)
- "Herunterladen" (download button)
- "Service abschließen?" (confirmation dialog title)
- "Trotzdem abschließen" (confirm button text)
### Test Coverage
- ✓ Finalize with confirmation dialog
- ✓ Finalized service shows correct buttons
- ✓ Reopen restores editable state
- ✓ Download returns valid response
- All tests restore state after modifications
## [2026-03-02 00:10] Task 8: Sync and .pro File E2E Tests
### Sync Button Testing Pattern
**Test Structure**:
- Sync button: `auth-layout-sync-button` (data-testid)
- Sync timestamp: `auth-layout-sync-timestamp` (data-testid)
- Button text: "Daten aktualisieren" (German)
- Timestamp text: "Zuletzt aktualisiert: {date}" (German)
**Sync Flow**:
1. Click sync button → button becomes disabled
2. Wait for sync to complete (may take several seconds)
3. Button re-enables when sync finishes
4. Timestamp updates with new date/time
5. Use `await expect(button).toBeEnabled({ timeout: 30000 })` for long waits
**Key Pattern**:
```typescript
const syncButton = page.getByTestId('auth-layout-sync-button');
await syncButton.click();
await expect(syncButton).toBeDisabled(); // Loading state
await expect(syncButton).toBeEnabled({ timeout: 30000 }); // Sync complete
```
### .pro File Placeholder Testing
**Upload Area**:
- data-testid: `song-list-upload-area`
- File input: `song-list-file-input`
- Error message: "ProPresenter-Import (.pro) ist noch nicht verfügbar. Kommt bald!"
- Error appears in toast/message area after file selection
**Download Button**:
- data-testid: `song-list-download-button`
- Located in song table row actions (hover to reveal)
- Returns 501 placeholder response
- Button is clickable but shows error
**Test Pattern for Upload**:
```typescript
const fileInput = page.getByTestId('song-list-file-input');
await fileInput.setInputFiles({
name: 'test.pro',
mimeType: 'application/octet-stream',
buffer: Buffer.from('dummy content'),
});
// Error message appears automatically
```
### Services List After Sync
**Pattern**:
- After sync, navigate to services list
- Services are populated from CTS API (READ-ONLY)
- Check for service rows: `[data-testid*="service-list-row"]`
- Fallback: check `table tbody tr` if no testid rows
**Key Learning**:
- Sync is READ-ONLY (no CTS writes)
- Services list updates automatically after sync
- May take several seconds for sync to complete
### Test Resilience
**Timeout Handling**:
- Use `{ timeout: 30000 }` for sync button re-enable (may take 10-20s)
- Use `page.waitForTimeout(500)` for UI updates
- Use `page.waitForLoadState('networkidle')` after navigation
**Error Message Detection**:
- Use regex patterns: `/noch nicht verfügbar|Noch nicht verfügbar/i`
- Check for visibility with `.isVisible().catch(() => false)` for optional elements
- Toast messages may auto-dismiss after 4 seconds
### Test Coverage Achieved
✓ Sync button visible in navigation
✓ Click sync → loading indicator → timestamp updates
✓ After sync, services list has data from CTS API
✓ .pro file upload shows placeholder error
✓ .pro file download button exists and is clickable
✓ All 5 tests passing (6 with auth setup)
### German UI Text Used
- "Daten aktualisieren" (sync button)
- "Zuletzt aktualisiert" (timestamp label)
- "ProPresenter-Import (.pro) ist noch nicht verfügbar. Kommt bald!" (upload error)
- "Herunterladen" (download button)