Pan to offscreen elements in video mode - #22
Conversation
Flesh out the ask 'have video mode scroll down to show elements that are not visible'. Chosen design: never scroll in-test; capture a beyond-viewport screenshot at highlight time and fabricate an eased camera pan in post, consistent with video mode's record-facts/render- effects philosophy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
commit: |
The rendered video must show a below-the-fold waitFor target (smooth pan in, outlined hold, pan back) while the raw recording proves the live page never scrolled. Currently the highlight records an out-of-frame rect and the element never appears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
At highlight time, an offscreen target now captures a beyond-viewport clipped screenshot (fullPage+clip; Chromium renders it without scrolling) plus the current and destination scroll offsets. The renderer replaces the capture span with a synthetic eased crop pan over that tall still: travel to the element, hold the usual highlight, and pan back to the live scroll position. The live page never scrolls: no scroll event fires, scroll position is unchanged, and IntersectionObservers never see the element. The one cost is a single zoomed-out frame Chromium leaks into the screencast while capturing beyond the viewport; the pan piece consumes that span from the source (plus a settle margin) so it never reaches the rendered video. Also fixes a pre-existing race this exposed: a selector-driven trim start resolves over the protocol and could land 1-2ms after a highlight recorded at effectively the same moment, silently dropping that highlight from the render. A trim start within one source frame after a highlight start now moves back to it. The previously intermittent text-cursor spec passes 8/8 with the clamp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Playwright auto-scrolls the live page for the click, so the rendered pan must land exactly where post-click footage resumes instead of panning back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-action highlights (click, check, ...) on offscreen elements now pan too. The capture extends one viewport beyond the estimated destination, and after the action completes the pan adopts the browser's actual scroll position, so the synthetic pan lands exactly where live footage resumes — replacing Playwright's instant auto-scroll jump with a smooth travel. waitFor pans return to the live scroll position; action pans stay. Fill keeps its existing reveal pipeline unpanned. Focused metadata specs cover the horizontal pan axis and the inner-scroll-container fallback, where window scrolling cannot reveal the element and plain highlighting is kept. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A waitFor pan followed immediately by a click pan on the same element currently pans all the way back to the origin before panning down again. Once the awaited element is on camera it should stay on camera. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A waitFor pan directly followed by another pan no longer travels all the way back to the live scroll position just to pan down again: the return leg is suppressed and the next pan enters from the previous destination — a zero-length entry when both point at the same view, so the hold simply continues into the action. Also files a task for a pre-existing pointer-tail spec flake observed identically on main. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Centering matches Chromium's own scroll-for-action alignment, so a wait-then-act pan pair hands over with zero travel, and keeps the held element clear of the caption band at the bottom of the frame. The demo spec is now a committed public-behavior test (and the PR media fixture): its step captions narrate which operations really scroll the live page and which only pan the video. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Match the todo-app baseline's 480x720 viewport so raw and rendered recordings can be compared at useful scale in one PR video. Document the review-media follow-up in the task.
Resolve the video-mode overlap by keeping main's unbounded still inputs for chronological fill-frame rendering while retaining the scroll-pan metadata, filters, and coverage. Also adopt main's readable Playwright fixture naming.
Flatten the demo's test.step wrappers while keeping its live-scroll and pan-mode assertions. The checked-in PR media fixture now renders without captions obscuring the narrow side-by-side view.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 50c8e66. Configure here.
| ...options.highlight.rect, | ||
| x: documentRect.x - scroll.x, | ||
| y: documentRect.y - scroll.y, | ||
| }; |
There was a problem hiding this comment.
Stay pan breaks when scroll unchanged
Medium Severity
finalizePanHighlightAfterAction always adopts the post-action scroll, but when the action does not move the page (blur, click({ force: true }), or a handler that scrolls back), pan.to collapses onto pan.from. panDurationMs still enforces PAN_MIN_MS, and the hold rect is rewritten to offscreen coordinates, so the render inserts a idle pan and an invisible highlight before live footage resumes.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 50c8e66. Configure here.


When a test waits for or clicks an element outside the viewport, the rendered video now pans to it — a smooth eased camera travel, the usual pointer/outline hold, then onward — without the test ever scrolling the page.
What really scrolls in Playwright, and what the video does
waitFor(),expect(...).toBeVisible()and other queriesclick(),check(),hover()and other interactionsscrollIntoViewIfNeededbefore actingTwo refinements keep multi-scroll tests watchable:
waitForpan directly followed by another pan (the extremely common wait-then-click) skips its return leg; the next pan enters from the previous destination — zero travel when both point at the same view, so the hold simply continues into the action.The stay-pan adopts the browser's actual scroll destination after the action, so the pan lands pixel-exact on the resumed footage — replacing what used to be an instant jump-cut.
fill()keeps its reveal pipeline unpanned, and elements clipped by inner scroll containers keep plain highlighting.How it stays true to the record-facts/render-effects philosophy: at highlight time video mode captures a beyond-viewport screenshot (
fullPage+ document-coordinateclip— Chromium renders offscreen pixels without scrolling: no scroll event, scroll position untouched, IntersectionObservers never fire, so lazy-loading can't trigger). The renderer animates a viewport-sized crop over that tall still with cosine-eased ffmpeg time expressions.Known costs, handled: Chromium implements beyond-viewport capture by momentarily resizing the renderer, leaking one zoomed-out frame into the raw screencast; the pan piece consumes that span from the source so it never reaches the rendered video. The page observes a single no-op
resizeevent with unchanged dimensions.Also fixes a pre-existing race this work exposed: a selector-driven
trimStartresolves over the protocol and could land 1–2ms after a highlight recorded at effectively the same moment, silently dropping that highlight from the render. A trim start within one source frame after a highlight start now moves back to it (the flaky text-cursor spec went from 2/3 failing to 8/8 passing).Raw vs rendered
The checked-in demo —
spec/scroll-pan-demo.spec.ts— uses the same narrow 480×720 viewport as the todo-app spec. It has targets both below the fold (success card, "View summary" button) and above it (header badge, "Copy status link" button), and asserts the live scroll position plus every recorded pan mode. The clip is caption-free so none of the page or motion is obscured.Raw (left): waits never move; the two clicks cause instant browser scrolls. Rendered (right): waits pan smoothly, adjacent wait/click pans hand over without yo-yoing, and click pans meet the live footage at its real scroll destination. The raw stream ends first and holds its final frame while the rendered timeline completes.
scroll-pan-raw-vs-rendered.mp4
Todo-app baseline
Standard visual baseline — no pans trigger here (everything fits the viewport), pacing unchanged.
todo-app.mp4
Raw recording (recording/rendering behavior changed on this branch, per repo guidance):
todo-app-raw.mp4
Validation
108 passed / 4 provider-gated skips. After merging current
main: 8 focused pan/fill-ordering checks passed, plus typecheck, build, and publint. Caption-free narrow demo: 1/1 passed. Task file: tasks/video-mode-scroll-pan.md🤖 Generated with Claude Code
Session: c46b4c3b-ccee-4225-aa58-7d8d22fcba39
Note
Medium Risk
Large changes to video-mode recording and ffmpeg rendering paths; behavior is heavily tested but regressions in highlight timing, trim, or non-pan highlights are possible.
Overview
video mode now pans the rendered video to offscreen highlight targets using a beyond-viewport screenshot and ffmpeg crop animation, while the live page never scrolls for
waitFor(and similar queries).waitForhighlights use pan down, hold, pan back; actions likeclickuse pan down and stay, withfinalizePanHighlightAfterActionaligning the synthetic pan to Playwright’s real post-scroll position so live footage resumes without a jump-cut.fillstays unpanned; targets clipped by inner scroll containers skip panning. Adjacent pans hand over (suppress return legs) so wait-then-click does not yo-yo.Metadata adds
VideoModePan; the renderer consumes Chromium’s capture-flash span so it never appears in the output. A trimStart race fix keeps highlights that land within one frame after selector trim start. README documents behavior and limitations;spec/scroll-pan-demo.spec.tsand FFmpeg/metadata specs cover pans.Reviewed by Cursor Bugbot for commit 50c8e66. Bugbot is set up for automated code reviews on this repo. Configure here.