Single-file ink-painting wuxia sect-survival RPG. Lead a sect through 100 days: martial arts combos, cultivation, tactical combat, faction war, chronicle endings. - src/ 13 modules (data, sim, combat, render, UI, app shell) - wuxia.html self-contained build (no dependencies) - tools/build.js bundler - tests: headless 100-day sims + jsdom UI smoke + real-browser Chromium click-through (playwright)
90 lines
4.3 KiB
JavaScript
90 lines
4.3 KiB
JavaScript
#!/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); });
|