From 819fee986e75debbd1ef6031368e6fdb127aec0e Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Fri, 6 Nov 2026 09:44:07 +0000 Subject: [PATCH] chore: roll Playwright to 1.66.0-next (58d69dcc39) --- DRIVER_VERSION | 2 +- README.md | 2 +- playwright/_impl/_protocol.py | 5 +++ playwright/_impl/browser_context.py | 2 + playwright/_impl/locator.py | 4 ++ playwright/_impl/protocol/validator.py | 6 +++ playwright/_impl/protocol_channels.py | 10 +++++ playwright/_impl/protocol_types.py | 1 + tests/async/library/test_screencast.py | 9 +++-- tests/async/page/test_page_event_popup.py | 48 ++++++++++++++++++++++- tests/sync/library/test_screencast.py | 9 +++-- tests/sync/page/test_page_event_popup.py | 48 ++++++++++++++++++++++- tests/upstream_not_ported.txt | 2 + tests/upstream_registry.md | 2 + 14 files changed, 138 insertions(+), 12 deletions(-) diff --git a/DRIVER_VERSION b/DRIVER_VERSION index eb650ea4c..ae783c68c 100644 --- a/DRIVER_VERSION +++ b/DRIVER_VERSION @@ -1 +1 @@ -0ea36910324ed8aa2a5f460317cbd35881ee88d4 +58d69dcc392af3a6994eab0323dc2e883f05f396 diff --git a/README.md b/README.md index ea25d51b6..baf2e4d5f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 158.0.8127.2 | ✅ | ✅ | ✅ | +| Chromium 158.0.8127.18 | ✅ | ✅ | ✅ | | WebKit 27.2 | ✅ | ✅ | ✅ | | Firefox 159.0 | ✅ | ✅ | ✅ | diff --git a/playwright/_impl/_protocol.py b/playwright/_impl/_protocol.py index 3e0d7384c..10503a598 100644 --- a/playwright/_impl/_protocol.py +++ b/playwright/_impl/_protocol.py @@ -634,6 +634,7 @@ }, }, "strictSelectors": "boolean?", + "locateOnlyVisible": "boolean?", "serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]}, "selectorEngines": {"type": "array?", "items": "SelectorEngine"}, "testIdAttributeName": "string?", @@ -821,6 +822,7 @@ }, }, "strictSelectors": "boolean?", + "locateOnlyVisible": "boolean?", "serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]}, "selectorEngines": {"type": "array?", "items": "SelectorEngine"}, "testIdAttributeName": "string?", @@ -949,6 +951,7 @@ }, }, "strictSelectors": "boolean?", + "locateOnlyVisible": "boolean?", "serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]}, "selectorEngines": {"type": "array?", "items": "SelectorEngine"}, "testIdAttributeName": "string?", @@ -1285,6 +1288,7 @@ }, }, "strictSelectors": "boolean?", + "locateOnlyVisible": "boolean?", "serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]}, "selectorEngines": {"type": "array?", "items": "SelectorEngine"}, "testIdAttributeName": "string?", @@ -1375,6 +1379,7 @@ }, }, "strictSelectors": "boolean?", + "locateOnlyVisible": "boolean?", "timezoneId": "string?", "tracesDir": "string?", "artifactsDir": "string?", diff --git a/playwright/_impl/browser_context.py b/playwright/_impl/browser_context.py index 306c47f4d..2a9c19451 100644 --- a/playwright/_impl/browser_context.py +++ b/playwright/_impl/browser_context.py @@ -944,6 +944,8 @@ async def prepare_browser_context_params(options: Dict[str, Any]) -> Dict[str, A else None ), "strictSelectors": options.get("strict_selectors"), + # Not ported: locateOnlyVisible is JavaScript-only (docs: langs: js). + "locateOnlyVisible": None, "selectorEngines": options.get("selectorEngines"), "testIdAttributeName": options.get("testIdAttributeName"), "proxy": options.get("proxy"), diff --git a/playwright/_impl/locator.py b/playwright/_impl/locator.py index 6888d1ac0..a27110fcc 100644 --- a/playwright/_impl/locator.py +++ b/playwright/_impl/locator.py @@ -20,6 +20,10 @@ Python code of a normalized locator, and repr() shows the selector otherwise. The JavaScript-only API (util.inspect.custom, toString(), ariaSnapshotJSON(), the object form of the highlight style, signal) is not ported. + +Not ported: selectorForQuery(), for the JavaScript-only locateOnlyVisible context +option (docs: langs: js). Python queries keep the unmodified selector, including +screenshot masks and locator handlers. """ import json diff --git a/playwright/_impl/protocol/validator.py b/playwright/_impl/protocol/validator.py index c8f925af7..9fa10c900 100644 --- a/playwright/_impl/protocol/validator.py +++ b/playwright/_impl/protocol/validator.py @@ -324,6 +324,7 @@ ) ), "strictSelectors": t_optional(t_boolean), + "locateOnlyVisible": t_optional(t_boolean), "serviceWorkers": t_optional(t_enum(["allow", "block"])), "selectorEngines": t_optional(t_array(t_type("SelectorEngine"))), "testIdAttributeName": t_optional(t_string), @@ -783,6 +784,7 @@ ) ), "strictSelectors": t_optional(t_boolean), + "locateOnlyVisible": t_optional(t_boolean), "serviceWorkers": t_optional(t_enum(["allow", "block"])), "selectorEngines": t_optional(t_array(t_type("SelectorEngine"))), "testIdAttributeName": t_optional(t_string), @@ -923,6 +925,7 @@ ) ), "strictSelectors": t_optional(t_boolean), + "locateOnlyVisible": t_optional(t_boolean), "serviceWorkers": t_optional(t_enum(["allow", "block"])), "selectorEngines": t_optional(t_array(t_type("SelectorEngine"))), "testIdAttributeName": t_optional(t_string), @@ -1100,6 +1103,7 @@ ) ), "strictSelectors": t_optional(t_boolean), + "locateOnlyVisible": t_optional(t_boolean), "serviceWorkers": t_optional(t_enum(["allow", "block"])), "selectorEngines": t_optional(t_array(t_type("SelectorEngine"))), "testIdAttributeName": t_optional(t_string), @@ -1717,6 +1721,7 @@ ) ), "strictSelectors": t_optional(t_boolean), + "locateOnlyVisible": t_optional(t_boolean), "serviceWorkers": t_optional(t_enum(["allow", "block"])), "selectorEngines": t_optional(t_array(t_type("SelectorEngine"))), "testIdAttributeName": t_optional(t_string), @@ -1869,6 +1874,7 @@ ) ), "strictSelectors": t_optional(t_boolean), + "locateOnlyVisible": t_optional(t_boolean), "timezoneId": t_optional(t_string), "tracesDir": t_optional(t_string), "artifactsDir": t_optional(t_string), diff --git a/playwright/_impl/protocol_channels.py b/playwright/_impl/protocol_channels.py index 94220c522..8de46b6c6 100644 --- a/playwright/_impl/protocol_channels.py +++ b/playwright/_impl/protocol_channels.py @@ -332,6 +332,7 @@ def launchBrowser( baseURL: Optional[str], recordVideo: Optional[channels.AndroidDeviceLaunchBrowserParamsRecordVideo], strictSelectors: Optional[bool], + locateOnlyVisible: Optional[bool], serviceWorkers: Optional[Literal["allow", "block"]], selectorEngines: Optional[Sequence[channels.SelectorEngine]], testIdAttributeName: Optional[str], @@ -369,6 +370,7 @@ def launchBrowser( "baseURL": baseURL, "recordVideo": recordVideo, "strictSelectors": strictSelectors, + "locateOnlyVisible": locateOnlyVisible, "serviceWorkers": serviceWorkers, "selectorEngines": selectorEngines, "testIdAttributeName": testIdAttributeName, @@ -816,6 +818,7 @@ def newContext( baseURL: Optional[str], recordVideo: Optional[channels.BrowserNewContextParamsRecordVideo], strictSelectors: Optional[bool], + locateOnlyVisible: Optional[bool], serviceWorkers: Optional[Literal["allow", "block"]], selectorEngines: Optional[Sequence[channels.SelectorEngine]], testIdAttributeName: Optional[str], @@ -852,6 +855,7 @@ def newContext( "baseURL": baseURL, "recordVideo": recordVideo, "strictSelectors": strictSelectors, + "locateOnlyVisible": locateOnlyVisible, "serviceWorkers": serviceWorkers, "selectorEngines": selectorEngines, "testIdAttributeName": testIdAttributeName, @@ -894,6 +898,7 @@ def newContextForReuse( baseURL: Optional[str], recordVideo: Optional[channels.BrowserNewContextForReuseParamsRecordVideo], strictSelectors: Optional[bool], + locateOnlyVisible: Optional[bool], serviceWorkers: Optional[Literal["allow", "block"]], selectorEngines: Optional[Sequence[channels.SelectorEngine]], testIdAttributeName: Optional[str], @@ -930,6 +935,7 @@ def newContextForReuse( "baseURL": baseURL, "recordVideo": recordVideo, "strictSelectors": strictSelectors, + "locateOnlyVisible": locateOnlyVisible, "serviceWorkers": serviceWorkers, "selectorEngines": selectorEngines, "testIdAttributeName": testIdAttributeName, @@ -1804,6 +1810,7 @@ def launchPersistentContext( channels.BrowserTypeLaunchPersistentContextParamsRecordVideo ], strictSelectors: Optional[bool], + locateOnlyVisible: Optional[bool], serviceWorkers: Optional[Literal["allow", "block"]], selectorEngines: Optional[Sequence[channels.SelectorEngine]], testIdAttributeName: Optional[str], @@ -1857,6 +1864,7 @@ def launchPersistentContext( "baseURL": baseURL, "recordVideo": recordVideo, "strictSelectors": strictSelectors, + "locateOnlyVisible": locateOnlyVisible, "serviceWorkers": serviceWorkers, "selectorEngines": selectorEngines, "testIdAttributeName": testIdAttributeName, @@ -1940,6 +1948,7 @@ def launch( offline: Optional[bool], recordVideo: Optional[channels.ElectronLaunchParamsRecordVideo], strictSelectors: Optional[bool], + locateOnlyVisible: Optional[bool], timezoneId: Optional[str], tracesDir: Optional[str], artifactsDir: Optional[str], @@ -1966,6 +1975,7 @@ def launch( "offline": offline, "recordVideo": recordVideo, "strictSelectors": strictSelectors, + "locateOnlyVisible": locateOnlyVisible, "timezoneId": timezoneId, "tracesDir": tracesDir, "artifactsDir": artifactsDir, diff --git a/playwright/_impl/protocol_types.py b/playwright/_impl/protocol_types.py index 9ead111e8..54053e0ad 100644 --- a/playwright/_impl/protocol_types.py +++ b/playwright/_impl/protocol_types.py @@ -570,6 +570,7 @@ class BrowserContextInitializerOptions(TypedDict): baseURL: NotRequired[str] recordVideo: NotRequired[BrowserContextInitializerOptionsRecordVideo] strictSelectors: NotRequired[bool] + locateOnlyVisible: NotRequired[bool] serviceWorkers: NotRequired[Literal["allow", "block"]] selectorEngines: NotRequired[List[SelectorEngine]] testIdAttributeName: NotRequired[str] diff --git a/tests/async/library/test_screencast.py b/tests/async/library/test_screencast.py index fa727a215..d7facf710 100644 --- a/tests/async/library/test_screencast.py +++ b/tests/async/library/test_screencast.py @@ -88,10 +88,11 @@ async def test_SC_001_screencast_start_delivers_frames_via_on_frame_callback( # Each frame must be a valid JPEG (starts with FF D8) assert frame[0] == 0xFF assert frame[1] == 0xD8 - dimensions = jpeg_dimensions(frame) - # Frame should be scaled down to fit the maximum size. - assert dimensions["width"] == 500 - assert dimensions["height"] == 200 + # Chromium scales the first frame against the window size it had before the viewport + # was applied, so only the last frame is reliably scaled down to fit the maximum size. + dimensions = jpeg_dimensions(frames[-1]) + assert dimensions["width"] == 500 + assert dimensions["height"] == 200 await context.close() diff --git a/tests/async/page/test_page_event_popup.py b/tests/async/page/test_page_event_popup.py index f9ec9a286..5cad61ee4 100644 --- a/tests/async/page/test_page_event_popup.py +++ b/tests/async/page/test_page_event_popup.py @@ -16,9 +16,12 @@ import asyncio +from concurrent.futures import Future + +import pytest from playwright.async_api import Page -from tests.server import Server +from tests.server import Server, TestServerRequest, reactor async def test_PO_001_should_work_smoke(page: Page) -> None: @@ -185,3 +188,46 @@ async def test_PO_013_should_report_popup_opened_from_iframes( await frame.evaluate("() => window.open('')") popup = await popup_info.value assert popup + + +async def test_PO_030_should_finish_loading_after_opening_a_popup_while_a_cross_site_iframe_is_loading( + page: Page, server: Server, is_headless_shell: bool +) -> None: + if is_headless_shell: + pytest.skip( + "fixme: Without site isolation, which is the default in headless shell, the opener never fires the load event" + ) + + # Python: a thread-safe future gates the response on the Twisted reactor thread. + frame_may_finish: Future[None] = Future() + + def handle_frame(request: TestServerRequest) -> None: + def finish() -> None: + request.setHeader("Content-Type", "text/html") + request.write(b"
frame") + request.finish() + + frame_may_finish.add_done_callback(lambda _: reactor.callFromThread(finish)) + + server.set_route("/frame.html", handle_frame) + + def handle_opener(request: TestServerRequest) -> None: + request.setHeader("Content-Type", "text/html") + request.write( + f""" + + + """.encode() + ) + request.finish() + + server.set_route("/opener.html", handle_opener) + + async with page.expect_popup() as popup_info: + await page.goto(server.PREFIX + "/opener.html", wait_until="commit") + popup = await popup_info.value + # The iframe is still loading when the popup opens. + frame_may_finish.set_result(None) + await popup.wait_for_load_state("load") + await page.wait_for_load_state("load") + assert await page.evaluate("() => document.readyState") == "complete" diff --git a/tests/sync/library/test_screencast.py b/tests/sync/library/test_screencast.py index d702a94c4..08544e22b 100644 --- a/tests/sync/library/test_screencast.py +++ b/tests/sync/library/test_screencast.py @@ -98,10 +98,11 @@ def test_SC_001_screencast_start_delivers_frames_via_on_frame_callback( # Each frame must be a valid JPEG (starts with FF D8) assert frame[0] == 0xFF assert frame[1] == 0xD8 - dimensions = jpeg_dimensions(frame) - # Frame should be scaled down to fit the maximum size. - assert dimensions["width"] == 500 - assert dimensions["height"] == 200 + # Chromium scales the first frame against the window size it had before the viewport + # was applied, so only the last frame is reliably scaled down to fit the maximum size. + dimensions = jpeg_dimensions(frames[-1]) + assert dimensions["width"] == 500 + assert dimensions["height"] == 200 context.close() diff --git a/tests/sync/page/test_page_event_popup.py b/tests/sync/page/test_page_event_popup.py index 01f461d3e..4b64347c1 100644 --- a/tests/sync/page/test_page_event_popup.py +++ b/tests/sync/page/test_page_event_popup.py @@ -15,10 +15,13 @@ """Port of tests/page/page-event-popup.spec.ts.""" +from concurrent.futures import Future from typing import Any, Callable, List +import pytest + from playwright.sync_api import Page -from tests.server import Server +from tests.server import Server, TestServerRequest, reactor def test_PO_001_should_work_smoke(page: Page) -> None: @@ -180,3 +183,46 @@ def test_PO_013_should_report_popup_opened_from_iframes( frame.evaluate("() => window.open('')") popup = popup_info.value assert popup + + +def test_PO_030_should_finish_loading_after_opening_a_popup_while_a_cross_site_iframe_is_loading( + page: Page, server: Server, is_headless_shell: bool +) -> None: + if is_headless_shell: + pytest.skip( + "fixme: Without site isolation, which is the default in headless shell, the opener never fires the load event" + ) + + # Python: a thread-safe future gates the response on the Twisted reactor thread. + frame_may_finish: Future[None] = Future() + + def handle_frame(request: TestServerRequest) -> None: + def finish() -> None: + request.setHeader("Content-Type", "text/html") + request.write(b"frame") + request.finish() + + frame_may_finish.add_done_callback(lambda _: reactor.callFromThread(finish)) + + server.set_route("/frame.html", handle_frame) + + def handle_opener(request: TestServerRequest) -> None: + request.setHeader("Content-Type", "text/html") + request.write( + f""" + + + """.encode() + ) + request.finish() + + server.set_route("/opener.html", handle_opener) + + with page.expect_popup() as popup_info: + page.goto(server.PREFIX + "/opener.html", wait_until="commit") + popup = popup_info.value + # The iframe is still loading when the popup opens. + frame_may_finish.set_result(None) + popup.wait_for_load_state("load") + page.wait_for_load_state("load") + assert page.evaluate("() => document.readyState") == "complete" diff --git a/tests/upstream_not_ported.txt b/tests/upstream_not_ported.txt index d8e47a868..d8d92c8f0 100644 --- a/tests/upstream_not_ported.txt +++ b/tests/upstream_not_ported.txt @@ -53,6 +53,8 @@ IS-031 browserContext.addInitScript(script, arg, { exposeFunctions }) expos IS-032 browserContext.addInitScript(script, arg, { exposeFunctions }) exposes functions passed in arg; the arg parameter and the exposeFunctions option are JavaScript-only (docs: langs: js); Python add_init_script takes only script=