- Unit.js: add health/armor to entity data
- CombatSystem.js: integration hooks for M2 combat loop
- EntityStateMachine.js: orchestrator.registerEntity refactor
- playwright.config.js: M2 e2e config
- tests/e2e/combat-loop.{spec,e2e}.js: M2 combat loop verification
- tests/e2e/milestone-2-combat-loop.spec.js: M2 acceptance test
- .gitignore: exclude debug scripts + screenshots from commits
103 lines
3.9 KiB
JavaScript
103 lines
3.9 KiB
JavaScript
import { test, expect } from '@playwright/test';
|
|
|
|
test('M2 combat loop — spawn, auto-engage, projectiles, death', async ({ page }) => {
|
|
const url = process.env.RESTITUTION_URL || 'https://restitution.damascusfront.net';
|
|
|
|
// ── 1. Boot ──
|
|
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Lobby: click CREATE GAME → wait for code → click Start Game
|
|
await page.click('button:has-text("CREATE GAME")');
|
|
await page.waitForTimeout(3000);
|
|
await page.waitForSelector('button:has-text("Start Game")', { timeout: 10000 });
|
|
await page.click('button:has-text("Start Game")');
|
|
|
|
// Wait for Phaser canvas
|
|
await page.waitForSelector('canvas', { timeout: 15000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
// ── 2. Spawn player unit (F) ──
|
|
await page.keyboard.press('KeyF');
|
|
await page.waitForTimeout(800);
|
|
|
|
// ── 3. Spawn enemy via evaluate near player ──
|
|
const enemySpawned = await page.evaluate(() => {
|
|
const scene = window.game?.scene?.getScene('Map_Player');
|
|
if (!scene) return { ok: false, reason: 'no_scene' };
|
|
|
|
// Get player position (team-A unit closest to camera center)
|
|
const tm = scene.teamManager;
|
|
const playerUnits = tm?.getTeamUnits('team-A');
|
|
const playerUnit = playerUnits ? Array.from(playerUnits)[0] : null;
|
|
if (!playerUnit) return { ok: false, reason: 'no_player_unit' };
|
|
|
|
// Spawn enemy infantry on team-B, 5 tiles east of player
|
|
const tileX = Math.round(playerUnit.x / 32) + 5;
|
|
const tileY = Math.round(playerUnit.y / 32);
|
|
const enemy = scene.unitFactory?.spawnInfantry({ x: tileX, y: tileY }, 'team-B');
|
|
return { ok: !!enemy, enemyId: enemy?.name || null, tileX, tileY };
|
|
});
|
|
expect(enemySpawned.ok, `Enemy spawn failed: ${enemySpawned.reason}`).toBe(true);
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
// ── 4. Screenshot: units spawned ──
|
|
await page.screenshot({ path: '/root/restitution/e2e-screenshots/01_spawned.png', fullPage: false });
|
|
|
|
// ── 5. Verify both sides exist via TeamManager ──
|
|
const bothSides = await page.evaluate(() => {
|
|
const tm = window.game?.scene?.getScene('Map_Player')?.teamManager;
|
|
if (!tm) return false;
|
|
const all = tm.getAllUnitsGrouped();
|
|
let hasA = false, hasB = false;
|
|
for (const [teamId, set] of all) {
|
|
if (set.size > 0) {
|
|
if (teamId === 'team-A') hasA = true;
|
|
if (teamId === 'team-B') hasB = true;
|
|
}
|
|
}
|
|
return hasA && hasB;
|
|
});
|
|
expect(bothSides).toBe(true);
|
|
|
|
// ── 6. Wait for combat to tick, verify projectiles ──
|
|
await page.waitForTimeout(4000);
|
|
|
|
const projectilesExist = await page.evaluate(() => {
|
|
const combat = window.game?.scene?.getScene('Map_Player')?.orchestrator?.systems?.combat;
|
|
if (!combat) return false;
|
|
const count = combat.projectiles?.countActive?.() ?? 0;
|
|
return count > 0;
|
|
});
|
|
expect(projectilesExist).toBe(true);
|
|
|
|
// ── 7. Screenshot: mid-combat ──
|
|
await page.screenshot({ path: '/root/restitution/e2e-screenshots/02_mid_combat.png', fullPage: false });
|
|
|
|
// ── 8. Let combat run, wait for death ──
|
|
await page.waitForTimeout(10000);
|
|
|
|
const deathCheck = await page.evaluate(() => {
|
|
const tm = window.game?.scene?.getScene('Map_Player')?.teamManager;
|
|
if (!tm) return { ok: false, reason: 'no_tm' };
|
|
|
|
const all = tm.getAllUnitsGrouped();
|
|
let alive = 0, dead = 0;
|
|
const aliveNames = [], deadNames = [];
|
|
for (const [, set] of all) {
|
|
for (const u of set) {
|
|
const isDead = u.dead || (typeof u.isDead === 'function' && u.isDead());
|
|
if (isDead) { dead++; deadNames.push(u.name || 'unknown'); }
|
|
else { alive++; aliveNames.push(u.name || 'unknown'); }
|
|
}
|
|
}
|
|
return { alive, dead, aliveNames, deadNames };
|
|
});
|
|
|
|
expect(deathCheck.dead).toBeGreaterThanOrEqual(1);
|
|
|
|
// ── 9. Screenshot: post-death ──
|
|
await page.screenshot({ path: '/root/restitution/e2e-screenshots/03_post_death.png', fullPage: false });
|
|
});
|