Zum Inhalt

viewer/tests/e2e/walking-smoke.test.mjs

// End-to-end smoke for walking-mode: drives the page with puppeteer-core
// + the system Chrome. We focus on DOM lifecycle (CTA → HUD → exit)
// which doesn't need WebGL2 — visual fidelity of the rendered splat is
// verified manually with ./serve.sh on a real browser.

import { test, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { setTimeout as wait } from 'node:timers/promises';
import puppeteer from 'puppeteer-core';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(HERE, '..', '..');
const CHROME = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const PORT = 8127;
const URL_BASE = `http://localhost:${PORT}`;

let server = null;
let browser = null;

before(async () => {
  server = spawn('python3', ['-m', 'http.server', String(PORT)], {
    cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'],
  });
  // wait until the port is responsive
  for (let i = 0; i < 30; i++) {
    try {
      const r = await fetch(URL_BASE + '/');
      if (r.ok) break;
    } catch { /* still booting */ }
    await wait(100);
  }
  browser = await puppeteer.launch({
    executablePath: CHROME,
    headless: 'new',
    args: ['--no-sandbox', '--disable-features=Translate', '--hide-scrollbars'],
  });
});

after(async () => {
  if (browser) await browser.close();
  if (server) { server.kill(); await wait(200); }
});

test('viewer page: loads, exposes walking DOM elements (all hidden initially)', async () => {
  const page = await browser.newPage();
  await page.goto(URL_BASE + '/', { waitUntil: 'domcontentloaded' });
  const present = await page.evaluate(() => ({
    cta: !!document.getElementById('cta-walk'),
    hud: !!document.getElementById('walk-hud'),
    crosshair: !!document.getElementById('walk-crosshair'),
    exit: !!document.getElementById('walk-exit'),
    mode: !!document.getElementById('walk-mode-indicator'),
    hint: !!document.getElementById('walk-controls-hint'),
    ctaHidden: document.getElementById('cta-walk').hidden,
    hudHidden: document.getElementById('walk-hud').hidden,
  }));
  assert.equal(present.cta, true);
  assert.equal(present.hud, true);
  assert.equal(present.crosshair, true);
  assert.equal(present.exit, true);
  assert.equal(present.mode, true);
  assert.equal(present.hint, true);
  assert.equal(present.ctaHidden, true, 'CTA hidden before splat loads');
  assert.equal(present.hudHidden, true, 'HUD hidden before walking enters');
  await page.close();
});

test('HUD preview page: CTA visible, HUD overlay shown for visual smoke', async () => {
  const page = await browser.newPage();
  const errors = [];
  page.on('pageerror', (e) => errors.push(e.message));
  await page.goto(URL_BASE + '/tests/visual/hud-preview.html',
                  { waitUntil: 'domcontentloaded' });
  const state = await page.evaluate(() => ({
    ctaVisible: document.getElementById('cta-walk').classList.contains('is-visible'),
    ctaText: document.getElementById('cta-walk').textContent.trim(),
    hudHidden: document.getElementById('walk-hud').hidden,
    mode: document.getElementById('walk-mode-indicator').textContent.trim(),
    hint: document.getElementById('walk-controls-hint').textContent.trim(),
  }));
  assert.equal(state.ctaVisible, true);
  assert.match(state.ctaText, /Walk through this scene/);
  assert.equal(state.hudHidden, false);
  assert.match(state.mode, /Walk/);
  assert.match(state.hint, /WASD/);
  assert.deepEqual(errors, [], `unexpected JS errors: ${errors.join(', ')}`);
  await page.close();
});

test('HUD lifecycle: showCTA → click → onClick fires → CTA hides', async () => {
  const page = await browser.newPage();
  // Use the isolated fixture so app.js / viewer auto-load don't race the test.
  await page.goto(URL_BASE + '/tests/visual/hud-isolated.html',
                  { waitUntil: 'domcontentloaded' });
  const result = await page.evaluate(async () => {
    const { HUD } = await import('/js/hud.js');
    const stage = document.getElementById('stage');
    const hud = new HUD(stage);
    let clicked = false;
    hud.showCTA(() => { clicked = true; });
    const ctaVisibleAfterShow = !document.getElementById('cta-walk').hidden;
    document.getElementById('cta-walk').click();
    // wait longer than CTA_FADE_MS (500) so the hide setTimeout has fired
    await new Promise(r => setTimeout(r, 900));
    const cta = document.getElementById('cta-walk');
    return {
      ctaVisibleAfterShow, clicked,
      ctaHiddenAfterClick: cta.hidden,
      ctaClassList: cta.className,
    };
  });
  assert.equal(result.ctaVisibleAfterShow, true,
               'CTA visible right after showCTA()');
  assert.equal(result.clicked, true, 'onClick callback fired');
  assert.equal(result.ctaHiddenAfterClick, true,
               `CTA hidden after click+fade (classList="${result.ctaClassList}")`);
  await page.close();
});

test('HUD lifecycle: enterWalkingUI shows overlay, exit-button fires callback', async () => {
  const page = await browser.newPage();
  await page.goto(URL_BASE + '/tests/visual/hud-isolated.html',
                  { waitUntil: 'domcontentloaded' });
  const result = await page.evaluate(async () => {
    const { HUD } = await import('/js/hud.js');
    const stage = document.getElementById('stage');
    const hud = new HUD(stage);
    let exitFired = false;
    hud.enterWalkingUI({ onExit: () => { exitFired = true; } });
    const hudShown = !document.getElementById('walk-hud').hidden;
    const stageControlsHidden = document.getElementById('stage-controls').hidden;
    document.getElementById('walk-exit').click();
    hud.exitWalkingUI();
    const hudHiddenAfter = document.getElementById('walk-hud').hidden;
    return { hudShown, stageControlsHidden, exitFired, hudHiddenAfter };
  });
  assert.equal(result.hudShown, true, 'walk-hud visible after enterWalkingUI');
  assert.equal(result.stageControlsHidden, true, 'stage-controls hidden in walking mode');
  assert.equal(result.exitFired, true, 'exit button callback fired');
  assert.equal(result.hudHiddenAfter, true, 'walk-hud hidden after exitWalkingUI');
  await page.close();
});