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)
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
#!/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); });
|
||||
Reference in New Issue
Block a user