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

18 KiB

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:

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:

// 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:

// 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:

- 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:

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:

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:

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:

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:

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:

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:

// 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:

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:

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:

// 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:

// 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:

// 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:

- 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

# 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

# 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

# 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