/** * Cucumber hooks for EuchreCamp E2E tests */ import { Before, After, BeforeAll, AfterAll, setDefaultTimeout } from '@cucumber/cucumber'; import { chromium, Browser } from '@playwright/test'; import { world } from './world'; import path from 'path'; import fs from 'fs'; // Set default timeout for Cucumber steps (30 seconds) setDefaultTimeout(30000); // Global browser instance let browser: Browser; // Load environment files const envPath = path.resolve(process.cwd(), '.env'); const envDevPath = path.resolve(process.cwd(), '.env.development'); if (fs.existsSync(envPath)) { require('dotenv').config({ path: envPath }); } if (fs.existsSync(envDevPath)) { require('dotenv').config({ path: envDevPath, override: true }); } // Database safety check - prevent tests from running against production function isProductionDatabase(): boolean { const dbUrl = process.env.DATABASE_URL || ''; return dbUrl.includes('euchre_camp') && !dbUrl.includes('_dev') && !dbUrl.includes('test'); } if (isProductionDatabase()) { console.error(''); console.error('='.repeat(80)); console.error('CRITICAL ERROR: Cucumber 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 a development/test database.'); console.error(''); console.error('To fix this:'); console.error(' 1. Set DATABASE_URL to a dev/test database'); console.error(' 2. Or run: npm run test:acceptance (which uses Playwright tests)'); console.error(''); console.error('Aborting test execution to prevent data corruption.'); console.error(''); process.exit(1); } /** * Before all scenarios: Launch browser */ BeforeAll(async function () { console.log('🌍 Launching browser for Cucumber tests...'); browser = await chromium.launch(); }); /** * After all scenarios: Close browser and cleanup */ AfterAll(async function () { console.log('🌍 Closing browser...'); await browser.close(); console.log('🌍 Disconnecting from database...'); if (world.prisma) { await world.prisma.$disconnect(); } }); /** * Before each scenario: Create new page context */ Before(async function () { try { // Create new context and page world.context = await browser.newContext(); world.page = await world.context.newPage(); } catch (error) { console.error('❌ Failed to create new browser context:', error); console.log('🌍 Attempting to relaunch browser...'); // Try to relaunch browser try { await browser.close(); } catch (e) { // Ignore close errors } // Relaunch browser browser = await chromium.launch({ headless: process.env.CI ? true : false, }); // Retry creating context world.context = await browser.newContext(); world.page = await world.context.newPage(); console.log('✅ Browser relaunched successfully'); } // Log console messages for debugging world.page.on('console', msg => { console.log(`🌍 BROWSER ${msg.type()}:`, msg.text()); }); // Log network errors world.page.on('requestfailed', request => { console.log('❌ REQUEST FAILED:', request.url()); }); console.log('🌍 Created new page context'); }); /** * After each scenario: Close page and clean up test data */ After(async function () { console.log('🌍 Cleaning up after scenario...'); // Clean up test data from dev database try { const prisma = await world.getPrisma(); const dbUrl = process.env.DATABASE_URL || ''; // Safety check: only clean up dev/test databases if (dbUrl.includes('_dev') || dbUrl.includes('test') || dbUrl.includes('ci')) { // Use Prisma API for cleanup instead of raw SQL to avoid column name issues // Find test tournaments first const testTournaments = await prisma.event.findMany({ where: { OR: [ { name: { startsWith: 'Test Tournament' } }, { name: { startsWith: 'Test Schedule Tournament' } } ] }, select: { id: true } }); const tournamentIds = testTournaments.map(t => t.id); if (tournamentIds.length > 0) { // Delete bracket matchups via Prisma await prisma.bracketMatchup.deleteMany({ where: { round: { eventId: { in: tournamentIds } } } }); // Delete rounds await prisma.tournamentRound.deleteMany({ where: { eventId: { in: tournamentIds } } }); // Delete event participants await prisma.eventParticipant.deleteMany({ where: { eventId: { in: tournamentIds } } }); // Delete tournaments await prisma.event.deleteMany({ where: { id: { in: tournamentIds } } }); } // Delete test players await prisma.player.deleteMany({ where: { OR: [ { name: { startsWith: 'Tournament Player' } }, { name: { startsWith: 'Schedule Player' } }, { name: { startsWith: 'Test Player' } }, { name: { startsWith: 'Test Activity Player' } } ] } }); // Delete test users await prisma.user.deleteMany({ where: { email: { startsWith: 'cucumber-' } } }); console.log('🌍 Test data cleaned up from dev database'); } else { console.log('🌍 Skipping database cleanup (not a dev/test database)'); } } catch (error) { console.log('🌍 Database cleanup error (non-critical):', error); } // Close page and context if (world.page) { await world.page.close(); } if (world.context) { await world.context.close(); } }); /** * Custom hook for navigating to a page */ Before({ tags: '@navigation' }, async function () { console.log('🌍 Navigation hook triggered'); }); /** * Custom hook for authenticated scenarios */ Before({ tags: '@authenticated' }, async function () { console.log('🌍 Authenticated hook triggered'); }); /** * Tag hooks for specific scenarios */ Before({ tags: '@slow' }, async function () { console.log('🌍 Slow test - extending timeout'); }); Before({ tags: '@flaky' }, async function () { console.log('🌍 Flaky test - retry enabled'); });