#!/usr/bin/env node /* Full real-browser flow test: title -> create -> play days via REAL clicks (true hit-testing), capturing console/404s/pageerrors throughout. */ const { chromium } = require('/app/node_modules/.pnpm/playwright@1.61.1/node_modules/playwright'); (async () => { const browser = await chromium.launch({ executablePath: '/root/.cache/ms-playwright/chromium-1234/chrome-linux64/chrome', args: ['--no-sandbox'] }); const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); const logs = []; page.on('console', m => { if (m.type() === 'error' || m.type() === 'warning') logs.push(`[${m.type()}] ${m.text()}`); }); page.on('pageerror', e => logs.push(`[PAGEERROR] ${e.message}`)); page.on('requestfailed', r => logs.push(`[REQFAIL] ${r.url()}`)); page.on('response', r => { if (r.status() >= 400) logs.push(`[HTTP ${r.status()}] ${r.url()}`); }); await page.goto(process.argv[2] || 'http://127.0.0.1:8914/wuxia.html', { waitUntil: 'load' }); await page.waitForTimeout(1000); const step = async (name, fn) => { try { await fn(); console.log('✓', name); } catch (e) { console.log('✗', name, '—', e.message.split('\n')[0]); } await page.waitForTimeout(150); }; // title -> create await step('click New Journey', () => page.click('.title-menu .btn:has-text("New Journey")')); await step('pick background', () => page.click('.bg-card:has-text("Old Soldier")')); await step('fill names', async () => { await page.fill('.inp-name', 'Cloud Tester'); await page.fill('.inp-sect', 'Sky Ridge Sect'); }); await step('pick difficulty', () => page.click('.diff-list .diff-card:has-text("Wanderer")')); await step('Set Out', () => page.click('.create-actions .btn:has-text("Set Out")')); // dismiss intro notice await step('dismiss intro', async () => { const b = page.locator('.modal .btn', { hasText: 'Continue' }).first(); if (await b.count()) await b.click(); else throw new Error('no intro modal'); }); // canvas should be clickable during play (drag pan) await step('canvas drag-pan works', async () => { const cv = page.locator('#cv'); const box = await cv.boundingBox(); await page.mouse.move(box.x + 600, box.y + 400); await page.mouse.down(); await page.mouse.move(box.x + 500, box.y + 330, { steps: 4 }); await page.mouse.up(); }); // do actions via the action bar buttons for (const act of ['Train', 'Meditate']) { await step(`action bar: ${act}`, () => page.click(`.action-bar .btn:has-text("${act}")`)); } // open each panel by nav button and close for (const p of ["Map", "Sect", "Disciples", "Arts", "Factions", "Journal", "☰"]) { await step(`panel ${p}`, async () => { await page.click(`#navbtns .btn:has-text("${p}")`); await page.waitForSelector('.panel-layer:not(.hidden)', { timeout: 2000 }); await page.click('.panel-head .closebtn'); }); } // end several days through the real button for (let i = 0; i < 4; i++) { await step(`end day ${i + 1}`, async () => { // resolve any modal (buttons OR event choices) first for (let k = 0; k < 5; k++) { const anyBtn = page.locator('.modal .btn:visible, .modal .choice:visible:not(.disabled)').first(); if (await anyBtn.count()) { await anyBtn.click().catch(() => {}); await page.waitForTimeout(150); } else break; } await page.click('.action-bar .btn:has-text("End Day")'); await page.waitForTimeout(350); for (let k = 0; k < 5; k++) { const cont = page.locator('.modal .btn:visible, .modal .choice:visible:not(.disabled)').first(); if (await cont.count()) { await cont.click().catch(() => {}); await page.waitForTimeout(150); } else break; } }); } const state = await page.evaluate(() => window.W && window.W.state ? { day: window.W.state.day, ap: window.W.state.ap } : null); console.log('state after clicks:', JSON.stringify(state)); await page.screenshot({ path: '/tmp/gameplay_after_fix.png' }); console.log('\nErrors seen:'); const interesting = logs.filter(l => !l.includes('favicon.ico')); interesting.length ? interesting.forEach(l => console.log(' ', l)) : console.log(' (none besides favicon)'); await browser.close(); })().catch(e => { console.error('FLOW ERROR:', e.message); process.exit(1); });