Files
euchre_camp/e2e/schedule-tab.test.ts
T
david 005cf7293d
Pull Request / unit-tests (pull_request) Successful in 2m31s
Pull Request / analyze-bump-type (pull_request) Successful in 12s
Pull Request / build-and-deploy-ci (pull_request) Failing after 42m4s
fix: reduce timeout values and fix test auth issues
- Reduce URL wait timeouts from 10000ms to 5000ms across tests
- Fix tournament-edit-allowTies: add login before navigating to edit page
- Fix epic4-tournament-creation: fill name field before clicking Next
- Fix cucumber config: remove broken cucumber-pretty formatter
2026-05-18 20:05:15 -07:00

235 lines
7.7 KiB
TypeScript

/**
* Issue #7: Schedule Tab
* Acceptance Test: Schedule Generation and Display
*
* User Story: As a tournament admin, I want a Schedule tab to view round matchups
*
* Acceptance Criteria:
* - Schedule tab added to tournament detail page
* - Displays round-robin schedule with round numbers
* - Round-robin schedule can be generated from teams
* - Matches are linkable to result entry
*/
import { test, expect } from '@playwright/test';
import { prisma } from '@/lib/prisma';
const BASE_URL = process.env.CI ? 'https://euchre-ci.notsosm.art' : 'http://localhost:3000';
function getTestCredentials() {
const timestamp = Date.now();
return {
email: `schedule-admin-${timestamp}@example.com`,
password: 'AdminPassword123!',
name: `Schedule Admin ${timestamp}`,
};
}
test.describe.serial('Issue #7: Schedule Tab', () => {
let testEmail: string;
let testPassword: string;
let tournamentId: number;
test.beforeAll(async () => {
const credentials = getTestCredentials();
testEmail = credentials.email;
testPassword = credentials.password;
// Create admin user via API
const response = await fetch(`${BASE_URL}/api/auth/sign-up/email`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Origin: BASE_URL,
},
body: JSON.stringify({
email: testEmail,
password: testPassword,
name: credentials.name,
}),
});
console.log('Schedule test user creation response:', response.status);
// Update user to club_admin role
const user = await prisma.user.findUnique({
where: { email: testEmail },
});
if (user) {
await prisma.user.update({
where: { id: user.id },
data: { role: 'club_admin' },
});
}
// Create players for teams
const players = await Promise.all([
prisma.player.create({
data: { name: 'Alice', normalizedName: 'alice' },
}),
prisma.player.create({
data: { name: 'Bob', normalizedName: 'bob' },
}),
prisma.player.create({
data: { name: 'Charlie', normalizedName: 'charlie' },
}),
prisma.player.create({
data: { name: 'Diana', normalizedName: 'diana' },
}),
]);
// Create tournament
const tournament = await prisma.event.create({
data: {
name: `Schedule Test Tournament ${Date.now()}`,
format: 'round_robin',
ownerId: user?.id,
},
});
tournamentId = tournament.id;
// Register participants (teams are now ephemeral and generated during schedule creation)
await Promise.all(
players.map((player) =>
prisma.eventParticipant.create({
data: {
eventId: tournamentId,
playerId: player.id,
status: 'registered',
registrationDate: new Date(),
},
})
)
);
});
test.afterAll(async () => {
try {
// Clean up schedule data
if (tournamentId) {
await prisma.bracketMatchup.deleteMany({ where: { eventId: tournamentId } });
await prisma.tournamentRound.deleteMany({ where: { eventId: tournamentId } });
await prisma.eventParticipant.deleteMany({ where: { eventId: tournamentId } });
await prisma.event.delete({ where: { id: tournamentId } }).catch(() => {});
}
// Clean up user
const user = await prisma.user.findUnique({ where: { email: testEmail } });
if (user) {
await prisma.user.delete({ where: { id: user.id } });
}
// Clean up players
await prisma.player.deleteMany({
where: { normalizedName: { in: ['alice', 'bob', 'charlie', 'diana'] } },
});
} catch (error) {
console.error('Cleanup error:', error);
}
});
test('Schedule tab link exists on tournament detail page', async ({ page }) => {
// Login
await page.goto('/auth/login');
await page.fill('input[name="email"]', testEmail);
await page.fill('input[name="password"]', testPassword);
await page.click('button[type="submit"]');
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
// Navigate to tournament detail
await page.goto(`/admin/tournaments/${tournamentId}`);
// Check Schedule tab link exists - use button since page uses buttons for tabs
const scheduleLink = page.locator('button', { hasText: 'Schedule' });
await expect(scheduleLink).toBeVisible();
});
test('Schedule page loads with no schedule message', async ({ page }) => {
// Login
await page.goto('/auth/login');
await page.fill('input[name="email"]', testEmail);
await page.fill('input[name="password"]', testPassword);
await page.click('button[type="submit"]');
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
// Navigate to schedule page
await page.goto(`/admin/tournaments/${tournamentId}/schedule`);
// Check page content
await expect(page.locator('h1')).toContainText('Tournament Schedule');
await expect(page.locator('text=No Schedule Generated')).toBeVisible();
await expect(page.locator('button', { hasText: 'Generate Schedule' })).toBeVisible();
});
test('Generate schedule creates rounds and matchups', async ({ page }) => {
// Login
await page.goto('/auth/login');
await page.fill('input[name="email"]', testEmail);
await page.fill('input[name="password"]', testPassword);
await page.click('button[type="submit"]');
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
// Navigate to schedule page
await page.goto(`/admin/tournaments/${tournamentId}/schedule`);
// Click generate schedule
await page.click('button:has-text("Generate Schedule")');
// Wait for success message or page reload
await page.waitForTimeout(3000);
// Verify rounds were created in database
const rounds = await prisma.tournamentRound.findMany({
where: { eventId: tournamentId },
});
expect(rounds.length).toBeGreaterThan(0);
// Verify matchups were created
const matchups = await prisma.bracketMatchup.findMany({
where: { eventId: tournamentId },
});
expect(matchups.length).toBeGreaterThan(0);
});
test('Schedule page displays generated rounds and matchups', async ({ page }) => {
// Login
await page.goto('/auth/login');
await page.fill('input[name="email"]', testEmail);
await page.fill('input[name="password"]', testPassword);
await page.click('button[type="submit"]');
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
// Navigate to schedule page
await page.goto(`/admin/tournaments/${tournamentId}/schedule`);
// Check that rounds are displayed
await expect(page.locator('text=Round 1')).toBeVisible();
// Check that team names are displayed
await expect(page.locator('text=Alice + Bob')).toBeVisible();
await expect(page.locator('text=Charlie + Diana')).toBeVisible();
// Check that "Enter Result" link exists for pending matchups
await expect(page.locator('a:has-text("Enter Result")')).toBeVisible();
});
test('Schedule API returns rounds with matchups', async ({ page }) => {
// Login
await page.goto('/auth/login');
await page.fill('input[name="email"]', testEmail);
await page.fill('input[name="password"]', testPassword);
await page.click('button[type="submit"]');
await page.waitForURL(/\/(admin|players)/, { timeout: 5000 });
// Call the schedule API
const response = await page.request.get(
`/api/tournaments/${tournamentId}/schedule`
);
expect(response.ok()).toBe(true);
const data = await response.json();
expect(data.rounds).toBeDefined();
expect(data.rounds.length).toBeGreaterThan(0);
expect(data.rounds[0].bracketMatchups).toBeDefined();
});
});