Capturing demo frames
Marketing helixui without GIFs is hard. Recording GIFs manually drifts — a UI tweak invalidates yesterday’s recording. So we automate.
The script
tests/demos.spec.ts defines three demo flows:
| Test | What it captures |
|---|---|
demo: three-brands | DNA Lab cycling through wildtype → studio → botanic → solstice → noir. |
demo: markdown-to-pptx | Markdown studio, hovering and clicking the .pptx export. |
demo: breed-a-theme | DNA Lab rolling 4 times and clicking Share. |
Each test screenshots after every scripted interaction. Frames land in
tests/__demos__/<demo>/frame-NNN.png.
Running
# 1) Start the site somewhere reachable.pnpm dev:site # or pnpm preview:site after build
# 2) In another terminal, run the demo capture.pnpm demosYou’ll see ~12 PNGs per demo. They’re 1280×800 by default.
Turning frames into GIFs / MP4s
Frames are just PNGs — assemble them however you like.
ffmpeg -framerate 12 -i tests/__demos__/three-brands/frame-%03d.png \ -vf "fps=12,scale=1280:-1:flags=lanczos" \ apps/site/public/demos/three-brands.gif
# Or video for higher-fidelity Twitter cards:ffmpeg -framerate 12 -i tests/__demos__/three-brands/frame-%03d.png \ -c:v libx264 -pix_fmt yuv420p -movflags +faststart \ apps/site/public/demos/three-brands.mp4Gifski produces smaller / sharper GIFs:
gifski --fps 12 --width 1280 tests/__demos__/three-brands/*.png \ -o apps/site/public/demos/three-brands.gifCI integration
Demo frames are not committed to git — they’re build artifacts.
A separate workflow (.github/workflows/demos.yml, coming next
sprint) runs the capture on release tags and uploads to the release’s
artifact bucket. The README references the latest frame via a
stable URL.
Adding a new demo
- Add a
test('demo: <name>')block totests/demos.spec.ts. - Use the
shoot(page, dir, index)helper for each frame. - Run
pnpm demos. Verify the frames are clean. - Pipe through ffmpeg / Gifski; commit the output (the GIF / MP4)
to
apps/site/public/demos/.
Tips
- Avoid animations during shots. Pass
animations: 'disabled'topage.screenshotif the animation timing makes frames look weird. - Use
await page.waitForTimeout(300)between interactions so state has time to land. - Don’t capture the cursor. Playwright doesn’t render a cursor by
default. If you need one for a tutorial GIF, draw it in post or use
the
chromiumSandboxedShowCursorflag.