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

31 KiB
Raw Blame History

Phase 4: Settings E2E Test Implementation Plan

Date: January 19, 2026 Status: Planning Complete Estimated Effort: 5 days (Week 8 per main plan) Dependencies: Phase 1-3 complete (346+ tests passing)


Table of Contents

  1. Executive Summary
  2. Research Findings
  3. Test File Specifications
  4. Test Data Fixtures Required
  5. Implementation Order
  6. Risks and Blockers
  7. Success Metrics

1. Executive Summary

Scope

Phase 4 covers E2E testing for all Settings-related functionality:

Area Frontend Page API Endpoints Est. Tests
System Settings SystemSettings.tsx /settings, /settings/validate-url, /settings/test-url 25
SMTP Settings SMTPSettings.tsx /settings/smtp, /settings/smtp/test, /settings/smtp/test-email 18
Notifications Notifications.tsx /notifications/providers/*, /notifications/templates/*, /notifications/external-templates/* 22
User Management UsersPage.tsx /users/*, /users/invite, /invite/* 28
Encryption Management EncryptionManagement.tsx /admin/encryption/* 15
Account Settings Account.tsx /auth/profile, /auth/password, /settings 20

Total Estimated Tests: ~128 tests across 6 test files

Key Findings from Research

  1. Settings Navigation: Main settings page (Settings.tsx) provides tab navigation to 4 sub-routes:

    • /settings/system → System Settings
    • /settings/notifications → Notifications
    • /settings/smtp → SMTP Settings
    • /settings/account → Account Settings
  2. Encryption Management is accessed via a separate route (likely /encryption or admin panel)

  3. User Management is accessed via /users route, not part of settings tabs

  4. All settings use React Query for data fetching with standardized patterns

  5. Form validation is primarily client-side with some server-side validation


2. Research Findings

2.1 System Settings (SystemSettings.tsx)

Route: /settings/system

UI Components:

  • Feature Toggles (Cerberus, CrowdSec Console Enrollment, Uptime Monitoring)
  • General Configuration (Caddy Admin API, SSL Provider, Domain Link Behavior, Language)
  • Application URL (with validation and connectivity test)
  • System Health Status Card
  • Update Checker
  • WebSocket Status Card

Form Fields:

Field Input Type ID/Selector Validation
Caddy Admin API text #caddy-api URL format
SSL Provider select #ssl-provider Enum: auto, letsencrypt-staging, letsencrypt-prod, zerossl
Domain Link Behavior select #domain-behavior Enum: same_tab, new_tab, new_window
Public URL text input with validation icon URL format, reachability test
Feature Toggles switch aria-label patterns Boolean

API Endpoints:

Method Endpoint Purpose
GET /settings Fetch all settings
POST /settings Update setting
POST /settings/validate-url Validate public URL format
POST /settings/test-url Test public URL reachability (SSRF-protected)
GET /health System health status
GET /system/updates Check for updates
GET /feature-flags Get feature flags
PUT /feature-flags Update feature flags

Key Selectors:

// Feature toggles
page.getByRole('switch').filter({ has: page.getByText(/cerberus|crowdsec|uptime/i) })

// General config
page.locator('#caddy-api')
page.locator('#ssl-provider')
page.locator('#domain-behavior')

// Save button
page.getByRole('button', { name: /save settings/i })

// URL test button
page.getByRole('button', { name: /test/i }).filter({ hasText: /test/i })

2.2 SMTP Settings (SMTPSettings.tsx)

Route: /settings/smtp

UI Components:

  • SMTP Configuration Card (host, port, username, password, from address, encryption)
  • Connection Test Button
  • Send Test Email Section

Form Fields:

Field Input Type ID/Selector Validation
SMTP Host text #smtp-host Required
SMTP Port number #smtp-port Required, numeric
Username text #smtp-username Optional
Password password #smtp-password Optional
From Address email (needs ID) Email format
Encryption select (needs ID) Enum: none, ssl, starttls
Test Email To email (needs ID) Email format

API Endpoints:

Method Endpoint Purpose
GET /settings/smtp Get SMTP config
POST /settings/smtp Update SMTP config
POST /settings/smtp/test Test SMTP connection
POST /settings/smtp/test-email Send test email

Key Selectors:

page.locator('#smtp-host')
page.locator('#smtp-port')
page.locator('#smtp-username')
page.locator('#smtp-password')
page.getByRole('button', { name: /save/i })
page.getByRole('button', { name: /test connection/i })
page.getByRole('button', { name: /send test email/i })

2.3 Notifications (Notifications.tsx)

Route: /settings/notifications

UI Components:

  • Provider List with CRUD
  • Provider Form Modal (name, type, URL, template, event checkboxes)
  • Template Selection (built-in + external/saved)
  • Template Form Modal for external templates
  • Preview functionality
  • Test notification button

Provider Types:

  • Discord, Slack, Gotify, Telegram, Generic Webhook, Custom Webhook

Form Fields (Provider):

Field Input Type Selector Validation
Name text input[name="name"] Required
Type select select[name="type"] Required
URL text input[name="url"] Required, URL format
Template select select[name="template"] Optional
Config (JSON) textarea textarea[name="config"] JSON format for custom
Enabled checkbox input[name="enabled"] Boolean
Notify Events checkboxes input[name="notify_*"] Boolean flags

API Endpoints:

Method Endpoint Purpose
GET /notifications/providers List providers
POST /notifications/providers Create provider
PUT /notifications/providers/:id Update provider
DELETE /notifications/providers/:id Delete provider
POST /notifications/providers/test Test provider
POST /notifications/providers/preview Preview notification
GET /notifications/templates Get built-in templates
GET /notifications/external-templates Get saved templates
POST /notifications/external-templates Create template
PUT /notifications/external-templates/:id Update template
DELETE /notifications/external-templates/:id Delete template
POST /notifications/external-templates/preview Preview template

Key Selectors:

// Provider list
page.getByRole('button', { name: /add.*provider/i })
page.getByRole('button', { name: /edit/i })
page.getByRole('button', { name: /delete/i })

// Provider form
page.locator('input[name="name"]')
page.locator('select[name="type"]')
page.locator('input[name="url"]')

// Event checkboxes
page.locator('input[name="notify_proxy_hosts"]')
page.locator('input[name="notify_certs"]')

2.4 User Management (UsersPage.tsx)

Route: /users

UI Components:

  • User List Table (email, name, role, status, last login, actions)
  • Invite User Modal
  • Edit Permissions Modal
  • Delete Confirmation Dialog
  • URL Preview for Invite Links

Form Fields (Invite):

Field Input Type Selector Validation
Email email input[type="email"] Required, email format
Role select select (role) admin, user
Permission Mode select select (permission_mode) allow_all, deny_all
Permitted Hosts checkboxes dynamic Host selection

API Endpoints:

Method Endpoint Purpose
GET /users List users
POST /users Create user
GET /users/:id Get user
PUT /users/:id Update user
DELETE /users/:id Delete user
PUT /users/:id/permissions Update permissions
POST /users/invite Send invite
POST /users/preview-invite-url Preview invite URL
GET /invite/validate Validate invite token
POST /invite/accept Accept invitation

Key Selectors:

// User list
page.getByRole('button', { name: /invite.*user/i })
page.getByRole('table')
page.getByRole('row')

// Invite modal
page.getByLabel(/email/i)
page.locator('select').filter({ hasText: /user|admin/i })
page.getByRole('button', { name: /send.*invite/i })

// Actions
page.getByRole('button', { name: /settings|permissions/i })
page.getByRole('button', { name: /delete/i })

2.5 Encryption Management (EncryptionManagement.tsx)

Route: /encryption (or via admin panel)

UI Components:

  • Status Overview Cards (current version, providers updated, providers outdated, next key status)
  • Rotation Confirmation Dialog
  • Rotation History Table/List
  • Validate Keys Button

API Endpoints:

Method Endpoint Purpose
GET /admin/encryption/status Get encryption status
POST /admin/encryption/rotate Rotate encryption key
GET /admin/encryption/history Get rotation history
POST /admin/encryption/validate Validate key configuration

Key Selectors:

// Status cards
page.getByRole('heading', { name: /current.*version/i })
page.getByRole('heading', { name: /providers.*updated/i })

// Actions
page.getByRole('button', { name: /rotate.*key/i })
page.getByRole('button', { name: /validate/i })

// Confirmation dialog
page.getByRole('dialog')
page.getByRole('button', { name: /confirm/i })
page.getByRole('button', { name: /cancel/i })

2.6 Account Settings (Account.tsx)

Route: /settings/account

UI Components:

  • Profile Card (name, email)
  • Certificate Email Card (use account email checkbox, custom email)
  • Password Change Card
  • API Key Card (view, copy, regenerate)
  • Password Confirmation Modal (for sensitive changes)
  • Email Update Confirmation Modal

Form Fields:

Field Input Type ID/Selector Validation
Name text #profile-name Required
Email email #profile-email Required, email format
Certificate Email checkbox + email #useUserEmail, #cert-email Email format
Current Password password #current-password Required for changes
New Password password #new-password Strength requirements
Confirm Password password #confirm-password Must match

API Endpoints:

Method Endpoint Purpose
GET /auth/profile (via getProfile) Get user profile
PUT /auth/profile (via updateProfile) Update profile
POST /auth/regenerate-api-key Regenerate API key
POST /auth/change-password Change password
GET /settings Get settings
POST /settings Update settings (caddy.email)

Key Selectors:

// Profile
page.locator('#profile-name')
page.locator('#profile-email')
page.getByRole('button', { name: /save.*profile/i })

// Certificate Email
page.locator('#useUserEmail')
page.locator('#cert-email')

// Password
page.locator('#current-password')
page.locator('#new-password')
page.locator('#confirm-password')
page.getByRole('button', { name: /update.*password/i })

// API Key
page.getByRole('button').filter({ has: page.locator('svg.lucide-copy') })
page.getByRole('button').filter({ has: page.locator('svg.lucide-refresh-cw') })

3. Test File Specifications

3.1 System Settings (tests/settings/system-settings.spec.ts)

Priority: P0 (Core functionality) Estimated Tests: 25

test.describe('System Settings', () => {
  // Navigation & Page Load (3 tests)
  test('should load system settings page') // P0
  test('should display all setting sections') // P0
  test('should navigate between settings tabs') // P1

  // Feature Toggles (5 tests)
  test('should toggle Cerberus security feature') // P0
  test('should toggle CrowdSec console enrollment') // P0
  test('should toggle uptime monitoring') // P0
  test('should persist feature toggle changes') // P0
  test('should show overlay during feature update') // P1

  // General Configuration (6 tests)
  test('should update Caddy Admin API URL') // P0
  test('should change SSL provider') // P0
  test('should update domain link behavior') // P1
  test('should change language setting') // P1
  test('should validate invalid Caddy API URL') // P1
  test('should save general settings successfully') // P0

  // Application URL (5 tests)
  test('should validate public URL format') // P0
  test('should test public URL reachability') // P0
  test('should show error for unreachable URL') // P1
  test('should show success for reachable URL') // P1
  test('should update public URL setting') // P0

  // System Status (4 tests)
  test('should display system health status') // P0
  test('should show version information') // P1
  test('should check for updates') // P1
  test('should display WebSocket status') // P2

  // Accessibility (2 tests)
  test('should be keyboard navigable') // P1
  test('should have proper ARIA labels') // P1
});

3.2 SMTP Settings (tests/settings/smtp-settings.spec.ts)

Priority: P0 (Email notifications dependency) Estimated Tests: 18

test.describe('SMTP Settings', () => {
  // Page Load & Display (3 tests)
  test('should load SMTP settings page') // P0
  test('should display SMTP configuration form') // P0
  test('should show loading skeleton while fetching') // P2

  // Form Validation (4 tests)
  test('should validate required host field') // P0
  test('should validate port is numeric') // P0
  test('should validate from address format') // P0
  test('should validate encryption selection') // P1

  // CRUD Operations (4 tests)
  test('should save SMTP configuration') // P0
  test('should update existing SMTP configuration') // P0
  test('should clear password field on save') // P1
  test('should preserve masked password on edit') // P1

  // Connection Testing (4 tests)
  test('should test SMTP connection successfully') // P0
  test('should show error on connection failure') // P0
  test('should send test email') // P0
  test('should show error on test email failure') // P1

  // Accessibility (3 tests)
  test('should be keyboard navigable') // P1
  test('should have proper form labels') // P1
  test('should announce errors to screen readers') // P2
});

3.3 Notifications (tests/settings/notifications.spec.ts)

Priority: P1 (Important but not blocking) Estimated Tests: 22

test.describe('Notification Providers', () => {
  // Provider List (4 tests)
  test('should display notification providers list') // P0
  test('should show empty state when no providers') // P1
  test('should display provider type badges') // P2
  test('should filter providers by type') // P2

  // Provider CRUD (8 tests)
  test('should create Discord notification provider') // P0
  test('should create Slack notification provider') // P0
  test('should create generic webhook provider') // P0
  test('should edit existing provider') // P0
  test('should delete provider with confirmation') // P0
  test('should enable/disable provider') // P1
  test('should validate provider URL') // P1
  test('should validate provider name required') // P1

  // Template Management (5 tests)
  test('should select built-in template') // P1
  test('should create custom template') // P1
  test('should preview template with sample data') // P1
  test('should edit external template') // P2
  test('should delete external template') // P2

  // Testing & Preview (3 tests)
  test('should test notification provider') // P0
  test('should show test success feedback') // P1
  test('should preview notification content') // P1

  // Event Selection (2 tests)
  test('should configure notification events') // P1
  test('should persist event selections') // P1
});

3.4 User Management (tests/settings/user-management.spec.ts)

Priority: P0 (Security critical) Estimated Tests: 28

test.describe('User Management', () => {
  // User List (5 tests)
  test('should display user list') // P0
  test('should show user status badges') // P1
  test('should display role badges') // P1
  test('should show last login time') // P2
  test('should show pending invite status') // P1

  // Invite User (8 tests)
  test('should open invite user modal') // P0
  test('should send invite with valid email') // P0
  test('should validate email format') // P0
  test('should select user role') // P0
  test('should configure permission mode') // P0
  test('should select permitted hosts') // P1
  test('should show invite URL preview') // P1
  test('should copy invite link') // P1

  // Permission Management (5 tests)
  test('should open permissions modal') // P0
  test('should update permission mode') // P0
  test('should add permitted hosts') // P0
  test('should remove permitted hosts') // P1
  test('should save permission changes') // P0

  // User Actions (6 tests)
  test('should enable/disable user') // P0
  test('should change user role') // P0
  test('should delete user with confirmation') // P0
  test('should prevent self-deletion') // P0
  test('should prevent deleting last admin') // P0
  test('should resend invite for pending user') // P2

  // Accessibility & Security (4 tests)
  test('should be keyboard navigable') // P1
  test('should require admin role for access') // P0
  test('should show error for regular user access') // P0
  test('should have proper ARIA labels') // P2
});

3.5 Encryption Management (tests/settings/encryption-management.spec.ts)

Priority: P0 (Security critical) Estimated Tests: 15

test.describe('Encryption Management', () => {
  // Status Display (4 tests)
  test('should display encryption status cards') // P0
  test('should show current key version') // P0
  test('should show provider update counts') // P0
  test('should indicate next key configuration status') // P1

  // Key Rotation (6 tests)
  test('should open rotation confirmation dialog') // P0
  test('should cancel rotation from dialog') // P1
  test('should execute key rotation') // P0
  test('should show rotation progress') // P1
  test('should display rotation success message') // P0
  test('should handle rotation failure gracefully') // P0

  // Key Validation (3 tests)
  test('should validate key configuration') // P0
  test('should show validation success message') // P1
  test('should show validation errors') // P1

  // History (2 tests)
  test('should display rotation history') // P1
  test('should show history details') // P2
});

3.6 Account Settings (tests/settings/account-settings.spec.ts)

Priority: P0 (User-facing critical) Estimated Tests: 20

test.describe('Account Settings', () => {
  // Profile Management (5 tests)
  test('should display user profile') // P0
  test('should update profile name') // P0
  test('should update profile email') // P0
  test('should require password for email change') // P0
  test('should show email change confirmation dialog') // P1

  // Certificate Email (4 tests)
  test('should toggle use account email') // P1
  test('should enter custom certificate email') // P1
  test('should validate certificate email format') // P1
  test('should save certificate email') // P1

  // Password Change (5 tests)
  test('should change password with valid inputs') // P0
  test('should validate current password') // P0
  test('should validate password strength') // P0
  test('should validate password confirmation match') // P0
  test('should show password strength meter') // P1

  // API Key Management (4 tests)
  test('should display API key') // P0
  test('should copy API key to clipboard') // P0
  test('should regenerate API key') // P0
  test('should confirm API key regeneration') // P1

  // Accessibility (2 tests)
  test('should be keyboard navigable') // P1
  test('should have proper form labels') // P1
});

4. Test Data Fixtures Required

4.1 Settings Fixtures (tests/fixtures/settings.ts)

// tests/fixtures/settings.ts

export interface SMTPConfig {
  host: string;
  port: number;
  username: string;
  password: string;
  from_address: string;
  encryption: 'none' | 'ssl' | 'starttls';
}

export const validSMTPConfig: SMTPConfig = {
  host: 'smtp.test.local',
  port: 587,
  username: 'testuser',
  password: 'testpass123',
  from_address: 'noreply@test.local',
  encryption: 'starttls',
};

export const invalidSMTPConfigs = {
  missingHost: { ...validSMTPConfig, host: '' },
  invalidPort: { ...validSMTPConfig, port: -1 },
  invalidEmail: { ...validSMTPConfig, from_address: 'not-an-email' },
};

export interface SystemSettings {
  caddyAdminApi: string;
  sslProvider: 'auto' | 'letsencrypt-staging' | 'letsencrypt-prod' | 'zerossl';
  domainLinkBehavior: 'same_tab' | 'new_tab' | 'new_window';
  publicUrl: string;
}

export const defaultSystemSettings: SystemSettings = {
  caddyAdminApi: 'http://localhost:2019',
  sslProvider: 'auto',
  domainLinkBehavior: 'new_tab',
  publicUrl: 'http://localhost:8080',
};

export function generatePublicUrl(valid: boolean = true): string {
  if (valid) {
    return `https://charon-test-${Date.now()}.example.com`;
  }
  return 'not-a-valid-url';
}

4.2 User Fixtures (tests/fixtures/users.ts)

// tests/fixtures/users.ts (extend existing)

export interface InviteRequest {
  email: string;
  role: 'admin' | 'user';
  permission_mode: 'allow_all' | 'deny_all';
  permitted_hosts?: number[];
}

export function generateInviteEmail(): string {
  return `invited-${Date.now()}@test.local`;
}

export const validInviteRequest: InviteRequest = {
  email: generateInviteEmail(),
  role: 'user',
  permission_mode: 'allow_all',
};

export const adminInviteRequest: InviteRequest = {
  email: generateInviteEmail(),
  role: 'admin',
  permission_mode: 'allow_all',
};

4.3 Notification Fixtures (tests/fixtures/notifications.ts)

// tests/fixtures/notifications.ts

export interface NotificationProviderConfig {
  name: string;
  type: 'discord' | 'slack' | 'gotify' | 'telegram' | 'generic' | 'webhook';
  url: string;
  config?: string;
  template?: string;
  enabled: boolean;
  notify_proxy_hosts: boolean;
  notify_certs: boolean;
  notify_uptime: boolean;
}

export function generateProviderName(): string {
  return `test-provider-${Date.now()}`;
}

export const discordProvider: NotificationProviderConfig = {
  name: generateProviderName(),
  type: 'discord',
  url: 'https://discord.com/api/webhooks/test/token',
  enabled: true,
  notify_proxy_hosts: true,
  notify_certs: true,
  notify_uptime: false,
};

export const slackProvider: NotificationProviderConfig = {
  name: generateProviderName(),
  type: 'slack',
  url: 'https://hooks.slack.com/services/T00/B00/XXXXX',
  enabled: true,
  notify_proxy_hosts: true,
  notify_certs: false,
  notify_uptime: true,
};

export const genericWebhookProvider: NotificationProviderConfig = {
  name: generateProviderName(),
  type: 'generic',
  url: 'https://webhook.test.local/notify',
  config: '{"message": "{{.Message}}"}',
  template: 'minimal',
  enabled: true,
  notify_proxy_hosts: true,
  notify_certs: true,
  notify_uptime: true,
};

4.4 Encryption Fixtures (tests/fixtures/encryption.ts)

// tests/fixtures/encryption.ts

export interface EncryptionStatus {
  current_version: number;
  next_key_configured: boolean;
  legacy_key_count: number;
  providers_on_current_version: number;
  providers_on_older_versions: number;
}

export const healthyEncryptionStatus: EncryptionStatus = {
  current_version: 2,
  next_key_configured: true,
  legacy_key_count: 0,
  providers_on_current_version: 5,
  providers_on_older_versions: 0,
};

export const needsRotationStatus: EncryptionStatus = {
  current_version: 1,
  next_key_configured: true,
  legacy_key_count: 1,
  providers_on_current_version: 3,
  providers_on_older_versions: 2,
};

5. Implementation Order

Day 1: System Settings & Account Settings (P0 tests)

Order File Tests Rationale
1 system-settings.spec.ts P0 only (15) Core configuration, affects all features
2 account-settings.spec.ts P0 only (12) User authentication/profile critical

Deliverables:

  • ~27 P0 tests passing
  • Fixtures for settings created
  • Wait helpers for form submission verified

Day 2: User Management (P0 + P1 tests)

Order File Tests Rationale
3 user-management.spec.ts All P0 + P1 (24) Security-critical, admin functionality

Deliverables:

  • ~24 tests passing
  • User invite flow verified
  • Permission management tested

Day 3: SMTP Settings & Encryption Management

Order File Tests Rationale
4 smtp-settings.spec.ts All P0 + P1 (15) Email notification dependency
5 encryption-management.spec.ts All P0 + P1 (12) Security-critical

Deliverables:

  • ~27 tests passing
  • SMTP test with mock server (if available)
  • Key rotation flow verified

Day 4: Notifications (All tests)

Order File Tests Rationale
6 notifications.spec.ts All tests (22) Complete provider management

Deliverables:

  • ~22 tests passing
  • Multiple provider types tested
  • Template management verified

Day 5: Remaining Tests & Integration

Order Task Tests Rationale
7 P1/P2 tests in all files ~15 Complete coverage
8 Cross-settings integration 5-8 Verify settings interactions
9 Accessibility sweep All files Ensure a11y compliance

Deliverables:

  • All ~128 tests passing
  • Full accessibility coverage
  • Documentation updated

6. Risks and Blockers

6.1 Identified Risks

Risk Impact Likelihood Mitigation
SMTP test requires real mail server Medium High Use MailHog mock or skip connection tests in CI
Encryption rotation may affect other tests High Medium Run encryption tests in isolation, restore state after
User deletion tests may affect auth state High Medium Create dedicated test users, never delete admin used for auth
Notification webhooks need mock endpoints Medium High Use MSW or route mocking

6.2 Blockers to Address

  1. SMTP Mock Server

    • Need MailHog or similar in docker-compose.playwright.yml
    • Profile: --profile notification-tests
  2. Encryption Test Isolation

    • May need database snapshot/restore between tests
    • Or use mocked API responses for destructive operations
  3. Missing IDs/data-testid

    • Several form fields lack proper id attributes
    • Recommendation: Add data-testid to SMTP form fields

6.3 Required Fixes Before Implementation

Component Issue Fix Required
SMTPSettings.tsx Missing id on from_address, encryption fields Add id="smtp-from-address", id="smtp-encryption"
Notifications.tsx Uses class-based styling selectors Add data-testid to key elements
Account.tsx Password confirmation modal needs accessible name Add aria-labelledby

7. Success Metrics

7.1 Coverage Targets

Metric Target Measurement
Test Count 128+ tests Playwright test count
Pass Rate 100% CI pipeline status
P0 Coverage 100% All P0 scenarios have tests
P1 Coverage 95%+ Most P1 scenarios covered
Accessibility WCAG 2.2 AA Playwright accessibility checks

7.2 Quality Gates

  • All tests pass locally before PR
  • All tests pass in CI (all 4 shards)
  • No flaky tests (0 retries needed)
  • Code coverage for settings pages > 80%
  • No accessibility violations (a11y audit)

7.3 Definition of Done

  • All 6 test files created and passing
  • Fixtures created and documented
  • Integration with existing test infrastructure
  • Documentation updated in current_spec.md
  • PR reviewed and merged

Appendix A: Selector Reference Quick Guide

// Common selectors for Settings E2E tests

// Navigation
const settingsNav = {
  systemTab: page.getByRole('link', { name: /system/i }),
  notificationsTab: page.getByRole('link', { name: /notifications/i }),
  smtpTab: page.getByRole('link', { name: /smtp/i }),
  accountTab: page.getByRole('link', { name: /account/i }),
};

// Buttons
const buttons = {
  save: page.getByRole('button', { name: /save/i }),
  cancel: page.getByRole('button', { name: /cancel/i }),
  test: page.getByRole('button', { name: /test/i }),
  delete: page.getByRole('button', { name: /delete/i }),
  confirm: page.getByRole('button', { name: /confirm/i }),
  add: page.getByRole('button', { name: /add/i }),
};

// Forms
const forms = {
  input: (name: string) => page.getByRole('textbox', { name: new RegExp(name, 'i') }),
  select: (name: string) => page.getByRole('combobox', { name: new RegExp(name, 'i') }),
  checkbox: (name: string) => page.getByRole('checkbox', { name: new RegExp(name, 'i') }),
  switch: (name: string) => page.getByRole('switch', { name: new RegExp(name, 'i') }),
};

// Feedback
const feedback = {
  toast: page.locator('[role="alert"]'),
  error: page.getByText(/error|failed|invalid/i),
  success: page.getByText(/success|saved|updated/i),
};

// Modals
const modals = {
  dialog: page.getByRole('dialog'),
  title: page.getByRole('heading').first(),
  close: page.getByRole('button', { name: /close|×/i }),
};

Appendix B: API Endpoint Reference

Category Method Endpoint Auth Required
Settings GET /settings Yes
Settings POST /settings Yes (Admin)
Settings POST /settings/validate-url Yes
Settings POST /settings/test-url Yes
SMTP GET /settings/smtp Yes (Admin)
SMTP POST /settings/smtp Yes (Admin)
SMTP POST /settings/smtp/test Yes (Admin)
SMTP POST /settings/smtp/test-email Yes (Admin)
Notifications GET /notifications/providers Yes
Notifications POST /notifications/providers Yes (Admin)
Notifications PUT /notifications/providers/:id Yes (Admin)
Notifications DELETE /notifications/providers/:id Yes (Admin)
Notifications POST /notifications/providers/test Yes
Users GET /users Yes (Admin)
Users POST /users/invite Yes (Admin)
Users PUT /users/:id Yes (Admin)
Users DELETE /users/:id Yes (Admin)
Encryption GET /admin/encryption/status Yes (Admin)
Encryption POST /admin/encryption/rotate Yes (Admin)
Profile GET /auth/profile Yes
Profile PUT /auth/profile Yes
Profile POST /auth/change-password Yes
Profile POST /auth/regenerate-api-key Yes

Last Updated: January 19, 2026 Author: GitHub Copilot Status: Ready for Implementation