Skip to content

Highlight visible waitFor results in video mode - #15

Merged
mmkal merged 25 commits into
mainfrom
feature/video-mode-waitfor-highlight
Aug 3, 2026
Merged

Highlight visible waitFor results in video mode#15
mmkal merged 25 commits into
mainfrom
feature/video-mode-waitfor-highlight

Conversation

@mmkal

@mmkal mmkal commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Visible locator.waitFor() results now receive the same post-rendered pointer/outline hold as clicks and other meaningful actions. The elapsed wait remains dead air, and the synthetic hold does not add its configured duration to test runtime.

await page.getByText("Todo details").waitFor();
// The rendered video moves to and holds “Todo details” for 1000ms by default.

The existing highlight.duration controls the hold. Callers can retain the old behavior with skipMethods: ["waitFor"]. Hidden or detached terminal states do not produce an empty highlight.

Video mode's own selector and dialog-overlay waits bypass action middleware, so internal bookkeeping cannot appear as user-facing highlights.

Rendered holds no longer calibrate their source timeline by matching the final page screenshot. That heuristic could select an identical state from seconds earlier—or miss a final paint that Playwright never encoded—and replay old footage. The settled recorder endpoint is now the sole clock marker, with a frame-level regression for a final state that repeats an earlier state.

videoMode() now uses the showcase pacing without extra options: dead air is capped at 300ms, pointer highlights hold for 1000ms, the final frame holds for 1000ms, and startup trimming remains automatic.

Showcase

todo-app.spec.ts is a client-only kitchen-sink example with an in-memory TodoDB. The explicit test flow changes db.setDelay(ms) between interactions, then:

  • signs in through an “Enter the password” prompt;
  • creates two todos, one by one;
  • opens both cards and waits for each delayed body;
  • displays spinner-backed progress for every fake database operation.

The reviewed raw/rendered comparison is attached below. The deliberately slow raw sign-in makes the dead-air compression easy to compare.

Validation

  • 91 passed, 3 provider-gated skips
  • typecheck
  • build
  • publint
  • frame audit: 10 stale sign-in matches before the fix, 0 after
  • 30 local review videos resolve with no missing sources, including raw and before/fixed todo recordings

Codex session: 019fc75f-5c86-7f43-90c6-aaafd1ebb46e

Videos

Rendered

todo-app.webm

Raw Playwright recording

todo-app-raw.webm

Raw vs rendered

Raw is left; rendered is right.

todo-raw-vs-rendered.mp4

Note

Medium Risk
Changes default video pacing and rendering clock logic in video-mode.ts, which can alter every rendered artifact; behavior is heavily tested but regressions would be visual/timing rather than test-runtime failures.

Overview
Video mode now treats successful visible waitFor() like other locator actions: the wait stays dead air (still compressible via deadAirThreshold), then the rendered video points at and holds the resolved element using the shared highlight mode and duration. Opt out with skipMethods: ["waitFor"]; hidden/detached results do not get an empty highlight. Internal trim-start and overlay waits use waitFor_original so bookkeeping does not show up as user highlights.

Defaults shift to review-friendly pacing without extra options: dead-air cap 300ms, final hold 1000ms (was 3000ms), and waitFor is no longer skipped by default.

Timeline calibration drops final-page screenshot matching (it could latch an earlier identical frame and replay stale footage). Source timing now relies on the settled recorder endpoint; post-resolution wait highlights advance the source cursor by a frame before resuming live footage.

Docs & process: README/AGENTS describe the new behavior and require attaching the todo-app.spec.ts rendered video (and raw when recording changes) on every PR.

Fixture: New spec/todo-app.spec.ts kitchen-sink demo (TodoDB, spinner UI, prompt login) as the shared visual baseline. A skipped spec documents a future-fill compositing bug for follow-up.

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

@pkg-pr-new

pkg-pr-new Bot commented Aug 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: ec7c933

mmkal added a commit that referenced this pull request Aug 3, 2026
## Summary

Render `alert()`, `confirm()`, and `prompt()` wholly in video
post-production with ASS shapes and text. The raw Playwright recording
stays clean: video mode freezes the last app frame before the native
dialog, draws a deterministic dialog over that frame, then resumes after
Playwright resolves the real dialog.

```ts
page.once("dialog", (dialog) => dialog.accept("secret"));
await page.getByRole("button", { name: "Sign in" }).click();

// Rendered video: neutral prompt → typed text → selected OK.
// Raw video: application page only.
```

This removes the dialog DOM overlay, init script, browser screenshots,
and per-dialog image layers. Playwright's real `Dialog` still owns
listener order, automatic dismissal, accepted text, and accept/dismiss
behavior.

## What changes

- Freeze one clean raw frame for the full synthetic-dialog interval.
- Draw dimming, panel, wrapped message, prompt input, and buttons in
`video-mode-dialogs.ass`.
- Reveal prompt responses one Unicode grapheme at a time.
- Keep both actions neutral until typing ends; select the resolved
action blue once, with the pointer composited above it.
- Preserve alert, confirm, prompt, back-to-back dialog, no-listener
auto-dismiss, dead-air, pointer, and final-hold behavior.

## Trade-offs

| Area | ASS approach |
| --- | --- |
| Code | `src/plugins/video-mode.ts` is +565/−303 lines versus `main`
(+262 net). Layout and ASS helpers replace page lifecycle and
screenshot-layer code. |
| Render cost | Adds one libass filter inside the existing FFmpeg graph,
not another encode. It removes browser DOM mutation and per-dialog
screenshot inputs. No benchmark is claimed. |
| Layout | Deterministic rectangles and explicit wrapping are easy to
reason about, but text metrics are approximate and complex/native
browser layout is intentionally out of scope. |
| Portability | No new package or browser-context requirement. It uses
FFmpeg's existing ASS/libass support, already used by video-mode
captions and address bars. |
| Fidelity | Stable across browsers and immune to live-capture frame
leaks, but it is a designed synthetic dialog rather than a pixel copy of
any browser's native prompt. |

## Review videos

Focused prompt — rendered:


https://github.com/user-attachments/assets/0b33eb3a-ba6d-4b15-972a-66bcdf90d932

Focused prompt — raw:


https://github.com/user-attachments/assets/162a8e53-3d2d-4994-adf5-00ce8e666309

Todo app from #15 — rendered:


https://github.com/user-attachments/assets/edd3c617-42cd-4e3d-9126-535bcd7aab8f

Todo app from #15 — raw:


https://github.com/user-attachments/assets/83eeae7e-d389-4ea7-9daf-7439a7640f2c

## Validation

- 31 FFmpeg integration specs, including exact 25 fps frame-order and
raw-artwork regressions
- Prompt sequence stress: 10/10 passes across 5 workers
- Full suite: 98 passed, 3 provider-gated skipped
- `pnpm typecheck`
- `pnpm build`
- `pnpm exec publint`

Coding agent session: `019fc7f7-de88-7690-8d0a-31790c4dbc29`

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches core video-mode FFmpeg rendering and dialog timing/metadata;
behavior is heavily tested but regressions could affect rendered
artifact fidelity or highlight ordering.
> 
> **Overview**
> **videoMode** no longer injects synthetic dialog DOM, init scripts, or
per-dialog screenshots. Dialog scenes are drawn in post with ASS
(`video-mode-dialogs.ass`): freeze the last clean app frame before the
native dialog, overlay panel/message/buttons, then resume raw footage
after resolution.
> 
> Prompt handling is tightened: metadata can carry **`defaultValue`**
separately from **`promptText`** (e.g. explicit empty accept vs Unicode
default). Rendered prompts reveal text by grapheme, keep OK/Cancel
neutral until typing finishes, then highlight the resolved action in
blue—ordering is covered by new FFmpeg pixel/frame regressions and
raw-video checks that synthetic white panels never appear.
> 
> Playwright’s real **`Dialog`** still drives accept/dismiss, listener
order, and auto-dismiss; README documents the ASS-only path. Large
refactor in `video-mode.ts` (~+565/−303) replacing overlay lifecycle
with layout helpers and an FFmpeg **ass** filter step.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
95d27a5. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@mmkal
mmkal marked this pull request as ready for review August 3, 2026 16:26

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 73633c7. Configure here.

Comment thread spec/todo-app.spec.ts
@mmkal
mmkal merged commit 41f9b15 into main Aug 3, 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