LifeTown Online — browser multiplayer life-sim MVP
- Server-authoritative Node/WS server: sessions, zones (town + instanced homes + public venue interiors), NPC routines, economy, relationships, persistence - TypeScript + Three.js client: character creator, Maple Court district, enterable venues (café, city hall, gym, store, arcade), build mode with 58-item furniture catalog, needs/mood systems, phone UI, day/night + weather, procedural audio - Verified by two-client e2e protocol suite and headless-browser walkthrough
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// LifeTown Online - headless verification: one short browser session per screen
|
||||
import { chromium } from 'playwright-core';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const EXE = '/root/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell';
|
||||
const URL = 'http://127.0.0.1:7788';
|
||||
const OUT = '/tmp/lifetown-shots';
|
||||
fs.mkdirSync(OUT, { recursive: true });
|
||||
const ARGS = ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu-sandbox'];
|
||||
|
||||
const allErrors = [];
|
||||
|
||||
async function withPage(fn, label) {
|
||||
let browser;
|
||||
try {
|
||||
browser = await chromium.launch({ executablePath: EXE, args: ARGS, timeout: 30000 });
|
||||
const page = await browser.newPage({ viewport: { width: 960, height: 600 } });
|
||||
page.on('pageerror', (e) => allErrors.push(`[${label}] ${String(e).slice(0, 250)}`));
|
||||
page.on('console', (m) => { if (m.type() === 'error') allErrors.push(`[${label}] CONSOLE ${m.text().slice(0, 200)}`); });
|
||||
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 20000 });
|
||||
await fn(page);
|
||||
console.log(`[shot] OK ${label}`);
|
||||
} catch (e) {
|
||||
console.log(`[shot] FAIL ${label}: ${String(e).slice(0, 140)}`);
|
||||
} finally {
|
||||
try { await browser?.close(); } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
async function enterGame(page) {
|
||||
const uname = 'Sunny' + Math.floor(Math.random() * 900000 + 100000);
|
||||
await page.waitForTimeout(700);
|
||||
await page.click('#btn-newchar', { force: true });
|
||||
await page.fill('#cr-name', uname);
|
||||
await page.click('#btn-startlife', { force: true });
|
||||
await page.waitForTimeout(4200);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await withPage(async (page) => {
|
||||
await page.waitForTimeout(1100);
|
||||
await page.screenshot({ path: `${OUT}/1-title.png` });
|
||||
}, 'title');
|
||||
|
||||
await withPage(async (page) => {
|
||||
await page.waitForTimeout(700);
|
||||
await page.click('#btn-newchar', { force: true });
|
||||
await page.fill('#cr-name', 'Sunny' + Math.floor(Math.random() * 900000 + 100000));
|
||||
await page.waitForTimeout(1200);
|
||||
await page.screenshot({ path: `${OUT}/2-creator.png` });
|
||||
}, 'creator');
|
||||
|
||||
await withPage(async (page) => {
|
||||
await enterGame(page);
|
||||
await page.screenshot({ path: `${OUT}/3-town.png` });
|
||||
await page.keyboard.down('KeyW');
|
||||
await page.waitForTimeout(1200);
|
||||
await page.keyboard.up('KeyW');
|
||||
await page.waitForTimeout(400);
|
||||
await page.screenshot({ path: `${OUT}/4-walked.png` });
|
||||
}, 'town');
|
||||
|
||||
await withPage(async (page) => {
|
||||
await enterGame(page);
|
||||
await page.click('[data-act="phone"]', { force: true });
|
||||
await page.waitForTimeout(900);
|
||||
await page.screenshot({ path: `${OUT}/5-phone.png` });
|
||||
}, 'phone');
|
||||
|
||||
await withPage(async (page) => {
|
||||
await enterGame(page);
|
||||
await page.click('[data-act="home"]', { force: true });
|
||||
await page.waitForTimeout(600);
|
||||
await page.screenshot({ path: `${OUT}/6-homemenu.png` });
|
||||
const btns = await page.$$('#dialog-body button');
|
||||
if (btns[1]) await btns[1].click({ force: true });
|
||||
await page.waitForTimeout(3200);
|
||||
await page.screenshot({ path: `${OUT}/7-home.png` });
|
||||
}, 'home');
|
||||
|
||||
await withPage(async (page) => {
|
||||
await enterGame(page);
|
||||
await page.click('[data-act="home"]', { force: true });
|
||||
await page.waitForTimeout(500);
|
||||
const btns = await page.$$('#dialog-body button');
|
||||
if (btns[1]) await btns[1].click({ force: true });
|
||||
await page.waitForTimeout(3000);
|
||||
await page.click('[data-act="build"]', { force: true });
|
||||
await page.waitForTimeout(700);
|
||||
await page.click('#btn-shop', { force: true });
|
||||
await page.waitForTimeout(900);
|
||||
await page.screenshot({ path: `${OUT}/8-catalog.png` });
|
||||
await page.click('#catalog-close', { force: true });
|
||||
await page.waitForTimeout(400);
|
||||
await page.screenshot({ path: `${OUT}/9-buildmode.png` });
|
||||
}, 'build');
|
||||
|
||||
console.log(allErrors.length ? `PAGE ERRORS (${allErrors.length}):\n` + allErrors.slice(0, 10).join('\n') : 'NO PAGE ERRORS');
|
||||
})();
|
||||
Reference in New Issue
Block a user