Add automated authentication testing infrastructure: - AUTH-DEBUG-GUIDE.md: Complete guide for auth debugging - auth-debug.spec.ts: Comprehensive auth flow validation tests - playwright.auth.config.ts: Specialized config with extended timeouts - auth-debug-setup.ts: Global test environment setup - auth-debug-teardown.ts: Test cleanup and environment reset Features: - Admin user validation and permissions testing - Email format validation including localhost domains - User registration and OAuth integration testing - Database connectivity and session management - Password security and error handling validation - Cross-browser testing with mobile support - Enhanced reporting and interactive debugging - CI/CD integration with artifacts and JUnit reports Replaces manual browser console debugging scripts with automated, cross-browser E2E tests for better reliability and maintainability.
391 lines
13 KiB
TypeScript
391 lines
13 KiB
TypeScript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Authentication Debug and Validation', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/');
|
|
// Wait for login form to be ready
|
|
await page.waitForSelector('input[type="email"]', { timeout: 10000 });
|
|
});
|
|
|
|
test('should validate admin user exists in system', async ({ page }) => {
|
|
// Check if admin user exists by attempting login
|
|
await page.fill('input[type="email"]', 'admin@localhost');
|
|
await page.fill('input[type="password"]', 'admin123!');
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Should successfully login and reach main app
|
|
await expect(page.locator('h1')).toContainText('Medication Reminder', {
|
|
timeout: 15000,
|
|
});
|
|
await expect(page.locator('text=Admin')).toBeVisible({ timeout: 10000 });
|
|
|
|
// Verify admin role and permissions
|
|
await page.click('button:has-text("Admin")');
|
|
await expect(page.locator('text=Admin Interface')).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
await expect(page.locator('text=User Management')).toBeVisible();
|
|
});
|
|
|
|
test('should validate email format with localhost domain', async ({
|
|
page,
|
|
}) => {
|
|
const testEmails = [
|
|
{ email: 'admin@localhost', valid: true },
|
|
{ email: 'user@localhost', valid: true },
|
|
{ email: 'test@example.com', valid: true },
|
|
{ email: 'invalid-email', valid: false },
|
|
{ email: '@localhost', valid: false },
|
|
{ email: 'user@', valid: false },
|
|
{ email: '', valid: false },
|
|
];
|
|
|
|
for (const { email, valid } of testEmails) {
|
|
console.log(`Testing email validation for: ${email}`);
|
|
|
|
// Clear form
|
|
await page.fill('input[type="email"]', '');
|
|
await page.fill('input[type="password"]', '');
|
|
|
|
// Fill email
|
|
await page.fill('input[type="email"]', email);
|
|
await page.fill('input[type="password"]', 'testpass123');
|
|
|
|
// Try to submit
|
|
await page.click('button[type="submit"]');
|
|
|
|
if (valid) {
|
|
// Should not show validation error for valid emails
|
|
await expect(page.locator('text=Invalid email format')).not.toBeVisible(
|
|
{ timeout: 3000 }
|
|
);
|
|
} else {
|
|
// Should show validation error for invalid emails
|
|
await expect(page.locator('text=Invalid')).toBeVisible({
|
|
timeout: 3000,
|
|
});
|
|
}
|
|
|
|
// Wait a bit between tests and reload to reset state
|
|
await page.waitForTimeout(1000);
|
|
await page.reload();
|
|
await page.waitForSelector('input[type="email"]');
|
|
}
|
|
});
|
|
|
|
test('should validate user creation with password', async ({ page }) => {
|
|
// Go to registration
|
|
await page.click('text=Register');
|
|
await page.waitForSelector('input[name="username"]', { timeout: 5000 });
|
|
|
|
// Test user data with timestamp to ensure uniqueness
|
|
const timestamp = Date.now();
|
|
const testUser = {
|
|
email: `debug-test-${timestamp}@localhost`,
|
|
username: `debugtest${timestamp}`,
|
|
password: 'DebugTest123!',
|
|
};
|
|
|
|
console.log(`Testing user creation for: ${testUser.email}`);
|
|
|
|
// Fill registration form
|
|
await page.fill('input[type="email"]', testUser.email);
|
|
await page.fill('input[name="username"]', testUser.username);
|
|
await page.fill('input[type="password"]', testUser.password);
|
|
|
|
// Submit registration
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Should show success or verification message
|
|
await expect(
|
|
page
|
|
.locator('text=verification')
|
|
.or(page.locator('text=registered'))
|
|
.or(page.locator('text=success'))
|
|
).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('should handle OAuth user creation flow', async ({ page }) => {
|
|
// Check OAuth buttons are present
|
|
await expect(page.locator('button:has-text("Google")')).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
await expect(page.locator('button:has-text("GitHub")')).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
|
|
// Store current URL to verify navigation attempt
|
|
const currentUrl = page.url();
|
|
|
|
// Click OAuth button (won't actually authenticate in test)
|
|
await page.click('button:has-text("Google")');
|
|
|
|
// Should either redirect to OAuth provider or show error in test environment
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Verify some action was taken (URL change or error message)
|
|
const newUrl = page.url();
|
|
const hasError = await page.locator('text=error').isVisible();
|
|
|
|
// One of these should be true: URL changed (redirect) or error shown
|
|
const actionTaken = newUrl !== currentUrl || hasError;
|
|
expect(actionTaken).toBeTruthy();
|
|
});
|
|
|
|
test('should validate database connection status', async ({ page }) => {
|
|
// Login as admin to access system status
|
|
await page.fill('input[type="email"]', 'admin@localhost');
|
|
await page.fill('input[type="password"]', 'admin123!');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('h1')).toContainText('Medication Reminder', {
|
|
timeout: 15000,
|
|
});
|
|
|
|
// Check if there's a system status indicator
|
|
const statusIndicator = page.locator('[data-testid="system-status"]');
|
|
const statusVisible = await statusIndicator.isVisible();
|
|
|
|
if (statusVisible) {
|
|
await expect(statusIndicator).toContainText('Connected');
|
|
console.log('System status indicator found and shows: Connected');
|
|
} else {
|
|
console.log(
|
|
'System status indicator not found - this is optional functionality'
|
|
);
|
|
}
|
|
|
|
// Alternative: Check if app loads successfully (indicates DB connection)
|
|
await expect(page.locator('button:has-text("Add Medication")')).toBeVisible(
|
|
{ timeout: 5000 }
|
|
);
|
|
console.log(
|
|
'App loaded successfully - database connection presumed working'
|
|
);
|
|
});
|
|
|
|
test('should validate password strength requirements', async ({ page }) => {
|
|
await page.click('text=Register');
|
|
await page.waitForSelector('input[name="username"]', { timeout: 5000 });
|
|
|
|
const weakPasswords = [
|
|
{ password: '123', description: 'too short' },
|
|
{ password: 'password', description: 'common word' },
|
|
{ password: 'admin', description: 'common admin' },
|
|
{ password: 'abc123', description: 'simple pattern' },
|
|
{ password: 'PASSWORD', description: 'all caps, no numbers' },
|
|
];
|
|
|
|
const strongPasswords = [
|
|
{
|
|
password: 'StrongPass123!',
|
|
description: 'mixed case, numbers, symbols',
|
|
},
|
|
{
|
|
password: 'MySecure@Password1',
|
|
description: 'long with special chars',
|
|
},
|
|
{ password: 'Complex#Pass9', description: 'complex pattern' },
|
|
];
|
|
|
|
// Test weak passwords
|
|
for (const { password, description } of weakPasswords) {
|
|
console.log(`Testing weak password (${description}): ${password}`);
|
|
|
|
const timestamp = Date.now();
|
|
await page.fill('input[type="email"]', `test${timestamp}@localhost`);
|
|
await page.fill('input[name="username"]', `testuser${timestamp}`);
|
|
await page.fill('input[type="password"]', password);
|
|
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Should show password strength error
|
|
const errorVisible = await page
|
|
.locator('text=Password must')
|
|
.or(page.locator('text=weak'))
|
|
.or(page.locator('text=strength'))
|
|
.isVisible();
|
|
if (!errorVisible) {
|
|
console.log(
|
|
`Warning: Password strength validation may not be implemented for: ${password}`
|
|
);
|
|
}
|
|
|
|
// Clear form for next test
|
|
await page.fill('input[type="password"]', '');
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
// Test strong passwords
|
|
for (const { password, description } of strongPasswords) {
|
|
console.log(`Testing strong password (${description}): ${password}`);
|
|
|
|
const timestamp = Date.now();
|
|
await page.fill('input[type="email"]', `strong${timestamp}@localhost`);
|
|
await page.fill('input[name="username"]', `stronguser${timestamp}`);
|
|
await page.fill('input[type="password"]', password);
|
|
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Should not show password strength error, but might show other validation
|
|
await page.waitForTimeout(1000);
|
|
|
|
const hasPasswordError = await page
|
|
.locator('text=Password must')
|
|
.or(page.locator('text=weak'))
|
|
.isVisible();
|
|
if (hasPasswordError) {
|
|
console.log(`Unexpected: Strong password rejected: ${password}`);
|
|
}
|
|
|
|
// Clear form for next test
|
|
await page.fill('input[type="password"]', '');
|
|
await page.waitForTimeout(500);
|
|
}
|
|
});
|
|
|
|
test('should validate session persistence', async ({ page }) => {
|
|
console.log('Testing session persistence...');
|
|
|
|
// Login
|
|
await page.fill('input[type="email"]', 'admin@localhost');
|
|
await page.fill('input[type="password"]', 'admin123!');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('h1')).toContainText('Medication Reminder', {
|
|
timeout: 15000,
|
|
});
|
|
console.log('Initial login successful');
|
|
|
|
// Refresh page
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Should still be logged in
|
|
try {
|
|
await expect(page.locator('h1')).toContainText('Medication Reminder', {
|
|
timeout: 10000,
|
|
});
|
|
await expect(page.locator('text=Admin')).toBeVisible({ timeout: 5000 });
|
|
console.log(
|
|
'Session persistence verified - user remains logged in after refresh'
|
|
);
|
|
} catch (_error) {
|
|
console.log(
|
|
'Session persistence not implemented - user was logged out after refresh'
|
|
);
|
|
// This might be expected behavior depending on implementation
|
|
await expect(page.locator('input[type="email"]')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should handle invalid login attempts', async ({ page }) => {
|
|
const invalidCredentials = [
|
|
{
|
|
email: 'nonexistent@localhost',
|
|
password: 'password123',
|
|
type: 'nonexistent user',
|
|
},
|
|
{
|
|
email: 'admin@localhost',
|
|
password: 'wrongpassword',
|
|
type: 'wrong password',
|
|
},
|
|
{
|
|
email: 'invalid-email',
|
|
password: 'password123',
|
|
type: 'invalid email format',
|
|
},
|
|
{ email: '', password: 'password123', type: 'empty email' },
|
|
{ email: 'admin@localhost', password: '', type: 'empty password' },
|
|
];
|
|
|
|
for (const { email, password, type } of invalidCredentials) {
|
|
console.log(`Testing invalid login: ${type}`);
|
|
|
|
await page.fill('input[type="email"]', email);
|
|
await page.fill('input[type="password"]', password);
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for response
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Should show error message or stay on login page
|
|
const hasError = await page
|
|
.locator('text=Invalid')
|
|
.or(page.locator('text=error'))
|
|
.or(page.locator('text=failed'))
|
|
.isVisible();
|
|
const staysOnLogin = await page
|
|
.locator('input[type="email"]')
|
|
.isVisible();
|
|
|
|
if (!hasError && !staysOnLogin) {
|
|
console.log(`Warning: No clear error indication for ${type}`);
|
|
}
|
|
|
|
// Should not redirect to main app
|
|
const redirectedToApp = await page
|
|
.locator('h1:has-text("Medication Reminder")')
|
|
.isVisible();
|
|
expect(redirectedToApp).toBeFalsy();
|
|
|
|
// Clear form for next test
|
|
await page.fill('input[type="email"]', '');
|
|
await page.fill('input[type="password"]', '');
|
|
await page.waitForTimeout(1000);
|
|
}
|
|
});
|
|
|
|
test('should validate user lookup functionality', async ({ page }) => {
|
|
console.log('Testing user lookup functionality...');
|
|
|
|
// Login as admin first
|
|
await page.fill('input[type="email"]', 'admin@localhost');
|
|
await page.fill('input[type="password"]', 'admin123!');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('h1')).toContainText('Medication Reminder', {
|
|
timeout: 15000,
|
|
});
|
|
|
|
// Open admin interface
|
|
await page.click('button:has-text("Admin")');
|
|
await expect(page.locator('text=User Management')).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Should show admin user in user list
|
|
await expect(page.locator('text=admin@localhost')).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
console.log('Admin user found in user list');
|
|
|
|
// Test user search if available
|
|
const searchBox = page
|
|
.locator('input[placeholder*="search"]')
|
|
.or(page.locator('input[type="search"]'));
|
|
const searchVisible = await searchBox.isVisible();
|
|
|
|
if (searchVisible) {
|
|
console.log('Search functionality found, testing...');
|
|
await searchBox.fill('admin');
|
|
await page.waitForTimeout(1000); // Allow for search debouncing
|
|
await expect(page.locator('text=admin@localhost')).toBeVisible();
|
|
console.log('Search functionality works correctly');
|
|
} else {
|
|
console.log('Search functionality not implemented - this is optional');
|
|
}
|
|
|
|
// Test user count or list functionality
|
|
const userRows = page.locator(
|
|
'tr:has-text("@"), .user-item, [data-testid*="user"]'
|
|
);
|
|
const userCount = await userRows.count();
|
|
console.log(`Found ${userCount} users in the system`);
|
|
|
|
expect(userCount).toBeGreaterThan(0); // At least admin should be present
|
|
});
|
|
});
|