Skip to content

chore(bidi): implement screencasting using browsingContext.captureScreenshot#41569

Merged
Skn0tt merged 1 commit into
microsoft:mainfrom
hbenl:screencast
Jul 7, 2026
Merged

chore(bidi): implement screencasting using browsingContext.captureScreenshot#41569
Skn0tt merged 1 commit into
microsoft:mainfrom
hbenl:screencast

Conversation

@hbenl

@hbenl hbenl commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

This PR implements screencasting by requesting browsingContext.captureScreenshot every 40 milliseconds.
Unlike the implementations for other protocols, the screenshots are not scaled to the requested video size by the browser because browsingContext.captureScreenshot doesn't have a scaling option yet, so the trace viewer and the onFrame() callback of the Screencast API receive unscaled screenshots but the VideoRecorder scales them using ffmpeg.
I've changed the ffmpeg filter to use scale instead of crop to deal with screenshots that are larger than the requested video size. This should not make any difference with the other protocols: the screenshots from those protocols are never larger than the requested size and the VideoRecorder would have failed with larger screenshots anyway because the current filter expression uses the pad filter first which fails for frames that are larger than the requested size.

Fixes the following tests:

  • library/browsertype-connect.spec.ts:
    • "launchServer > should name video file after page guid when connecting to remote browser with artifactsDir"
    • "launchServer > should save videos to artifactsDir"
    • "launchServer > should saveAs videos from remote browser"
    • "run-server > should name video file after page guid when connecting to remote browser with artifactsDir"
    • "run-server > should save videos to artifactsDir"
    • "run-server > should saveAs videos from remote browser"
  • library/multiclient.spec.ts:
    • "screencast should deliver cached last frame to a new client"
  • library/trace-viewer.spec.ts:
    • "canvas clipping"
    • "canvas clipping in iframe"
    • "canvas disabled title"
  • library/tracing.spec.ts:
    • "should collect trace with resources, but no js"
    • "should produce screencast frames crop"
  • library/video.spec.ts:
    • all 24 tests that are currently failing
    • "screencast > should be 800x600 with null viewport" (unskipped)
    • "screencast > should work for popup" (unskipped)
  • library/screencast.spec.ts:
    • "onFrame receives viewport size"
    • "start dispose stops recording"
    • "start returns a disposable that stops screencast"
    • "start should work when recordVideo is set"

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

const w = this._size.width;
const h = this._size.height;
const args = `-loglevel error -f matroska -fpsprobesize 0 -probesize 32 -analyzeduration 0 -i pipe:0 -y -an -r ${fps} -c:v vp8 -qmin 0 -qmax 50 -crf 8 -deadline realtime -speed 8 -b:v 1M -threads 1 -vf pad=${w}:${h}:0:0:gray,crop=${w}:${h}:0:0`.split(' ');
const args = `-loglevel error -f matroska -fpsprobesize 0 -probesize 32 -analyzeduration 0 -i pipe:0 -y -an -r ${fps} -c:v vp8 -qmin 0 -qmax 50 -crf 8 -deadline realtime -speed 8 -b:v 1M -threads 1 -vf scale=w='min(iw,${w})':h='min(ih,${h})':force_original_aspect_ratio=decrease:eval=frame,pad=${w}:${h}:0:0:gray`.split(' ');

@Skn0tt Skn0tt Jul 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This file and especially these args are scary to touch, so we'll want to do some careful QA and eyeballing on the video results before merging. We already touched them once in this release, i'm unsure if that's good or bad.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did a QA pass. For even, in-bounds frames the new no-op scale is a bit-exact passthrough, zero pixels changed (800×600, 640×480, 800×400, 400×600 all identical).

On odd-dimensioned frames there's some difference because now libswscale engages, but all our existing producers will give even-dimensioned frames. (the comment on that is contradicting the impl)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

On odd-dimensioned frames there's some difference

Does this block this PR? Or is there anything else you want me to fix?

}

this._waitingForScreenshot = true;
const payload = await this._session.sendMayFail('browsingContext.captureScreenshot', {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems very expensive, especially on typical QA pages where paints are <25fps. Have you checked performance? I wonder how this compares to non-bidi Firefox, for example on todomvc, and on media and animation heavy pages like microsoft.com or github.com.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I've checked and didn't see any issues even at higher resolutions and frame rates. At some point it'll start "dropping frames" (i.e. skipping captureScreenshot calls if the previous one hasn't finished yet) but Playwright's screencast pipeline is designed to handle these "gaps" and the worst thing I saw happening was a slight stutter in animations in the video.
In my experiments, Firefox with Juggler always sends frames at 25fps, even on static pages. Compared to that, the only downsides with this implementation are the lack of browser-side scaling and the overhead of having to send a command for every screenshot (instead of receiving them as events).

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@Skn0tt Skn0tt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We discussed this on thursday with the team. Please inject the custom ffmpeg args from bidiPage.ts, so we don't need to worry about non-bidi video for this.

In general, it seems this PR is working around bidi protocol limitations, specifically missing dimensions on browsingContext.captureScreenshot, and a missing event-style delivery for frames. We think this area is where the bidirectionality of bidi can shine, for example by letting the producer decide the number of frames.

Let's land this in the spirit of progress, but please consider improving the protocol in this area. Video is only becoming more important with agents.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Test results for "MCP"

2 failed
❌ [firefox] › mcp/annotate.spec.ts:173 › user-initiated annotate downloads zip with feedback.md @mcp-macos-latest-firefox
❌ [firefox] › mcp/annotate.spec.ts:446 › should switch screencast to -s session on show --annotate @mcp-macos-latest-firefox

7089 passed, 1009 skipped


Merge workflow run.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

4 flaky ⚠️ [chromium-library] › library/video.spec.ts:645 › screencast › should capture full viewport `@realtime-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:680 › screencast › should capture full viewport on hidpi `@realtime-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:476 › screencast › should capture static page in persistent context @smoke `@chromium-ubuntu-22.04-node24`
⚠️ [playwright-test] › ui-mode-test-output.spec.ts:118 › should collapse repeated console messages for test `@ubuntu-latest-node22`

49494 passed, 1164 skipped


Merge workflow run.

@Skn0tt Skn0tt merged commit fdd0bc9 into microsoft:main Jul 7, 2026
47 of 49 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.

2 participants