Skip to content

Render fills the bottom ~85px of every frame with the page background color when falling back to system Chrome (preview is fine) #1699

Description

@Antoniaiaiaiaia

Render fills the bottom ~85px of every frame with the page background color when falling back to system Chrome (preview is fine)

Summary

On macOS, when no dedicated chrome-headless-shell is installed and the render falls back to the system Google Chrome, every rendered frame gets a solid horizontal band across the bottom ~85px. The band is the <body> background color, not anything in the composition. Live preview renders correctly — the band only appears in the rendered video.

Installing the dedicated chrome-headless-shell makes the band disappear, so this looks specific to the system-Chrome capture path.

Environment

  • hyperframes 0.7.5
  • macOS 26.3 (build 25D125), Apple M4
  • Node v22.22.0
  • Render browser: system Google Chrome 149.0.7827.155 (no chrome-headless-shell in ~/.cache/puppeteer; hyperframes browser ensure reports Source: system)
  • Capture mode: screenshot (macOS, so not the Linux beginframe path)

Repro

  1. On a machine with no chrome-headless-shell installed (so render uses system Chrome).
  2. Any 1920×1080 composition whose <body>/html has a visible background color, e.g.:
    <head><style>html,body{margin:0;width:1920px;height:1080px;background:#FAF9F5;}</style></head>
    with a #root that paints a full-bleed gradient to the bottom edge.
  3. npx hyperframes render .
  4. Inspect any frame: the bottom ~85px is a flat band of the <body> color, with a hard horizontal edge that also clips the composition's content (e.g. card shadows) above it.

In our case the edge sits at ~y=988 of 1080; the band measures (251,248,245), exactly the body #FAF9F5.

Evidence that the band is the page background

Setting the body background to red and re-rendering turns the bottom band red (255,24,0) — confirming the band is the page background bleeding through, not a composition layer.

This matches what pageScreenshotCapture does: Page.captureScreenshot with clip {x:0,y:0,width,height}, captureBeyondViewport: false, and no Emulation.setDefaultBackgroundColorOverride. If the captured surface is shorter than the clip, Chrome backfills the out-of-surface region with the page's base background color.

What fixes it

Installing the dedicated headless shell and pointing the renderer at it:

npx -y @puppeteer/browsers install chrome-headless-shell@stable --path ~/.cache/puppeteer
# or, ad hoc:
PRODUCER_HEADLESS_SHELL_PATH=/path/to/chrome-headless-shell npx hyperframes render .

With chrome-headless-shell the full 1080px renders correctly and the band is gone — same composition, same flags, same machine.

Notes / what I could and couldn't isolate

  • The captured surface appears to be ~85px shorter than the requested 1080 only on the system-Chrome path. createCaptureSession does call page.setViewport({width, height, deviceScaleFactor}), so the layout viewport is 1080 and the composition lays out fine — but the captured surface (fromSurface: true) ends up short.
  • I could not reproduce it with a standalone puppeteer-core script launching the same system Chrome binary with --window-size=1920,1080, setViewport(1920×1080), then Page.captureScreenshot({clip, captureBeyondViewport:false}) — that produces a correct full-height frame. So it does not seem to be simply "system Chrome cannot render at 1080"; something in the HyperFrames launch/capture setup interacts with the system-Chrome fallback to produce the short surface.

Questions

  1. Is the system-Chrome fallback expected to produce correct renders, or is chrome-headless-shell effectively required? If the dedicated shell is required for correct output, would it make sense for hyperframes doctor / browser ensure to warn (rather than silently pass) when only system Chrome is available?
  2. Independently of the surface issue, would it be reasonable for the opaque capture path to defend against a short surface — e.g. captureBeyondViewport: true, or setDefaultBackgroundColorOverride to transparent — so a surface/clip mismatch can't bleed the page background into the frame?

Happy to share a minimal repo, full frames, and the red-background comparison if useful.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions