Files
100-days-after/scripts/smoke.mjs
T
deepseek 486201655b 100 Days After — complete playable survival strategy game
- Seeded procedural runs (survivors, locations, weather, events)
- 100-day campaign with 5 phases, scripted story beats, Day-100 finale
- 8 endings incl. hidden 'First Light Again'
- Data-driven events (people/world/story packs) with callback chains
- Relationships, memories, grief, karma/hope systems
- Tactical encounters (fight/hide/run/negotiate) with honest odds
- 8 upgradeable camp buildings, 13 location templates
- Canvas parallax scenes, procedural SVG portraits, weather FX
- Synthesized adaptive audio (WebAudio, no assets)
- Save slots + autosave (localStorage), shareable seeds
- Tests: headless multi-seed simulator + browser E2E (playwright)
2026-08-23 06:58:47 +00:00

137 lines
5.6 KiB
JavaScript

/* Browser smoke test: boots the game, starts a run, plays a few days
* through the real UI, and fails on any console error or page crash.
*
* node scripts/smoke.mjs [url]
*/
import { chromium } from 'playwright-core';
const url = process.argv[2] ?? 'http://127.0.0.1:5199';
const exe = '/root/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell';
const browser = await chromium.launch({ executablePath: exe, args: ['--no-sandbox','--disable-gpu','--disable-dev-shm-usage'] });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
page.setDefaultTimeout(9000);
const errors = [];
page.on('console', msg => {
if (msg.type() === 'error') errors.push('console: ' + msg.text());
});
page.on('pageerror', err => errors.push('pageerror: ' + err.message));
const step = async (name, fn) => {
try { await fn(); console.log('ok ', name); }
catch (e) { console.log('FAIL', name, '-', e.message.split('\n')[0]); errors.push(name + ': ' + e.message); }
};
await step('load page', async () => {
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.title-screen', { timeout: 10000 });
});
await step('new run dialog', async () => {
await page.click('text=NEW RUN');
await page.waitForSelector('.seed-input');
});
await step('start run', async () => {
await page.fill('.seed-input', '777001');
await page.click('text=BEGIN DAY 1');
await page.waitForSelector('.game-layout', { timeout: 10000 });
});
await step('HUD renders', async () => {
await page.waitForFunction(() => document.querySelector('#hud-day')?.textContent?.includes('DAY'));
const resCount = await page.locator('.res-chip').count();
if (resCount !== 7) throw new Error('expected 7 resource chips, got ' + resCount);
});
await step('survivor list renders', async () => {
const n = await page.locator('.surv-card:not(.dead)').count();
if (n < 3 || n > 4) throw new Error('unexpected survivor count ' + n);
await page.locator('.surv-card:not(.dead)').first().click();
await page.waitForSelector('.sd-body');
await page.click('.modal-x');
});
await step('map tab renders', async () => {
await page.click('[data-tab="map"]');
const entries = await page.locator('.map-entry').count();
if (entries < 2) throw new Error('map too empty');
// hover preview shouldn't crash
await page.locator('.map-entry').nth(1).hover();
await page.click('[data-tab="log"]');
});
await step('scavenge action flow', async () => {
await page.click('.action:has-text("SCAVENGE")');
await page.waitForSelector('.site-grid .site-card');
await page.locator('.site-card').first().click();
const rows = page.locator('.party-check .check-row');
await rows.first().locator('input').check();
if (await rows.count() > 1) await rows.nth(1).locator('input').check();
await page.click('text=HEAD OUT');
await page.waitForSelector('.report, .enc-modal', { timeout: 8000 });
// resolve possible encounter
for (let i = 0; i < 6; i++) {
const enc = page.locator('.enc-modal .btn.choice');
if (await enc.count()) { await enc.first().click(); await page.waitForTimeout(300); continue; }
const cont = page.locator('.modal-card .btn:has-text("CONTINUE")');
if (await cont.count()) { await cont.first().click(); break; }
break;
}
await page.waitForTimeout(400);
});
// play several days via END DAY until an event modal appears and handle choices
for (let d = 0; d < 8; d++) {
await step(`day cycle ${d + 1}`, async () => {
// close any open report/event by clicking CONTINUE / first choice repeatedly
for (let i = 0; i < 10; i++) {
const choice = page.locator('.ev-choice:not(.locked)');
const cont = page.locator('.modal-card .btn:has-text("CONTINUE")');
const encBtns = page.locator('.enc-modal .btn.choice');
if (await encBtns.count()) { await encBtns.first().click(); await page.waitForTimeout(250); continue; }
if (await choice.count()) { await choice.first().click(); await page.waitForTimeout(250); continue; }
if (await cont.count()) { await cont.first().click(); await page.waitForTimeout(250); continue; }
break;
}
const endBtn = page.locator('#btn-endday');
if (await endBtn.isEnabled()) await endBtn.click();
await page.waitForTimeout(500);
for (let i = 0; i < 12; i++) {
const cont = page.locator('.modal-card .btn:has-text("CONTINUE")');
const encBtns = page.locator('.enc-modal .btn.choice');
const evChoice = page.locator('.ev-choice:not(.locked)');
if (await encBtns.count()) { await encBtns.first().click(); await page.waitForTimeout(250); continue; }
if (await cont.count()) { await cont.first().click(); await page.waitForTimeout(350); continue; }
if (await evChoice.count()) { await evChoice.first().click(); await page.waitForTimeout(250); continue; }
break;
}
});
}
await step('save to slot', async () => {
await page.click('#btn-menu');
await page.click('text=SAVE SLOT 1');
await page.waitForTimeout(300);
});
await step('reload + continue via load modal', async () => {
await page.reload({ waitUntil: 'domcontentloaded' });
await page.waitForSelector('.title-screen');
await page.click('text=LOAD RUN');
await page.waitForSelector('.save-row');
await page.locator('.save-row .btn:has-text("LOAD")').first().click();
await page.waitForSelector('.game-layout', { timeout: 8000 });
});
console.log('\n---- RESULT ----');
if (errors.length) {
console.log(errors.length + ' ERRORS:');
for (const e of errors.slice(0, 20)) console.log(' -', e);
process.exitCode = 1;
} else {
console.log('SMOKE TEST PASSED — no console/page errors across boot, play, save, load.');
}
await browser.close();