bb6be245b7
## Summary This PR implements the tournament schedule tab functionality and fixes all remaining E2E test failures. ### Changes Included 1. **Tournament Schedule Feature** - Added tournament schedule page at `/admin/tournaments/[id]/schedule` - Implemented "Generate Schedule" button functionality - Added schedule generation logic for round-robin tournaments 2. **E2E Test Fixes** - Fixed database connection issues in production builds - Improved test reliability with better error handling and debugging - Updated test infrastructure to use environment variables instead of hardcoded values 3. **CI/CD Updates** - Added E2E test job to PR workflow - Configured tests to run against development database - Moved database password to Gitea secrets 4. **Code Quality** - Removed hardcoded passwords from codebase - Improved Prisma client configuration - Enhanced authentication and navigation components ### Test Results All 16 E2E test scenarios are now passing: - Authentication tests: ✅ - Registration tests: ✅ - Tournament schedule tests: ✅ - Player schedule tests: ✅ - Admin navigation tests: ✅ ### Database Configuration - Tests run against `euchre_camp_dev` database - Production builds use environment variables for database configuration - Database password stored in Gitea secrets as `DB_PASSWORD` ### CI Pipeline The PR workflow now includes: 1. Unit tests 2. E2E tests (using production build) 3. Version bump analysis E2E tests must pass before PR can be merged. Reviewed-on: #27 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
213 lines
6.9 KiB
TypeScript
213 lines
6.9 KiB
TypeScript
/**
|
|
* Global setup for Playwright tests
|
|
* Creates test users and saves authentication state
|
|
*/
|
|
|
|
import { chromium, type FullConfig } from '@playwright/test';
|
|
import { prisma } from '@/lib/prisma';
|
|
import { cleanupAllTestData } from '@/__tests__/test-utils';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
|
|
// Load .env file first, then .env.development (which will override .env)
|
|
const envPath = path.resolve(process.cwd(), '.env');
|
|
const envDevPath = path.resolve(process.cwd(), '.env.development');
|
|
|
|
// Load base .env file
|
|
if (fs.existsSync(envPath)) {
|
|
require('dotenv').config({ path: envPath });
|
|
}
|
|
|
|
// Load .env.development file (will override .env settings)
|
|
if (fs.existsSync(envDevPath)) {
|
|
require('dotenv').config({ path: envDevPath, override: true });
|
|
}
|
|
|
|
const authFile = 'playwright/.auth/user.json';
|
|
const adminAuthFile = 'playwright/.auth/admin.json';
|
|
|
|
// Check if we're using the dev database
|
|
function isDevDatabase(): boolean {
|
|
const dbUrl = process.env.DATABASE_URL || '';
|
|
return dbUrl.includes('euchre_camp_dev');
|
|
}
|
|
|
|
function isProductionDatabase(): boolean {
|
|
const dbUrl = process.env.DATABASE_URL || '';
|
|
return dbUrl.includes('euchre_camp') && !dbUrl.includes('_dev');
|
|
}
|
|
|
|
// Strict check - fail if using production database
|
|
if (isProductionDatabase()) {
|
|
console.error('');
|
|
console.error('='.repeat(80));
|
|
console.error('CRITICAL ERROR: Tests are attempting to run against PRODUCTION database!');
|
|
console.error('='.repeat(80));
|
|
console.error('');
|
|
console.error('Current DATABASE_URL:', process.env.DATABASE_URL);
|
|
console.error('');
|
|
console.error('Tests MUST run against the development database (euchre_camp_dev)');
|
|
console.error('');
|
|
console.error('To fix this:');
|
|
console.error(' 1. Run: npm run test:acceptance');
|
|
console.error(' 2. Or set: DATABASE_URL environment variable to dev database URL');
|
|
console.error(' 3. Or load .env.development: source .env.development && npm run test:acceptance');
|
|
console.error('');
|
|
console.error('Aborting test execution to prevent data corruption.');
|
|
console.error('');
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!isDevDatabase()) {
|
|
console.warn('⚠️ WARNING: DATABASE_URL does not contain euchre_camp_dev');
|
|
console.warn(' Current DATABASE_URL:', process.env.DATABASE_URL);
|
|
}
|
|
|
|
export default async function globalSetup(config: FullConfig) {
|
|
const baseURL = config.projects[0]?.use?.baseURL || 'http://localhost:3000';
|
|
|
|
const browser = await chromium.launch();
|
|
const context = await browser.newContext();
|
|
const page = await context.newPage();
|
|
|
|
// Log all responses for debugging
|
|
page.on('response', response => {
|
|
if (response.url().includes('/api/auth')) {
|
|
console.log('API Response:', response.status(), response.url());
|
|
}
|
|
});
|
|
|
|
// Generate unique test credentials
|
|
const timestamp = Date.now();
|
|
const testEmail = `setup-user-${timestamp}@example.com`;
|
|
const testPassword = 'TestPassword1234!';
|
|
const testName = 'Setup User';
|
|
|
|
try {
|
|
// Navigate to registration page
|
|
console.log('Navigating to registration page...');
|
|
await page.goto(`${baseURL}/auth/register`);
|
|
|
|
// Fill in registration form
|
|
await page.fill('input[name="name"]', testName);
|
|
await page.fill('input[name="email"]', testEmail);
|
|
await page.fill('input[name="password"]', testPassword);
|
|
|
|
// Submit the form
|
|
console.log('Submitting registration form...');
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for the sign-up API call to complete
|
|
console.log('Waiting for sign-up API call...');
|
|
try {
|
|
await page.waitForResponse(response =>
|
|
response.url().includes('/api/auth/sign-up/email') && response.status() === 200,
|
|
{ timeout: 10000 }
|
|
);
|
|
console.log('Sign-up API call successful');
|
|
} catch {
|
|
console.log('Sign-up API call failed or timed out');
|
|
}
|
|
|
|
// Wait a bit for session to be established
|
|
console.log('Waiting for session establishment...');
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check if we're already authenticated
|
|
const currentUrl = page.url();
|
|
console.log('Current URL after registration:', currentUrl);
|
|
|
|
// Save the authentication state
|
|
await context.storageState({ path: authFile });
|
|
|
|
console.log(`Created and authenticated test user: ${testEmail}`);
|
|
|
|
// Now create admin user
|
|
const adminTimestamp = timestamp + 1;
|
|
const adminEmail = `setup-admin-${adminTimestamp}@example.com`;
|
|
const adminPassword = 'AdminPassword123!';
|
|
const adminName = 'Setup Admin';
|
|
|
|
// Navigate to registration page again
|
|
console.log('Navigating to registration page for admin...');
|
|
await page.goto(`${baseURL}/auth/register`);
|
|
|
|
// Fill in registration form
|
|
await page.fill('input[name="name"]', adminName);
|
|
await page.fill('input[name="email"]', adminEmail);
|
|
await page.fill('input[name="password"]', adminPassword);
|
|
|
|
// Submit the form
|
|
console.log('Submitting admin registration form...');
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for the sign-up API call to complete
|
|
console.log('Waiting for admin sign-up API call...');
|
|
try {
|
|
await page.waitForResponse(response =>
|
|
response.url().includes('/api/auth/sign-up/email') && response.status() === 200,
|
|
{ timeout: 10000 }
|
|
);
|
|
console.log('Admin sign-up API call successful');
|
|
} catch {
|
|
console.log('Admin sign-up API call failed or timed out');
|
|
}
|
|
|
|
// Wait a bit for session to be established
|
|
console.log('Waiting for admin session establishment...');
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Update user role to admin via database
|
|
const user = await prisma.user.findUnique({
|
|
where: { email: adminEmail }
|
|
});
|
|
|
|
if (user) {
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: { role: 'club_admin' }
|
|
});
|
|
console.log('Updated user role to club_admin');
|
|
}
|
|
|
|
// Navigate to admin page to refresh session
|
|
console.log('Navigating to admin page for admin user...');
|
|
await page.goto(`${baseURL}/admin`);
|
|
await page.waitForLoadState('networkidle');
|
|
console.log('Admin page loaded:', page.url());
|
|
|
|
// Wait a bit to ensure session is refreshed
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Refresh the page to force session reload
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle');
|
|
console.log('Page reloaded');
|
|
|
|
// Save the authentication state
|
|
await context.storageState({ path: adminAuthFile });
|
|
|
|
console.log(`Created and authenticated admin user: ${adminEmail}`);
|
|
|
|
} catch (error) {
|
|
console.error('Global setup error:', error);
|
|
throw error;
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
|
|
// Return teardown function
|
|
return async () => {
|
|
console.log('\n=== Global Teardown ===');
|
|
|
|
// Clean up all test data
|
|
try {
|
|
await cleanupAllTestData();
|
|
} catch (error) {
|
|
console.error('Error cleaning up test data:', error);
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
}
|
|
};
|
|
}
|