Files
healer-man/scripts/playtest-dungeon-race.cjs
T
2026-09-26 18:57:44 -04:00

103 lines
6.4 KiB
JavaScript

const { chromium } = require('C:/Users/phenom/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const out = path.resolve('playtest-artifacts/dungeon-race');
fs.mkdirSync(out, { recursive: true });
const base = process.env.RACE_TEST_URL || 'http://127.0.0.1:5173';
(async () => {
const browser = await chromium.launch({ channel: 'msedge', headless: true });
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
const errors = [];
page.on('pageerror', (error) => errors.push(error.message));
try {
await page.goto(base, { waitUntil: 'domcontentloaded', timeout: 120_000 });
await page.getByRole('button', { name: /Play offline/ }).click();
await page.getByRole('button', { name: /Create character/ }).click();
await page.getByRole('button', { name: /Customize & name/ }).click();
await page.getByLabel('Character name').fill('RaceTester');
await page.getByRole('button', { name: /Create character/ }).click();
await page.getByRole('button', { name: /Continue|Enter world/ }).first().click({ force: true });
await page.getByRole('button', { name: 'Continue', exact: true }).click();
await page.locator('.main-menu-screen').waitFor();
await page.getByRole('button', { name: /Dungeon Race/ }).click();
await page.locator('.dungeon-race-screen').waitFor();
await page.screenshot({ path: path.join(out, '01-setup-desktop.png') });
assert.equal(await page.getByRole('button', { name: 'Offline practice' }).getAttribute('aria-pressed'), 'true');
await page.locator('.dungeon-race-screen__setup select').first()
.selectOption('bite-sized-stormwind-stockade');
await page.getByRole('button', { name: 'Start practice' }).click();
await page.locator('.game-shell').waitFor({ timeout: 120_000 });
await page.locator('.dungeon-race-hud').waitFor({ timeout: 120_000 });
await page.locator('.dungeon-boss-intro').waitFor({ state: 'visible', timeout: 120_000 });
await page.locator('.dungeon-boss-intro').waitFor({ state: 'hidden', timeout: 120_000 });
await page.locator('.dungeon-race-countdown').waitFor({ state: 'visible', timeout: 30_000 });
await page.locator('.dungeon-race-countdown').waitFor({ state: 'hidden', timeout: 30_000 });
await page.screenshot({ path: path.join(out, '02-active-desktop.png') });
const state = await page.evaluate(async () => {
const { useDungeonRaceStore } = await import('/src/app/dungeonRaceStore.ts');
const { useGameStore } = await import('/src/game/store.ts');
const race = useDungeonRaceStore.getState();
return { mode: race.mode, status: race.status, inGame: race.inGame,
dungeonId: race.dungeonId, activeDungeonId: useGameStore.getState().activeDungeonId,
opponentSeats: race.opponent?.type === 'bot' ? race.opponent.roster.length : 0 };
});
assert.equal(state.mode, 'practice');
assert.equal(state.status, 'active');
assert.equal(state.inGame, true);
assert.equal(state.opponentSeats, 5);
assert.equal(state.activeDungeonId, state.dungeonId);
await page.evaluate(async () => {
const { useDungeonRaceStore } = await import('/src/app/dungeonRaceStore.ts');
const race = useDungeonRaceStore.getState();
useDungeonRaceStore.setState({ startAt: Date.now() - race.opponent.finishMs - 1000, result: null });
useDungeonRaceStore.getState().tick();
});
await page.locator('.dungeon-race-toast--loss').waitFor();
assert.equal(await page.locator('.game-shell').count(), 1);
await page.screenshot({ path: path.join(out, '03-loss-continues-desktop.png') });
await page.setViewportSize({ width: 960, height: 540 });
await page.screenshot({ path: path.join(out, '04-loss-thor-size.png') });
const card = await page.evaluate(async () => {
const { useCombatStore } = await import('/src/game/combatStore.ts');
const { BOSS_CARD_CATALOG } = await import('/src/game/bossCardCatalog.ts');
const definition = BOSS_CARD_CATALOG.find((entry) => entry.dungeons.some((dungeon) => dungeon.id === 'stormwind-stockade'));
const mob = Object.values(useCombatStore.getState().mobs)[0];
if (!definition || !mob) throw new Error(`Boss-card fixture is unavailable: definition=${Boolean(definition)} mob=${Boolean(mob)}.`);
const before = useCombatStore.getState().bossCards.counts[definition.key]?.normal ?? 0;
useCombatStore.setState((state) => ({ mobs: { ...state.mobs,
[mob.id]: { ...mob, name: definition.name, boss: true, dead: true, hasLoot: true,
looted: false, despawned: false, pendingLoot: [],
bossCardDrop: { bossKey: definition.key, spawnId: mob.id,
claimId: `${Date.now()}:1`, rarities: ['normal'] } } } }));
const looted = useCombatStore.getState().lootMob(mob.id);
return { looted, before, after: useCombatStore.getState().bossCards.counts[definition.key]?.normal ?? 0 };
});
console.log('Post-loss boss-card result:', card);
assert.equal(card.after, card.before + 1);
await page.waitForTimeout(300);
await page.screenshot({ path: path.join(out, '05-card-after-loss-fixture.png') });
await page.keyboard.press('Escape');
await page.getByRole('dialog', { name: 'Paused' }).getByRole('button', { name: 'Main menu' }).click();
await page.locator('.main-menu-screen').waitFor();
const afterExit = await page.evaluate(async () => {
const { useDungeonRaceStore } = await import('/src/app/dungeonRaceStore.ts');
const race = useDungeonRaceStore.getState();
return { mode: race.mode, inGame: race.inGame, matchId: race.matchId };
});
assert.deepEqual(afterExit, { mode: null, inGame: false, matchId: null });
await page.getByRole('button', { name: /Dungeon Race/ }).click();
await page.getByRole('button', { name: 'Start practice' }).click();
await page.locator('.game-shell').waitFor({ timeout: 120_000 });
await page.locator('.dungeon-race-hud').waitFor({ timeout: 120_000 });
assert.deepEqual(errors, []);
console.log(JSON.stringify({ ok: true, state, card, afterExit, screenshots: out }, null, 2));
} catch (error) {
await page.screenshot({ path: path.join(out, 'failure.png') });
console.error((await page.locator('body').innerText()).slice(0, 5000));
throw error;
} finally { await browser.close(); }
})().catch((error) => { console.error(error); process.exitCode = 1; });