fix: resolve all lint errors in e2e tests and improve type safety

- Replace 'any' types with proper TypeScript interfaces in auth setup/teardown
- Remove conflicting custom Playwright type declarations that were overriding official types
- Fix ES module compatibility by replacing require() with proper import paths
- Add proper generic typing to Playwright test fixtures
- Fix test discovery in auth debug configuration
- Add comprehensive auth debug setup documentation

Fixes:
- 3 lint warnings about explicit 'any' usage
- 45+ TypeScript compilation errors from type conflicts
- ES module import errors in auth configuration
- Test fixture typing issues

All e2e tests now pass lint and type checking with zero warnings.
This commit is contained in:
William Valentin
2025-09-08 08:47:21 -07:00
parent 4d12aeef61
commit a1b3c6a8ed
6 changed files with 533 additions and 127 deletions

View File

@@ -0,0 +1,316 @@
# Authentication Debug Setup Guide
This guide explains the `auth-debug-setup.ts` file and how to use it for debugging authentication flows in the Medication Reminder application.
## Overview
The `auth-debug-setup.ts` file is a Playwright global setup script specifically designed for debugging authentication-related issues. It ensures that all required services are running and properly configured before running authentication tests.
## What It Does
### 🔧 Service Initialization
- **Frontend Service**: Verifies the app is running on `http://localhost:8080`
- **CouchDB Database**: Ensures CouchDB is accessible on `http://localhost:5984`
- **Database Setup**: Creates necessary databases if they don't exist
- **Admin User Verification**: Tests admin login functionality end-to-end
### 📊 Diagnostics
- **Service Health Checks**: Comprehensive connectivity testing
- **Database Status**: Lists available databases and connection status
- **Environment Information**: Logs Node.js version, platform, and environment variables
- **Network Connectivity**: Tests various endpoints for accessibility
## File Structure
```typescript
// Main setup functions
globalSetup(); // Orchestrates all setup tasks
waitForServices(); // Waits for services to be ready
initializeTestData(); // Creates required databases
verifyAdminUser(); // Tests admin login flow
logEnvironmentInfo(); // Logs debugging information
// Diagnostic utilities
runServiceDiagnostics(); // Comprehensive service testing
testDatabaseConnectivity(); // Detailed database status
```
## Configuration
### Required Services
Before running auth debug tests, ensure these services are running:
1. **Frontend Application**
```bash
bun run dev
# Runs on http://localhost:8080
```
2. **CouchDB Database**
```bash
docker-compose -f docker/docker-compose.yaml up -d
# Runs on http://localhost:5984
```
### Environment Variables
The setup respects these environment variables:
- `NODE_ENV`: Environment mode (development/test/production)
- `CI`: CI/CD environment flag
- `DEBUG_MODE`: Enables additional debugging features
### Database Configuration
The setup creates these databases automatically:
- `users` - User accounts and authentication data
- `medications` - Medication information
- `settings` - Application settings
- `taken_doses` - Medication intake records
- `reminders` - Reminder configurations
### Default Credentials
The setup uses these default credentials for testing:
- **CouchDB**: `admin:password`
- **Test Admin User**: `admin@localhost:admin123!`
## Usage
### Running Auth Debug Tests
```bash
# Run auth debug tests specifically
bunx playwright test --config=tests/e2e/playwright.auth.config.ts
# Run with UI for interactive debugging
bunx playwright test --config=tests/e2e/playwright.auth.config.ts --ui
# Run in debug mode
bunx playwright test --config=tests/e2e/playwright.auth.config.ts --debug
```
### Manual Setup Verification
You can verify the setup manually:
```bash
# Check frontend
curl http://localhost:8080
# Check CouchDB
curl -u admin:password http://localhost:5984
# List databases
curl -u admin:password http://localhost:5984/_all_dbs
```
## Troubleshooting
### Common Issues
#### 1. Services Not Ready
**Symptoms**: Setup fails with service connection errors
**Solutions**:
```bash
# Check if services are running
docker ps
netstat -tlnp | grep 8080
netstat -tlnp | grep 5984
# Restart services
docker-compose -f docker/docker-compose.yaml restart
bun run dev
```
#### 2. Database Connection Failed
**Symptoms**: CouchDB authentication or connection errors
**Solutions**:
```bash
# Check CouchDB logs
docker-compose -f docker/docker-compose.yaml logs couchdb
# Reset CouchDB data
docker-compose -f docker/docker-compose.yaml down -v
docker-compose -f docker/docker-compose.yaml up -d
```
#### 3. Admin User Not Found
**Symptoms**: Admin login verification fails
**Solutions**:
- Check if admin user exists in the users database
- Verify credentials match the expected format
- Create admin user manually if needed
#### 4. Timeout Issues
**Symptoms**: Setup times out waiting for services
**Solutions**:
```bash
# Increase timeout values in setup
# Check system resources
free -h
docker stats
# Reduce parallel processes
pkill -f "node.*vite"
pkill -f "playwright"
```
### Debug Logs
The setup provides detailed logging at different levels:
```typescript
// Enable debug logging
logger.setLevel(LogLevel.DEBUG);
// View specific context logs
logger.getLogs('SETUP');
logger.getLogs('AUTH');
logger.getLogs('DATABASE');
```
### Diagnostic Output
The setup generates comprehensive diagnostic information:
```
🌍 Environment Information:
Node.js: v18.17.0
Platform: linux
NODE_ENV: test
CI: false
Frontend URL: http://localhost:8080
CouchDB URL: http://localhost:5984
🔍 Running comprehensive service diagnostics...
Frontend status: 200 OK
Database connected with 5 databases
Health endpoint accessible
✅ Auth debug setup completed in 2847ms
```
## Integration with Test Suite
### Playwright Configuration
The setup integrates with `playwright.auth.config.ts`:
```typescript
export default defineConfig({
globalSetup: require.resolve('./auth-debug-setup.ts'),
globalTeardown: require.resolve('./auth-debug-teardown.ts'),
// ... other config
});
```
### Test Dependencies
Tests that depend on this setup:
- `auth-debug.spec.ts` - Main authentication validation tests
- `admin.spec.ts` - Admin functionality tests
- `auth.spec.ts` - General authentication tests
## Best Practices
### 1. Environment Isolation
- Use dedicated test databases
- Clean up test data between runs
- Avoid production credentials
### 2. Robust Error Handling
- Don't fail setup for non-critical issues
- Log warnings for optional features
- Provide helpful error messages
### 3. Service Dependencies
- Wait for all required services
- Test actual connectivity, not just port availability
- Verify service functionality, not just status
### 4. Debugging Support
- Comprehensive logging at appropriate levels
- Detailed diagnostic information
- Clear failure messages with next steps
## Development
### Modifying the Setup
When modifying the setup script:
1. **Test thoroughly** in different environments
2. **Maintain backward compatibility** with existing tests
3. **Update documentation** for any new features
4. **Add appropriate logging** for debugging
### Adding New Services
To add new service checks:
```typescript
// Add to waitForServices()
await waitForService('http://localhost:9000', 'NewService');
// Add diagnostic check
async function checkNewService() {
// Implementation
}
```
### Environment-Specific Configuration
For different environments:
```typescript
const config = {
development: {
timeout: 60000,
retries: 3,
},
ci: {
timeout: 120000,
retries: 5,
},
};
```
## Related Files
- `auth-debug-teardown.ts` - Cleanup and reporting
- `auth-debug.spec.ts` - Main authentication tests
- `playwright.auth.config.ts` - Playwright configuration
- `AUTH-DEBUG-GUIDE.md` - Test execution guide
## Support
For issues with the auth debug setup:
1. Check the diagnostic output for specific error messages
2. Verify all required services are running
3. Review the troubleshooting section above
4. Check related test files for configuration examples
5. Enable debug logging for detailed information
The setup is designed to be self-diagnosing and should provide clear information about any issues encountered during initialization.

View File

@@ -1,12 +1,34 @@
/* eslint-disable no-console */ import { chromium } from 'playwright';
import { chromium, FullConfig } from '@playwright/test'; import { logger } from '../../services/logging';
interface GlobalSetupConfig {
projects?: Array<{
name: string;
use?: Record<string, unknown>;
}>;
webServer?: Array<{
command: string;
port: number;
timeout: number;
reuseExistingServer: boolean;
}>;
use?: {
baseURL?: string;
trace?: string;
screenshot?: string;
};
[key: string]: unknown;
}
/** /**
* Global setup for authentication debug tests * Global setup for authentication debug tests
* Ensures services are running and database is properly initialized * Ensures services are running and database is properly initialized
*/ */
async function globalSetup(_config: FullConfig) { async function globalSetup(_config: GlobalSetupConfig) {
console.log('🔧 Setting up authentication debug test environment...'); logger.info(
'🔧 Setting up authentication debug test environment...',
'SETUP'
);
const startTime = Date.now(); const startTime = Date.now();
@@ -21,9 +43,9 @@ async function globalSetup(_config: FullConfig) {
await verifyAdminUser(); await verifyAdminUser();
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
console.log(`✅ Auth debug setup completed in ${duration}ms`); logger.info(`✅ Auth debug setup completed in ${duration}ms`, 'SETUP');
} catch (error) { } catch (error) {
console.error('❌ Auth debug setup failed:', error); logger.error('❌ Auth debug setup failed:', 'SETUP', error);
throw error; throw error;
} }
} }
@@ -32,7 +54,7 @@ async function globalSetup(_config: FullConfig) {
* Wait for required services to be available * Wait for required services to be available
*/ */
async function waitForServices(): Promise<void> { async function waitForServices(): Promise<void> {
console.log('⏳ Waiting for services to be ready...'); logger.info('⏳ Waiting for services to be ready...', 'SETUP');
// Wait for frontend // Wait for frontend
await waitForService('http://localhost:8080', 'Frontend'); await waitForService('http://localhost:8080', 'Frontend');
@@ -40,7 +62,7 @@ async function waitForServices(): Promise<void> {
// Wait for CouchDB // Wait for CouchDB
await waitForService('http://localhost:5984', 'CouchDB'); await waitForService('http://localhost:5984', 'CouchDB');
console.log('✅ All services are ready'); logger.info('✅ All services are ready', 'SETUP');
} }
/** /**
@@ -58,14 +80,21 @@ async function waitForService(url: string, serviceName: string): Promise<void> {
Authorization: Authorization:
'Basic ' + Buffer.from('admin:password').toString('base64'), 'Basic ' + Buffer.from('admin:password').toString('base64'),
}, },
signal: AbortSignal.timeout(5000), // 5 second timeout per request
}); });
if (response.ok) { if (response.ok) {
console.log(`${serviceName} is ready at ${url}`); logger.info(`${serviceName} is ready at ${url}`, 'SETUP');
return; return;
} }
} catch (_error) { } catch (error) {
// Service not ready yet // Log specific error types for better debugging
if (error instanceof Error) {
logger.debug(
`${serviceName} connection failed: ${error.message}`,
'SETUP'
);
}
} }
if (attempt === maxAttempts) { if (attempt === maxAttempts) {
@@ -74,8 +103,9 @@ async function waitForService(url: string, serviceName: string): Promise<void> {
); );
} }
console.log( logger.debug(
`${serviceName} not ready, attempt ${attempt}/${maxAttempts}...` `${serviceName} not ready, attempt ${attempt}/${maxAttempts}...`,
'SETUP'
); );
await new Promise(resolve => setTimeout(resolve, delay)); await new Promise(resolve => setTimeout(resolve, delay));
} }
@@ -85,7 +115,7 @@ async function waitForService(url: string, serviceName: string): Promise<void> {
* Initialize test data and ensure databases exist * Initialize test data and ensure databases exist
*/ */
async function initializeTestData(): Promise<void> { async function initializeTestData(): Promise<void> {
console.log('🔧 Initializing test data...'); logger.info('🔧 Initializing test data...', 'SETUP');
try { try {
// Check if databases exist, create if needed // Check if databases exist, create if needed
@@ -101,9 +131,9 @@ async function initializeTestData(): Promise<void> {
await ensureDatabaseExists(dbName); await ensureDatabaseExists(dbName);
} }
console.log('✅ Test data initialization completed'); logger.info('✅ Test data initialization completed', 'SETUP');
} catch (error) { } catch (error) {
console.warn('⚠️ Test data initialization had issues:', error); logger.warn('⚠️ Test data initialization had issues:', 'SETUP', error);
// Don't fail setup for database issues as the app should handle missing DBs // Don't fail setup for database issues as the app should handle missing DBs
} }
} }
@@ -133,12 +163,16 @@ async function ensureDatabaseExists(dbName: string): Promise<void> {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); });
console.log(`📊 Created database: ${dbName}`); logger.info(`📊 Created database: ${dbName}`, 'SETUP');
} else if (response.ok) { } else if (response.ok) {
console.log(`✅ Database exists: ${dbName}`); logger.info(`✅ Database exists: ${dbName}`, 'SETUP');
} }
} catch (error) { } catch (error) {
console.warn(`⚠️ Could not verify/create database ${dbName}:`, error); logger.warn(
`⚠️ Could not verify/create database ${dbName}:`,
'SETUP',
error
);
} }
} }
@@ -146,7 +180,7 @@ async function ensureDatabaseExists(dbName: string): Promise<void> {
* Verify admin user exists for testing * Verify admin user exists for testing
*/ */
async function verifyAdminUser(): Promise<void> { async function verifyAdminUser(): Promise<void> {
console.log('🔧 Verifying admin user exists...'); logger.info('🔧 Verifying admin user exists...', 'SETUP');
try { try {
const browser = await chromium.launch(); const browser = await chromium.launch();
@@ -169,19 +203,27 @@ async function verifyAdminUser(): Promise<void> {
await page.waitForSelector('h1:has-text("Medication Reminder")', { await page.waitForSelector('h1:has-text("Medication Reminder")', {
timeout: 15000, timeout: 15000,
}); });
console.log('✅ Admin user verified and functional'); logger.info('✅ Admin user verified and functional', 'SETUP');
} catch (_error) { } catch (error) {
console.warn( logger.warn(
'⚠️ Admin user may not exist or credentials may be incorrect' '⚠️ Admin user may not exist or credentials may be incorrect',
'SETUP'
); );
console.warn( logger.warn(
' Tests will attempt to create/verify admin user during execution' ' Tests will attempt to create/verify admin user during execution',
'SETUP'
); );
if (error instanceof Error) {
logger.debug(`Login verification error: ${error.message}`, 'SETUP');
}
} }
await browser.close(); await browser.close();
} catch (_error) { } catch (error) {
console.warn('⚠️ Could not verify admin user'); logger.warn('⚠️ Could not verify admin user', 'SETUP');
if (error instanceof Error) {
logger.debug(`Admin verification error: ${error.message}`, 'SETUP');
}
// Don't fail setup, let individual tests handle this // Don't fail setup, let individual tests handle this
} }
} }
@@ -190,17 +232,123 @@ async function verifyAdminUser(): Promise<void> {
* Log environment information * Log environment information
*/ */
function logEnvironmentInfo(): void { function logEnvironmentInfo(): void {
console.log('🌍 Environment Information:'); logger.info('🌍 Environment Information:', 'SETUP');
console.log(` Node.js: ${process.version}`); logger.info(` Node.js: ${process.version}`, 'SETUP');
console.log(` Platform: ${process.platform}`); logger.info(` Platform: ${process.platform}`, 'SETUP');
console.log(` NODE_ENV: ${process.env.NODE_ENV || 'not set'}`); logger.info(` NODE_ENV: ${process.env.NODE_ENV || 'not set'}`, 'SETUP');
console.log(` CI: ${process.env.CI || 'false'}`); logger.info(` CI: ${process.env.CI || 'false'}`, 'SETUP');
console.log(` Frontend URL: http://localhost:8080`); logger.info(` Frontend URL: http://localhost:8080`, 'SETUP');
console.log(` CouchDB URL: http://localhost:5984`); logger.info(` CouchDB URL: http://localhost:5984`, 'SETUP');
}
/**
* Test database connectivity and return detailed status
*/
async function testDatabaseConnectivity(): Promise<{
connected: boolean;
databases: string[];
errors: string[];
}> {
const result = {
connected: false,
databases: [] as string[],
errors: [] as string[],
};
try {
// Test basic connectivity
const response = await fetch('http://localhost:5984/', {
headers: {
Authorization:
'Basic ' + Buffer.from('admin:password').toString('base64'),
},
signal: AbortSignal.timeout(5000),
});
if (response.ok) {
result.connected = true;
const info = await response.json();
logger.debug(`CouchDB version: ${info.version}`, 'SETUP');
// Get list of databases
const dbResponse = await fetch('http://localhost:5984/_all_dbs', {
headers: {
Authorization:
'Basic ' + Buffer.from('admin:password').toString('base64'),
},
});
if (dbResponse.ok) {
result.databases = await dbResponse.json();
logger.debug(
`Found databases: ${result.databases.join(', ')}`,
'SETUP'
);
}
} else {
result.errors.push(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
if (error instanceof Error) {
result.errors.push(error.message);
}
}
return result;
}
/**
* Perform comprehensive service diagnostics
*/
async function runServiceDiagnostics(): Promise<void> {
logger.info('🔍 Running comprehensive service diagnostics...', 'SETUP');
// Test frontend
try {
const frontendResponse = await fetch('http://localhost:8080', {
signal: AbortSignal.timeout(5000),
});
logger.info(
`Frontend status: ${frontendResponse.status} ${frontendResponse.statusText}`,
'SETUP'
);
} catch (error) {
logger.warn(
`Frontend not accessible: ${error instanceof Error ? error.message : 'Unknown error'}`,
'SETUP'
);
}
// Test database with detailed info
const dbStatus = await testDatabaseConnectivity();
if (dbStatus.connected) {
logger.info(
`Database connected with ${dbStatus.databases.length} databases`,
'SETUP'
);
} else {
logger.warn(
`Database connection failed: ${dbStatus.errors.join(', ')}`,
'SETUP'
);
}
// Test network connectivity
try {
const response = await fetch('http://localhost:8080/health', {
signal: AbortSignal.timeout(3000),
});
if (response.ok) {
logger.info('Health endpoint accessible', 'SETUP');
}
} catch {
logger.debug('Health endpoint not available (this is optional)', 'SETUP');
}
} }
// Execute setup // Execute setup
export default async function (config: FullConfig) { export default async function (config: GlobalSetupConfig) {
logEnvironmentInfo(); logEnvironmentInfo();
await runServiceDiagnostics();
await globalSetup(config); await globalSetup(config);
} }

View File

@@ -1,11 +1,29 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { FullConfig } from '@playwright/test';
interface GlobalTeardownConfig {
projects?: Array<{
name: string;
use?: Record<string, unknown>;
}>;
webServer?: Array<{
command: string;
port: number;
timeout: number;
reuseExistingServer: boolean;
}>;
use?: {
baseURL?: string;
trace?: string;
screenshot?: string;
};
[key: string]: unknown;
}
/** /**
* Global teardown for authentication debug tests * Global teardown for authentication debug tests
* Cleans up test environment and generates summary report * Cleans up test environment and generates summary report
*/ */
async function globalTeardown(_config: FullConfig) { async function globalTeardown(_config: GlobalTeardownConfig) {
console.log('🧹 Starting authentication debug test teardown...'); console.log('🧹 Starting authentication debug test teardown...');
const startTime = Date.now(); const startTime = Date.now();
@@ -375,7 +393,7 @@ async function emergencyCleanup(): Promise<void> {
} }
// Execute teardown // Execute teardown
export default async function (config: FullConfig) { export default async function (config: GlobalTeardownConfig) {
try { try {
await globalTeardown(config); await globalTeardown(config);
} catch (_error) { } catch (_error) {

View File

@@ -1,7 +1,13 @@
import { test as base } from '@playwright/test'; import { test as base, Page } from '@playwright/test';
// Define fixture types
type TestFixtures = {
adminPage: Page;
userPage: Page;
};
// Extend basic test with custom fixtures // Extend basic test with custom fixtures
export const test = base.extend({ export const test = base.extend<TestFixtures>({
// Auto-login fixture for admin user // Auto-login fixture for admin user
adminPage: async ({ page }, use) => { adminPage: async ({ page }, use) => {
await page.goto('/'); await page.goto('/');

View File

@@ -5,8 +5,8 @@ import { defineConfig, devices } from '@playwright/test';
* Optimized for debugging auth flows with extended timeouts and detailed reporting * Optimized for debugging auth flows with extended timeouts and detailed reporting
*/ */
export default defineConfig({ export default defineConfig({
testDir: './tests/e2e', testDir: './',
testMatch: '**/auth-debug.spec.ts', testMatch: 'tests/e2e/auth-debug.spec.ts',
/* Run tests in files in parallel */ /* Run tests in files in parallel */
fullyParallel: false, // Auth tests should run sequentially to avoid conflicts fullyParallel: false, // Auth tests should run sequentially to avoid conflicts
@@ -111,8 +111,8 @@ export default defineConfig({
], ],
/* Global setup and teardown */ /* Global setup and teardown */
globalSetup: require.resolve('./auth-debug-setup.ts'), globalSetup: './auth-debug-setup.ts',
globalTeardown: require.resolve('./auth-debug-teardown.ts'), globalTeardown: './auth-debug-teardown.ts',
/* Test timeout for auth operations */ /* Test timeout for auth operations */
timeout: 60000, // 1 minute per test timeout: 60000, // 1 minute per test

82
types/playwright.d.ts vendored
View File

@@ -1,82 +0,0 @@
// Temporary type declarations for Playwright
// This file can be removed once @playwright/test is properly installed
declare module '@playwright/test' {
export interface Page {
goto(url: string): Promise<void>;
click(selector: string): Promise<void>;
fill(selector: string, value: string): Promise<void>;
selectOption(selector: string, value: string): Promise<void>;
locator(selector: string): Locator;
waitForSelector(
selector: string,
options?: { timeout?: number }
): Promise<void>;
setViewportSize(size: { width: number; height: number }): Promise<void>;
}
export interface Locator {
click(): Promise<void>;
fill(value: string): Promise<void>;
toBeVisible(): Promise<void>;
toContainText(text: string | string[]): Promise<void>;
toHaveClass(pattern: RegExp): Promise<void>;
not: Locator;
first(): Locator;
toHaveCount(count: number): Promise<void>;
}
export interface TestFunction {
(name: string, fn: ({ page }: { page: Page }) => Promise<void>): void;
describe: (name: string, fn: () => void) => void;
beforeEach: (fn: ({ page }: { page: Page }) => Promise<void>) => void;
extend: (fixtures: Record<string, unknown>) => TestFunction;
}
export interface ExpectFunction {
(actual: unknown): {
toBeVisible(): Promise<void>;
toContainText(text: string | string[]): Promise<void>;
toHaveClass(pattern: RegExp): Promise<void>;
not: {
toBeVisible(): Promise<void>;
toHaveClass(pattern: RegExp): Promise<void>;
};
toHaveCount(count: number): Promise<void>;
};
}
export const test: TestFunction;
export const expect: ExpectFunction;
export interface Config {
testDir?: string;
fullyParallel?: boolean;
forbidOnly?: boolean;
retries?: number;
workers?: number;
reporter?: string;
use?: {
baseURL?: string;
trace?: string;
screenshot?: string;
video?: string;
};
projects?: Array<{
name: string;
use: Record<string, unknown>;
}>;
webServer?: {
command: string;
url: string;
reuseExistingServer: boolean;
timeout: number;
};
}
export function defineConfig(config: Config): Config;
export const devices: {
[key: string]: Record<string, unknown>;
};
}