Repository navigation
Record a still page that ends before its first frame - #29
Merged
Merged
Conversation
Merged
brynary
added a commit
that referenced
this pull request
Sep 27, 2026
Brings the finished no-frames fix from #29: retries for a page that has not painted, a white frame when there is still none, and a skip only when ffmpeg fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brynary
added a commit
that referenced
this pull request
Sep 27, 2026
Brings the blank-recording warning from #29. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brynary
added this pull request to stack #55
October 5, 2026 13:05
With --video on Chromium, a short flow on a still page could fail as a runtime error: "video recording failed: the page produced no frames". On macOS CI this failed one run of combined_report_keeps_embedded_media_and_missing_scenarios_offline, and the rerun passed. Chrome sends a screencast frame only when the page paints, and the frame reaches the shim some time after the paint: 5 to 450 ms on a loaded machine. The recorder starts the screencast on the blank page, and a flow such as VISIT then SCREENSHOT can reach endFlow first. The recorder then stopped the screencast, dropped the frame on its way, and failed the file. With logging added, each such frame arrived 10 to 33 ms after the stop. Now, when no frame has arrived, the recorder captures the page once with Page.captureScreenshot and holds that frame from the start of the recording at the constant rate. When the capture fails or takes more than 5 seconds, as for a crashed page, the shim skips the recording and says why in the new endFlow field videoSkipped. Rust reports it as a warning, and the file's result does not change, as for SCREENSHOT. The recorder now takes a frame source, so its unit tests can use a page that sends no frame. Before the fix, the CI test failed 3 of 50 runs with 10 copies at once on low-priority cores, and a new test of four short flows failed 20 of 20 runs. After the fix, they pass 100 of 100 and 20 of 20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A skipped recording kept runtime.videoFps in the JSON report, but the machine-output doc says the field is absent without a recording. Rust now clears it when the shim skips the recording. The fake shim starts a 60 fps recording before it reports the skip, so the test sees the field go away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With --video on Chromium, an ffmpeg failure at endFlow failed endFlow, so the file became a runtime error (exit 3). But a recording is evidence, not a result (SPEC 14.1). Now the shim discards the recording and says why in videoSkipped, as it does for a page that it cannot capture. Rust reports a warning, and the file's result does not change. A missing ffmpeg at startFlow is still a runtime error (SPEC 13). The recorder now waits for ffmpeg's "close" event, not "exit". Node can send "exit" before the last stderr output, and the warning then loses ffmpeg's error text. A new unit test runs a fake ffmpeg that writes part of its output, prints an error, and exits with status 1. The recorder skips the recording with that error and leaves no file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On GitHub Actions ubuntu-24.04, a lone VISIT /stable.html in short_video_flows_on_still_pages_pass_with_a_recording passed with "warning: video recording skipped: the page produced no frames, and capturing it failed: ... Unable to capture screenshot". The test expects a recording, so it failed. Chrome gives that error when the page has not painted yet, as right after a navigation or on a new tab: it has nothing to copy. With 10 copies of the test at once on low-priority cores, the old recorder failed 7 of 250 runs this way: 6 times right after a VISIT, and once on the blank page of an HTTP-only flow. Each failed capture took 2 to 11 ms. With logging added, a new capture 25 ms later, or the one after it, worked every time, within 360 ms. Now, when a capture finds nothing painted, the recorder waits 50 ms and tries again, for up to 1 second. The screencast still feeds the recorder while it tries, so a frame that arrives is used as a capture would be. When the recorder still has no frame, or the capture fails for another reason, as for a crashed page, it holds a white frame of the viewport's size for the whole recording, as Playwright's recorder does. All the captures together still stop after 5 seconds. Only an ffmpeg failure skips a recording now. The white frame is a JPEG from jpegjs in playwright-core's utilsBundle, the encoder that Playwright's recorder uses for its own white frame. The package's export map includes that bundle, and the shim already takes the image comparator from its core bundle. Node has no JPEG encoder, and Playwright's ffmpeg decodes only MJPEG and VP8, so a PNG frame cannot go into the stream. The frame source now gives the retry time, so the unit tests use 200 ms, not 1 second. New tests cover a capture that works on the second try, a page that never paints (its decoded frames are white), a crashed page (a white frame with no retry), and a screencast frame that arrives between tries. The fake shim test now reports an ffmpeg failure, the only reason for a skip. After the change, under the same load, the short test passed 250 of 250 runs. For 100 of them, rounds of the old and the new recorder alternated, so both had the same load; the old recorder failed 4 of its 100. In the 1,000 new recordings, 7 captures found nothing painted, and each of those recordings got a frame on the second or third try, within 650 ms. No recording needed the white frame, and none was skipped. The combined-report test passed 100 of 100 runs, as it passed 50 of 50 before. Its 18 recordings without a frame at the stop needed no retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With --video on Chromium, a page that the recorder cannot capture, as a crashed one, gets a recording that holds a white frame. The report then showed a white video with no explanation. Now the shim says why in the new endFlow field videoBlank, and Rust reports "warning: video recording is blank: <reason>", masked like other warnings. The recording stays in the report with its frame rate, and the file's result does not change. A page that has not painted after 1 second of tries also gets the warning, with the reason "the page did not paint within 1000ms". Its video is white too, so it needs the same explanation. A capture that takes more than 5 seconds gives "capturing the page took more than 5000ms". For a crashed page, Chrome answers "cdpSession.send: Protocol error (Page.captureScreenshot): Internal error", and the warning shows that text after "capturing the page failed:". The recorder's unit tests now check the reason for a page that never paints and for a crashed page. A real frame or a working capture still gives no reason. The fake shim has a new FAKE_SHIM_VIDEO_BLANK option, and a new acceptance test checks the masked warning in stdout and in the JSON report, the passed status, the listed recording, and the frame rate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brynary
force-pushed
the
video-no-frames
branch
from
October 5, 2026 13:05
99a4bb4 to
5aa4432
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #28. Fixes the flaky
combined_report_keeps_embedded_media_and_missing_scenarios_offlinefailure on macOS CI (#27's first run):endFlow failed: ... video recording failed: the page produced no frames, exit 3.Root cause
On Chromium, Whirl's recorder starts the screencast in
startFlow, on the blank page beforeVISIT. Chrome sends a screencast frame only when the page paints, and each frame reaches Node some time after the paint. A short flow on a still page can reachendFlowfirst.ScreencastRecorder.stop()then had no frame and threw, and a failedendFlowturned a passing file into a runtime error.Evidence from temporary logging: in all 23 zero-frame cases, the first frame arrived 10–33 ms after
stop, andstopcame 7–21 ms after the start. Under load the first frame took 5–450 ms. Playwright's own recorder, used for Firefox and WebKit, writes a white frame when it gets none, so only Chromium failed.Fix
stop, the recorder captures the page once (Page.captureScreenshot, 5 s limit) and holds that frame from the start of the recording at the constant rate. Waiting for a first frame at start or at stop would slow every flow, still depend on timing, and still need this fallback.endFlowreturnsvideoSkippedwith the reason, Rust reportswarning: video recording skipped: …, the HTML report shows "Recording unavailable.", and the JSON report has noruntime.videoFps. Neither warning changes the file's result (SPEC §14.1).Follow-up: a page that has not painted
#30's CI then failed once on Ubuntu: right after a
VISIT, Chrome answered the capture with "Unable to capture screenshot", because the new document had not painted yet, and the recording was skipped. Two more commits finish the fix:15f3dae: an ffmpeg failure or a stall over 10 s atendFlownow skips the recording with a warning instead of failing the file. A missing ffmpeg atstartFlowis still a runtime error.7308c4c: when Chrome has nothing painted, the recorder waits 50 ms and captures again, for up to 1 s; a screencast frame that arrives meanwhile serves as well. When there is still no frame, or the capture fails for another reason, the recording holds a white frame of the viewport's size, as Playwright's own recorder does for Firefox and WebKit.99a4bb4: a recording that holds only a white frame now carries a warning,video recording is blank: …, that says why: the capture failed, as for a crashed page, or the page did not paint within 1 s. The recording stays listed. A failure to move the finished recording into place stays a runtime error.Under the same load (10 at a time on low-priority cores), the short-flow test failed 7 of 250 runs before and 0 of 250 after. The 7 unpainted pages each got a frame on the second or third try, within 650 ms. In 100 alternating rounds, the old shim failed 4 and the new shim 0.
The flake is gone
The original test failed only under heavy load: 10 copies at a time on low-priority cores (
taskpolicy -b) on an 18-core Mac.VISIT+SCREENSHOTthrough the CLI, 8 jobs, low priorityVISIT-only flows, 1 jobTests
short_video_flows_on_still_pages_pass_with_a_recording: four short flows in one browser exit 0, each with a recording that decodes.a_skipped_recording_is_a_warning_and_the_file_still_passes, through the fake shim, with the JSON and HTML reports.WHIRL_TEST_ALL_BROWSERS=1.mise run checkpasses. SPEC §13, the shim protocol doc, and the CHANGELOG describe the change.Notes
endFlowstill fails the file, as the protocol doc says.🤖 Generated with Claude Code