const { chromium } = require('C:/Users/phenom/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const directory = path.resolve('playtest-artifacts', 'party-heal-popups'); const runtimeData = fs.mkdtempSync(path.join(os.tmpdir(), 'healer-man-party-heals-')); fs.mkdirSync(directory, { recursive: true }); (async () => { const { createGameServer } = await import('../server/server.mjs'); const runtime = createGameServer({ dataDir: runtimeData, enableShelvedModes: false }); await new Promise((resolve) => runtime.server.listen(4194, '127.0.0.1', resolve)); const browser = await chromium.launch({ channel: 'msedge', headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); page.setDefaultTimeout(180_000); const pageErrors = []; page.on('pageerror', (error) => pageErrors.push(error.message)); const moduleUrls = fs.readdirSync('dist/assets') .filter((file) => /^(?:index|GameRuntime|combatStore)-.*\.js$/.test(file)) .map((file) => `/assets/${file}`); await page.addInitScript((urls) => { const stores = async () => { const values = (await Promise.all(urls.map((url) => import(url)))).flatMap(Object.values); const candidates = values.filter((value) => value && typeof value.getState === 'function'); const party = candidates.find((store) => Array.isArray(store.getState()?.members) && typeof store.getState()?.initializeParty === 'function'); const combat = candidates.find((store) => Array.isArray(store.getState()?.combatEvents) && typeof store.getState()?.healActor === 'function'); const shell = candidates.find((store) => Array.isArray(store.getState()?.characters) && typeof store.getState()?.openMainMenu === 'function'); if (!party || !combat || !shell) throw new Error('Party/combat/shell stores were not found.'); return { party, combat, shell }; }; window.openMainMenuForPartyHealPlaytest = async () => { const { shell } = await stores(); shell.getState().openMainMenu(); }; window.enterDungeonForPartyHealPlaytest = async () => { const { shell } = await stores(); const ok = shell.getState().enterDungeon('wailing-caverns'); return { ok, notice: shell.getState().notice, phase: shell.getState().phase }; }; window.openDungeonsForPartyHealPlaytest = async () => { const { shell } = await stores(); shell.getState().openDungeons(); }; window.triggerPartyHealPopup = async () => { const { party, combat } = await stores(); const member = party.getState().members[0]; if (!member) throw new Error('No NPC party member is available.'); const injuredHealth = Math.max(1, member.maxHealth - 300); party.setState({ members: party.getState().members.map((candidate) => candidate.id === member.id ? { ...candidate, health: injuredHealth } : candidate), }); const actor = combat.getState().actors[member.id]; if (!actor) throw new Error('The NPC party actor is unavailable.'); combat.setState({ actors: { ...combat.getState().actors, [member.id]: { ...actor, health: injuredHealth, alive: true }, } }); const mob = Object.values(combat.getState().mobs).find((candidate) => !candidate.dead && !candidate.despawned); if (!mob) throw new Error('No living dungeon enemy is available.'); combat.setState({ mobs: { ...combat.getState().mobs, [mob.id]: { ...mob, engaged: true } } }); const result = combat.getState().healActor(member.id, 137, { sourceActorId: '__player__', abilityId: 'party-frame-playtest-heal', now: Date.now(), amountAlreadyModified: true, }); return { memberId: member.id, amount: result.effectiveAmount }; }; }, moduleUrls); try { await page.goto('http://127.0.0.1:4194', { waitUntil: 'domcontentloaded' }); 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('HealpopupQA'); await page.getByRole('button', { name: /Create character/ }).click(); await page.evaluate(() => window.openMainMenuForPartyHealPlaytest()); await page.locator('.main-menu-screen').waitFor(); await page.evaluate(() => window.openDungeonsForPartyHealPlaytest()); await page.locator('.dungeon-select-screen').waitFor(); const entry = await page.evaluate(() => window.enterDungeonForPartyHealPlaytest()); assert.equal(entry.ok, true, JSON.stringify(entry)); await page.locator('.party-frames').waitFor(); await page.locator('.party-frame').nth(4).waitFor(); await page.locator('.loading-screen').waitFor({ state: 'detached' }); const result = await page.evaluate(() => window.triggerPartyHealPopup()); assert.equal(result.amount, 137); const popup = page.locator('.party-frame__heal-popup', { hasText: '+137' }); await popup.waitFor({ state: 'visible' }); const targetFrame = page.locator('.party-frame').filter({ has: popup }); assert.equal(await targetFrame.count(), 1, 'heal number belongs to exactly one receiving frame'); assert.equal(await page.locator('.desktop-action-feedback').count(), 0, 'center action feedback is absent'); assert.equal((await page.locator('.action-bar__feedback').allInnerTexts()).join('').trim(), '', 'controller bar has no combat log text'); await page.screenshot({ path: path.join(directory, 'party-heal-popup.png') }); assert.deepEqual(pageErrors, []); console.log(`Party-frame heal popup playtest passed for ${result.memberId}.`); } finally { await browser.close(); await new Promise((resolve) => runtime.server.close(resolve)); fs.rmSync(runtimeData, { recursive: true, force: true }); } })().catch((error) => { console.error(error); process.exitCode = 1; });