#!/usr/bin/env node // Check examples, real audio gains, live CPU inference, and responsive layouts. import assert from "node:assert/strict"; import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { chromium } from "../demo/web/node_modules/playwright/index.mjs"; const base = process.env.HENDRIX_SPACE_URL || "https://jackboyla-hendrix.static.hf.space"; const resultPath = process.env.HENDRIX_SPACE_RESULT || "progress/evaluations/huggingface_space_redesign_check.json"; const screenshotPath = process.env.HENDRIX_SPACE_SCREENSHOT || "progress/evaluations/huggingface_space_redesign.png"; const browser = await chromium.launch({ headless: true, args: ["--disable-gpu"] }); const errors = [], failedResponses = [], audioUploads = [], results = []; async function check(page, embedded) { await page.addInitScript(() => { window.__blendTargets = []; const original = AudioParam.prototype.setTargetAtTime; AudioParam.prototype.setTargetAtTime = function (value, time, constant) { window.__blendTargets.push(value); return original.call(this, value, time, constant); }; }); const modelRequests = []; page.on("pageerror", error => errors.push(String(error))); page.on("request", request => { if (request.method() === "POST" && request.url().startsWith(base)) audioUploads.push(request.url()); if (request.url().includes("/transcribe")) audioUploads.push(request.url()); if (request.url().includes("/models/")) modelRequests.push(request.url()); }); page.on("response", response => { if (response.status() >= 400 && response.url().startsWith(base)) failedResponses.push({ url: response.url(), status: response.status() }); }); let app; if (embedded) { const repoId = process.env.HENDRIX_SPACE_REPO || "jackboyla/hendrix"; await page.goto(`https://huggingface.co/spaces/${repoId}`, { waitUntil: "domcontentloaded", timeout: 60_000 }); await page.waitForFunction(origin => [...document.querySelectorAll("iframe")].some(frame => frame.src.startsWith(origin)), base, { timeout: 60_000 }); app = page.frameLocator(`iframe[src^="${base}"]`).first(); } else { await page.goto(`${base}/local.html?backend=wasm`, { waitUntil: "domcontentloaded", timeout: 60_000 }); app = page.mainFrame(); } const download = app.getByRole("link", { name: "Download MIDI" }); await download.waitFor({ timeout: 60_000 }); assert.equal(await app.locator(".example-card").count(), 14); assert.doesNotMatch(await app.locator("body").textContent(), /D26|research checkpoint/); const isolation = await app.locator("html").evaluate(() => ({ isolated: crossOriginIsolated, secure: isSecureContext, sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined" })); assert.equal(isolation.secure, true); if (!embedded) assert.equal(isolation.isolated, true); assert.deepEqual(modelRequests, [], "prepared examples must work without downloading the inference model"); // Verify every catalog asset, not just the featured example. const catalogResult = await app.locator("html").evaluate(async () => { const catalog = await (await fetch("/examples/catalog.json")).json(); const bad = []; for (const example of catalog.examples) { for (const url of [example.audio_url, example.notes_url]) { const response = await fetch(url); if (!response.ok) bad.push({ url, status: response.status }); } } return { count: catalog.examples.length, categories: [...new Set(catalog.examples.map(example => example.category))], bad }; }); assert.equal(catalogResult.count, 14); assert.deepEqual(catalogResult.bad, []); const blend = app.getByRole("slider", { name: "Original and MIDI blend" }); await blend.waitFor(); // MIDI endpoints stay disabled until the actual sampled-instrument synth is ready. await app.getByRole("button", { name: "MIDI", exact: true }).waitFor(); const synthStarted = Date.now(); while (await blend.isDisabled()) { if (Date.now() - synthStarted > 60_000) throw new Error("MIDI instrument sounds did not become ready"); await new Promise(resolve => setTimeout(resolve, 100)); } await app.getByRole("button", { name: "Play recording" }).click(); await app.getByRole("button", { name: "Pause playback" }).waitFor(); await blend.focus(); await page.keyboard.press("End"); await app.locator(".blend-readout").filter({ hasText: "100% MIDI" }).waitFor(); const midiGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4)); assert.deepEqual(midiGains, [0, 1, 0, 0], "MIDI endpoint must mute original gain and open MIDI gain"); assert.equal(await app.getByRole("button", { name: "Pause playback" }).count(), 1); await page.keyboard.press("Home"); await app.locator(".blend-readout").filter({ hasText: "100% original" }).waitFor(); const originalGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4)); assert.deepEqual(originalGains, [1, 0, 0, 0], "Original endpoint must open original gain and mute MIDI gain"); await page.keyboard.press("ArrowRight"); await app.locator(".blend-readout").filter({ hasText: "99% original" }).waitFor(); const mixedGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4)); assert.deepEqual(mixedGains, [0.99, 0.01, 0, 0], "slider must drive both actual audio gains"); await app.getByRole("button", { name: "Pause playback" }).click(); const preparedMidi = await app.locator("html").evaluate(async (_html, url) => Array.from(new Uint8Array(await (await fetch(url)).arrayBuffer())), await download.getAttribute("href")); assert.equal(Buffer.from(preparedMidi).subarray(0, 4).toString(), "MThd"); // Browse and load a second source; then return to the featured mix. await app.getByRole("button", { name: "Guitar", exact: true }).click(); assert.equal(await app.locator(".example-card").count(), 4); await app.getByRole("button", { name: "Load Funk guitar", exact: true }).click(); await app.getByRole("heading", { name: "Funk guitar", exact: true }).waitFor(); await download.waitFor(); await app.getByRole("button", { name: "Pause playback" }).waitFor(); await app.getByRole("button", { name: "Pause playback" }).click(); await app.getByRole("button", { name: "All", exact: true }).click(); await app.getByRole("button", { name: "Load Full-band groove", exact: true }).click(); await app.getByRole("heading", { name: "Full-band groove", exact: true }).waitFor(); await app.getByRole("button", { name: "Pause playback" }).waitFor(); await app.getByRole("button", { name: "Pause playback" }).click(); assert.deepEqual(modelRequests, [], "all prepared listening checks must stay independent of inference download"); if (!embedded) { await mkdir(dirname(screenshotPath), { recursive: true }); await page.screenshot({ path: screenshotPath, fullPage: true }); await page.setViewportSize({ width: 390, height: 844 }); const layout = await app.locator("html").evaluate(() => ({ width: innerWidth, scrollWidth: document.documentElement.scrollWidth })); assert.ok(layout.scrollWidth <= layout.width, `mobile layout overflow: ${JSON.stringify(layout)}`); await page.screenshot({ path: screenshotPath.replace(/\.png$/, "_mobile.png"), fullPage: true }); await page.setViewportSize({ width: 1280, height: 1000 }); } // Check the live path too; use a short excerpt to keep this a bounded test. await app.locator(".limit-label select").selectOption("2.05"); const started = Date.now(); await app.getByRole("button", { name: "Transcribe audio" }).click(); await app.locator('.run-status:has-text("Audio never left this device"), .run-status.error').waitFor({ timeout: 180_000 }); const status = await app.locator(".run-status").textContent(); const diagnostic = await app.locator(".diagnostic-output").textContent().catch(() => ""); assert.match(status, /^[1-9]\d* notes from /, `${status}; ${diagnostic}`); assert.match(diagnostic, /backend=wasm cache=cpu/); const midi = await app.locator("html").evaluate(async (_html, url) => Array.from(new Uint8Array(await (await fetch(url)).arrayBuffer())), await download.getAttribute("href")); assert.equal(Buffer.from(midi).subarray(0, 4).toString(), "MThd"); const result = { embedded, ...isolation, catalog: catalogResult, midiGains, originalGains, mixedGains, preparedMidiBytes: preparedMidi.length, status, diagnostic, liveMidiBytes: midi.length, liveWallSeconds: (Date.now() - started) / 1000, modelRequests }; results.push(result); console.log(JSON.stringify(result)); } try { await check(await browser.newPage({ viewport: { width: 1280, height: 1000 } }), false); if (process.env.HENDRIX_SPACE_SKIP_EMBED !== "1") { await check(await browser.newPage({ viewport: { width: 1440, height: 1000 } }), true); } assert.deepEqual(errors, []); assert.deepEqual(failedResponses, []); assert.deepEqual(audioUploads, []); await mkdir(dirname(resultPath), { recursive: true }); await writeFile(resultPath, JSON.stringify({ base, results, errors, failedResponses, audioUploads }, null, 2) + "\n"); console.log(`Example, audio blend, MIDI, layout, and live transcription checks passed: ${resultPath}`); } finally { await browser.close(); }