/** * Global setup for Playwright tests * Creates test users and saves authentication state */ import { chromium, type FullConfig } from '@playwright/test'; import { PrismaClient } from '@prisma/client'; import { PrismaPg } from '@prisma/adapter-pg'; import path from 'path'; import fs from 'fs'; const authFile = 'playwright/.auth/user.json'; const adminAuthFile = 'playwright/.auth/admin.json'; function isDatabase(url: string, name: string): boolean { return url.includes(name); } function isProductionDatabase(): boolean { const dbUrl = process.env.DATABASE_URL || ''; return isDatabase(dbUrl, 'euchre_camp') && !isDatabase(dbUrl, '_dev') && !isDatabase(dbUrl, '_ci'); } function isCIDatabase(): boolean { const dbUrl = process.env.DATABASE_URL || ''; return isDatabase(dbUrl, '_ci'); } 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 development (euchre_camp_dev) or CI (euchre_camp_ci)'); console.error(''); console.error('Aborting test execution to prevent data corruption.'); console.error(''); process.exit(1); } function createPrismaClient() { const databaseUrl = process.env.DATABASE_URL; if (!databaseUrl) throw new Error('DATABASE_URL is required'); const adapter = new PrismaPg({ connectionString: databaseUrl }); return new PrismaClient({ adapter }); } async function resetDatabaseSchema(prisma: PrismaClient) { console.log('Resetting database schema...'); const tables = await prisma.$queryRaw<{ tablename: string }[]>` SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename NOT LIKE '_prisma_migrations' `; if (tables.length > 0) { await prisma.$executeRawUnsafe(` DROP SCHEMA public CASCADE; CREATE SCHEMA public; `); console.log(`Dropped ${tables.length} tables`); } console.log('Running migrations...'); const { execSync } = await import('child_process'); execSync('bunx prisma migrate deploy', { stdio: 'inherit' }); } async function createTestUsers(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(); page.on('response', response => { if (response.url().includes('/api/auth')) { console.log('API Response:', response.status(), response.url()); } }); const timestamp = Date.now(); const testEmail = `setup-user-${timestamp}@example.com`; const testPassword = 'TestPassword1234!'; const testName = 'Setup User'; try { console.log('Navigating to registration page...'); await page.goto(`${baseURL}/auth/register`); await page.fill('input[name="name"]', testName); await page.fill('input[name="email"]', testEmail); await page.fill('input[name="password"]', testPassword); console.log('Submitting registration form...'); await page.click('button[type="submit"]'); try { await page.waitForResponse(response => response.url().includes('/api/auth/sign-up/email') && response.status() === 200, { timeout: 5000 } ); console.log('Sign-up API call successful'); } catch { console.log('Sign-up API call failed or timed out'); } await page.waitForTimeout(500); await context.storageState({ path: authFile }); console.log(`Created and authenticated test user: ${testEmail}`); // Clear session so admin registration doesn't get redirected await context.clearCookies(); const adminTimestamp = timestamp + 1; const adminEmail = `setup-admin-${adminTimestamp}@example.com`; const adminPassword = 'AdminPassword123!'; const adminName = 'Setup Admin'; console.log('Navigating to registration page for admin...'); await page.goto(`${baseURL}/auth/register`); await page.fill('input[name="name"]', adminName); await page.fill('input[name="email"]', adminEmail); await page.fill('input[name="password"]', adminPassword); console.log('Submitting admin registration form...'); await page.click('button[type="submit"]'); try { await page.waitForResponse(response => response.url().includes('/api/auth/sign-up/email') && response.status() === 200, { timeout: 5000 } ); console.log('Admin sign-up API call successful'); } catch { console.log('Admin sign-up API call failed or timed out'); } await page.waitForTimeout(500); const prisma = createPrismaClient(); 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'); } await prisma.$disconnect(); console.log('Navigating to admin page for admin user...'); await page.goto(`${baseURL}/admin`); await page.waitForLoadState('domcontentloaded'); console.log('Admin page loaded:', page.url()); await page.waitForTimeout(500); await page.reload(); await page.waitForLoadState('domcontentloaded'); console.log('Page reloaded'); await context.storageState({ path: adminAuthFile }); console.log(`Created and authenticated admin user: ${adminEmail}`); } finally { await browser.close(); } } export default async function globalSetup(config: FullConfig) { console.log('=== Global Setup ==='); console.log('DATABASE_URL:', process.env.DATABASE_URL); if (isCIDatabase()) { console.log('CI environment detected - will reset database schema'); const prisma = createPrismaClient(); await resetDatabaseSchema(prisma); await prisma.$disconnect(); } else if (!isProductionDatabase()) { console.log('Development environment - preserving existing data'); } await createTestUsers(config); console.log('=== Global Setup Complete ===\n'); }