[object Object]

← back to Quadrille Showroom

Add 2-min soak-test recorder (walk/open/Back-Next/Peruse; logs console errors + FPS)

179338491a1b90be1f6da6d9575c01e0a2a25c4f · 2026-06-27 10:33:24 -0700 · Steve

Files touched

Diff

commit 179338491a1b90be1f6da6d9575c01e0a2a25c4f
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jun 27 10:33:24 2026 -0700

    Add 2-min soak-test recorder (walk/open/Back-Next/Peruse; logs console errors + FPS)
---
 scripts/test-2min.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/scripts/test-2min.js b/scripts/test-2min.js
new file mode 100644
index 0000000..4ffea68
--- /dev/null
+++ b/scripts/test-2min.js
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+/**
+ * test-2min.js — a ~2 minute soak test with screen recording. Exercises walk,
+ * open, Back/Next nav, and a long Peruse; captures console errors + periodic FPS.
+ * Writes recordings/test-2min.webm + recordings/test-2min-report.json.
+ */
+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 sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+(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 errors = [], fpsLog = [];
+  page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
+  page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
+
+  await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
+  await page.waitForFunction(() => window._wingBoards && window._wingBoards.length > 0, { timeout: 25000 }).catch(() => {});
+  await sleep(2500);
+
+  const sampleFps = async (tag) => {
+    const fps = await page.evaluate(() => { const el = document.getElementById('fps-counter'); return el ? parseInt(el.textContent) : null; });
+    fpsLog.push({ tag, fps }); return fps;
+  };
+
+  const start = Date.now();
+  let round = 0;
+  // ~120s loop of real interactions
+  while (Date.now() - start < 118000) {
+    round++;
+    // walk in
+    await page.keyboard.down('KeyW'); await sleep(1200); await page.keyboard.up('KeyW');
+    await sleep(800); await sampleFps('walk');
+    // click a board to open (isolated, centred)
+    await page.mouse.click(800, 430); await sleep(2200); await sampleFps('open');
+    // flip through with Next / Back
+    for (let i = 0; i < 4; i++) { await page.click('#btn-fan-right').catch(()=>{}); await sleep(1600); }
+    await page.click('#btn-fan-left').catch(()=>{}); await sleep(1500);
+    await sampleFps('nav');
+    await page.screenshot({ path: path.join(OUT, `t2-r${round}.png`) }).catch(()=>{});
+    // escape, peruse a stretch
+    await page.keyboard.press('Escape'); await sleep(800);
+    await page.keyboard.press('KeyP'); await sleep(11000); await sampleFps('peruse');
+    await page.keyboard.press('Escape'); await sleep(800);
+  }
+
+  const stats = await page.evaluate(() => (window._getSceneStats ? window._getSceneStats() : null));
+  const fpsVals = fpsLog.map(f => f.fps).filter(n => n > 0);
+  const report = {
+    when: new Date().toISOString(), rounds: round, durationSec: Math.round((Date.now() - start) / 1000),
+    errors, errorCount: errors.length,
+    fpsMin: Math.min(...fpsVals), fpsAvg: Math.round(fpsVals.reduce((a, b) => a + b, 0) / fpsVals.length), fpsSamples: fpsLog,
+    sceneStats: stats,
+  };
+  fs.writeFileSync(path.join(OUT, 'test-2min-report.json'), JSON.stringify(report, null, 2));
+  console.log('rounds', round, '| errors', errors.length, '| fps min/avg', report.fpsMin, '/', report.fpsAvg);
+  if (errors.length) console.log('ERRORS:', errors.slice(0, 8).join(' || '));
+
+  await page.close();
+  const vid = await page.video().path().catch(() => null);
+  await ctx.close(); await browser.close();
+  if (vid && fs.existsSync(vid)) { fs.renameSync(vid, path.join(OUT, 'test-2min.webm')); console.log('✓ test-2min.webm'); }
+})().catch(e => { console.error('TEST FAILED:', e.message); process.exit(1); });

← 6e2efdd Usable view: 6ft back + dead-centre + 65° open + ISOLATE (hi  ·  back to Quadrille Showroom  ·  Photoreal room (SDXL warm-oak floor + limewash plaster walls e7268c6 →