/** * Authentication step definitions for EuchreCamp E2E tests * * These steps interact ONLY with the browser UI - no direct database access. * All user creation happens via the registration/login UI. */ import { Given, When, Then } from '@cucumber/cucumber'; import { expect } from '@playwright/test'; import { world } from '../support/world'; // Generate unique test credentials function generateTestCredentials() { const timestamp = Date.now(); return { email: `cucumber-test-${timestamp}@example.com`, password: 'TestPassword1234!', name: `Cucumber Test User ${timestamp}` }; } /** * Precondition: I am logged in as a player * Creates a new user via the registration UI */ Given('I am logged in as a player', async function () { console.log('🌍 Creating and logging in as a player via UI...'); // Generate unique credentials for each test run const timestamp = Date.now(); const credentials = { email: `cucumber-player-${timestamp}@example.com`, password: 'TestPassword1234!', // 16+ characters for minPasswordLength=8 name: `Cucumber Player ${timestamp}`, }; world.user = credentials; // Start monitoring network requests const requests: string[] = []; const responses: string[] = []; const consoleLogs: string[] = []; world.page.on('request', req => requests.push(`${req.method()} ${req.url()}`)); world.page.on('response', res => responses.push(`${res.status()} ${res.url()}`)); world.page.on('console', msg => consoleLogs.push(`${msg.type()}: ${msg.text()}`)); world.page.on('pageerror', err => console.log('🌍 Page error:', err)); world.page.on('crash', () => console.log('🌍 Page crashed')); // Navigate to registration page await world.page.goto(`${world.baseURL}/auth/register`); await world.page.waitForLoadState('domcontentloaded'); // Fill registration form await world.page.fill('input[name="name"]', credentials.name); await world.page.fill('input[name="email"]', credentials.email); await world.page.fill('input[name="password"]', credentials.password); // Submit form await world.page.click('button[type="submit"]'); // Wait for redirect to profile page (using waitForURL which is more reliable) // Timeout is high (60s) to accommodate slow dev server (HMR, etc.) try { console.log('🌍 Waiting for redirect to profile page...'); await world.page.waitForURL(/\/players\/\d+\/profile/, { timeout: 60000 }); console.log(`🌍 Player registered and redirected to profile: ${credentials.email}`); // Extract player ID from URL for later use (e.g., schedule page) const currentUrl = world.page.url(); const match = currentUrl.match(/\/players\/(\d+)\/profile/); if (match) { world.playerId = match[1]; console.log(`🌍 Extracted player ID: ${world.playerId}`); } } catch (e) { console.log('🌍 Registration redirect did not complete as expected'); console.log(`🌍 Current URL: ${world.page.url()}`); console.log('🌍 Recent requests:', requests.slice(-10)); console.log('🌍 Recent responses:', responses.slice(-10)); console.log('🌍 Browser console logs:', consoleLogs.slice(-10)); // Debug: dump page content const content = await world.page.content(); console.log('🌍 Page content (first 500 chars):', content.substring(0, 500)); } // Verify we're logged in by checking for sign-out button // Use a more specific locator for the button try { const signOutButton = world.page.locator('button:has-text("Sign out")'); await expect(signOutButton).toBeVisible({ timeout: 10000 }); console.log(`🌍 Login verified on profile page: ${credentials.email}`); } catch (e) { console.log('🌍 Sign out button not visible on profile, trying home page...'); await world.page.goto(`${world.baseURL}/`); await world.page.waitForLoadState('domcontentloaded'); try { const signOutButton = world.page.locator('button:has-text("Sign out")'); await expect(signOutButton).toBeVisible({ timeout: 10000 }); console.log(`🌍 Login verified on home page: ${credentials.email}`); } catch (e2) { console.log('🌍 Could not verify login status'); } } console.log(`🌍 Player created: ${credentials.email}`); }); /** * Precondition: I am logged in as a tournament admin * Note: In the actual app, admin roles are assigned by club admins or via API. * For acceptance tests, we'll assign the tournament_admin role directly via Prisma. */ Given('I am logged in as a tournament admin', async function () { console.log('🌍 Creating and logging in as a tournament admin...'); const credentials = generateTestCredentials(); world.user = credentials; await world.page.goto(`${world.baseURL}/auth/register`); await world.page.waitForLoadState('domcontentloaded'); await world.page.fill('input[name="name"]', credentials.name); await world.page.fill('input[name="email"]', credentials.email); await world.page.fill('input[name="password"]', credentials.password); await world.page.click('button[type="submit"]'); // Wait for redirect await world.page.waitForURL(/\/players\/\d+\/profile/, { timeout: 15000 }); // Extract user ID from the URL (e.g., /players/2147/profile) const currentUrl = world.page.url(); const match = currentUrl.match(/\/players\/(\d+)\/profile/); if (match) { const playerId = match[1]; world.playerId = playerId; // Get the user ID from the database const prisma = await world.getPrisma(); const player = await prisma.player.findUnique({ where: { id: parseInt(playerId) }, include: { user: true } }); if (player && player.user) { const userId = player.user.id; (world.user as any).id = userId; console.log(`🌍 User ID extracted: ${userId}`); // Assign tournament_admin role to the user await prisma.user.update({ where: { id: userId }, data: { role: 'tournament_admin' } }); console.log(`🌍 Assigned tournament_admin role to user: ${userId}`); } } console.log(`🌍 User created: ${credentials.email}`); }); /** * Precondition: I am logged in as a club admin * Uses a pre-existing admin user from the database */ Given('I am logged in as a club admin', async function () { console.log('🌍 Logging in as existing club admin...'); // Use the admin user created by seed.js const adminEmail = 'david@dhg.lol'; const adminPassword = 'adminadmin'; world.user = { email: adminEmail, password: adminPassword, name: 'David Admin', }; await world.page.goto(`${world.baseURL}/auth/login`); await world.page.waitForLoadState('domcontentloaded'); await world.page.fill('input[name="email"]', adminEmail); await world.page.fill('input[name="password"]', adminPassword); await world.page.click('button[type="submit"]'); // Wait for redirect after login try { await world.page.waitForURL((url) => !url.toString().includes('/auth/login'), { timeout: 10000 }); console.log(`🌍 Club admin logged in: ${adminEmail}`); } catch (e) { console.log('🌍 Login redirect timed out, current URL:', world.page.url()); } }); /** * Registration steps (UI-only) */ When('I register with valid credentials', async function () { const credentials = generateTestCredentials(); world.user = credentials; await world.page.goto(`${world.baseURL}/auth/register`); await world.page.waitForLoadState('domcontentloaded'); await world.page.fill('input[name="name"]', credentials.name); await world.page.fill('input[name="email"]', credentials.email); await world.page.fill('input[name="password"]', credentials.password); await world.page.click('button[type="submit"]'); console.log(`🌍 Registered with credentials: ${credentials.email}`); }); When('I register with duplicate email', async function () { if (!world.user) { throw new Error('No existing user to duplicate'); } await world.page.goto(`${world.baseURL}/auth/register`); await world.page.waitForLoadState('domcontentloaded'); await world.page.fill('input[name="name"]', world.user.name); await world.page.fill('input[name="email"]', world.user.email); await world.page.fill('input[name="password"]', world.user.password); await world.page.click('button[type="submit"]'); console.log(`🌍 Attempted registration with duplicate: ${world.user.email}`); }); When('I fill in {string} with the same email', async function (fieldName: string) { if (!world.user) { throw new Error('No user credentials available'); } const selector = `input[name="email"]`; console.log(`🌍 Filling ${fieldName} with same email: ${world.user.email}`); await world.page.fill(selector, world.user.email); }); When('I register with weak password', async function () { const credentials = generateTestCredentials(); credentials.password = 'weak'; // Too short await world.page.goto(`${world.baseURL}/auth/register`); await world.page.waitForLoadState('domcontentloaded'); await world.page.fill('input[name="name"]', credentials.name); await world.page.fill('input[name="email"]', credentials.email); await world.page.fill('input[name="password"]', credentials.password); await world.page.click('button[type="submit"]'); console.log('🌍 Attempted registration with weak password'); }); /** * Login steps (UI-only) */ When('I log in with valid credentials', async function () { if (!world.user) { throw new Error('No user credentials available'); } await world.page.goto(`${world.baseURL}/auth/login`); await world.page.waitForLoadState('domcontentloaded'); await world.page.fill('input[name="email"]', world.user.email); await world.page.fill('input[name="password"]', world.user.password); await world.page.click('button[type="submit"]'); console.log(`🌍 Logged in as: ${world.user.email}`); }); When('I log in with invalid credentials', async function () { await world.page.goto(`${world.baseURL}/auth/login`); await world.page.waitForLoadState('domcontentloaded'); await world.page.fill('input[name="email"]', 'nonexistent@example.com'); await world.page.fill('input[name="password"]', 'wrongpassword'); await world.page.click('button[type="submit"]'); console.log('🌍 Attempted login with invalid credentials'); }); /** * Logout steps (UI-only) */ When('I log out', async function () { // Click on logout button await world.page.click('text=Sign out'); console.log('🌍 Logged out'); }); /** * Note: The step "I am logged in as a player" is already defined at line 26 */ Then('I should not be logged in', async function () { // Check that we're on login page or don't see logout button const currentUrl = world.page.url(); expect(currentUrl).toContain('/auth/login'); console.log('🌍 Verified user is not logged in'); }); Then('I should see a registration error', async function () { // Check for error message in page content const content = await world.page.content(); expect(content).toContain('error'); console.log('🌍 Verified registration error is visible'); }); Then('I should see an error message', async function () { // Check for any error message on the page const content = await world.page.content(); expect(content).toMatch(/error|Error/i); console.log('🌍 Verified error message is visible'); }); Then('I should remain on the login page', async function () { const currentUrl = world.page.url(); expect(currentUrl).toContain('/auth/login'); console.log('🌍 Verified still on login page'); }); Then('I should remain on the registration page', async function () { const currentUrl = world.page.url(); expect(currentUrl).toContain('/auth/register'); console.log('🌍 Verified still on registration page'); }); /** * Helper: Verify user was created via UI (no database check) */ Then('my user account should exist', async function () { if (!world.user) { throw new Error('No user credentials available'); } // We can't check the database directly in acceptance tests // Instead, verify we're logged in (user profile page) await expect(world.page.locator('text=Welcome')).toBeVisible(); console.log('🌍 Verified user account was created successfully'); }); /** * Schedule steps (player) */ When('I go to my schedule page', async function () { console.log('🌍 Going to schedule page'); // Navigate to the schedule page using the extracted player ID if (!world.playerId) { throw new Error('Player ID not found. Ensure "I am logged in as a player" was run first.'); } await world.page.goto(`${world.baseURL}/players/${world.playerId}/schedule`); await world.page.waitForLoadState('domcontentloaded'); }); Given('I have upcoming matches in my schedule', async function () { console.log('🌍 Setting up upcoming matches in schedule'); const prisma = await world.getPrisma(); const timestamp = Date.now(); // Get the current player if (!world.playerId) { throw new Error('No player ID found. Make sure user is logged in as a player first.'); } const currentPlayerId = parseInt(world.playerId, 10); // Create 3 other players for the match const opponent1 = await prisma.player.create({ data: { name: `Opponent ${timestamp} 1`, normalizedName: `opponent ${timestamp} 1`, currentElo: 1000, }, }); const opponent2 = await prisma.player.create({ data: { name: `Opponent ${timestamp} 2`, normalizedName: `opponent ${timestamp} 2`, currentElo: 1000, }, }); const partner1 = await prisma.player.create({ data: { name: `Partner ${timestamp}`, normalizedName: `partner ${timestamp}`, currentElo: 1000, }, }); // Create a tournament const tournament = await prisma.event.create({ data: { name: `Test Schedule Tournament ${timestamp}`, eventDate: new Date(Date.now() + 86400000), // Tomorrow status: 'planned', }, }); // Create a match with the current player as player1P1 (played tomorrow) await prisma.match.create({ data: { eventId: tournament.id, player1P1Id: currentPlayerId, player1P2Id: partner1.id, player2P1Id: opponent1.id, player2P2Id: opponent2.id, team1Score: 10, team2Score: 5, status: 'completed', playedAt: new Date(Date.now() + 86400000), // Tomorrow }, }); console.log(`🌍 Created tournament "${tournament.name}" with 1 match for player ${currentPlayerId}`); }); /** * Tournament schedule steps */ Given('a tournament exists with {int} teams', async function (teamCount: number) { console.log(`🌍 Setting up tournament with ${teamCount} teams`); // Get Prisma client const prisma = await world.getPrisma(); const timestamp = Date.now(); // Get the current user ID for ownership const userId = world.user?.id; if (!userId) { throw new Error('User ID not found. Ensure user is logged in before creating tournament.'); } // Always create a new tournament for test isolation const tournament = await prisma.event.create({ data: { name: `Test Tournament ${timestamp}`, createdAt: new Date(), ownerId: userId, // Set the owner to the current user }, }); // Euchre is 2v2, so each team has 2 players // Create teamCount * 2 players and add them as participants const playerCount = teamCount * 2; for (let i = 1; i <= playerCount; i++) { const player = await prisma.player.create({ data: { name: `Tournament Player ${i} ${timestamp}`, normalizedName: `tournament player ${i} ${timestamp}`, currentElo: 1000, gamesPlayed: 0, wins: 0, losses: 0, }, }); await prisma.eventParticipant.create({ data: { eventId: tournament.id, playerId: player.id, }, }); } world.tournament = tournament; world.tournamentTeamCount = teamCount; console.log(`🌍 Created tournament: ${tournament.name} (ID: ${tournament.id}) with ${playerCount} players (${teamCount} teams)`); }); When('I go to the tournament schedule page', async function () { console.log('🌍 Going to tournament schedule page'); const tournamentId = world.tournament?.id || 1; await world.page.goto(`${world.baseURL}/admin/tournaments/${tournamentId}/schedule`); await world.page.waitForLoadState('load'); // Wait for client components to hydrate await world.page.waitForTimeout(1000); }); Given('a tournament has a generated schedule', async function () { console.log('🌍 Creating tournament with generated schedule'); const prisma = await world.getPrisma(); const timestamp = Date.now(); // Get the current user ID for ownership const userId = world.user?.id; if (!userId) { throw new Error('User ID not found. Ensure user is logged in before creating tournament.'); } // Create a tournament const tournament = await prisma.event.create({ data: { name: `Test Schedule Tournament ${timestamp}`, createdAt: new Date(), ownerId: userId, // Set the owner to the current user }, }); // Create 4 players and add them as participants const players = []; for (let i = 1; i <= 4; i++) { const player = await prisma.player.create({ data: { name: `Schedule Player ${i} ${timestamp}`, normalizedName: `schedule player ${i} ${timestamp}`, currentElo: 1000, gamesPlayed: 0, wins: 0, losses: 0, }, }); players.push(player); await prisma.eventParticipant.create({ data: { eventId: tournament.id, playerId: player.id, }, }); } // Generate schedule via API const response = await fetch(`${world.baseURL}/api/tournaments/${tournament.id}/schedule`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }); if (!response.ok) { console.log('🌍 Failed to generate schedule:', response.status, response.statusText); // Try to get error details try { const errorData = await response.json(); console.log('🌍 Error details:', errorData); } catch { // Ignore } } else { const data = await response.json(); console.log('🌍 Schedule generated:', data); } world.tournament = tournament; world.tournamentTeamCount = 4; console.log(`🌍 Tournament with schedule created: ${tournament.name} (ID: ${tournament.id})`); }); Given('there are recent activities in the system', async function () { // Create test activities using the activity logger const prisma = await world.getPrisma() // Use timestamp to ensure unique names const timestamp = Date.now() // Create a test player first const player = await prisma.player.create({ data: { name: `Test Activity Player ${timestamp}`, normalizedName: `test activity player ${timestamp}`, currentElo: 1000, gamesPlayed: 0, wins: 0, losses: 0, }, }) // Create an activity await (prisma as any).activity.create({ data: { type: 'player_registration', description: `Test Activity Player ${timestamp} registered`, playerId: player.id, }, }) console.log('🌍 Created test activity for player:', player.name) }) Given('there are multiple players in the system', async function () { const prisma = await world.getPrisma() // Use timestamp to ensure unique names const timestamp = Date.now() // Create multiple test players for (let i = 1; i <= 5; i++) { await prisma.player.create({ data: { name: `Test Player ${i} ${timestamp}`, normalizedName: `test player ${i} ${timestamp}`, currentElo: 1000 + i * 10, gamesPlayed: 0, wins: 0, losses: 0, }, }) } console.log('🌍 Created 5 test players') })