Skip to content

fix(pdf): detect covered content in readiness gate, keep zero-screenshot fast path#839

Merged
Kikobeats merged 2 commits into
masterfrom
fix/pdf-covered-fast-path
Jul 16, 2026
Merged

fix(pdf): detect covered content in readiness gate, keep zero-screenshot fast path#839
Kikobeats merged 2 commits into
masterfrom
fix/pdf-covered-fast-path

Conversation

@Kikobeats

@Kikobeats Kikobeats commented Jul 16, 2026

Copy link
Copy Markdown
Member

Supersedes #838.

Bug

PR #837's fast path trusts DOM paint signals (visible images, 200+ chars of text) to skip the blank-SPA screenshot poll. A fixed opaque overlay (loading screen) or white-on-white text passes every DOM signal while a capture stays white: blank PDF.

Why not #838

#838 fixes it with a probe screenshot on every painted page. That makes the fast path behaviorally identical to the poll loop's first iteration: painted pages pay one capture, the whole point of #837 (zero screenshots for painted content) is gone, and the fall-through case captures twice back-to-back.

Fix

Detect the cover inside the in-page readiness snapshot instead, keeping the fast path screenshot-free:

  • covered signal in waitForReady's snapshot: hit-test up to 4 counted content samples via elementFromPoint, walking up from the hit for an unrelated opaque viewport-covering layer (the walk stops at the first common ancestor, whose background always paints below its own descendants). A root-level scan of <body> children catches pointer-events: none overlays hit-testing can't see (how fading loaders are styled).
  • Text whose color matches its effective background (white-on-white) no longer counts toward the text paint signal.
  • The pdf fast path requires !ready.covered; fix(pdf): verify probe screenshot before skipping blank-SPA poll #838's probe is removed and the poll loop restored. A clean painted page takes zero captures again.
  • The cover check only runs when the fast path could trigger (painted > 0 || text >= 200), so blank pages pay nothing extra per poll.

Covered or suspicious pages fall through to the existing screenshot poll, where a capture verifies ground truth.

Cost

Page #837 #838 this PR
clean painted page 0 captures (but blank-PDF bug) 1 capture 0 captures
overlay page 0 captures, blank PDF probe + duplicate capture + poll poll from first capture
blank SPA poll (unchanged) poll (unchanged) poll (unchanged)

Known limits

Deep-nested pointer-events: none overlays (not direct <body> children) and exotic paint (white canvas, blend modes) escape DOM detection; documented in code. Those pages keep #837's residual risk instead of paying a capture on every request.

Validation

  • 7 new live-browser snapshot tests: overlay over text, overlay over image, pointer-events: none overlay, transparent click-catcher, opaque app shell containing the content, white-on-white text
  • 2 new pdf unit tests for covered fixtures; fast-path tests back to asserting zero captures with white-screenshot mocks, so any regression to capturing fails loud
  • 15/15 wait-for-ready live tests, 43/43 screenshot package tests, 11/11 pdf tests, standard clean

🤖 Generated with Claude Code


Note

Medium Risk
Changes when PDF auto-wait skips vs runs the screenshot poll, which directly affects blank vs correct output; scope is limited to readiness heuristics with broad new test coverage.

Overview
Fixes blank PDFs when DOM paint signals look ready but a full-viewport loading overlay (or similar) still blocks what a capture would show.

waitForReady snapshot now exposes covered and tightens the text signal: text that matches its effective background (e.g. white-on-white) no longer counts as painted. Coverage is detected in-page by hit-testing up to four content sample points (elementFromPoint + walk for unrelated opaque layers) and by scanning direct body children for pointer-events: none fixed/absolute overlays that hit-testing misses. The cover check runs only when the page could qualify for the painted fast path.

PDF waitUntil: auto only skips the screenshot poll when !ready.covered in addition to the existing painted / height / font checks, so overlay pages fall through to the existing white-screenshot poll without adding a probe capture on every painted page.

New live-browser tests cover overlays, click-catchers, app shells, and white-on-white text; PDF unit tests assert covered fixtures still poll screenshots while clean painted pages stay at zero captures.

Reviewed by Cursor Bugbot for commit 4a68266. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved readiness detection when page content is hidden beneath opaque overlays.
    • Prevented automatic screenshot checks from being skipped when visible text or images are covered.
    • Excluded white-on-white text from readiness signals to avoid false positives.
    • Improved handling of transparent overlays and overlays that do not intercept pointer events.
    • Added coverage validation for common app-shell layouts and painted content.

cursoragent and others added 2 commits July 16, 2026 04:08
The #837 painted/text fast path trusted in-page DOM signals alone. A page
with 200+ chars of SSR text (or a decoded hero image) under a fixed white
loading overlay passed waitForReady and skipped the white-screen poll,
shipping a blank PDF.

Take one cheap probe capture when the gate reports painted content; only
skip the poll loop when that capture is non-white. Adds a regression test
for the overlay case.
…screenshot fast path

A fixed opaque overlay (or white-on-white text) passes every DOM paint
signal while a capture stays white, so PR #837's fast path could ship a
blank PDF. PR #838 fixed it with a probe screenshot on every painted
page, which pays a capture on the fast path and makes the branch
equivalent to the poll loop's first iteration.

Instead, detect the cover in the in-page snapshot itself:

- `covered`: hit-test the counted content samples for an unrelated
  opaque viewport-filling layer, plus a root-level scan for
  `pointer-events: none` overlays hit-testing can't see
- skip text whose color matches its effective background (invisible on
  capture) when counting the `text` paint signal
- pdf fast path requires `!ready.covered`; the probe screenshot is
  removed, so a clean painted page takes zero captures again

Covered or suspicious pages fall through to the existing blank-SPA
screenshot poll, where a capture verifies ground truth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 85333182-af8e-4b34-b3d9-bc073c893503

📥 Commits

Reviewing files that changed from the base of the PR and between 895c897 and 4a68266.

📒 Files selected for processing (4)
  • packages/browserless/test/pdf.js
  • packages/pdf/src/index.js
  • packages/screenshot/src/wait-for-ready.js
  • packages/screenshot/test/wait-for-ready-live.js

📝 Walkthrough

Walkthrough

The screenshot readiness snapshot now reports whether painted content is obscured by an opaque layer. PDF waitUntil: 'auto' skips blank-page polling only for uncovered painted content, with new coverage fixtures and tests.

Changes

Covered readiness flow

Layer / File(s) Summary
Coverage detection and readiness state
packages/screenshot/src/wait-for-ready.js
The snapshot samples painted images and text, excludes same-color or transparent text, detects opaque viewport-covering layers, and returns covered in readiness state.
Coverage validation
packages/screenshot/test/wait-for-ready-live.js
Tests cover opaque and pointer-events-none overlays, transparent click-catchers, app shells, uncovered content, and white-on-white text.
PDF auto-wait integration
packages/pdf/src/index.js, packages/browserless/test/pdf.js
The auto-wait fast path requires content to be uncovered, with fixtures and tests verifying polling for covered text and images.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PageContent
  participant WaitForReady
  participant WaitUntilAuto
  participant ScreenshotCapture
  PageContent->>WaitForReady: report painted content and coverage
  WaitForReady->>WaitUntilAuto: return covered readiness state
  WaitUntilAuto->>ScreenshotCapture: retain blank/white polling when covered
  ScreenshotCapture-->>WaitUntilAuto: return non-white capture
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: detecting covered content in the PDF readiness gate while preserving the zero-screenshot fast path.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pdf-covered-fast-path

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 80.796% (-1.5%) from 82.326% — fix/pdf-covered-fast-path into master

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants