[object Object]

← back to Quadrille Showroom

Add automated Playwright walkthrough recorder (move-around screen-record + scene stats + spec-card proof)

a76a8f6a11052724ac3c8c1685d1e5fceb6b1a8a · 2026-06-27 00:05:19 -0700 · Steve

Files touched

Diff

commit a76a8f6a11052724ac3c8c1685d1e5fceb6b1a8a
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jun 27 00:05:19 2026 -0700

    Add automated Playwright walkthrough recorder (move-around screen-record + scene stats + spec-card proof)
---
 .gitignore                    |   3 ++
 scripts/walkthrough-record.js | 115 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 118 insertions(+)

diff --git a/.gitignore b/.gitignore
index a5e2879..7e6cdd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,6 @@ build/
 # generated image artifacts (rebuild via scripts/gen-assets.js)
 data/gen-tiles/
 data/gen-rooms/
+
+# generated walkthrough recordings (rebuildable via scripts/walkthrough-record.js)
+recordings/
diff --git a/scripts/walkthrough-record.js b/scripts/walkthrough-record.js
new file mode 100644
index 0000000..6779b5e
--- /dev/null
+++ b/scripts/walkthrough-record.js
@@ -0,0 +1,115 @@
+#!/usr/bin/env node
+/**
+ * walkthrough-record.js — automated "move around" screen recording of the showroom.
+ * Drives a real Chromium through: load → walk to the wall (proximity book-match) →
+ * inspect a wing (new spec-sheet card) → fan cascade → Peruse auto-tour → scene stats.
+ * Records WebM video + key screenshots; convert to MP4 via ffmpeg afterward.
+ *
+ * Run: NODE_PATH=$HOME/.npm-global/lib/node_modules node scripts/walkthrough-record.js
+ * Local 127.0.0.1 is auth-exempt, so no basic-auth needed.
+ */
+const path = require('path');
+const fs = require('fs');
+const { chromium } = require('playwright');
+
+const URL = process.env.SHOWROOM_URL || 'http://127.0.0.1:7690/';
+const OUT = path.join(__dirname, '..', 'recordings');
+fs.mkdirSync(OUT, { recursive: true });
+const SHOTS = path.join(OUT, 'shots');
+fs.mkdirSync(SHOTS, { recursive: true });
+
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+async function holdKey(page, key, ms) {
+  await page.keyboard.down(key);
+  await sleep(ms);
+  await page.keyboard.up(key);
+}
+
+(async () => {
+  const browser = await chromium.launch({ args: ['--use-gl=angle', '--enable-webgl', '--ignore-gpu-blocklist'] });
+  const ctx = await browser.newContext({
+    viewport: { width: 1600, height: 900 },
+    recordVideo: { dir: OUT, size: { width: 1600, height: 900 } },
+  });
+  const page = await ctx.newPage();
+  const logs = [];
+  page.on('console', m => logs.push(`[${m.type()}] ${m.text()}`));
+
+  console.log('→ loading', URL);
+  await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
+
+  // Wait for the engine to build the wing wall.
+  await page.waitForFunction(() => window._wingBoards && window._wingBoards.length > 0, { timeout: 25000 }).catch(() => {});
+  await sleep(2500); // let the loader fade + first textures stream in
+  await page.screenshot({ path: path.join(SHOTS, '01-overview.png') });
+  console.log('  wings:', await page.evaluate(() => (window._wingBoards || []).length));
+
+  // 1) Walk forward toward the wall — triggers proximity book-match on the nearest wing.
+  console.log('→ walking to the wall (W)');
+  await holdKey(page, 'KeyW', 1600);
+  await sleep(1200);
+  await page.screenshot({ path: path.join(SHOTS, '02-bookmatch.png') });
+
+  // Strafe a little so the footage "moves around".
+  await holdKey(page, 'KeyD', 700);
+  await holdKey(page, 'KeyA', 1100);
+  await sleep(900);
+  await page.screenshot({ path: path.join(SHOTS, '03-strafe.png') });
+
+  // 2) Inspect a wing by clicking near screen-centre (a wing face is dead ahead at the wall).
+  console.log('→ inspecting a wing (click)');
+  await page.mouse.click(800, 430);
+  await sleep(1800);
+  // The spec-sheet detail card should now be visible — capture it.
+  const detailVisible = await page.evaluate(() => {
+    const el = document.getElementById('wing-detail');
+    return el && !el.classList.contains('hidden');
+  });
+  await page.screenshot({ path: path.join(SHOTS, '04-spec-card.png') });
+  console.log('  spec card visible:', detailVisible);
+  // Pull the rendered spec rows so we can prove the specs surfaced.
+  const specText = await page.evaluate(() => {
+    const grid = document.getElementById('detail-spec-grid');
+    if (!grid) return null;
+    return [...grid.querySelectorAll('.wd-spec')].map(r => r.querySelector('dt').textContent + ': ' + r.querySelector('dd').textContent);
+  });
+  console.log('  SPEC SHEET:', JSON.stringify(specText));
+
+  // 3) Fan cascade to browse adjacent patterns.
+  console.log('→ fan cascade');
+  await page.click('#btn-fan-right').catch(() => {});
+  await sleep(1500);
+  await page.screenshot({ path: path.join(SHOTS, '05-fan.png') });
+  await page.keyboard.press('Escape');
+  await sleep(800);
+
+  // 4) Peruse — endless auto-tour. Flies wing→wing, opens each like a book, shows the card.
+  console.log('→ Peruse auto-tour (P) for ~14s');
+  await page.keyboard.press('KeyP');
+  for (let i = 0; i < 4; i++) {
+    await sleep(3500);
+    await page.screenshot({ path: path.join(SHOTS, `06-peruse-${i + 1}.png`) });
+  }
+  await page.keyboard.press('Escape');
+  await sleep(1000);
+
+  // Scene diagnostics — proves the render is healthy.
+  const stats = await page.evaluate(() => (window._getSceneStats ? window._getSceneStats() : 'n/a'));
+  const fps = await page.evaluate(() => { const el = document.getElementById('fps-counter'); return el ? el.textContent : 'n/a'; });
+  console.log('→ SCENE STATS:', JSON.stringify(stats));
+  console.log('→ FPS:', fps);
+
+  await page.close();        // finalizes the video file
+  const vid = await page.video().path().catch(() => null);
+  await ctx.close();
+  await browser.close();
+
+  // Rename the video deterministically.
+  let finalWebm = path.join(OUT, 'walkthrough.webm');
+  if (vid && fs.existsSync(vid)) { fs.renameSync(vid, finalWebm); }
+  fs.writeFileSync(path.join(OUT, 'walkthrough-stats.json'), JSON.stringify({ url: URL, stats, fps, specText, detailVisible, when: new Date().toISOString() }, null, 2));
+  fs.writeFileSync(path.join(OUT, 'walkthrough-console.log'), logs.join('\n'));
+  console.log('✓ video:', finalWebm);
+  console.log('✓ shots:', SHOTS);
+})().catch(e => { console.error('RECORD FAILED:', e.message); process.exit(1); });

← 72bec59 China Seas specs (27"×27" / Grasscloth 36") + spec-sheet det  ·  back to Quadrille Showroom  ·  Boards 30"×6ft (real sample wings, 8/wall + paging); fan-out 5921951 →