Skip to content

Commit b69703c

Browse files
fix(server): verify the native display viewport capture without a window resize (#22176)
Co-authored-by: Claude Opus 5.5 <[email protected]>
1 parent 02b899b commit b69703c

2 files changed

Lines changed: 9 additions & 49 deletions

File tree

‎apps/server/src/preview/ServerBrowserPage.test.ts‎

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -370,9 +370,7 @@ describe("server browser drag", () => {
370370
});
371371
});
372372

373-
// TODO(#19913 follow-up): still flaky in CI after #22154 (Test Server 6 red on most main runs 05:57-06:24Z).
374-
// Skipped again to keep main green; needs a CI-reproducible fix, not another local-only one.
375-
it.skip("captures the native display viewport after page script reassigns the globals", async () => {
373+
it("captures the native display viewport after page script reassigns the globals", async () => {
376374
// Context deviceScaleFactor changes DPR but does not scale raw CDP captures.
377375
const browser = await chromium.launch({
378376
headless: true,
@@ -410,21 +408,14 @@ it.skip("captures the native display viewport after page script reassigns the gl
410408
zoom: 1,
411409
offset: { x: 0, y: 0 },
412410
});
413-
const { windowId } = await cdp.send("Browser.getWindowForTarget");
414-
// CDP acknowledges the bounds change before the renderer applies it, and a resize event can
415-
// fire for a size on the way there. Page script replaced innerWidth, so read the layout.
416-
const resize = await page.evaluateHandle(`({
417-
done: new Promise(resolve => window.addEventListener("resize", () => {
418-
const { clientWidth, clientHeight } = document.documentElement;
419-
if (clientWidth === 390 && clientHeight === 844) resolve();
420-
})),
421-
})`);
422-
await cdp.send("Browser.setWindowBounds", {
423-
windowId,
424-
bounds: { width: 390, height: 844 },
411+
// Window bounds are ignored by headless Chromium on some hosts; a metrics override resizes
412+
// the page everywhere, and Playwright's viewportSize() stays stale as it would on desktop.
413+
await cdp.send("Emulation.setDeviceMetricsOverride", {
414+
width: 390,
415+
height: 844,
416+
deviceScaleFactor: 0,
417+
mobile: false,
425418
});
426-
await resize.evaluate("resize => resize.done");
427-
await resize.dispose();
428419
expect(await ServerBrowserPage.readPageMetrics(cdp)).toMatchObject({
429420
ratio: 1.5,
430421
width: 390,

‎apps/server/src/preview/ServerBrowserPage.ts‎

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const MAX_EVALUATION_BYTES = 64_000;
2626
const MAX_VISIBLE_TEXT_LENGTH = 20_000;
2727
const MAX_SCREENSHOT_WIDTH = 1280;
2828
const WAIT_POLL_MS = 100;
29-
const RESIZED_FRAME_MS = 2_000;
3029
export const DIAGNOSTIC_BUFFER_LIMIT = 200;
3130

3231
export class ServerBrowserOperationError extends Error {
@@ -485,36 +484,6 @@ const renderingOf = async (input: SnapshotInput) => {
485484
};
486485
};
487486

488-
// A desktop window reports a resize before its compositor draws a frame at the new size, so an
489-
// unclipped capture can still return the old frame. Bounded: a frame that never matches is kept.
490-
// Headless pages are sized by the server, which awaits each resize.
491-
const captureResized = async (
492-
input: SnapshotInput,
493-
options: {
494-
readonly scale: number;
495-
readonly viewport: Viewport;
496-
readonly offset: { readonly x: number; readonly y: number } | undefined;
497-
readonly renderScale: number;
498-
},
499-
deadline: number,
500-
) => {
501-
if (input.renderScale !== "display")
502-
return captureViewport(input.page, input.cdp, { format: "png", ...options });
503-
const expected = {
504-
width: options.viewport.width * options.renderScale * options.scale,
505-
height: options.viewport.height * options.renderScale * options.scale,
506-
};
507-
const giveUpAt = Math.min(Date.now() + RESIZED_FRAME_MS, deadline - 2_000);
508-
for (;;) {
509-
const data = await captureViewport(input.page, input.cdp, { format: "png", ...options });
510-
const size = pngSize(data);
511-
const matches =
512-
Math.abs(size.width - expected.width) <= 1 && Math.abs(size.height - expected.height) <= 1;
513-
if (matches || Date.now() + WAIT_POLL_MS >= giveUpAt) return data;
514-
await new Promise((resolve) => setTimeout(resolve, WAIT_POLL_MS));
515-
}
516-
};
517-
518487
const capture = async (
519488
input: SnapshotInput,
520489
deadline: number,
@@ -537,7 +506,7 @@ const capture = async (
537506
"url" | "title" | "loading" | "visibleText" | "interactiveElements"
538507
>
539508
>,
540-
captureResized(input, { scale, viewport, offset, renderScale }, deadline),
509+
captureViewport(input.page, input.cdp, { format: "png", scale, viewport, offset }),
541510
]);
542511
void pageAndScreenshot.catch(constVoid);
543512
// Leaves time for the second read; a frame that never loads holds the tree until then.

0 commit comments

Comments
 (0)