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/rom-status-effects'); 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(4195, '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:4195', { 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('RomPriestStatusQA'); 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 page.locator('.dungeon-select-row').first().waitFor(); const entry = await page.evaluate(async () => { const { shell } = await window.romStatusStores(); const ok = shell.getState().enterDungeon('wailing-caverns'); return { ok, notice: shell.getState().notice }; }); assert.equal(entry.ok, true, JSON.stringify(entry)); 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); }); const setup = await page.evaluate(async () => { const { game, combat, party } = await window.romStatusStores(); party?.getState().setCommand('stop'); const mob = Object.values(combat.getState().mobs) .find((candidate) => !candidate.dead && candidate.homePosition); if (!mob) throw new Error('No living dungeon mob is available.'); const [x, y, z] = mob.homePosition; game.getState().relocatePlayer({ footPosition: [x + 3, y, z], yaw: -Math.PI / 2 }); combat.getState().setPlayerPosition(game.getState().playerPosition); combat.setState((state) => ({ mobs: { ...state.mobs, [mob.id]: { ...state.mobs[mob.id], health: 100_000, maxHealth: 100_000, } } })); combat.getState().selectMob(mob.id); const now = Date.now(); combat.setState((state) => ({ effects: [ ...state.effects, ...[1, 2, 3].map((index) => ({ id: `rom-status-poison-${index}`, kind: 'dot', sourceAbilityId: 'rom-priest-primary-4', sourceActorId: `qa-source-${index}`, targetId: mob.id, coefficient: 0, remainingTicks: 6, intervalMs: 2_000, nextTickAt: now + 10_000, })), ] })); return { mobId: mob.id }; }); await page.waitForTimeout(500); const castState = await page.evaluate(async () => { const { combat } = await window.romStatusStores(); const cast = combat.getState().castAbility('rom-priest-primary-9', undefined, Date.now()); return { cast, activeCast: combat.getState().activeCast }; }); assert.equal(castState.cast.ok, true, JSON.stringify({ ...setup, ...castState })); assert.equal(castState.activeCast.mode, 'channel'); await page.locator('.timed-effect-strip--target .timed-effect').first().waitFor(); assert.equal(await page.locator('.timed-effect-strip--target .timed-effect').count(), 1); assert.equal(await page.locator('.timed-effect-strip--target .timed-effect__stacks').innerText(), '3'); await page.screenshot({ path: path.join(directory, 'chain-of-light-and-stacked-dot.png') }); assert.deepEqual(errors, []); console.log(`RoM channel and stacked DoT playtest passed: ${directory}`); } finally { await browser.close(); await new Promise((resolve) => runtime.server.close(resolve)); const tempRoot = fs.realpathSync(os.tmpdir()); const resolvedData = fs.realpathSync(runtimeData); if (path.dirname(resolvedData) === tempRoot && path.basename(resolvedData).startsWith('healer-man-rom-status-')) { fs.rmSync(resolvedData, { recursive: true, force: true }); } } })().catch((error) => { console.error(error); process.exitCode = 1; });