clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

test(browser): cover long authored title layouts

Dami committed 463653d
commit
463653dbaf7bfc8e1cc4e6d2bf125b85c9bf2f83
parent
7d21b78fc3aa506e5d899233cb47292a2439c75a
author
Dami <dami.etoile@gmail.com>
1 file changed, +127 -0
Atests/browser/long-title.mjs127++++++++++++++++++++++++++++++
A tests/browser/long-title.mjs +127 -0
@@ -0,0 +1,127 @@
1+#!/usr/bin/env node2+3+/** Long authored title acceptance for public INDEX, proof HUD, and Win card layout. */4+import assert from 'node:assert/strict';5+import { mkdtemp, rm, writeFile } from 'node:fs/promises';6+import { tmpdir } from 'node:os';7+import { resolve } from 'node:path';8+9+const configuredPlaywright = process.env.PLAYWRIGHT_MODULE || 'playwright';10+const playwrightModule = configuredPlaywright.startsWith('/')11+ ? `${configuredPlaywright.replace(/\/$/, '')}${configuredPlaywright.endsWith('.js') ? '' : '/index.js'}`12+ : configuredPlaywright;13+const imported = await import(playwrightModule);14+const { chromium } = imported.default || imported;15+const baseUrl = process.env.MIAU_URL || 'http://127.0.0.1:5199/';16+const screenshotDir = '/private/tmp/miau-stage-qa';17+const authoredTitle = 'A Very Long Authored Proof Title for Narrow Screens';18+const fixture = `module.exports = {19+ manifest: { id: 'long-title-qa', name: 'Long Title QA', version: '1.0.0', author: 'Browser QA' },20+ vocabulary: ['MIAU', 'FLAG'],21+ palette: { MIAU: '🐈', FLAG: '🚩' },22+ levels: [{23+ id: 'long-title-proof', title: ${JSON.stringify(authoredTitle)},24+ hint: 'One careful step reaches the flag.', width: 4, height: 3,25+ objects: [26+ { kind: 'word', text: 'MIAU', x: 0, y: 0 },27+ { kind: 'word', text: 'IS', x: 1, y: 0 },28+ { kind: 'word', text: 'YOU', x: 2, y: 0 },29+ { kind: 'word', text: 'FLAG', x: 0, y: 1 },30+ { kind: 'word', text: 'IS', x: 1, y: 1 },31+ { kind: 'word', text: 'WIN', x: 2, y: 1 },32+ { kind: 'object', noun: 'MIAU', x: 0, y: 2 },33+ { kind: 'object', noun: 'FLAG', x: 1, y: 2 }34+ ]35+ }]36+};`;37+38+const tempDir = await mkdtemp(`${tmpdir()}/miau-long-title-`);39+const fixturePath = resolve(tempDir, 'long-title-qa.cjs');40+await writeFile(fixturePath, fixture);41+const browser = await chromium.launch({ headless: true });42+const errors = [];43+44+async function waitScene(page, expected) {45+ await page.waitForFunction(value => document.querySelector('.stage-shell')?.dataset.scene === value, expected, { timeout: 10000 });46+}47+48+async function canvasMetrics(page) {49+ return page.evaluate(() => {50+ const canvas = document.querySelector('.stage-canvas')?.getBoundingClientRect();51+ return {52+ scene: document.querySelector('.stage-shell')?.dataset.scene,53+ scrollHeight: document.scrollingElement?.scrollHeight,54+ innerWidth,55+ innerHeight,56+ canvas: canvas && { x: canvas.x, y: canvas.y, width: canvas.width, height: canvas.height },57+ state: JSON.parse(window.render_game_to_text()),58+ };59+ });60+}61+62+async function assertViewport(page, label) {63+ const metrics = await canvasMetrics(page);64+ assert.equal(metrics.scrollHeight, metrics.innerHeight, `${label}: page must not scroll`);65+ assert.deepEqual(metrics.canvas, {66+ x: 0, y: 0, width: metrics.innerWidth, height: metrics.innerHeight,67+ }, `${label}: canvas must cover viewport`);68+ return metrics;69+}70+71+try {72+ const setupContext = await browser.newContext({ viewport: { width: 1280, height: 800 } });73+ const setup = await setupContext.newPage();74+ await setup.goto(`${baseUrl.replace(/\/$/, '')}/?dev=1`, { waitUntil: 'networkidle' });75+ await setup.keyboard.press('Enter');76+ await waitScene(setup, 'index');77+ await setup.locator('#mod-input').setInputFiles(fixturePath);78+ await waitScene(setup, 'gift');79+ await setup.keyboard.press('Enter');80+ await waitScene(setup, 'index');81+ await setup.locator('[data-pack-id="long-title-qa"]').click();82+ await setup.locator('#copy-share').click();83+ const shareUrl = await setup.locator('#share-url-input').inputValue();84+ assert.ok(shareUrl.includes('#pack='), 'setup should create a data-only share URL');85+ await setupContext.close();86+87+ for (const [name, viewport] of [88+ ['desktop', { width: 1280, height: 800 }],89+ ['mobile', { width: 390, height: 844 }],90+ ]) {91+ const context = await browser.newContext({ viewport, isMobile: name === 'mobile', hasTouch: name === 'mobile' });92+ const page = await context.newPage();93+ page.on('console', message => { if (message.type() === 'error') errors.push(`${name}: console: ${message.text()}`); });94+ page.on('pageerror', error => errors.push(`${name}: page: ${error.message}`));95+ await page.goto(shareUrl, { waitUntil: 'networkidle' });96+ await waitScene(page, 'gift');97+ await page.keyboard.press('Enter');98+ await waitScene(page, 'index');99+ await page.waitForTimeout(700);100+ let metrics = await assertViewport(page, `${name} INDEX`);101+ assert.equal(metrics.state.level, authoredTitle, `${name}: INDEX must retain authored title`);102+ await page.screenshot({ path: `${screenshotDir}/long-title-${name}-index.png` });103+104+ await page.keyboard.press('Enter');105+ await waitScene(page, 'proof');106+ await page.waitForTimeout(700);107+ metrics = await assertViewport(page, `${name} proof`);108+ assert.equal(metrics.state.level, authoredTitle, `${name}: proof must retain authored title`);109+ assert.equal(metrics.state.moves, 0, `${name}: proof starts at zero moves`);110+ await page.screenshot({ path: `${screenshotDir}/long-title-${name}-proof.png` });111+112+ await page.keyboard.press('ArrowRight');113+ await page.waitForFunction(() => JSON.parse(window.render_game_to_text()).mode === 'won', null, { timeout: 10000 });114+ await waitScene(page, 'win');115+ await page.waitForTimeout(1400);116+ metrics = await assertViewport(page, `${name} Win`);117+ assert.equal(metrics.state.mode, 'won', `${name}: Win card follows the one-step proof`);118+ await page.screenshot({ path: `${screenshotDir}/long-title-${name}-win.png` });119+ await context.close();120+ }121+122+ assert.deepEqual(errors, [], 'long-title flow should have no browser errors');123+ console.log(`✓ long authored title fits INDEX/proof/Win at desktop and mobile (${authoredTitle.length} chars)`);124+} finally {125+ await browser.close();126+ await rm(tempDir, { recursive: true, force: true });127+}