Skip to content

cdp backend silently caps capture at --tile-height: page_height detection fails under Brave (document.fonts.ready has no timeout) #133

Description

@SatishRockzz

Summary

pixelshot's default cdp backend never actually measures the true page height when run against Brave Browser as CHROME_PATH — it silently falls back to whatever --tile-height was passed, so any page taller than one tile is truncated to a single viewport-height capture. Content below that point (e.g. a table further down a long Wikipedia article) is never rendered at all, with no error or warning.

Reproduction

uv tool install pixelrag
CHROME_PATH="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" \
  pixelshot "https://en.wikipedia.org/wiki/2010_UEFA_Champions_League_Final" \
  --output ./tiles --tile-height 1568 --wait-network-idle

cat ./tiles/en.wikipedia.org_wiki_2010_UEFA_Champions_League_Final.png.tiles/tiles.json
# {"page_height": 1568, "tiles": ["tile_0000.jpg"], "complete": true}

Re-running with --tile-height 500 instead of 1568 reproduces page_height: 500 — i.e. page_height always exactly equals whatever --tile-height is passed, for a page that is obviously several thousand pixels tall (it has an infobox, "Road to the final" table, full match report, and a "Statistics" section well past any of these values). This isn't a short-page edge case; the height detection itself is failing every time and falling back silently.

Root cause (best guess)

In pixelrag_render/backends/cdp.py, _readiness_expr():

await new Promise(res => { ... load event, with LOAD_TIMEOUT_MS guard ... });
await document.fonts.ready;   # <-- no timeout guard
await Promise.race([
    new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))),
    new Promise(r => setTimeout(r, 1000)),
]);

Every other async step in this expression has an explicit timeout/race guard against headless quirks (the load event, and explicitly the rAF call per the comment about requestAnimationFrame never ticking in some headless modes). document.fonts.ready has none. Under Brave's headless implementation this promise appears to never resolve, so the whole Runtime.evaluate call presumably times out or errors at the CDP layer, which is silently swallowed in capture_url:

try:
    page_height = result["result"]["value"]
except (KeyError, TypeError):
    page_height = tile_h

...falling back to tile_h with no logging, so the failure is invisible to the caller.

Suggested fix

  • Wrap document.fonts.ready in the same kind of Promise.race(..., setTimeout(...)) guard already used for the rAF step.
  • Log (at least at debug/warning level) when the page_height fallback path is hit, so this failure mode isn't silent.

Environment

  • pixelrag==0.4.0 (via uv tool install pixelrag)
  • macOS (Darwin 25.6.0, arm64)
  • CHROME_PATH pointed at Brave Browser (no Google Chrome/Chromium installed on this machine)
  • Backend: cdp (default). Note --backend playwright is documented in --help and the module docstring but isn't actually wired up in render_urls() in this version — it raises Unknown backend: 'playwright'. Choose 'cdp'. — so it isn't currently a usable workaround.

Happy to provide more logs/repro if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions