fix(screenshot): never let the text walk fail a capture#845
Conversation
`waitForReady`'s snapshot built a TreeWalker over `document.body || document.documentElement`. A document that is mid-parse or detached has neither, and `createTreeWalker` throws on a null root. Because that is not a context-destroyed error, the gate rethrows it by design, so a survivable DOM state failed the whole render. Observed on production 3.42.8, three times in six hours, on documents of 6, 25 and 426 pages: chunk:failed ... message="Failed to execute 'createTreeWalker' on 'Document'" Guard the root, and wrap the walk so a DOM mutating underneath it keeps the count taken so far. `text` is only a paint signal: not being able to read it means "no text seen yet", which leaves the gate conservative — it re-polls, or falls through to the blank-page screenshot check — instead of failing a capture that would otherwise succeed. The rethrow for genuine evaluation errors is unchanged; its test still passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughSnapshot readiness sampling now handles missing document roots, detached DOM traversal, and traversal mutations without aborting. A live test verifies that removing the document root produces zero measurements and a non-timeout result. ChangesSnapshot readiness resilience
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/screenshot/src/wait-for-ready.js`:
- Around line 130-133: Safeguard the return payload in the wait-for-ready
capture logic so accesses to document.documentElement.scrollHeight and related
dimensions remain safe when documentElement is null; reuse the existing
document/body fallback where appropriate. Also update the viewport dimension
fallback near the initial width/height calculations to avoid dereferencing
document.documentElement when window.innerWidth or innerHeight is falsy.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: bc3cf399-3c20-48a8-bed8-ae61902a227f
📒 Files selected for processing (1)
packages/screenshot/src/wait-for-ready.js
The #845 guard covered the TreeWalker root but the same null-documentElement state still threw one line before the return: `scrollHeight` (and `clientWidth`/ `clientHeight` behind a zero innerWidth) dereferenced the root unguarded, so the production error moved instead of dying. Resolve the root once and degrade every read to zero; a transient height 0 just resets the quiet window and re-polls. Live regression test reproduces the exact state (document.removeChild of the root) and fails without the guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1ecHpTgQwD4cH7vutmCon
Bug
waitForReady's snapshot builds a TreeWalker overdocument.body || document.documentElement. A document that is mid-parse or detached has neither, andcreateTreeWalkerthrows on a null root.That throw is not a context-destroyed error, so the gate rethrows it by design (added in #841 so genuine evaluation bugs would not masquerade as timeouts). The effect is that a survivable, transient DOM state fails the entire capture.
Observed on production 3.42.8, three times in six hours, across very different documents:
Fix
Guard the root, and wrap the walk so a DOM mutating underneath it keeps whatever was counted:
textis only a paint signal. Failing to read it means "no text seen yet", which leaves the gate more conservative, not less: it re-polls, or falls through to the blank-page screenshot check. It can never make a blank page look painted.The rethrow for genuine evaluation errors is untouched, and its test still passes.
Tests: 8 pass.
🤖 Generated with Claude Code
Note
Low Risk
Localized defensive changes to screenshot readiness heuristics; behavior when the DOM is normal is unchanged, and the failure mode becomes more conservative polling rather than looser capture.
Overview
Fixes production capture failures where
waitForReady's in-pagesnapshot()threw on transient DOM states (mid-parse or detached documents with nobody/documentElement), which bubbled out ofpage.evaluateand aborted the screenshot because only context-destroyed errors are retried.snapshot()now routes layout reads through a guardedroot(scrollHeight, viewport fallbacks), only creates aTreeWalkerwhendocument.body || rootexists (otherwise a no-op walker), and wraps the text walk intry/catchso DOM changes mid-scan keep partial counts instead of killing the poll. Missing text is treated as "not painted yet," so the gate stays conservative and re-polls rather than falsely declaring readiness.Adds a live browser test that removes
documentElementand asserts the gate returns zeroed signals without timing out as a hard failure.Reviewed by Cursor Bugbot for commit f70e7d2. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit