452 lines
15 KiB
TypeScript
452 lines
15 KiB
TypeScript
/**
|
|
* 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('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('🌍 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('domcontentloaded');
|
|
});
|
|
|
|
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')
|
|
})
|