const { selectDefaultTestDungeon, openSelectedDungeonRoles } = require('./playtest-defaults.cjs'); 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('artifacts/playtest/healer-mana-recovery'); const runtimeData = fs.mkdtempSync(path.join(os.tmpdir(), 'healer-man-rom-status-')); 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(4198, '127.0.0.1', resolve)); const browser = await chromium.launch({ channel: 'msedge', headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); page.setDefaultTimeout(180_000); const errors = []; page.on('pageerror', (error) => errors.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) => { window.romStatusStores = async () => { const exports = (await Promise.all(urls.map((url) => import(url)))).flatMap(Object.values); const stores = exports.filter((value) => value && typeof value.getState === 'function'); return { game: stores.find((store) => typeof store.getState()?.relocatePlayer === 'function'), shell: stores.find((store) => typeof store.getState()?.saveActiveProgression === 'function'), combat: stores.find((store) => typeof store.getState()?.castAbility === 'function'), party: stores.find((store) => typeof store.getState()?.setCommand === 'function' && Array.isArray(store.getState()?.members)), }; }; }, moduleUrls); try { await page.goto('http://127.0.0.1:4198', { waitUntil: 'domcontentloaded' }); await page.getByRole('button', { name: /Play offline/ }).click(); await page.getByRole('button', { name: /Create character/ }).click(); await page.locator('.creator-category-tabs button').filter({ hasText: 'RoM' }).click(); await page.locator('.class-option').filter({ hasText: 'Priest' }).click(); await page.getByRole('button', { name: /Customize & name/ }).click(); await page.getByLabel('Character name').fill('ManaAuraQA'); await page.getByRole('button', { name: /Create character/ }).click(); await page.locator('.character-select-screen').waitFor(); await page.evaluate(async () => { const { shell } = await window.romStatusStores(); const state = shell.getState(); const character = state.characters.find((entry) => entry.id === state.selectedCharacterId); if (!character) throw new Error('Priest character was not selected.'); shell.setState({ activeCharacter: character }); shell.getState().saveActiveProgression(30, 0, character.talentRanks); shell.setState({ activeCharacter: null }); shell.getState().openMainMenu(); }); await page.locator('.main-menu-screen').waitFor(); await page.evaluate(async () => { const { shell } = await window.romStatusStores(); shell.getState().openDungeons(); }); await page.locator('.dungeon-select-screen').waitFor(); await selectDefaultTestDungeon(page); await openSelectedDungeonRoles(page); await page.locator('.dungeon-role-option--healer').click(); await page.locator('.party-frames').waitFor(); await page.locator('.loading-screen').waitFor({ state: 'detached' }); await page.evaluate(async () => { const { game } = await window.romStatusStores(); game.getState().closeOverlay(); }); await page.locator('.dungeon-boss-intro').waitFor({ state: 'detached' }); await page.waitForFunction(async () => { const { combat } = await window.romStatusStores(); return combat.getState().level >= 30 && Object.values(combat.getState().mobs) .some((mob) => !mob.dead && mob.homePosition); }); await page.keyboard.press('Escape'); await page.getByRole('button', { name: 'Boss guide', exact: true }).click(); await page.getByRole('heading', { name: 'Boss Guide', exact: true }).waitFor(); const bossGuide = await page.locator('.boss-guide-panel__bosses').innerText(); const selectedBoss = await page.locator('.boss-guide-panel__bosses button.is-active strong').innerText(); await page.screenshot({ path: path.join(directory, 'selected-boss.png') }); await page.keyboard.press('Escape'); await page.getByRole('button', { name: 'Resume game' }).click(); const metadata = await page.evaluate(async () => { const { combat, game, party } = await window.romStatusStores(); const state = combat.getState(); return { dungeonId: game.getState().activeDungeonId, boss: Object.values(state.mobs).filter((mob) => mob.boss).map((mob) => mob.name), classId: state.classId, level: state.level, mana: state.resourcePools.mana, maximum: state.maxResourcePools.mana, members: party.getState().members.map((member) => ({ name: member.name, role: member.role })), aura: state.auras.filter((aura) => aura.definition.id === 'healer-mana-recovery'), assets: performance.getEntriesByType('resource').filter((entry) => /assets\/.*\.js/.test(entry.name)).map((entry) => entry.name) }; }); metadata.boss = [selectedBoss]; assert.equal(metadata.dungeonId, 'bite-sized-stormwind-stockade'); assert(metadata.aura.length > 0, 'Recovery buff is visible out of combat'); await page.screenshot({ path: path.join(directory, 'buff-enabled.png') }); await page.keyboard.press('Escape'); await page.getByRole('button', { name: 'Options', exact: true }).click(); const row = page.locator('.option-row').filter({ hasText: 'Healer mana recovery' }); await row.scrollIntoViewIfNeeded(); assert(await row.getByRole('button', { name: 'On', exact: true }).count()); await page.screenshot({ path: path.join(directory, 'menu-enabled.png') }); await page.setViewportSize({ width: 960, height: 540 }); await row.scrollIntoViewIfNeeded(); await page.screenshot({ path: path.join(directory, 'menu-compact.png') }); await page.setViewportSize({ width: 1440, height: 900 }); await row.getByRole('button', { name: 'On', exact: true }).click(); await row.getByRole('button', { name: 'Off', exact: true }).waitFor(); const off = await page.evaluate(async () => { const { combat, shell } = await window.romStatusStores(); return { enabled: combat.getState().settings.healerManaRecovery, auraCount: combat.getState().auras.filter((aura) => aura.definition.id === 'healer-mana-recovery').length, saved: shell.getState().activeCharacter?.settings?.healerManaRecovery }; }); assert.equal(off.enabled, false); assert.equal(off.auraCount, 0); assert.equal(off.saved, false); await page.screenshot({ path: path.join(directory, 'menu-disabled.png') }); await row.getByRole('button', { name: 'Off', exact: true }).click(); await page.getByRole('button', { name: 'Return to pause menu' }).click(); await page.getByRole('button', { name: 'Resume game' }).click(); const labels = await page.locator('button.action-slot--friendly').evaluateAll((buttons) => buttons.map((button) => button.getAttribute('aria-label'))); let healButton = page.getByRole('button', { name: /^Urgent Heal,/ }).first(); if (!await healButton.count()) healButton = page.locator('button.action-slot--friendly').first(); const before = await page.evaluate(async () => { const { combat } = await window.romStatusStores(); return combat.getState().resource; }); await healButton.click(); const samples = []; for (let index = 0; index < 40; index++) { await page.waitForTimeout(100); samples.push(await page.evaluate(async () => { const { combat } = await window.romStatusStores(); const state = combat.getState(); return { now: Date.now(), resource: state.resource, max: state.maxResource, casting: state.activeCast?.abilityId, engaged: Object.values(state.mobs).some((mob) => mob.engaged && !mob.dead) }; })); } assert(samples.some((sample) => sample.resource < before), 'Ordinary healing cast spends mana'); const lowIndex = samples.findIndex((sample) => sample.resource === Math.min(...samples.map((entry) => entry.resource))); assert(samples.slice(lowIndex + 1).some((sample) => sample.resource > samples[lowIndex].resource), 'Mana recovers between fights'); await page.screenshot({ path: path.join(directory, 'after-heal.png') }); fs.writeFileSync(path.join(directory, 'result.json'), JSON.stringify({ metadata, bossGuide, off, labels, before, samples, errors }, null, 2)+'\n'); assert.deepEqual(errors, []); console.log(JSON.stringify({ dungeonId: metadata.dungeonId, boss: metadata.boss, off, before, minimum: Math.min(...samples.map((entry) => entry.resource)), after: samples.at(-1).resource, errors, directory }, null, 2)); } finally { await browser.close(); await new Promise((resolve) => runtime.server.close(resolve)); } })().catch((error) => { console.error(error); process.exitCode = 1; });