From 3bbfea38cf871e2be186870201795a8ab9291a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 27 May 2026 11:26:38 -0400 Subject: [PATCH] fix(engine): use captureBeyondViewport on all CDP screenshot paths (#1094) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(engine): use captureBeyondViewport on all CDP screenshot paths Chrome's compositor rounds the viewport boundary inward under multi-tab load, clipping the bottom/right edge of tall portrait compositions (1080x1920). The explicit clip rect already constrains output to exact composition dimensions, making the viewport-boundary pre-clip from captureBeyondViewport:false both redundant and unreliable. Set captureBeyondViewport:true on all three CDP screenshot call sites: pageScreenshotCapture, captureScreenshotWithAlpha, and captureAlphaPng. Add portrait-edge-bleed regression test: 1080x1920 grid with bright magenta bottom rows, rendered with 4 workers. Any compositor clipping at the bottom edge drops PSNR sharply against the golden baseline. Closes #1009 * fix(engine): address review feedback on captureBeyondViewport - Add backref comments on captureScreenshotWithAlpha and captureAlphaPng pointing to pageScreenshotCapture for the rationale, so the next reader doesn't treat the flag as unintentional copy-paste - Note in test meta.json that the static grid fixture covers the capture-side clipping path but not the video-element compositor surface timing that produces the t≈37s self-healing in #1009 * test(producer): use video element in portrait-edge-bleed regression test Replace the static CSS grid with a 1080x1920 portrait video element — matches the original bug report shape where the compositor surface allocation timing causes the bottom-edge clipping. The video has a dark top region and bright magenta bottom 480px, so any viewport clipping at the bottom edge drops PSNR sharply. Baseline regenerated in Docker with 4 workers. --- .github/workflows/regression.yml | 2 +- .../engine/src/services/screenshotService.ts | 10 ++++-- .../tests/portrait-edge-bleed/meta.json | 13 ++++++++ .../portrait-edge-bleed/output/compiled.html | 31 ++++++++++++++++++ .../portrait-edge-bleed/output/output.mp4 | 3 ++ .../tests/portrait-edge-bleed/src/index.html | 32 +++++++++++++++++++ .../tests/portrait-edge-bleed/src/sample.mp4 | 3 ++ 7 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 packages/producer/tests/portrait-edge-bleed/meta.json create mode 100644 packages/producer/tests/portrait-edge-bleed/output/compiled.html create mode 100644 packages/producer/tests/portrait-edge-bleed/output/output.mp4 create mode 100644 packages/producer/tests/portrait-edge-bleed/src/index.html create mode 100644 packages/producer/tests/portrait-edge-bleed/src/sample.mp4 diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 0f9c1d3d8..cffb29e8b 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -75,7 +75,7 @@ jobs: - shard: shard-5 args: "style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression" - shard: shard-6 - args: "overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence" + args: "overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed" - shard: shard-7 args: "sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector" - shard: shard-8 diff --git a/packages/engine/src/services/screenshotService.ts b/packages/engine/src/services/screenshotService.ts index 7af0481ad..ffbe0be56 100644 --- a/packages/engine/src/services/screenshotService.ts +++ b/packages/engine/src/services/screenshotService.ts @@ -135,7 +135,11 @@ export async function pageScreenshotCapture(page: Page, options: CaptureOptions) format: isPng ? "png" : "jpeg", quality: isPng ? undefined : (options.quality ?? 80), fromSurface: true, - captureBeyondViewport: false, + // The explicit clip rect constrains output to exact composition + // dimensions. The viewport-boundary pre-clip from captureBeyondViewport: + // false is redundant, and Chrome's compositor rounds it inward under + // multi-tab load — clipping the bottom/right edge of tall viewports. + captureBeyondViewport: true, optimizeForSpeed: !isPng, clip, }); @@ -168,7 +172,7 @@ export async function captureScreenshotWithAlpha( const result = await client.send("Page.captureScreenshot", { format: "png", fromSurface: true, - captureBeyondViewport: false, + captureBeyondViewport: true, // see pageScreenshotCapture for rationale optimizeForSpeed: false, // `true` uses a zero-alpha-aware fast path that crushes real alpha values — observed empirically, CDP docs don't spell it out clip: { x: 0, y: 0, width, height, scale: 1 }, }); @@ -233,7 +237,7 @@ export async function captureAlphaPng(page: Page, width: number, height: number) const result = await client.send("Page.captureScreenshot", { format: "png", fromSurface: true, - captureBeyondViewport: false, + captureBeyondViewport: true, // see pageScreenshotCapture for rationale optimizeForSpeed: false, // must be false to preserve alpha clip: { x: 0, y: 0, width, height, scale: 1 }, }); diff --git a/packages/producer/tests/portrait-edge-bleed/meta.json b/packages/producer/tests/portrait-edge-bleed/meta.json new file mode 100644 index 000000000..12745182a --- /dev/null +++ b/packages/producer/tests/portrait-edge-bleed/meta.json @@ -0,0 +1,13 @@ +{ + "name": "portrait-edge-bleed", + "description": "Regression test for CDP viewport edge clipping on tall portrait compositions. Magenta rows at the bottom edge of a 1080x1920 canvas; any compositor clipping drops PSNR sharply. Uses 4 workers to exercise the multi-tab compositor path. Note: covers the capture-side clipping but not video-element compositor surface timing from #1009.", + "tags": ["regression", "capture"], + "minPsnr": 30, + "maxFrameFailures": 0, + "minAudioCorrelation": 0, + "maxAudioLagWindows": 1, + "renderConfig": { + "fps": 30, + "workers": 4 + } +} diff --git a/packages/producer/tests/portrait-edge-bleed/output/compiled.html b/packages/producer/tests/portrait-edge-bleed/output/compiled.html new file mode 100644 index 000000000..db671cf28 --- /dev/null +++ b/packages/producer/tests/portrait-edge-bleed/output/compiled.html @@ -0,0 +1,31 @@ + + + + + + + +
+ +
+ + + diff --git a/packages/producer/tests/portrait-edge-bleed/output/output.mp4 b/packages/producer/tests/portrait-edge-bleed/output/output.mp4 new file mode 100644 index 000000000..7ebc93068 --- /dev/null +++ b/packages/producer/tests/portrait-edge-bleed/output/output.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2120a0808f89d8fe95a7558f8b8741210eb3be593e77e453bda113f449647c2f +size 9315 diff --git a/packages/producer/tests/portrait-edge-bleed/src/index.html b/packages/producer/tests/portrait-edge-bleed/src/index.html new file mode 100644 index 000000000..477745f0b --- /dev/null +++ b/packages/producer/tests/portrait-edge-bleed/src/index.html @@ -0,0 +1,32 @@ + + + + + + + +
+ +
+ + + diff --git a/packages/producer/tests/portrait-edge-bleed/src/sample.mp4 b/packages/producer/tests/portrait-edge-bleed/src/sample.mp4 new file mode 100644 index 000000000..72ac97847 --- /dev/null +++ b/packages/producer/tests/portrait-edge-bleed/src/sample.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d13de201780ff981c5b35b5c6941ba3ec28b1344a6b0a7fb0f2e3b4f7677399 +size 8809