Files
wuxia-100-days-after/test/browser_flow.js
T
deepseek 3b1c7a72da WUXIA: 100 Days After — full game
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)
2026-08-23 07:01:52 +00:00

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); });