Animation fixes: - Fix held card positioning bug (was appearing at bottom of page) - Fix discard pile blank/white flash on turn transitions - Fix blank card at round end by skipping animations during round_over/game_over - Set card content before triggering flip animation to prevent flash - Center suit symbol on 10 cards Timing improvements: - Reduce post-discard delay from 700ms to 500ms - Reduce post-swap delay from 1800ms to 1000ms - Speed up swap flip animation from 1150ms to 550ms - Reduce CPU initial thinking delay from 150-250ms to 80-150ms - Pause now happens after swap completes (showing result) instead of before E2E test suite: - Add Playwright-based test bot that plays full games - State parser extracts game state from DOM for validation - AI brain ports decision logic for automated play - Freeze detector monitors for UI hangs - Visual validator checks CSS states - Full game, stress, and visual test specs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
254 lines
6.7 KiB
TypeScript
254 lines
6.7 KiB
TypeScript
/**
|
|
* Full game playthrough tests
|
|
* Tests complete game sessions with the bot
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { GolfBot } from '../bot/golf-bot';
|
|
import { FreezeDetector } from '../health/freeze-detector';
|
|
import { ScreenshotValidator } from '../visual/screenshot-validator';
|
|
|
|
test.describe('Full Game Playthrough', () => {
|
|
test('bot completes 3-hole game against CPU', async ({ page }) => {
|
|
test.setTimeout(180000); // 3 minutes for 3-hole game
|
|
|
|
const bot = new GolfBot(page);
|
|
const freezeDetector = new FreezeDetector(page);
|
|
const validator = new ScreenshotValidator(page);
|
|
|
|
// Navigate to game
|
|
await bot.goto();
|
|
|
|
// Create game and add CPU
|
|
const roomCode = await bot.createGame('TestBot');
|
|
expect(roomCode).toHaveLength(4);
|
|
|
|
await bot.addCPU('Sofia');
|
|
|
|
// Take screenshot of waiting room
|
|
await validator.capture('waiting-room');
|
|
|
|
// Start game with 3 holes
|
|
await bot.startGame({ holes: 3 });
|
|
|
|
// Verify game started
|
|
const phase = await bot.getGamePhase();
|
|
expect(['initial_flip', 'playing']).toContain(phase);
|
|
|
|
// Take screenshot of game start
|
|
await validator.capture('game-start', phase);
|
|
|
|
// Play through the entire game
|
|
const result = await bot.playGame(3);
|
|
|
|
// Take final screenshot
|
|
await validator.capture('game-over', 'game_over');
|
|
|
|
// Verify game completed
|
|
expect(result.success).toBe(true);
|
|
expect(result.rounds).toBeGreaterThanOrEqual(1);
|
|
|
|
// Check for errors
|
|
const errors = bot.getConsoleErrors();
|
|
expect(errors).toHaveLength(0);
|
|
|
|
// Verify no freezes occurred
|
|
const health = await freezeDetector.runHealthCheck();
|
|
expect(health.healthy).toBe(true);
|
|
});
|
|
|
|
test('bot completes 9-hole game against CPU', async ({ page }) => {
|
|
test.setTimeout(900000); // 15 minutes for 9-hole game
|
|
|
|
const bot = new GolfBot(page);
|
|
const freezeDetector = new FreezeDetector(page);
|
|
|
|
await bot.goto();
|
|
|
|
const roomCode = await bot.createGame('TestBot');
|
|
await bot.addCPU('Marcus');
|
|
|
|
await bot.startGame({ holes: 9 });
|
|
|
|
// Play full game
|
|
const result = await bot.playGame(9);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.rounds).toBe(9);
|
|
|
|
// Verify game ended properly
|
|
const finalPhase = await bot.getGamePhase();
|
|
expect(finalPhase).toBe('game_over');
|
|
|
|
// Check health
|
|
const health = await freezeDetector.runHealthCheck();
|
|
expect(health.healthy).toBe(true);
|
|
});
|
|
|
|
test('bot handles initial flip phase correctly', async ({ page }) => {
|
|
const bot = new GolfBot(page);
|
|
const validator = new ScreenshotValidator(page);
|
|
|
|
await bot.goto();
|
|
await bot.createGame('TestBot');
|
|
await bot.addCPU('Sofia');
|
|
await bot.startGame({ holes: 1, initialFlips: 2 });
|
|
|
|
// Wait for initial flip phase
|
|
await page.waitForTimeout(500);
|
|
|
|
// Take screenshot before flips
|
|
await validator.capture('before-initial-flip');
|
|
|
|
// Complete initial flips
|
|
await bot.completeInitialFlips();
|
|
|
|
// Take screenshot after flips
|
|
await validator.capture('after-initial-flip');
|
|
|
|
// Verify 2 cards are face-up
|
|
const state = await bot.getGameState();
|
|
const faceUpCount = state.myPlayer?.cards.filter(c => c.faceUp).length || 0;
|
|
expect(faceUpCount).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
test('bot recovers from rapid turn changes', async ({ page }) => {
|
|
test.setTimeout(90000); // 90 seconds
|
|
|
|
const bot = new GolfBot(page);
|
|
|
|
await bot.goto();
|
|
await bot.createGame('TestBot');
|
|
|
|
// Add multiple fast CPUs
|
|
await bot.addCPU('Maya'); // Aggressive
|
|
await bot.addCPU('Sage'); // Sneaky finisher
|
|
|
|
await bot.startGame({ holes: 1 });
|
|
|
|
// Play with health monitoring
|
|
let frozenCount = 0;
|
|
let turnCount = 0;
|
|
|
|
while (await bot.getGamePhase() !== 'round_over' && turnCount < 50) {
|
|
if (await bot.isMyTurn()) {
|
|
const result = await bot.playTurn();
|
|
expect(result.success).toBe(true);
|
|
turnCount++;
|
|
}
|
|
|
|
// Check for freeze
|
|
if (await bot.isFrozen(2000)) {
|
|
frozenCount++;
|
|
}
|
|
|
|
await page.waitForTimeout(100);
|
|
}
|
|
|
|
// Should not have frozen
|
|
expect(frozenCount).toBe(0);
|
|
});
|
|
|
|
test('game handles all players finishing', async ({ page }) => {
|
|
test.setTimeout(90000); // 90 seconds for single round
|
|
|
|
const bot = new GolfBot(page);
|
|
const validator = new ScreenshotValidator(page);
|
|
|
|
await bot.goto();
|
|
await bot.createGame('TestBot');
|
|
await bot.addCPU('Sofia');
|
|
await bot.startGame({ holes: 1 });
|
|
|
|
// Play until round over
|
|
const roundResult = await bot.playRound(100);
|
|
expect(roundResult.success).toBe(true);
|
|
|
|
// Take screenshot of round end
|
|
await validator.capture('round-end');
|
|
|
|
// Verify all player cards are revealed
|
|
const state = await bot.getGameState();
|
|
const allRevealed = state.myPlayer?.cards.every(c => c.faceUp) ?? false;
|
|
expect(allRevealed).toBe(true);
|
|
|
|
// Verify scoreboard is visible
|
|
const scoreboardVisible = await validator.expectVisible('#game-buttons');
|
|
expect(scoreboardVisible.passed).toBe(true);
|
|
});
|
|
});
|
|
|
|
test.describe('Game Settings', () => {
|
|
test('Speed Golf mode (flip on discard)', async ({ page }) => {
|
|
test.setTimeout(90000); // 90 seconds
|
|
|
|
const bot = new GolfBot(page);
|
|
|
|
await bot.goto();
|
|
await bot.createGame('TestBot');
|
|
await bot.addCPU('Sofia');
|
|
|
|
// Start with Speed Golf (always flip)
|
|
await bot.startGame({
|
|
holes: 1,
|
|
flipMode: 'always',
|
|
});
|
|
|
|
// Play through
|
|
const result = await bot.playRound(50);
|
|
expect(result.success).toBe(true);
|
|
|
|
// No errors should occur
|
|
expect(bot.getConsoleErrors()).toHaveLength(0);
|
|
});
|
|
|
|
test('Endgame mode (optional flip)', async ({ page }) => {
|
|
test.setTimeout(90000); // 90 seconds
|
|
|
|
const bot = new GolfBot(page);
|
|
|
|
await bot.goto();
|
|
await bot.createGame('TestBot');
|
|
await bot.addCPU('Sofia');
|
|
|
|
// Start with Endgame mode
|
|
await bot.startGame({
|
|
holes: 1,
|
|
flipMode: 'endgame',
|
|
});
|
|
|
|
// Play through
|
|
const result = await bot.playRound(50);
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(bot.getConsoleErrors()).toHaveLength(0);
|
|
});
|
|
|
|
test('Multiple decks with many players', async ({ page }) => {
|
|
test.setTimeout(90000);
|
|
|
|
const bot = new GolfBot(page);
|
|
|
|
await bot.goto();
|
|
await bot.createGame('TestBot');
|
|
|
|
// Add 4 CPUs (5 total players)
|
|
await bot.addCPU('Sofia');
|
|
await bot.addCPU('Marcus');
|
|
await bot.addCPU('Maya');
|
|
await bot.addCPU('Kenji');
|
|
|
|
// Start with 2 decks
|
|
await bot.startGame({
|
|
holes: 1,
|
|
decks: 2,
|
|
});
|
|
|
|
// Play through
|
|
const result = await bot.playRound(100);
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(bot.getConsoleErrors()).toHaveLength(0);
|
|
});
|
|
});
|