← back to Games Agentabrams
tests/new-games-2.mjs
85 lines
// Headless smoke test for the 2026-07-24 batch (pong, emoji-memory, spirograph,
// constellation-clock). Loads each standalone in a real Chromium page, drives a
// few inputs, and asserts it renders and throws no console/page errors.
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
import path from 'path';
import http from 'http';
import fs from 'fs';
const require = createRequire(import.meta.url);
let chromium;
try {
({ chromium } = require('/Users/macstudio3/.npm-global/lib/node_modules/playwright'));
} catch {
({ chromium } = require('playwright'));
}
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const MIME = { '.html': 'text/html', '.json': 'application/json', '.png': 'image/png', '.js': 'text/javascript' };
const server = http.createServer((req, res) => {
let p = decodeURIComponent(req.url.split('?')[0].split('#')[0]);
if (p.endsWith('/')) p += 'index.html';
const f = path.join(root, p);
if (!f.startsWith(root) || !fs.existsSync(f) || fs.statSync(f).isDirectory()) { res.writeHead(404); res.end('nf'); return; }
res.writeHead(200, { 'Content-Type': MIME[path.extname(f)] || 'text/plain' });
fs.createReadStream(f).pipe(res);
});
await new Promise(r => server.listen(0, '127.0.0.1', r));
const base = `http://127.0.0.1:${server.address().port}`;
const browser = await chromium.launch();
const results = [];
const check = (name, ok) => { results.push({ name, ok }); console.log((ok ? 'PASS' : 'FAIL') + ' ' + name); };
async function run(id, urlPath, drive, assertFn) {
const page = await browser.newPage({ viewport: { width: 700, height: 760 } });
const errors = [];
page.on('pageerror', e => errors.push('pageerror: ' + e.message));
page.on('console', m => { if (m.type() === 'error') errors.push('console: ' + m.text()); });
await page.goto(base + urlPath);
await drive(page);
await page.waitForTimeout(300);
const extraOk = await assertFn(page);
check(`${id}: renders + interactive`, extraOk);
check(`${id}: no console/page errors`, errors.length === 0);
if (errors.length) console.log(' ' + errors.join('\n '));
await page.close();
}
// --- pong: serve, move both paddles, confirm canvas + overlay gone ---
await run('pong', '/games/pong/', async (page) => {
await page.keyboard.press('Space'); // serve
for (const k of ['w', 's', 'ArrowUp', 'ArrowDown']) { await page.keyboard.down(k); await page.waitForTimeout(120); await page.keyboard.up(k); }
}, async (page) =>
(await page.locator('canvas#c').count()) === 1 &&
await page.locator('#msg').evaluate(el => el.classList.contains('hidden'))
);
// --- emoji-memory: flip two cards, confirm board built + moves increments ---
await run('emoji-memory', '/games/emoji-memory/', async (page) => {
const cards = page.locator('.card');
await cards.nth(0).click(); await page.waitForTimeout(120);
await cards.nth(1).click(); await page.waitForTimeout(200);
}, async (page) => {
const n = await page.locator('.card').count(); // 4x4 default = 16
const moves = parseInt(await page.locator('#moves').textContent(), 10);
return n === 16 && moves >= 1;
});
// --- spirograph: generative toy — just confirm it renders a canvas cleanly ---
await run('spirograph', '/games/spirograph/', async (page) => {
await page.waitForTimeout(400);
}, async (page) => (await page.locator('canvas').count()) >= 1);
// --- constellation-clock: ambient visualizer — canvas renders, no errors ---
await run('constellation-clock', '/games/constellation-clock/', async (page) => {
await page.waitForTimeout(400);
}, async (page) => (await page.locator('canvas').count()) >= 1);
await browser.close();
server.close();
const failed = results.filter(r => !r.ok);
console.log(`\n${results.length - failed.length}/${results.length} passed`);
process.exit(failed.length ? 1 : 0);