From 6ea6ae1f1f944ba212f0bdea4bd6441b97f726aa Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2026 09:43:45 +0000 Subject: [PATCH] chore: roll Playwright to 1.66.0-next (8534271f04) --- DRIVER_VERSION | 2 +- playwright/async_api/_generated.py | 15 +++-- playwright/sync_api/_generated.py | 15 +++-- tests/async/library/test_tracing.py | 4 ++ tests/async/page/test_page_evaluate.py | 22 +++++++ tests/async/page/test_page_event_pageerror.py | 55 +++++++++++++++- tests/sync/library/test_tracing.py | 4 ++ tests/sync/page/test_page_evaluate.py | 22 +++++++ tests/sync/page/test_page_event_pageerror.py | 62 ++++++++++++++++++- tests/upstream_registry.md | 1 + 10 files changed, 189 insertions(+), 13 deletions(-) diff --git a/DRIVER_VERSION b/DRIVER_VERSION index 851304148..997f4dfc8 100644 --- a/DRIVER_VERSION +++ b/DRIVER_VERSION @@ -1 +1 @@ -3b438229c6a2ccdbe2d3572755935892203d83c3 +8534271f0413bae6a455b7cf43b2637391bc614b diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 6d2c2ade7..fdc48ed1d 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -3552,10 +3552,17 @@ async def set_offline(self, offline: bool) -> None: offline : bool Whether to emulate network being offline for the browser context. - **NOTE** Offline emulation only affects requests that go through the browser's regular network stack, such as page - navigations, `fetch()`, `XMLHttpRequest` and WebSockets. It does not affect WebRTC traffic: established - `RTCPeerConnection`s keep sending and receiving media over UDP. To test WebRTC connection loss, interrupt the - connection outside the browser, for example by stopping the TURN server or using an OS-level firewall. + **NOTE** Offline emulation affects HTTP requests, such as page navigations, `fetch()` and `XMLHttpRequest`. + + WebSocket behavior depends on the browser and whether the connection is already established. Chromium and Firefox + fail new WebSocket connections while offline, but do not close already-open connections. Chromium can defer + incoming messages until the context goes back online, rather than lose them; Firefox can continue delivering + messages while offline. WebKit can allow both new WebSocket connections and traffic on established connections + while offline. Do not rely on offline emulation to test WebSocket message loss or connection closure. + + Offline emulation does not affect WebRTC traffic: established `RTCPeerConnection`s keep sending and receiving media + over UDP. To test WebRTC connection loss, interrupt the connection outside the browser, for example by stopping the + TURN server or using an OS-level firewall. """ diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index b4b29dea4..bf4d4df2f 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -3470,10 +3470,17 @@ def set_offline(self, offline: bool) -> None: offline : bool Whether to emulate network being offline for the browser context. - **NOTE** Offline emulation only affects requests that go through the browser's regular network stack, such as page - navigations, `fetch()`, `XMLHttpRequest` and WebSockets. It does not affect WebRTC traffic: established - `RTCPeerConnection`s keep sending and receiving media over UDP. To test WebRTC connection loss, interrupt the - connection outside the browser, for example by stopping the TURN server or using an OS-level firewall. + **NOTE** Offline emulation affects HTTP requests, such as page navigations, `fetch()` and `XMLHttpRequest`. + + WebSocket behavior depends on the browser and whether the connection is already established. Chromium and Firefox + fail new WebSocket connections while offline, but do not close already-open connections. Chromium can defer + incoming messages until the context goes back online, rather than lose them; Firefox can continue delivering + messages while offline. WebKit can allow both new WebSocket connections and traffic on established connections + while offline. Do not rely on offline emulation to test WebSocket message loss or connection closure. + + Offline emulation does not affect WebRTC traffic: established `RTCPeerConnection`s keep sending and receiving media + over UDP. To test WebRTC connection loss, interrupt the connection outside the browser, for example by stopping the + TURN server or using an OS-level firewall. """ diff --git a/tests/async/library/test_tracing.py b/tests/async/library/test_tracing.py index bb6d04562..4801a9669 100644 --- a/tests/async/library/test_tracing.py +++ b/tests/async/library/test_tracing.py @@ -138,6 +138,8 @@ async def test_TR_001_should_collect_trace_with_resources_but_no_js( assetdir: Path, tmp_path: Path, ) -> None: + # Python: the driver is out of process, so the in-process internalBody hook + # and its fetchedBodies checks do not apply (mode !== 'default' upstream). await context.tracing.start(screenshots=True, snapshots=True) await page.goto(server.PREFIX + "/frames/frame.html") await page.set_content("") @@ -177,6 +179,8 @@ async def test_TR_001_should_collect_trace_with_resources_but_no_js( script = resource_snapshot(events, "script.js") assert script assert "_file" not in script["snapshot"]["response"]["content"] + assert script["snapshot"]["response"]["bodySize"] > 0 + assert script["snapshot"]["response"]["headersSize"] > 0 async def test_TR_002_should_use_the_correct_title_for_event_driven_callbacks( diff --git a/tests/async/page/test_page_evaluate.py b/tests/async/page/test_page_evaluate.py index 4f3f37602..05e09e786 100644 --- a/tests/async/page/test_page_evaluate.py +++ b/tests/async/page/test_page_evaluate.py @@ -16,6 +16,7 @@ import asyncio import datetime +import json import math import os import re @@ -324,12 +325,33 @@ async def test_EV_031_should_include_a_non_error_rejection_value_in_the_error_me assert "null" in exc_info.value.message +@pytest.mark.xfail( + reason="python: protocol validation exceeds the recursion limit for a 200-level evaluation argument", + strict=True, +) async def test_EV_032_should_return_complex_objects(page: Page) -> None: object = {"foo": "bar!"} result = await page.evaluate("a => a", object) assert result is not object assert result == object + argument: Any = 0 + # Chromium rejects protocol arguments at 60 levels, WebKit at 200. + for _ in range(200): + argument = {"children": [argument]} + assert await page.evaluate( + "value => JSON.stringify(value).length", argument + ) == len(json.dumps(argument, separators=(",", ":"))) + + # An argument that is too deep rejects and does not hang. + for _ in range(800): + argument = {"children": [argument]} + with pytest.raises(Error) as exc_info: + await page.evaluate("value => JSON.stringify(value).length", argument) + assert re.search( + "Maximum call stack size exceeded|too much recursion", exc_info.value.message + ) + async def test_EV_033_should_return_na_n(page: Page) -> None: result = await page.evaluate("() => NaN") diff --git a/tests/async/page/test_page_event_pageerror.py b/tests/async/page/test_page_event_pageerror.py index 077393ec6..402c1527f 100644 --- a/tests/async/page/test_page_event_pageerror.py +++ b/tests/async/page/test_page_event_pageerror.py @@ -15,12 +15,13 @@ """Port of tests/page/page-event-pageerror.spec.ts.""" -from typing import Optional +import asyncio +from typing import List, Optional import pytest from playwright.async_api import Page -from tests.server import Server, TestServerRequest +from tests.server import Server, TestServerRequest, reactor def expect_failure(request: pytest.FixtureRequest, reason: str) -> None: @@ -349,3 +350,53 @@ def handle(request: TestServerRequest) -> None: assert "Invalid character" in error.message else: assert "illegal character" in error.message + + +async def test_ER_022_should_not_fire_for_a_caught_fetch_rejected_by_a_pending_navigation( + page: Page, server: Server +) -> None: + await page.goto(server.EMPTY_PAGE) + errors: List[str] = [] + page.on("pageerror", lambda error: errors.append(error.message)) + loop = asyncio.get_running_loop() + wait_response: asyncio.Future[TestServerRequest] = loop.create_future() + replacement_response: asyncio.Future[TestServerRequest] = loop.create_future() + # Python: the server handlers run on the reactor thread. + server.set_route( + "/wait", + lambda request: loop.call_soon_threadsafe(wait_response.set_result, request), + ) + server.set_route( + "/replacement", + lambda request: loop.call_soon_threadsafe( + replacement_response.set_result, request + ), + ) + async with page.expect_event( + "console", predicate=lambda message: message.text == "fetch done" + ) as fetch_done: + # Chromium does not run evaluate during a pending navigation, so the page starts the second fetch. + await page.evaluate( + """() => { + void fetch('/wait').catch(() => {}).then(() => fetch('/empty.html')).catch(() => {}).then(() => console.log('fetch done')); + }""" + ) + navigation = asyncio.create_task(page.goto(server.PREFIX + "/replacement")) + response = await replacement_response + # WebKit rejects the second fetch, because the document is about to go away. + reactor.callFromThread((await wait_response).finish) + await fetch_done.value + + def finish() -> None: + response.write(b"replacement") + response.finish() + + reactor.callFromThread(finish) + await navigation + async with page.expect_event("pageerror") as error_info: + await page.evaluate( + "() => setTimeout(() => { throw new Error('genuine error'); }, 0)" + ) + error = await error_info.value + assert error.message == "genuine error" + assert errors == ["genuine error"] diff --git a/tests/sync/library/test_tracing.py b/tests/sync/library/test_tracing.py index ced468124..8027c3caf 100644 --- a/tests/sync/library/test_tracing.py +++ b/tests/sync/library/test_tracing.py @@ -152,6 +152,8 @@ def test_TR_001_should_collect_trace_with_resources_but_no_js( assetdir: Path, tmp_path: Path, ) -> None: + # Python: the driver is out of process, so the in-process internalBody hook + # and its fetchedBodies checks do not apply (mode !== 'default' upstream). context.tracing.start(screenshots=True, snapshots=True) page.goto(server.PREFIX + "/frames/frame.html") page.set_content("") @@ -189,6 +191,8 @@ def test_TR_001_should_collect_trace_with_resources_but_no_js( script = resource_snapshot(events, "script.js") assert script assert "_file" not in script["snapshot"]["response"]["content"] + assert script["snapshot"]["response"]["bodySize"] > 0 + assert script["snapshot"]["response"]["headersSize"] > 0 def test_TR_002_should_use_the_correct_title_for_event_driven_callbacks( diff --git a/tests/sync/page/test_page_evaluate.py b/tests/sync/page/test_page_evaluate.py index 0f564ce95..1fa0b890a 100644 --- a/tests/sync/page/test_page_evaluate.py +++ b/tests/sync/page/test_page_evaluate.py @@ -15,6 +15,7 @@ """Port of tests/page/page-evaluate.spec.ts.""" import datetime +import json import math import os import re @@ -327,12 +328,33 @@ def test_EV_031_should_include_a_non_error_rejection_value_in_the_error_message( assert "null" in exc_info.value.message +@pytest.mark.xfail( + reason="python: protocol validation exceeds the recursion limit for a 200-level evaluation argument", + strict=True, +) def test_EV_032_should_return_complex_objects(page: Page) -> None: object = {"foo": "bar!"} result = page.evaluate("a => a", object) assert result is not object assert result == object + argument: Any = 0 + # Chromium rejects protocol arguments at 60 levels, WebKit at 200. + for _ in range(200): + argument = {"children": [argument]} + assert page.evaluate("value => JSON.stringify(value).length", argument) == len( + json.dumps(argument, separators=(",", ":")) + ) + + # An argument that is too deep rejects and does not hang. + for _ in range(800): + argument = {"children": [argument]} + with pytest.raises(Error) as exc_info: + page.evaluate("value => JSON.stringify(value).length", argument) + assert re.search( + "Maximum call stack size exceeded|too much recursion", exc_info.value.message + ) + def test_EV_033_should_return_na_n(page: Page) -> None: result = page.evaluate("() => NaN") diff --git a/tests/sync/page/test_page_event_pageerror.py b/tests/sync/page/test_page_event_pageerror.py index bf113321c..7991cdc4e 100644 --- a/tests/sync/page/test_page_event_pageerror.py +++ b/tests/sync/page/test_page_event_pageerror.py @@ -15,12 +15,14 @@ """Port of tests/page/page-event-pageerror.spec.ts.""" -from typing import Optional +import asyncio +from typing import Any, Callable, List, Optional import pytest +from playwright._impl._greenlets import wait_for_future from playwright.sync_api import Page -from tests.server import Server, TestServerRequest +from tests.server import Server, TestServerRequest, reactor def expect_failure(request: pytest.FixtureRequest, reason: str) -> None: @@ -349,3 +351,59 @@ def handle(request: TestServerRequest) -> None: assert "Invalid character" in error.message else: assert "illegal character" in error.message + + +def test_ER_022_should_not_fire_for_a_caught_fetch_rejected_by_a_pending_navigation( + page: Page, server: Server, sync_gather: Callable[..., List[Any]] +) -> None: + page.goto(server.EMPTY_PAGE) + errors: List[str] = [] + page.on("pageerror", lambda error: errors.append(error.message)) + loop = page._loop + wait_response: asyncio.Future[TestServerRequest] = loop.create_future() + replacement_response: asyncio.Future[TestServerRequest] = loop.create_future() + # Python: the server handlers run on the reactor thread. + server.set_route( + "/wait", + lambda request: loop.call_soon_threadsafe(wait_response.set_result, request), + ) + server.set_route( + "/replacement", + lambda request: loop.call_soon_threadsafe( + replacement_response.set_result, request + ), + ) + with page.expect_event( + "console", predicate=lambda message: message.text == "fetch done" + ) as fetch_done: + # Chromium does not run evaluate during a pending navigation, so the page starts the second fetch. + page.evaluate( + """() => { + void fetch('/wait').catch(() => {}).then(() => fetch('/empty.html')).catch(() => {}).then(() => console.log('fetch done')); + }""" + ) + + def finish_navigation() -> None: + wait_for_future(loop, page._dispatcher_fiber, replacement_response) + response = replacement_response.result() + # WebKit rejects the second fetch, because the document is about to go away. + wait_for_future(loop, page._dispatcher_fiber, wait_response) + reactor.callFromThread(wait_response.result().finish) + fetch_done.value + + def finish() -> None: + response.write(b"replacement") + response.finish() + + reactor.callFromThread(finish) + + sync_gather( + lambda: page.goto(server.PREFIX + "/replacement"), finish_navigation + ) + with page.expect_event("pageerror") as error_info: + page.evaluate( + "() => setTimeout(() => { throw new Error('genuine error'); }, 0)" + ) + error = error_info.value + assert error.message == "genuine error" + assert errors == ["genuine error"] diff --git a/tests/upstream_registry.md b/tests/upstream_registry.md index c1973c275..ee847431c 100644 --- a/tests/upstream_registry.md +++ b/tests/upstream_registry.md @@ -2064,6 +2064,7 @@ subsystem, add a row to the Subsystems table. Then update this file: | `ER-019` | `library/page-event-crash.spec.ts` | should be able to close context when page crashes | | `ER-020` | `library/page-event-crash.spec.ts` | should be able to close page after crash | | `ER-021` | `library/page-event-crash.spec.ts` | should reject in-flight worker.evaluate when page crashes | +| `ER-022` | `page/page-event-pageerror.spec.ts` | should not fire for a caught fetch rejected by a pending navigation | ### ES — Eval on selector