Stabilize Internal Docker Browser rendering
Return WebUI navigation after the initial document commit and align the socket timeout with browser command deadlines. Remove forced viewport nudges and reject cropped or mismatched screencast frames so tab changes and pointer movement do not stretch the viewport.
Alessandro committed
Jul 21, 2026 at 18:55 UTC
23af25e52ace7c8afba8c7aa1ecbb3346d121c39
4 files changed
+120
-198
plugins/_browser/api/ws_browser.py
+27
-11
@@ -230,13 +230,18 @@ class WsBrowser(WsHandler):
230
if command == "open":
231
result = await runtime.call("open", data.get("url") or "")
232
elif command == "navigate":
233
- result = await runtime.call("navigate", browser_id, data.get("url") or "")
233
+ result = await runtime.call(
234
+ "navigate",
235
+ browser_id,
236
+ data.get("url") or "",
237
+ wait_until="commit",
238
+ )
239
elif command == "back":
235
- result = await runtime.call("back", browser_id)
240
+ result = await runtime.call("back", browser_id, wait_until="commit")
241
elif command == "forward":
237
- result = await runtime.call("forward", browser_id)
242
+ result = await runtime.call("forward", browser_id, wait_until="commit")
243
elif command == "reload":
239
- result = await runtime.call("reload", browser_id)
244
+ result = await runtime.call("reload", browser_id, wait_until="commit")
245
elif command == "close":
246
result = await runtime.call("close_browser", browser_id)
247
elif command == "list":
@@ -717,18 +722,29 @@ class WsBrowser(WsHandler):
722
def _frame_dimensions(metadata: Any) -> dict[str, int]:
723
if not isinstance(metadata, dict):
724
return {}
720
- for width_key, height_key in (
721
- ("expectedWidth", "expectedHeight"),
722
- ("deviceWidth", "deviceHeight"),
723
- ("jpegWidth", "jpegHeight"),
724
- ):
725
+
726
+ def dimensions(width_key: str, height_key: str) -> tuple[int, int] | None:
727
try:
728
width = int(metadata.get(width_key) or 0)
729
height = int(metadata.get(height_key) or 0)
730
except (TypeError, ValueError):
729
- continue
731
+ return None
732
if width > 0 and height > 0:
731
- return {"width": width, "height": height}
733
+ return width, height
734
+ return None
735
+
736
+ expected = dimensions("expectedWidth", "expectedHeight")
737
+ jpeg = dimensions("jpegWidth", "jpegHeight")
738
+ if expected and jpeg:
739
+ width_scale = jpeg[0] / expected[0]
740
+ height_scale = jpeg[1] / expected[1]
741
+ if abs(width_scale - height_scale) <= 0.01:
742
+ return {"width": expected[0], "height": expected[1]}
743
+ return {"width": jpeg[0], "height": jpeg[1]}
744
+
745
+ for fallback in (jpeg, dimensions("deviceWidth", "deviceHeight"), expected):
746
+ if fallback:
747
+ return {"width": fallback[0], "height": fallback[1]}
748
return {}
749
750
async def _emit_viewer_state(
plugins/_browser/helpers/runtime.py
+47
-58
@@ -40,7 +40,6 @@ CHROME_SINGLETON_FILES = ("SingletonLock", "SingletonCookie", "SingletonSocket")
40
SCREENCAST_MAX_WIDTH = 4096
41
SCREENCAST_MAX_HEIGHT = 4096
42
VIEWPORT_SIZE_TOLERANCE = 4
43
-VIEWPORT_REMOUNT_PAUSE_SECONDS = 0.05
43
CLIPBOARD_BRIDGE_SCRIPT = r"""
44
(payload) => {
45
const action = String(payload?.action || "").trim().toLowerCase();
@@ -282,18 +281,6 @@ CLIPBOARD_BRIDGE_SCRIPT = r"""
281
_SAFE_CONTEXT_RE = re.compile(r"[^a-zA-Z0-9_.-]+")
282
283
285
-def _nudged_viewport(viewport: dict[str, int]) -> dict[str, int]:
286
- width = int(viewport["width"])
287
- height = int(viewport["height"])
288
- if width < 4096:
289
- return {"width": width + 1, "height": height}
290
- if width > 320:
291
- return {"width": width - 1, "height": height}
292
- if height < 4096:
293
- return {"width": width, "height": height + 1}
294
- return {"width": width, "height": height - 1}
295
-
296
-
284
def _safe_context_id(context_id: str) -> str:
285
return _SAFE_CONTEXT_RE.sub("_", str(context_id or "default")).strip("._") or "default"
286
@@ -344,7 +331,7 @@ class _BrowserScreencast:
331
self._expected_height = height
332
with contextlib.suppress(Exception):
333
await self.session.send("Page.enable")
347
- await self._apply_cdp_viewport_with_remount({"width": width, "height": height})
334
+ await self._apply_cdp_viewport({"width": width, "height": height})
335
await self.session.send(
336
"Page.startScreencast",
337
{
@@ -356,14 +343,6 @@ class _BrowserScreencast:
343
},
344
)
345
359
- async def _apply_cdp_viewport_with_remount(self, viewport: dict[str, int]) -> None:
360
- await self._apply_cdp_viewport(viewport)
361
- await asyncio.sleep(VIEWPORT_REMOUNT_PAUSE_SECONDS)
362
- await self._apply_cdp_viewport(_nudged_viewport(viewport))
363
- await asyncio.sleep(VIEWPORT_REMOUNT_PAUSE_SECONDS)
364
- await self._apply_cdp_viewport(viewport)
365
- await asyncio.sleep(VIEWPORT_REMOUNT_PAUSE_SECONDS)
366
-
346
async def _apply_cdp_viewport(self, viewport: dict[str, int]) -> None:
347
width = max(320, min(4096, int(viewport.get("width") or DEFAULT_VIEWPORT["width"])))
348
height = max(200, min(4096, int(viewport.get("height") or DEFAULT_VIEWPORT["height"])))
@@ -1247,38 +1226,59 @@ class _BrowserRuntimeCore:
1226
await self.ensure_started()
1227
return await self._state(self._resolve_browser_id(browser_id))
1228
1250
- async def navigate(self, browser_id: int | str | None, url: str) -> dict[str, Any]:
1229
+ async def navigate(
1230
+ self,
1231
+ browser_id: int | str | None,
1232
+ url: str,
1233
+ *,
1234
+ wait_until: str = "domcontentloaded",
1235
+ ) -> dict[str, Any]:
1236
await self.ensure_started()
1237
resolved_id = self._resolve_browser_id(browser_id)
1238
page = self._page(resolved_id)
1254
- await self._goto(page, normalize_url(url))
1239
+ await self._goto(page, normalize_url(url), wait_until=wait_until)
1240
self._maybe_promote(resolved_id)
1241
return await self._state(resolved_id)
1242
1258
- async def back(self, browser_id: int | str | None = None) -> dict[str, Any]:
1243
+ async def back(
1244
+ self,
1245
+ browser_id: int | str | None = None,
1246
+ *,
1247
+ wait_until: str = "domcontentloaded",
1248
+ ) -> dict[str, Any]:
1249
await self.ensure_started()
1250
resolved_id = self._resolve_browser_id(browser_id)
1251
page = self._page(resolved_id)
1262
- await page.go_back(wait_until="domcontentloaded", timeout=10000)
1263
- await self._settle(page)
1252
+ await page.go_back(wait_until=wait_until, timeout=10000)
1253
+ await self._settle(page, short=wait_until == "commit")
1254
self._maybe_promote(resolved_id)
1255
return await self._state(resolved_id)
1256
1267
- async def forward(self, browser_id: int | str | None = None) -> dict[str, Any]:
1257
+ async def forward(
1258
+ self,
1259
+ browser_id: int | str | None = None,
1260
+ *,
1261
+ wait_until: str = "domcontentloaded",
1262
+ ) -> dict[str, Any]:
1263
await self.ensure_started()
1264
resolved_id = self._resolve_browser_id(browser_id)
1265
page = self._page(resolved_id)
1271
- await page.go_forward(wait_until="domcontentloaded", timeout=10000)
1272
- await self._settle(page)
1266
+ await page.go_forward(wait_until=wait_until, timeout=10000)
1267
+ await self._settle(page, short=wait_until == "commit")
1268
self._maybe_promote(resolved_id)
1269
return await self._state(resolved_id)
1270
1276
- async def reload(self, browser_id: int | str | None = None) -> dict[str, Any]:
1271
+ async def reload(
1272
+ self,
1273
+ browser_id: int | str | None = None,
1274
+ *,
1275
+ wait_until: str = "domcontentloaded",
1276
+ ) -> dict[str, Any]:
1277
await self.ensure_started()
1278
resolved_id = self._resolve_browser_id(browser_id)
1279
page = self._page(resolved_id)
1280
- await page.reload(wait_until="domcontentloaded", timeout=15000)
1281
- await self._settle(page)
1280
+ await page.reload(wait_until=wait_until, timeout=15000)
1281
+ await self._settle(page, short=wait_until == "commit")
1282
self._maybe_promote(resolved_id)
1283
return await self._state(resolved_id)
1284
@@ -1748,33 +1748,16 @@ class _BrowserRuntimeCore:
1748
or abs(int(current_viewport.get("height") or 0) - viewport["height"])
1749
> VIEWPORT_SIZE_TOLERANCE
1750
)
1751
- should_remount_viewport = changed or restart_screencast
1752
- if should_remount_viewport:
1751
+ should_restart_screencast = changed or restart_screencast
1752
+ if should_restart_screencast:
1753
await self._stop_screencasts_for_browser(resolved_id)
1754
if changed:
1755
- await self._apply_viewport_with_remount(page, viewport)
1756
- elif restart_screencast:
1757
- await self._remount_viewport(page, viewport)
1758
- if should_remount_viewport:
1755
+ await page.set_viewport_size(viewport)
1756
+ if should_restart_screencast:
1757
await self._settle(page, short=True)
1758
self._maybe_promote(resolved_id)
1759
return {"state": await self._state(resolved_id), "viewport": viewport}
1760
1763
- async def _apply_viewport_with_remount(self, page: Any, viewport: dict[str, int]) -> None:
1764
- await page.set_viewport_size(viewport)
1765
- await asyncio.sleep(VIEWPORT_REMOUNT_PAUSE_SECONDS)
1766
- await self._remount_viewport(page, viewport)
1767
-
1768
- async def _remount_viewport(self, page: Any, viewport: dict[str, int]) -> None:
1769
- nudged_viewport = self._nudged_viewport(viewport)
1770
- await page.set_viewport_size(nudged_viewport)
1771
- await asyncio.sleep(VIEWPORT_REMOUNT_PAUSE_SECONDS)
1772
- await page.set_viewport_size(viewport)
1773
-
1774
- @staticmethod
1775
- def _nudged_viewport(viewport: dict[str, int]) -> dict[str, int]:
1776
- return _nudged_viewport(viewport)
1777
-
1761
async def _point_for(
1762
self,
1763
page: Any,
@@ -2223,17 +2206,23 @@ class _BrowserRuntimeCore:
2206
self._maybe_promote(resolved_id)
2207
return {"action": action or {}, "state": await self._state(resolved_id)}
2208
2226
- async def _goto(self, page: Any, url: str) -> None:
2209
+ async def _goto(
2210
+ self,
2211
+ page: Any,
2212
+ url: str,
2213
+ *,
2214
+ wait_until: str = "domcontentloaded",
2215
+ ) -> None:
2216
from playwright.async_api import Error as PlaywrightError
2217
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
2218
2219
try:
2231
- await page.goto(url, wait_until="domcontentloaded", timeout=30000)
2220
+ await page.goto(url, wait_until=wait_until, timeout=30000)
2221
except PlaywrightTimeoutError:
2233
- PrintStyle.warning(f"Browser navigation timed out after DOM handoff: {url}")
2222
+ PrintStyle.warning(f"Browser navigation timed out waiting for {wait_until}: {url}")
2223
except PlaywrightError as exc:
2224
PrintStyle.warning(f"Browser navigation showed a native error page for {url}: {exc}")
2236
- await self._settle(page)
2225
+ await self._settle(page, short=wait_until == "commit")
2226
2227
async def _settle(self, page: Any, short: bool = False) -> None:
2228
from playwright.async_api import Error as PlaywrightError
plugins/_browser/webui/browser-store.js
+5
-71
@@ -19,6 +19,7 @@ websocket.addHandlers(["ws_webui"]);
19
const EXTENSIONS_ROOT = "/a0/usr/_browser/extensions";
20
const BROWSER_SUBSCRIBE_TIMEOUT_MS = 60000;
21
const BROWSER_FIRST_INSTALL_TIMEOUT_MS = 300000;
22
+const BROWSER_COMMAND_TIMEOUT_MS = 45000;
23
const BROWSER_CONFIG_REFRESH_MS = 15000;
24
const BROWSER_VIEWER_TRANSPORT_SNAPSHOT = "snapshot";
25
const BROWSER_VIEWER_TRANSPORT_SCREENCAST = "screencast";
@@ -211,9 +212,6 @@ const model = {
212
_surfaceHandoffTimer: null,
213
_surfaceOpenedAt: 0,
214
_surfaceOpenSequence: 0,
214
- _canvasSurfaceReadySequence: 0,
215
- _canvasFirstFrameAcceptedSequence: 0,
216
- _canvasFirstFrameNudgeSequence: 0,
215
_openPromise: null,
216
_openSignature: "",
217
_connectSequence: 0,
@@ -751,10 +749,6 @@ const model = {
749
} finally {
750
if (this.isCurrentSurfaceOpen(surfaceSequence)) {
751
this.loading = false;
754
- if (this._mode === "canvas") {
755
- this._canvasSurfaceReadySequence = surfaceSequence;
756
- this.scheduleCanvasWidthNudgeAfterFirstFrame();
757
- }
752
}
753
}
754
},
@@ -1298,11 +1292,9 @@ const model = {
1292
const viewport = this.currentViewportSize() || this._lastViewport;
1293
if (!this.frameMatchesViewport(dimensions, viewport)) {
1294
this.requestViewportSyncAfterRejectedFrame();
1301
- if (!this.shouldAcceptMismatchedFrame(dimensions)) {
1302
- bitmap?.close?.();
1303
- options?.cleanup?.();
1304
- return;
1305
- }
1295
+ bitmap?.close?.();
1296
+ options?.cleanup?.();
1297
+ return;
1298
}
1299
if (bitmap && this.paintFrameBitmap(bitmap)) {
1300
this.clearFrameSrc();
@@ -1317,64 +1309,6 @@ const model = {
1309
this._lastFrameDimensions = dimensions;
1310
this._lastFrameAt = Date.now();
1311
options?.onAccepted?.();
1320
- this._canvasFirstFrameAcceptedSequence = surfaceSequence;
1321
- this.scheduleCanvasWidthNudgeAfterFirstFrame();
1322
- },
1323
-
1324
- shouldAcceptMismatchedFrame(dimensions = null) {
1325
- return Boolean(
1326
- dimensions?.width
1327
- && dimensions?.height
1328
- && (!this.hasFrame() || this._surfaceSwitching || this.isSwitchingBrowser())
1329
- );
1330
- },
1331
-
1332
- scheduleCanvasWidthNudgeAfterFirstFrame() {
1333
- const surfaceSequence = this._surfaceOpenSequence;
1334
- if (this._mode !== "canvas" || !this.isCurrentSurfaceOpen(surfaceSequence) || !this.activeBrowserId) {
1335
- return;
1336
- }
1337
- if (this._canvasFirstFrameNudgeSequence === surfaceSequence) {
1338
- return;
1339
- }
1340
- if (
1341
- this._canvasSurfaceReadySequence !== surfaceSequence
1342
- || this._canvasFirstFrameAcceptedSequence !== surfaceSequence
1343
- ) {
1344
- return;
1345
- }
1346
- this._canvasFirstFrameNudgeSequence = surfaceSequence;
1347
-
1348
- void (async () => {
1349
- await nextAnimationFrame();
1350
- await nextAnimationFrame();
1351
- if (!this.isCurrentSurfaceOpen(surfaceSequence) || this._mode !== "canvas") {
1352
- return;
1353
- }
1354
- this.forceRightCanvasWidthNudge();
1355
- })();
1356
- },
1357
-
1358
- forceRightCanvasWidthNudge() {
1359
- const canvas = rightCanvasStore;
1360
- if (!canvas || canvas.isMobileMode || !canvas.isOpen || canvas.activeSurfaceId !== "browser") {
1361
- return;
1362
- }
1363
-
1364
- const currentWidth = Number(canvas.width || 0);
1365
- if (!Number.isFinite(currentWidth) || currentWidth <= 0) {
1366
- return;
1367
- }
1368
- const maxWidth = Number(canvas.maxWidth?.() || currentWidth);
1369
- const minWidth = Number(canvas.minWidth || 420);
1370
- const direction = currentWidth < maxWidth ? 1 : -1;
1371
- const nudgedWidth = currentWidth + direction;
1372
- if (nudgedWidth < minWidth || nudgedWidth > maxWidth || nudgedWidth === currentWidth) {
1373
- return;
1374
- }
1375
-
1376
- canvas.setWidth?.(nudgedWidth, { persist: false });
1377
- this.queueViewportSync(true);
1312
},
1313
1314
frameMatchesViewport(dimensions = null, viewport = null) {
@@ -1525,7 +1459,7 @@ const model = {
1459
viewer_transport: this.requestedViewerTransport(),
1460
command,
1461
},
1528
- { timeoutMs: 20000 },
1462
+ { timeoutMs: BROWSER_COMMAND_TIMEOUT_MS },
1463
);
1464
const data = firstOk(response);
1465
this.applyTabScope(data);
tests/test_browser_agent_regressions.py
+41
-58
@@ -707,6 +707,7 @@ def test_browser_viewer_allows_slow_extension_startup():
707
708
assert "const BROWSER_SUBSCRIBE_TIMEOUT_MS = 60000;" in js
709
assert "const BROWSER_FIRST_INSTALL_TIMEOUT_MS = 300000;" in js
710
+ assert "const BROWSER_COMMAND_TIMEOUT_MS = 45000;" in js
711
assert "? BROWSER_FIRST_INSTALL_TIMEOUT_MS" in js
712
assert ": BROWSER_SUBSCRIBE_TIMEOUT_MS" in js
713
assert "Installing Chromium for the first Browser run" in js
@@ -796,32 +797,17 @@ def test_browser_canvas_surface_open_waits_for_visible_panel():
797
assert "forceCanvasWidthNudgeAfterBrowserMount" not in js
798
799
799
-def test_browser_canvas_nudges_width_after_first_accepted_frame():
800
+def test_browser_canvas_does_not_resize_after_first_accepted_frame():
801
js = (PROJECT_ROOT / "plugins" / "_browser" / "webui" / "browser-store.js").read_text(
802
encoding="utf-8"
803
)
804
804
- assert "_canvasSurfaceReadySequence" in js
805
- assert "_canvasFirstFrameAcceptedSequence" in js
806
- assert "_canvasFirstFrameNudgeSequence" in js
807
- assert "scheduleCanvasWidthNudgeAfterFirstFrame()" in js
808
- assert "this._canvasSurfaceReadySequence = surfaceSequence;" in js
809
- assert "const surfaceSequence = this._surfaceOpenSequence;" in js
810
- assert "this._canvasFirstFrameAcceptedSequence = surfaceSequence;" in js
811
- assert "forceRightCanvasWidthNudge()" in js
812
- assert "await nextAnimationFrame();" in js
813
- assert "globalThis.Alpine" not in js
814
- assert 'import { store as rightCanvasStore } from "/components/canvas/right-canvas-store.js";' in js
815
- assert "const canvas = rightCanvasStore;" in js
816
- assert 'canvas.activeSurfaceId !== "browser"' in js
817
- assert "canvas.setWidth?.(nudgedWidth, { persist: false })" in js
818
- assert "this.queueViewportSync(true)" in js
819
- frame_accept_index = js.index("this._lastFrameDimensions = dimensions;")
820
- frame_nudge_schedule_index = js.index(
821
- "this.scheduleCanvasWidthNudgeAfterFirstFrame();",
822
- frame_accept_index,
823
- )
824
- assert frame_accept_index < frame_nudge_schedule_index
805
+ assert "_canvasSurfaceReadySequence" not in js
806
+ assert "_canvasFirstFrameAcceptedSequence" not in js
807
+ assert "_canvasFirstFrameNudgeSequence" not in js
808
+ assert "scheduleCanvasWidthNudgeAfterFirstFrame" not in js
809
+ assert "forceRightCanvasWidthNudge" not in js
810
+ assert "canvas.setWidth?.(nudgedWidth" not in js
811
812
813
def test_browser_canvas_restarts_stream_after_page_navigation():
@@ -1423,18 +1409,17 @@ def test_browser_viewer_defaults_to_live_screencast_with_snapshot_fallback():
1409
assert "restart_stream: restartStream && this.usesScreencastTransport()" in browser_store
1410
assert 'restart_screencast=bool(data.get("restart_stream"))' in ws_browser
1411
assert "restart_screencast: bool = False" in runtime
1426
- assert "should_remount_viewport = changed or restart_screencast" in runtime
1427
- assert "VIEWPORT_REMOUNT_PAUSE_SECONDS = 0.05" in runtime
1428
- assert "await self._apply_cdp_viewport_with_remount" in runtime
1429
- assert "await self._apply_viewport_with_remount(page, viewport)" in runtime
1430
- assert "await self._remount_viewport(page, viewport)" in runtime
1431
- assert "await asyncio.sleep(VIEWPORT_REMOUNT_PAUSE_SECONDS)" in runtime
1432
- assert "def _nudged_viewport(viewport: dict[str, int])" in runtime
1412
+ assert "should_restart_screencast = changed or restart_screencast" in runtime
1413
+ assert "await self._apply_cdp_viewport({\"width\": width, \"height\": height})" in runtime
1414
+ assert "await page.set_viewport_size(viewport)" in runtime
1415
+ assert "VIEWPORT_REMOUNT_PAUSE_SECONDS" not in runtime
1416
+ assert "_nudged_viewport" not in runtime
1417
+ assert 'wait_until="commit"' in ws_browser
1418
assert 'restartStream: this._mode === "canvas" && this.usesScreencastTransport()' in browser_store
1419
assert "this.frameState = data.state || null" not in browser_store
1420
assert "function loadFrameDimensions(src)" in browser_store
1421
assert "frameMatchesViewport(dimensions = null, viewport = null)" in browser_store
1437
- assert "shouldAcceptMismatchedFrame(dimensions = null)" in browser_store
1422
+ assert "shouldAcceptMismatchedFrame" not in browser_store
1423
assert "requestViewportSyncAfterRejectedFrame()" in browser_store
1424
assert "this.applySnapshot(data.snapshot);" in browser_store
1425
assert "if (!this.frameCanvasReady || !this.usesScreencastTransport())" in browser_store
@@ -1495,6 +1480,27 @@ def test_browser_viewer_frame_payload_supports_binary_slim_frames():
1480
assert "state" not in payload
1481
1482
1483
+def test_browser_viewer_frame_dimensions_reject_crops_but_allow_uniform_scaling():
1484
+ dimensions = ws_browser_module.WsBrowser._frame_dimensions
1485
+
1486
+ assert dimensions(
1487
+ {
1488
+ "expectedWidth": 900,
1489
+ "expectedHeight": 600,
1490
+ "jpegWidth": 1800,
1491
+ "jpegHeight": 1200,
1492
+ }
1493
+ ) == {"width": 900, "height": 600}
1494
+ assert dimensions(
1495
+ {
1496
+ "expectedWidth": 900,
1497
+ "expectedHeight": 600,
1498
+ "jpegWidth": 900,
1499
+ "jpegHeight": 500,
1500
+ }
1501
+ ) == {"width": 900, "height": 500}
1502
+
1503
+
1504
def test_browser_navigation_errors_stay_inside_native_browser_page():
1505
runtime = (
1506
PROJECT_ROOT / "plugins" / "_browser" / "helpers" / "runtime.py"
@@ -1796,7 +1802,7 @@ async def test_browser_screencast_acknowledges_and_drops_stale_frames():
1802
for method, params in session.sent
1803
if method == "Emulation.setVisibleSize"
1804
]
1799
- assert metrics_calls[:3] == [
1805
+ assert metrics_calls == [
1806
{
1807
"width": 1118,
1808
"height": 662,
@@ -1804,26 +1810,8 @@ async def test_browser_screencast_acknowledges_and_drops_stale_frames():
1810
"mobile": False,
1811
"dontSetVisibleSize": True,
1812
},
1807
- {
1808
- "width": 1119,
1809
- "height": 662,
1810
- "deviceScaleFactor": 1,
1811
- "mobile": False,
1812
- "dontSetVisibleSize": True,
1813
- },
1814
- {
1815
- "width": 1118,
1816
- "height": 662,
1817
- "deviceScaleFactor": 1,
1818
- "mobile": False,
1819
- "dontSetVisibleSize": True,
1820
- },
1821
- ]
1822
- assert visible_calls[:3] == [
1823
- {"width": 1118, "height": 662},
1824
- {"width": 1119, "height": 662},
1825
- {"width": 1118, "height": 662},
1813
]
1814
+ assert visible_calls == [{"width": 1118, "height": 662}]
1815
start_index = next(
1816
index
1817
for index, (method, _params) in enumerate(session.sent)
@@ -3072,7 +3060,7 @@ async def test_browser_viewer_viewport_input_dispatches_resize(monkeypatch):
3060
3061
3062
@pytest.mark.anyio
3075
-async def test_browser_runtime_remounts_same_viewport_when_restarting_screencast():
3063
+async def test_browser_runtime_restarts_screencast_without_resizing_same_viewport():
3064
viewport_calls = []
3065
stopped = []
3066
settled = []
@@ -3106,16 +3094,13 @@ async def test_browser_runtime_remounts_same_viewport_when_restarting_screencast
3094
"state": {"id": 7},
3095
"viewport": {"width": 1280, "height": 720},
3096
}
3109
- assert viewport_calls == [
3110
- {"width": 1281, "height": 720},
3111
- {"width": 1280, "height": 720},
3112
- ]
3097
+ assert viewport_calls == []
3098
assert stopped == [7]
3099
assert settled == [True]
3100
3101
3102
@pytest.mark.anyio
3118
-async def test_browser_runtime_remounts_initial_changed_viewport():
3103
+async def test_browser_runtime_applies_changed_viewport_once():
3104
calls = []
3105
stopped = []
3106
settled = []
@@ -3151,8 +3136,6 @@ async def test_browser_runtime_remounts_initial_changed_viewport():
3136
}
3137
assert calls == [
3138
("viewport", {"width": 672, "height": 789}),
3154
- ("viewport", {"width": 673, "height": 789}),
3155
- ("viewport", {"width": 672, "height": 789}),
3139
]
3140
assert stopped == [7]
3141
assert settled == [True]