/** * 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 use the default player role and test admin features * as the dev site would handle them. */ Given('I am logged in as a tournament admin', async function () { console.log('🌍 Creating and logging in as a player (tournament admin role is assigned via UI/API)...'); // For now, use the same flow as player // In real usage, the admin would either: // 1. Be pre-created on the dev site // 2. Have role assigned via API // 3. Use the admin dashboard to manage users 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 }); 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('networkidle'); 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('networkidle'); 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('networkidle'); 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('networkidle'); 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('networkidle'); 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('🌍 Note: This step requires database setup via API or UI'); console.log('🌍 For acceptance tests, this would be set up before running the test'); // For true acceptance testing, we would: // 1. Create a tournament // 2. Add the player as a participant // 3. Generate a schedule // 4. The match would then appear in the player's schedule // For now, this is a placeholder that indicates data setup is needed // In a real test run, this data would already exist in the dev database }); /** * 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(); // Find or create a tournament let tournament = await prisma.event.findFirst({ orderBy: { createdAt: 'desc' }, }); if (!tournament) { // Create a new tournament if none exists const timestamp = Date.now(); tournament = await prisma.event.create({ data: { name: `Test Tournament ${timestamp}`, createdAt: new Date(), }, }); } world.tournament = tournament; world.tournamentTeamCount = teamCount; console.log(`🌍 Using tournament: ${tournament.name} (ID: ${tournament.id})`); }); 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('networkidle'); }); Given('a tournament has a generated schedule', async function () { console.log('🌍 Note: Tournament schedule requires generation via API or UI'); console.log('🌍 For acceptance tests, this would be created before running the test'); // In a real test run, we would: // 1. Create a tournament // 2. Add teams/participants // 3. Generate schedule via API or UI }); 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') })