Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DRIVER_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0ea36910324ed8aa2a5f460317cbd35881ee88d4
58d69dcc392af3a6994eab0323dc2e883f05f396
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->158.0.8127.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->158.0.8127.18<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->27.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->159.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

Expand Down
5 changes: 5 additions & 0 deletions playwright/_impl/_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@
},
},
"strictSelectors": "boolean?",
"locateOnlyVisible": "boolean?",
"serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]},
"selectorEngines": {"type": "array?", "items": "SelectorEngine"},
"testIdAttributeName": "string?",
Expand Down Expand Up @@ -821,6 +822,7 @@
},
},
"strictSelectors": "boolean?",
"locateOnlyVisible": "boolean?",
"serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]},
"selectorEngines": {"type": "array?", "items": "SelectorEngine"},
"testIdAttributeName": "string?",
Expand Down Expand Up @@ -949,6 +951,7 @@
},
},
"strictSelectors": "boolean?",
"locateOnlyVisible": "boolean?",
"serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]},
"selectorEngines": {"type": "array?", "items": "SelectorEngine"},
"testIdAttributeName": "string?",
Expand Down Expand Up @@ -1285,6 +1288,7 @@
},
},
"strictSelectors": "boolean?",
"locateOnlyVisible": "boolean?",
"serviceWorkers": {"type": "enum?", "literals": ["allow", "block"]},
"selectorEngines": {"type": "array?", "items": "SelectorEngine"},
"testIdAttributeName": "string?",
Expand Down Expand Up @@ -1375,6 +1379,7 @@
},
},
"strictSelectors": "boolean?",
"locateOnlyVisible": "boolean?",
"timezoneId": "string?",
"tracesDir": "string?",
"artifactsDir": "string?",
Expand Down
2 changes: 2 additions & 0 deletions playwright/_impl/browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
4 changes: 4 additions & 0 deletions playwright/_impl/locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions playwright/_impl/protocol/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
10 changes: 10 additions & 0 deletions playwright/_impl/protocol_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -369,6 +370,7 @@ def launchBrowser(
"baseURL": baseURL,
"recordVideo": recordVideo,
"strictSelectors": strictSelectors,
"locateOnlyVisible": locateOnlyVisible,
"serviceWorkers": serviceWorkers,
"selectorEngines": selectorEngines,
"testIdAttributeName": testIdAttributeName,
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -852,6 +855,7 @@ def newContext(
"baseURL": baseURL,
"recordVideo": recordVideo,
"strictSelectors": strictSelectors,
"locateOnlyVisible": locateOnlyVisible,
"serviceWorkers": serviceWorkers,
"selectorEngines": selectorEngines,
"testIdAttributeName": testIdAttributeName,
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -930,6 +935,7 @@ def newContextForReuse(
"baseURL": baseURL,
"recordVideo": recordVideo,
"strictSelectors": strictSelectors,
"locateOnlyVisible": locateOnlyVisible,
"serviceWorkers": serviceWorkers,
"selectorEngines": selectorEngines,
"testIdAttributeName": testIdAttributeName,
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -1857,6 +1864,7 @@ def launchPersistentContext(
"baseURL": baseURL,
"recordVideo": recordVideo,
"strictSelectors": strictSelectors,
"locateOnlyVisible": locateOnlyVisible,
"serviceWorkers": serviceWorkers,
"selectorEngines": selectorEngines,
"testIdAttributeName": testIdAttributeName,
Expand Down Expand Up @@ -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],
Expand All @@ -1966,6 +1975,7 @@ def launch(
"offline": offline,
"recordVideo": recordVideo,
"strictSelectors": strictSelectors,
"locateOnlyVisible": locateOnlyVisible,
"timezoneId": timezoneId,
"tracesDir": tracesDir,
"artifactsDir": artifactsDir,
Expand Down
1 change: 1 addition & 0 deletions playwright/_impl/protocol_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
9 changes: 5 additions & 4 deletions tests/async/library/test_screencast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
48 changes: 47 additions & 1 deletion tests/async/page/test_page_event_popup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"<body>frame</body>")
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"""
<iframe src="{server.CROSS_PROCESS_PREFIX}/frame.html"></iframe>
<script>window.open('{server.EMPTY_PAGE}')</script>
""".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"
9 changes: 5 additions & 4 deletions tests/sync/library/test_screencast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
48 changes: 47 additions & 1 deletion tests/sync/page/test_page_event_popup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"<body>frame</body>")
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"""
<iframe src="{server.CROSS_PROCESS_PREFIX}/frame.html"></iframe>
<script>window.open('{server.EMPTY_PAGE}')</script>
""".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"
2 changes: 2 additions & 0 deletions tests/upstream_not_ported.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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=<str> or path=.
IS-033 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=<str> or path=.
IS-034 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=<str> or path=.
# library/browsercontext-basic
BC-084 The locateOnlyVisible context option is JavaScript-only (docs: langs: js).
# library/browsercontext-credentials
AU-002 BrowserContext.setHTTPCredentials() is JavaScript-only (langs: js); it is not in the Python API.
AU-003 BrowserContext.setHTTPCredentials() is JavaScript-only (langs: js); it is not in the Python API.
Expand Down
2 changes: 2 additions & 0 deletions tests/upstream_registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ subsystem, add a row to the Subsystems table. Then update this file:
| `BC-081` | `library/browsercontext-reuse.spec.ts` | reuse {launch, connect} › should continue issuing events after closing the reused page *(2 copies)* |
| `BC-082` | `library/browsercontext-reuse.spec.ts` | reuse {launch, connect} › should work with routeWebSocket *(2 copies)* |
| `BC-083` | `library/browsercontext-reuse.spec.ts` | reuse {launch, connect} › should update viewport and media *(2 copies)* |
| `BC-084` | `library/browsercontext-basic.spec.ts` | should locate only visible elements when configured |

### BP — Browsers path

Expand Down Expand Up @@ -4190,6 +4191,7 @@ subsystem, add a row to the Subsystems table. Then update this file:
| `PO-027` | `library/popup.spec.ts` | should not throttle rAF in the opener page |
| `PO-028` | `library/popup.spec.ts` | should not throw when click closes popup |
| `PO-029` | `library/popup.spec.ts` | should load subresources of an about:blank popup when the opener has routes |
| `PO-030` | `page/page-event-popup.spec.ts` | should finish loading after opening a popup while a cross-site iframe is loading |

### PR — Proxy

Expand Down
Loading