Skip to content

video-mode: render a popup that is left open correctly - #45

Merged
mmkal merged 4 commits into
mainfrom
claude/dazzling-bardeen-f51f32
Oct 12, 2026
Merged

mmkal merged 4 commits into
mainfrom
claude/dazzling-bardeen-f51f32

Conversation

@mmkal

@mmkal mmkal commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

A popup the test leaves open rendered wrongly: it sat over the opener's final state, turned solid black for about a second, and the video ended on a black frame.

const popup = await popupPromise;
await popup.getByRole("button", { name: "Authorize" }).click();
await popup.getByText("Your code is 1234").waitFor();

// the popup stays open; the test goes back to the page that opened it
await page.getByLabel("Code").fill("1234");
await page.getByRole("button", { name: "Continue" }).click();

Before (main, spec/popup-video-left-open.spec.ts):

popup-left-open-before.mp4

After:

popup-left-open-after.mp4

The popup now shows over the opener as it was at that moment, slides away when the test goes back to the opener, and the video ends on the opener's last state.

Cause

  1. The calibration cover was never found at 1280x900. Playwright pads a screencast that doesn't scale evenly into the video (800x562 here) with a 2px gray strip. The detector wanted every pixel to be cover, so it never matched and calibration fell back to endpoint arithmetic, about 0.9s late. That put the popup over opener footage from 0.9s later, left the popup's own cover inside its overlay window (the black popup), and ran the render into the opener's cover (the black last frame).
  2. Nothing modelled "the test went back to the opener". A popup the test never closed stayed up until teardown, over the opener's actions.
  3. Teardown counted as test time. Popup recorders finalize first and each spends 1.1s settling; the render end and the popup's closedAt were stamped after that.

Changes

Change Purpose
detectCalibrationCoverStartMs crops to the screencast's own area (viewport scaled into the video, 4px inset) The cover can't paint the recorder's padding. Any viewport that pads (800x600 in the specs too) silently ran on the fallback.
Wall→raw offset: the cover reading (clamped at 0, as before) unless the endpoint reading is earlier The endpoint reading lands a second late when the page is still producing frames at close; it can now only pull the offset earlier. Otherwise this is what videos already ran on: unpadded viewports on the clamped cover reading, padded ones on the endpoint.
footageLeadInMs: the composite holds the opener's footage back by the part of a negative cover reading the main timeline ignored The recorder's first frame lands ~100ms after clock zero (more on CI), so the opener's footage ran that far ahead of the popup. The composite re-times footage anyway. Ordinary videos are left as they were, see below.
childCompositeLayers: a popup closes at the first of its close event, the first opener highlight after its last one, or the end of its footage A popup left open leaves the screen when the test goes back to its opener.
VideoModeFreeze, compositeTimeMs / compositeEndMs / compositeVideoTimeline; compositeChildOverlays applies freezes with setpts The exit animation needs time the footage doesn't have (the opener's next action follows within ms). The composite holds every source for the exit, and the render timeline moves onto that clock in one place.
Removed the loop that shifted opener highlights past a popup's fade Superseded: highlights after an exit move with their footage instead of away from it. Self-closing popups exit the same way.
state.endedAt, stamped by the first finalizer; the popup finalizer no longer stamps closedAt Teardown isn't test time. A popup closed by teardown is recorded as never closed, which is what the closedAt doc already said.
A popup never starts on the first frame; its footage from before composite time 0 is trimmed When the endpoint reading takes the offset negative, a popup opened in the first ~100ms landed at time 0, over everything before it. Left in, the early footage made ffmpeg drop the whole overlay.
CHILD_BIRTH_OPENER_HOLD_MS: opener frames around a popup's birth are dropped Chrome paints "Debugger paused in another tab" over the opener while Playwright holds the new popup paused; it sometimes reaches the screencast.
CHILD_FINAL_STATE_HOLD_MS (150ms, only if the test ends that soon after a popup highlight); projectChildHighlight gives every popup highlight a slice inside the overlay window Popup holds freeze composite footage, so the popup's last state has to be recorded before its cover replaces it.
spec/popup-video-left-open.spec.ts Asserts the render's storyboard (signed-out app → popup over signed-out app → signed-out app → signed-in app), plus a popup still open at test end. On main both fail with "black" scenes.
README sentence, task file Docs.

Worth a look

Self-closing popups now exit on a freeze too. The existing demo (spec/popup-overlay-demo.spec.ts), main first, then this branch. Main's has black frames as the popup leaves:

popup-demo-before.mp4
popup-demo-after.mp4

Verification

  • Full suite 3x over: 477 passed. Popup specs 6x over: 96 passed. video-mode-ffmpeg.spec.ts 4x over: 152 passed.
  • The cropped detector finds the cover in the original recordings that showed the bug (3640ms and 1360ms; undefined before).
  • spec/video-mode-ffmpeg.spec.ts "returns from an offscreen blur pan…" failed once in an earlier 3x full-suite run, and once on main in the same kind of run: the recording captured nothing but the cover. Not addressed here.

Not done

  • A popup still open at the end is cut away at the final hold (the final frame is a screenshot of the opener alone).
  • The popup backdrop desaturates the page as well as dimming it.
  • Step captions blank for the length of an exit freeze when one step ends before it and the next starts after.
  • Popup recordings are still calibrated from the cover alone.
  • Ordinary (no popup) videos still run their footage ahead of the annotations when the recorder starts late and the endpoint reading can't confirm it. I tried correcting it everywhere, two ways. Moving annotations earlier takes the render off the test's clock, and the frame-exact specs sample at clock-derived times: two failed on CI, where instant specs get ~80ms of footage before the cover. Padding the footage's start instead shows whatever frame the recorder caught first, which under load is a pan capture flash. Doing it properly needs the specs to read the footage start from metadata.

Baseline (spec/todo-app.spec.ts)

Rendered, 20.52s, same length as main:

todo-app-rendered.mp4

Raw Playwright recording:

todo-app-raw.mp4

Coding agent session: "Fix video mode's popup compositing", local_d2f641a1-e7f9-4d67-99c1-df2c2ee4f4aa (CLI 51fbfdea-aef4-46e0-ac4d-8e7700eb7e15)

🤖 Generated with Claude Code

mmkal and others added 4 commits October 5, 2026 01:03
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A popup the test leaves open (acts in it, then carries on in the opener)
rendered over the opener's final state, turned solid black for a second,
and the video ended on a black frame.

- Cover detection only looks inside the screencast's own area. Playwright
  pads a viewport that doesn't scale evenly into the video (1280x900 into
  800x562 leaves a 2px gray strip), so the every-pixel check never matched
  and calibration fell back to endpoint arithmetic, ~0.9s out.
- The wall->raw offset may be negative (the recorder's first frame lands
  ~100ms after clock zero) and is the earlier of the cover and endpoint
  readings: each fails late in its own way, and late is what shows.
- A popup still open slides away when the test goes back to its opener.
  The composite splices in a short freeze for the exit, since the opener's
  next action follows within milliseconds; self-closing popups exit the
  same way and the old highlight-shifting loop is gone.
- The test's end is stamped before popup recorders settle, and a popup
  closed by teardown is recorded as never closed.
- A popup never starts on the first frame, its footage from before time 0
  is trimmed, the opener's frames around a popup's birth are dropped
  (Chrome's "Debugger paused in another tab"), and a popup's last
  highlighted state is held 150ms before its teardown cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ge in the popup composite

CI showed what moving annotations earlier costs: the render starts that
far into the test's clock, and the frame-exact specs sample rendered
frames at clock-derived times. On the slow runner, where instant specs
get ~80ms of footage before the cover, two failed outright.

The main timeline goes back to its old rule (cover reading clamped at
0), except that the endpoint reading may pull it earlier and can no
longer push it later. The popup composite, which re-times footage
anyway, holds the opener's footage back by the part of the cover reading
the main timeline ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Oct 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/middlewright@45

commit: baac1e7

@mmkal
mmkal marked this pull request as ready for review October 12, 2026 16:44
@cursor

cursor Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes ffmpeg compositing, timeline calibration, and teardown timing in a large, timing-sensitive plugin; regression risk is mitigated by new storyboard specs but behavior shifts for all popup overlays (including self-closing exit freezes).

Overview
Fixes videoMode compositing when a test leaves an OAuth-style popup open and continues on the opener—wrong backdrop timing, a black popup tail, and a black final frame.

Calibration: detectCalibrationCoverStartMs now crops to the scaled viewport inside the recorder (ignoring Playwright’s gray padding), so cover detection works on uneven fits like 1280×900. Wall→raw offset uses the cover reading clamped at zero but can be pulled earlier by the endpoint reading (no longer ~1s late). Popup composites apply footageLeadInMs so opener footage stays aligned when the cover reading is slightly negative.

Left-open popups: Overlays end at popup close, first opener highlight after the popup’s last action, or footage end. Exit uses composite freezes (VideoModeFreeze, compositeVideoTimeline) so the slide-out has time without ghosting under the next opener action. Teardown is excluded via state.endedAt (first finalizer); teardown-closed popups keep closedAt unset.

Adds spec/popup-video-left-open.spec.ts (ffmpeg storyboard assertions) and a README note that an abandoned popup slides away when the test returns to its opener.

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

@mmkal
mmkal merged commit a5d8f4e into main Oct 12, 2026
3 checks passed
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.

1 participant