Files
hyperframes/Dockerfile.test
T
Miguel Ángel 110ea12597 fix(core,engine,producer): handle id-less media in sub-composition renders (#96)
## What

Move the id-less media fix into the shared timing compiler so producer can resolve durations for sub-composition videos before inlining, then carry the merged result through engine parsing, regression coverage, and the regression Docker image used in CI.

This PR now does five concrete things:

- assigns stable ids to id-less media in core `compileTimingAttrs()` so unresolved duration injection can target them
- keeps the engine-side `parseVideoElements()` support for `video[src]` plus the newer `data-duration` / natural-duration fallback from `main`
- makes producer prefer sub-composition media metadata over the later inlined-document parse when the same media id appears in both places
- makes `sub-composition-video` a runnable regression test by fixing its metadata and checking in the missing `output/compiled.html` snapshot
- removes the stale `pnpm-workspace.yaml` copy step from `Dockerfile.test`, so regression CI builds the Bun-based test image from the current workspace layout

## Why

- media without an explicit `id` could not participate in unresolved-duration resolution early enough
- producer could lose the resolved sub-composition timing by overwriting it with the later inlined parse
- the regression fixture intended to cover this case was not actually running in CI because its `meta.json` was incomplete and the required compiled snapshot was missing
- the regression image definition still expected a deleted `pnpm-workspace.yaml`, so GitHub Actions failed before the test shard could start

Putting the id-generation step in core makes the behavior reusable instead of relying on producer-only HTML patching.

## How

### Shared compiler

- core `compileTimingAttrs()` now auto-assigns stable ids to id-less `video` / `audio` tags
- those generated ids are returned in `unresolved`, so `injectDurations()` can add `data-duration` and `data-end` to the same media element later in the pipeline
- added core tests that cover auto-id assignment and duration injection for generated ids

### Producer

- when producer combines `subVideos` / `subAudios` with the media re-parsed from the final inlined HTML, it now lets the sub-composition metadata win
- this preserves the resolved/clamped timing already computed for nested media instead of overwriting it with the later parse
- `sub-composition-video` now has valid regression metadata and a checked-in `output/compiled.html` snapshot so CI actually executes it

### Engine

- resolved the merge conflict in `videoFrameExtractor` by keeping the broader `video[src]` parsing from this branch and the `data-duration` / natural-duration fallback that landed on `main`
- added a focused engine unit test for videos without ids

### CI image

- `Dockerfile.test` now copies only `package.json` and `bun.lock` at the workspace root before `bun install --frozen-lockfile`
- this matches the current monorepo layout and removes the obsolete pnpm-era dependency on `pnpm-workspace.yaml`

## Test plan

- [x] `bun run --filter @hyperframes/core test`
- [x] `bun run --filter @hyperframes/engine test`
- [x] `bun run --filter @hyperframes/producer test --update --sequential sub-composition-video`
- [x] `bun run --filter @hyperframes/producer test --sequential sub-composition-video`
- [x] Browser check with `agent-browser` against the compiled fixture page (`http://127.0.0.1:8123/compiled.html`)
- [x] Clean tracked-only Docker build of `Dockerfile.test` with the PR version of the file applied

## Notes

- Latest regression workflow is green on `main`, but before this PR the `sub-composition-video` fixture was being skipped by the harness rather than exercised end to end.
- The CI Docker fix was validated from a tracked-only export to avoid local untracked worktree artifacts affecting the result.
2026-04-01 02:59:22 +02:00

90 lines
3.1 KiB
Docker

# HyperFrames Producer - Regression Test Image
#
# Matches the production rendering environment (same Chromium, fonts, FFmpeg)
# but includes the full source + devDependencies for running the test harness.
#
# This ensures golden baselines match what production actually renders.
#
# Usage:
# docker build -f Dockerfile.test -t hyperframes-producer:test .
# docker run --rm -v ./packages/producer/tests:/app/packages/producer/tests hyperframes-producer:test
# docker run --rm -v ./packages/producer/tests:/app/packages/producer/tests hyperframes-producer:test --update
FROM node:22-bookworm-slim
# ── System dependencies (identical to production) ────────────────────────────
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
unzip \
ffmpeg \
chromium \
libgbm1 \
libnss3 \
libatk-bridge2.0-0 \
libdrm2 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libcups2 \
libasound2 \
libpangocairo-1.0-0 \
libxshmfence1 \
libgtk-3-0 \
# Font support — matches production
fonts-liberation \
fonts-noto-color-emoji \
fonts-noto-cjk \
fonts-noto-core \
fonts-noto-extra \
fonts-noto-ui-core \
fonts-freefont-ttf \
fonts-dejavu-core \
fontconfig \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean \
&& fc-cache -fv
# Use system Chromium (same as production)
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV CONTAINER=true
# Install chrome-headless-shell for deterministic BeginFrame rendering.
# This lightweight Chrome binary supports HeadlessExperimental.beginFrame.
# Install to ~/.cache/puppeteer/ where resolveHeadlessShellPath() looks.
RUN npx --yes @puppeteer/browsers install chrome-headless-shell@stable \
--path /root/.cache/puppeteer \
&& find /root/.cache/puppeteer/chrome-headless-shell -name "chrome-headless-shell" -type f \
&& echo "chrome-headless-shell installed"
WORKDIR /app
# Install bun
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:$PATH"
# Install dependencies (full, including devDependencies for tsx + test harness)
COPY package.json bun.lock ./
COPY packages/core/package.json packages/core/package.json
COPY packages/engine/package.json packages/engine/package.json
COPY packages/producer/package.json packages/producer/package.json
COPY packages/cli/package.json packages/cli/package.json
COPY packages/studio/package.json packages/studio/package.json
RUN bun install --frozen-lockfile
# Copy source
COPY packages/core/ packages/core/
COPY packages/engine/ packages/engine/
COPY packages/producer/ packages/producer/
# Build core runtime artifacts (needed by renderer)
RUN bun run --filter @hyperframes/core build:hyperframes-runtime:modular
# Generate embedded font data (deterministicFonts.ts imports this at runtime)
RUN cd packages/producer && bunx tsx scripts/generate-font-data.ts
WORKDIR /app/packages/producer
ENTRYPOINT ["bunx", "tsx", "src/regression-harness.ts", "--", "--sequential"]