Files
open-design/e2e/tests/hyperframes-runtime-render.test.ts
T
f2e1bec689 test(e2e): close recent release coverage gaps (#7441)
* test(e2e): close recent release coverage gaps

* test(e2e): provide hermetic ffmpeg binaries

* test(e2e): own ACP refusal fixture

* test(e2e): satisfy resource layout guard

* test(e2e): stabilize ACP and desktop regressions

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-08-27 08:18:50 +00:00

124 lines
4.8 KiB
TypeScript

// @vitest-environment node
import { execFile } from 'node:child_process';
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import ffmpegInstaller from '@ffmpeg-installer/ffmpeg';
import ffprobeInstaller from '@ffprobe-installer/ffprobe';
import { describe, expect, test } from 'vitest';
import { createSmokeSuite } from '@/vitest/suite';
const odBin = fileURLToPath(new URL('../../apps/daemon/bin/od.mjs', import.meta.url));
describe('HyperFrames bundled runtime end-to-end', () => {
test('[P0] real od media generate renders MP4 through the daemon-owned HyperFrames runtime', async () => {
const suite = await createSmokeSuite('hyperframes-runtime-render');
await suite.with.env(
{
HYPERFRAMES_FFMPEG_PATH: ffmpegInstaller.path,
HYPERFRAMES_FFPROBE_PATH: ffprobeInstaller.path,
},
async () => {
await suite.with.toolsDev(async ({ runtime }) => {
const daemonUrl = `http://127.0.0.1:${runtime.daemonPort}`;
const projectId = `hyperframes-render-${Date.now()}`;
const create = await fetch(`${daemonUrl}/api/projects`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
id: projectId,
name: 'HyperFrames real runtime render',
skillId: null,
designSystemId: null,
metadata: { kind: 'video' },
}),
});
expect(create.ok, await create.text()).toBe(true);
const compositionRel = '.hyperframes-cache/e2e-real-render';
const compositionDir = join(suite.dataDir, 'projects', projectId, compositionRel);
await mkdir(compositionDir, { recursive: true });
await writeFile(join(compositionDir, 'hyperframes.json'), JSON.stringify({
paths: { blocks: 'compositions', components: 'compositions/components', assets: 'assets' },
media: { autoProxy: false },
}), 'utf8');
await writeFile(join(compositionDir, 'meta.json'), JSON.stringify({
id: 'e2e-real-render',
name: 'E2E real render',
}), 'utf8');
await writeFile(join(compositionDir, 'index.html'), `<!doctype html>
<html><head><meta charset="utf-8"><style>
html,body{margin:0;width:320px;height:180px;overflow:hidden;background:#10253f}
.clip{display:grid;place-items:center;width:320px;height:180px;color:white;font:700 28px sans-serif}
</style></head><body>
<main id="root" data-composition-id="main" data-start="0" data-duration="0.1" data-width="320" data-height="180" data-fps="30">
<section class="clip" data-start="0" data-duration="0.1" data-track-index="1">Open Design</section>
</main>
<script>
window.__timelines=window.__timelines||{};
window.__timelines.main={duration:function(){return .1},seek:function(){}};
</script></body></html>`, 'utf8');
const rendered = await runOd(daemonUrl, [
'media',
'generate',
'--project', projectId,
'--surface', 'video',
'--model', 'hyperframes-html',
'--composition-dir', compositionRel,
'--output', 'hyperframes-e2e.mp4',
]);
expect(rendered.code, rendered.stderr || rendered.stdout).toBe(0);
const payload = JSON.parse(rendered.stdout.trim().split('\n').at(-1) ?? '{}') as {
file?: { kind?: string; mime?: string; name?: string; size?: number };
};
expect(payload.file).toMatchObject({
kind: 'video',
mime: 'video/mp4',
name: 'hyperframes-e2e.mp4',
size: expect.any(Number),
});
expect(payload.file?.size ?? 0).toBeGreaterThan(1_000);
const outputPath = join(suite.dataDir, 'projects', projectId, 'hyperframes-e2e.mp4');
const [outputStat, bytes] = await Promise.all([
stat(outputPath),
readFile(outputPath),
]);
expect(outputStat.size).toBe(payload.file?.size);
expect(bytes.subarray(4, 8).toString('ascii')).toBe('ftyp');
});
},
);
}, 180_000);
});
async function runOd(
daemonUrl: string,
args: string[],
): Promise<{ code: number; stderr: string; stdout: string }> {
return await new Promise((resolve) => {
execFile(
process.execPath,
[odBin, ...args],
{
env: { ...process.env, OD_DAEMON_URL: daemonUrl },
maxBuffer: 8 * 1024 * 1024,
timeout: 120_000,
},
(error, stdout, stderr) => {
const failure = error as { code?: number } | null;
resolve({
code: typeof failure?.code === 'number' ? failure.code : error ? 1 : 0,
stderr: stderr ?? '',
stdout: stdout ?? '',
});
},
);
});
}