Zum Inhalt

embed/tests/e2e/embed-smoke.test.mjs

// End-to-end smoke for the embed page: DOM lifecycle of gate/hint/error.
// Uses puppeteer-core + system Chrome. Real splat rendering is verified
// manually with ../serve.sh — headless GL is not a fidelity reference,
// which is also why the error-message assertion tolerates the WebGL2 path.
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, '..', '..');          // embed/
const CHROME = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const PORT = 8131;
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'],
  });
  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('no src: dropzone shown, gate + controls stay hidden', async () => {
  const page = await browser.newPage();
  await page.goto(URL_BASE + '/', { waitUntil: 'domcontentloaded' });
  const s = await page.evaluate(() => ({
    dropzone: !document.getElementById('dropzone').hidden,
    hint: document.getElementById('hint-card').hidden,
    gate: document.getElementById('gate').hidden,
    controls: document.getElementById('controls').hidden,
  }));
  assert.equal(s.dropzone, true, 'dropzone visible');
  assert.equal(s.hint, true, 'hint card hidden');
  assert.equal(s.gate, true, 'gate hidden');
  assert.equal(s.controls, true, 'controls hidden');
  await page.close();
});

test('with src: gate shown with scene label, hint hidden', async () => {
  const page = await browser.newPage();
  await page.goto(URL_BASE + '/?src=c/max_strasse.sog',
                  { waitUntil: 'domcontentloaded' });
  const s = await page.evaluate(() => ({
    gateHidden: document.getElementById('gate').hidden,
    label: document.getElementById('gate-label').textContent,
    hintHidden: document.getElementById('hint-card').hidden,
  }));
  assert.equal(s.gateHidden, false, 'gate visible');
  assert.equal(s.label, 'max strasse');
  assert.equal(s.hintHidden, true, 'hint hidden');
  await page.close();
});

test('poster param: gate gets background image + has-poster class', async () => {
  const page = await browser.newPage();
  await page.goto(URL_BASE + '/?src=x.sog&poster=poster.jpg',
                  { waitUntil: 'domcontentloaded' });
  const s = await page.evaluate(() => ({
    cls: document.getElementById('gate').classList.contains('has-poster'),
    bg: document.getElementById('gate').style.backgroundImage,
  }));
  assert.equal(s.cls, true);
  assert.match(s.bg, /poster\.jpg/);
  await page.close();
});

test('gate click with broken src: error card with retry appears', async () => {
  const page = await browser.newPage();
  await page.goto(URL_BASE + '/?src=does-not-exist.sog',
                  { waitUntil: 'domcontentloaded' });
  await page.click('#gate');
  await page.waitForFunction(
    () => !document.getElementById('error-card').hidden, { timeout: 30000 });
  const s = await page.evaluate(() => ({
    msg: document.getElementById('error-msg').textContent,
    retry: !!document.getElementById('btn-retry'),
    spinnerHidden: document.getElementById('spinner').hidden,
  }));
  assert.match(s.msg, /Could not load|WebGL2/);
  assert.equal(s.retry, true, 'retry button present');
  assert.equal(s.spinnerHidden, true, 'spinner gone');
  await page.close();
});

test('autoplay=1: gate never shows, load starts immediately', async () => {
  const page = await browser.newPage();
  await page.goto(URL_BASE + '/?src=does-not-exist.sog&autoplay=1',
                  { waitUntil: 'domcontentloaded' });
  const gateHidden = await page.evaluate(
    () => document.getElementById('gate').hidden);
  assert.equal(gateHidden, true, 'gate stays hidden with autoplay');
  await page.waitForFunction(
    () => !document.getElementById('error-card').hidden, { timeout: 30000 });
  await page.close();
});