Files
Charon/docs/plans/archive/skipped_tests_remediation.md
2026-03-04 18:34:49 +00:00

618 lines
18 KiB
Markdown

# Skipped Tests Remediation Plan
**Status:** Active
**Created:** 2026-02-12
**Owner:** Playwright Dev
**Priority:** High (Blocking PRs)
## Executive Summary
This plan addresses 4 skipped E2E tests across 2 test files. Analysis reveals 1 test requires code fix (in progress), 2 tests have incorrect locators (test bugs), and 2 tests require accessibility enhancements (future backlog).
**Impact:**
- **1 test** depends on route guard fix (Frontend Dev working)
- **2 tests** can be fixed immediately (wrong test locators)
- **2 tests** require feature implementation (accessibility backlog)
---
## Test Inventory
### Category A: Bug in Code (Requires Code Fix)
#### A.1: Session Expiration Route Guard
**Location:** `tests/core/authentication.spec.ts:323`
**Test:**
```typescript
test.fixme('should redirect to login when session expires')
```
**Issue:** Route guards not blocking access to protected routes after session expiration.
**Evidence of Existing Fix:**
The route guard has been updated with defense-in-depth validation:
```typescript
// frontend/src/components/RequireAuth.tsx
const hasToken = localStorage.getItem('charon_auth_token');
const hasUser = user !== null;
if (!isAuthenticated || !hasToken || !hasUser) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
```
**Root Cause:**
Test simulates session expiration by clearing cookies/localStorage, then reloads the page. The `AuthContext.tsx` uses `checkAuth()` on mount to validate the session:
```typescript
// frontend/src/context/AuthContext.tsx
useEffect(() => {
const checkAuth = async () => {
const storedToken = localStorage.getItem('charon_auth_token');
if (!storedToken) {
setIsLoading(false);
return;
}
setAuthToken(storedToken);
try {
const { data } = await client.get<User>('/auth/me');
setUser(data);
} catch {
setUser(null);
setAuthToken(null);
localStorage.removeItem('charon_auth_token');
} finally {
setIsLoading(false);
}
};
checkAuth();
}, []);
```
**Current Status:** ✅ Code fix merged (2026-01-30)
**Validation Task:**
```yaml
- task: Verify route guard fix
owner: Playwright Dev
priority: High
steps:
- Re-enable test by changing test.fixme() to test()
- Run: npx playwright test tests/core/authentication.spec.ts:323 --project=firefox
- Verify test passes
- If passes: Remove .fixme() marker
- If fails: Document failure mode and escalate to Frontend Dev
```
**Acceptance Criteria:**
- [ ] Test passes consistently (3/3 runs)
- [ ] Page redirects to `/login` within 10s after clearing auth state
- [ ] No console errors during redirect
- [ ] Test uses proper auth fixture isolation
**Estimated Effort:** 1 hour (validation + documentation)
---
### Category B: Bug in Test (Requires Test Fix)
#### B.1: Emergency Token Generation - Wrong Locator (Line 137)
**Location:** `tests/core/admin-onboarding.spec.ts:137`
**Current Test Code:**
```typescript
await test.step('Find emergency token section', async () => {
const emergencySection = page.getByText(/admin whitelist|emergency|break.?glass|recovery token/i);
const isVisible = await emergencySection.isVisible().catch(() => false);
if (!isVisible) {
test.skip(true, 'Emergency token feature not available in this deployment');
}
await expect(emergencySection).toBeVisible();
});
```
**Issue:** Test searches for text patterns that don't exist on the page.
**Evidence:**
- Feature EXISTS: `/settings/security` page with "Generate Token" button
- API exists: `POST /security/breakglass/generate`
- Hook exists: `useGenerateBreakGlassToken()`
- Button text: "Generate Token" (from `t('security.generateToken')`)
- Tooltip: "Generate a break-glass token for emergency access"
- Test searches: `/admin whitelist|emergency|break.?glass|recovery token/i`
**Problem:** Button text is generic ("Generate Token"), and the test doesn't search tooltips/aria-labels.
**Root Cause:** Test assumes button contains "emergency" or "break-glass" in visible text.
**Resolution:**
Use role-based locator with flexible name matching:
```typescript
await test.step('Find emergency token section', async () => {
// Navigate to security settings first
await page.goto('/settings/security', { waitUntil: 'domcontentloaded' });
await waitForLoadingComplete(page);
// Look for the generate token button - it may have different text labels
// but should be identifiable by role and contain "token" or "generate"
const generateButton = page.getByRole('button', { name: /generate.*token|token.*generate/i });
const isVisible = await generateButton.isVisible().catch(() => false);
if (!isVisible) {
test.skip(true, 'Break-glass token feature not available in this deployment');
}
await expect(generateButton).toBeVisible();
});
```
**Validation:**
```bash
npx playwright test tests/core/admin-onboarding.spec.ts:130-160 --project=firefox
```
**Acceptance Criteria:**
- [ ] Test finds button using role-based locator
- [ ] Test passes on `/settings/security` page
- [ ] No false positives (doesn't match unrelated buttons)
- [ ] Clear skip message if feature missing
**Estimated Effort:** 30 minutes
---
#### B.2: Emergency Token Generation - Wrong Locator (Line 146)
**Location:** `tests/core/admin-onboarding.spec.ts:146`
**Current Test Code:**
```typescript
await test.step('Generate emergency token', async () => {
const generateButton = page.getByRole('button', { name: /generate token/i });
const isGenerateVisible = await generateButton.isVisible().catch(() => false);
if (!isGenerateVisible) {
test.skip(true, 'Generate Token button not available in this deployment');
return;
}
await generateButton.click();
// Wait for modal or confirmation
await page.waitForSelector('[role="dialog"], [class*="modal"]', { timeout: 5000 }).catch(() => {
return Promise.resolve();
});
});
```
**Issue:** Same as B.1 - test needs to navigate to correct page first and use proper locator.
**Resolution:**
Merge with B.1 fix. The test should be in a single step that:
1. Navigates to `/settings/security`
2. Finds button with proper locator
3. Clicks and waits for modal/confirmation
**Combined Fix:**
```typescript
test('Emergency token can be generated', async ({ page }) => {
await test.step('Navigate to security settings and find token generation', async () => {
await page.goto('/settings/security', { waitUntil: 'domcontentloaded' });
await waitForLoadingComplete(page);
const generateButton = page.getByRole('button', { name: /generate.*token|token.*generate/i });
const isVisible = await generateButton.isVisible().catch(() => false);
if (!isVisible) {
test.skip(true, 'Break-glass token feature not available in this deployment');
}
await expect(generateButton).toBeVisible();
await expect(generateButton).toBeEnabled();
});
await test.step('Generate token and verify modal', async () => {
const generateButton = page.getByRole('button', { name: /generate.*token|token.*generate/i });
await generateButton.click();
// Wait for modal or inline token display
const modal = page.locator('[role="dialog"], [class*="modal"]');
const hasModal = await modal.isVisible({ timeout: 5000 }).catch(() => false);
// If no modal, token might display inline
if (!hasModal) {
const tokenDisplay = page.locator('[data-testid="breakglass-token"], input[readonly]');
await expect(tokenDisplay).toBeVisible({ timeout: 5000 });
} else {
await expect(modal).toBeVisible();
}
});
await test.step('Verify token displayed and copyable', async () => {
// Token input or display field
const tokenField = page.locator('input[readonly], [data-testid="breakglass-token"], [data-testid="emergency-token"], code').first();
await expect(tokenField).toBeVisible();
// Should have copy button near the token
const copyButton = page.getByRole('button', { name: /copy|clipboard/i });
const hasCopyButton = await copyButton.isVisible().catch(() => false);
if (hasCopyButton) {
await copyButton.click();
// Verify copy feedback (toast, button change, etc.)
const copiedFeedback = page.getByText(/copied/i).or(page.locator('[class*="success"]'));
await expect(copiedFeedback).toBeVisible({ timeout: 3000 });
}
});
});
```
**Acceptance Criteria:**
- [ ] Test navigates to correct page
- [ ] Button found with flexible locator
- [ ] Modal or inline display detected
- [ ] Token value and copy button verified
- [ ] Test passes 3/3 times
**Estimated Effort:** 30 minutes
---
### Category C: Accessibility Enhancements (Future Features)
#### C.1: Copy Button ARIA Labels
**Location:** `tests/manual-dns-provider.spec.ts:282`
**Test:**
```typescript
test.skip('should have proper ARIA labels on copy buttons', async ({ page }) => {
await test.step('Verify ARIA labels on copy buttons', async () => {
const copyButtons = page.getByRole('button', { name: /copy record/i });
const buttonCount = await copyButtons.count();
expect(buttonCount).toBeGreaterThan(0);
for (let i = 0; i < buttonCount; i++) {
const button = copyButtons.nth(i);
const ariaLabel = await button.getAttribute('aria-label');
const textContent = await button.textContent();
const isAccessible = ariaLabel || textContent?.trim();
expect(isAccessible).toBeTruthy();
}
});
});
```
**Current Implementation:**
The copy buttons **DO** have proper ARIA labels:
```tsx
// frontend/src/components/dns-providers/ManualDNSChallenge.tsx:298-311
<Button
variant="outline"
size="sm"
onClick={() => handleCopy('name', challenge.fqdn)}
aria-label={t('dnsProvider.manual.copyRecordName')} // ✅ HAS ARIA LABEL
className="flex-shrink-0"
>
{copiedField === 'name' ? (
<Check className="h-4 w-4 text-success" aria-hidden="true" />
) : (
<Copy className="h-4 w-4" aria-hidden="true" />
)}
<span className="sr-only">
{copiedField === 'name' ? t('dnsProvider.manual.copied') : t('dnsProvider.manual.copy')}
</span>
</Button>
```
**Status:****FEATURE ALREADY IMPLEMENTED**
**Action:**
Remove `.skip()` marker and verify test passes:
```bash
npx playwright test tests/manual-dns-provider.spec.ts:282 --project=firefox
```
**Acceptance Criteria:**
- [ ] Test finds copy buttons by role
- [ ] All copy buttons have `aria-label` attributes
- [ ] Labels are descriptive and unique
- [ ] Test passes 3/3 times
**Estimated Effort:** 15 minutes (validation only)
---
#### C.2: Status Change Announcements
**Location:** `tests/manual-dns-provider.spec.ts:299`
**Test:**
```typescript
test.skip('should announce status changes to screen readers', async ({ page }) => {
await test.step('Verify live region for status updates', async () => {
const liveRegion = page.locator('[aria-live="polite"]').or(page.locator('[role="status"]'));
await expect(liveRegion).toBeAttached();
});
});
```
**Current Implementation:**
The component has a `statusAnnouncerRef` but it's **NOT** properly configured for screen readers:
```tsx
// frontend/src/components/dns-providers/ManualDNSChallenge.tsx:250-256
<div
ref={statusAnnouncerRef}
role="status"
aria-live="polite"
aria-atomic="true"
className="sr-only"
/>
```
**Problem:**
The `<div>` exists with correct ARIA attributes, but it's **NEVER UPDATED** with text content when status changes. The ref is created but the text is not set when status changes occur.
**Evidence:**
```tsx
// Line 134: Ref created
const statusAnnouncerRef = useRef<HTMLDivElement>(null)
// Line 139-168: Status change effect exists but doesn't update announcer text
useEffect(() => {
if (currentStatus !== previousStatusRef.current) {
previousStatusRef.current = currentStatus
// ❌ Missing: statusAnnouncerRef.current.textContent = statusMessage
}
}, [currentStatus, pollData?.error_message, onComplete, t])
```
**Status:** 🔴 **FEATURE NOT IMPLEMENTED**
**Impact:**
- **Severity:** Medium
- **Users Affected:** Screen reader users
- **Workaround:** Screen reader users can query the status manually, but miss automatic updates
- **WCAG Level:** A (4.1.3 Status Messages)
**Required Implementation:**
```typescript
// In frontend/src/components/dns-providers/ManualDNSChallenge.tsx
// Update the status change effect (around line 139-168)
useEffect(() => {
if (currentStatus !== previousStatusRef.current) {
previousStatusRef.current = currentStatus
// Get the status config for current status
const statusInfo = STATUS_CONFIG[currentStatus]
// Construct announcement text
let announcement = t(statusInfo.labelKey)
// Add error message if available
if (currentStatus === 'failed' && pollData?.error_message) {
announcement += `. ${pollData.error_message}`
}
// Update the screen reader announcer
if (statusAnnouncerRef.current) {
statusAnnouncerRef.current.textContent = announcement
}
// Existing completion logic...
if (currentStatus === 'verified') {
toast.success(t('dnsProvider.manual.verifySuccess'))
onComplete(true)
} else if (TERMINAL_STATES.includes(currentStatus) && currentStatus !== 'verified') {
toast.error(pollData?.error_message || t('dnsProvider.manual.verifyFailed'))
onComplete(false)
}
}
}, [currentStatus, pollData?.error_message, onComplete, t])
```
**Validation:**
1. Add manual test with screen reader (NVDA/JAWS/VoiceOver)
2. Verify status changes are announced
3. Run E2E test to verify `aria-live` region updates
**Acceptance Criteria:**
- [ ] Status announcer ref is updated on every status change
- [ ] Announcement includes status name and error message (if applicable)
- [ ] Text is cleared and replaced on each change (not appended)
- [ ] Screen reader announces changes automatically
- [ ] E2E test passes with live region text verification
**Estimated Effort:** 2 hours (implementation + testing)
**Priority:** Medium - Accessibility improvement for screen reader users
**Action Item:**
```yaml
- task: Implement status change announcements
owner: Frontend Dev
priority: Medium
labels: [accessibility, enhancement, a11y]
milestone: Q1 2026
references:
- WCAG 4.1.3 Status Messages
- docs/guides/manual-dns-provider.md
```
---
## Implementation Roadmap
### Phase 1: Immediate (Block Current PR)
**Goal:** Fix tests that should already pass
| Task | Owner | Effort | Priority |
|------|-------|--------|----------|
| Verify session expiration fix (A.1) | Playwright Dev | 1h | Critical |
| Fix emergency token locators (B.1, B.2) | Playwright Dev | 1h | Critical |
| Verify copy button ARIA labels (C.1) | Playwright Dev | 15m | High |
**Total Effort:** ~2.25 hours
**Deliverables:**
- [ ] 3 tests re-enabled and passing
- [ ] Documentation updated with fix notes
- [ ] PR ready for review
**Exit Criteria:**
- All Phase 1 tests pass 3/3 times in Firefox
- No new console errors introduced
- Tests use proper fixtures and isolation
---
### Phase 2: Post-Green (Future Enhancements)
**Goal:** Implement missing accessibility features
| Task | Owner | Effort | Priority |
|------|-------|--------|----------|
| Implement status announcements (C.2) | Frontend Dev | 2h | Medium |
| Test announcements with screen readers | QA / Accessibility | 1h | Medium |
| Update E2E test to verify announcements | Playwright Dev | 30m | Medium |
**Total Effort:** ~3.5 hours
**Deliverables:**
- [ ] Status change announcer implemented
- [ ] Manual screen reader testing completed
- [ ] E2E test re-enabled and passing
- [ ] User guide updated with accessibility notes
**Exit Criteria:**
- Screen reader users receive automatic status updates
- E2E test verifies live region text content
- No WCAG 4.1.3 violations detected
---
## Risk Assessment
### High Risk
- **A.1 (Session Expiration):** If fix doesn't work, blocks route guard validation
- *Mitigation:* Frontend Dev available for debugging
- *Escalation:* Document exact failure mode, create new issue
### Medium Risk
- **C.2 (Status Announcements):** Requires frontend code change
- *Mitigation:* Non-blocking, can defer to next sprint
- *Impact:* Accessibility improvement, not critical functionality
### Low Risk
- **B.1, B.2 (Token Locators):** Simple test fix, no code changes
- **C.1 (ARIA Labels):** Feature already implemented, just needs validation
---
## Success Metrics
| Metric | Target | Current | Status |
|--------|--------|---------|--------|
| Skipped Tests | 0 | 4 | 🔴 |
| E2E Pass Rate | 100% | ~97% | 🟡 |
| Accessibility Coverage | 100% | ~95% | 🟡 |
**Post-Remediation:**
- **Skipped Tests:** 0 (all resolved or in backlog)
- **E2E Pass Rate:** 100% (all critical flows tested)
- **Accessibility Coverage:** 100% (all interactive elements accessible)
---
## Technical Debt Log
### Created During Remediation
None - all fixes are proper implementations, no shortcuts taken.
### Resolved During Remediation
1. **Vague test locators:** Emergency token tests now use role-based locators
2. **Missing navigation:** Tests now navigate to correct page before assertions
3. **Improper skip conditions:** Tests now have clear, actionable skip messages
---
## Appendix: Test Execution Reference
### Running Individual Tests
```bash
# Session expiration test
npx playwright test tests/core/authentication.spec.ts:323 --project=firefox
# Emergency token tests
npx playwright test tests/core/admin-onboarding.spec.ts:130-160 --project=firefox
# Copy button ARIA labels
npx playwright test tests/manual-dns-provider.spec.ts:282 --project=firefox
# Status announcements (after implementation)
npx playwright test tests/manual-dns-provider.spec.ts:299 --project=firefox
```
### Running Full Suites
```bash
# All authentication tests
npx playwright test tests/core/authentication.spec.ts --project=firefox
# All onboarding tests
npx playwright test tests/core/admin-onboarding.spec.ts --project=firefox
# All manual DNS tests
npx playwright test tests/manual-dns-provider.spec.ts --project=firefox
```
### Debug Mode
```bash
# Run with UI mode for visual debugging
npx playwright test <test-file> --ui
# Run with headed browser
npx playwright test <test-file> --headed --project=firefox
# Run with inspector
npx playwright test <test-file> --debug --project=firefox
```
---
## Change Log
| Date | Version | Changes |
|------|---------|---------|
| 2026-02-12 | 1.0 | Initial plan created |
---
## Approval & Sign-off
- [ ] **Technical Lead:** Reviewed and approved technical approach
- [ ] **Playwright Dev:** Agrees to Phase 1 timeline
- [ ] **Frontend Dev:** Agrees to Phase 2 timeline
- [ ] **QA Lead:** Reviewed test coverage impact
---
**Next Steps:**
1. Review this plan with team
2. Assign Phase 1 tasks to Playwright Dev
3. Create GitHub issues for Phase 2 tasks
4. Begin Phase 1 execution immediately