From 3f0439633d8e80a09c25be664df1fc2ac53a846c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:31:22 +0100 Subject: [PATCH 001/117] build(deps): bump setuptools from 75.5.0 to 75.6.0 (#2668) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 963a75a41..06681d51d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools==75.5.0", "setuptools-scm==8.1.0", "wheel==0.45.0", "auditwheel==6.1.0"] +requires = ["setuptools==75.6.0", "setuptools-scm==8.1.0", "wheel==0.45.0", "auditwheel==6.1.0"] build-backend = "setuptools.build_meta" [project] From c5acc36f1f7cc3d61b9ef70f722f894a6e588793 Mon Sep 17 00:00:00 2001 From: Daniel Nordio <15243341+ttm56p@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:42:23 +0200 Subject: [PATCH 002/117] devops: fix build process producing wheels with incorrect RECORD (#2671) --- setup.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index b4212cb9d..f3e9690f2 100644 --- a/setup.py +++ b/setup.py @@ -165,13 +165,12 @@ def _build_wheel( for whlfile in glob.glob(os.path.join(self.dist_dir, "*.whl")): os.makedirs("wheelhouse", exist_ok=True) if InWheel: - with InWheel( - in_wheel=whlfile, - out_wheel=os.path.join("wheelhouse", os.path.basename(whlfile)), - ): + wheelhouse_whl = os.path.join("wheelhouse", os.path.basename(whlfile)) + shutil.move(whlfile, wheelhouse_whl) + with InWheel(in_wheel=wheelhouse_whl, out_wheel=whlfile): print(f"Updating RECORD file of {whlfile}") print("Copying new wheels") - shutil.move("wheelhouse", self.dist_dir) + shutil.rmtree("wheelhouse") def _download_and_extract_local_driver( self, From 445f80a0d9864898d8f1cd0518c147b24850b873 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:52:14 +0100 Subject: [PATCH 003/117] build(deps): bump wheel from 0.45.0 to 0.45.1 (#2667) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 06681d51d..b4de55327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools==75.6.0", "setuptools-scm==8.1.0", "wheel==0.45.0", "auditwheel==6.1.0"] +requires = ["setuptools==75.6.0", "setuptools-scm==8.1.0", "wheel==0.45.1", "auditwheel==6.1.0"] build-backend = "setuptools.build_meta" [project] From 1909d207ba7fc4ce4b0b39c5f5b7e4666c4d33a1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 10 Dec 2024 09:22:35 -0800 Subject: [PATCH 004/117] chore: roll Playwright to v1.49.1 (#2684) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f3e9690f2..f4c93dc3c 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.49.0-beta-1732210972000" +driver_version = "1.49.1" base_wheel_bundles = [ { From c686e25b82a77106fdc4fc2fa44c018cf14e0dd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:04:08 -0800 Subject: [PATCH 005/117] build(deps): bump pyopenssl from 24.2.1 to 24.3.0 (#2676) --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 3a1791441..cad04f4d0 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -8,7 +8,7 @@ objgraph==3.6.2 Pillow==10.4.0 pixelmatch==0.3.0 pre-commit==3.5.0 -pyOpenSSL==24.2.1 +pyOpenSSL==24.3.0 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==6.0.0 From 8429cf083ae3a61cbeaf90e99a5d352e619979e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:05:43 -0800 Subject: [PATCH 006/117] build(deps): bump pytest from 8.3.3 to 8.3.4 (#2678) --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index cad04f4d0..3e01db05e 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -9,7 +9,7 @@ Pillow==10.4.0 pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==24.3.0 -pytest==8.3.3 +pytest==8.3.4 pytest-asyncio==0.24.0 pytest-cov==6.0.0 pytest-repeat==0.9.3 From 4f2cdde7af89a85d53ac3ea6e00823b7fd72ef25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:06:37 -0800 Subject: [PATCH 007/117] build(deps): bump twisted from 24.10.0 to 24.11.0 (#2677) --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 3e01db05e..5aa0b0fc4 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -17,6 +17,6 @@ pytest-timeout==2.3.1 pytest-xdist==3.6.1 requests==2.32.3 service_identity==24.2.0 -twisted==24.10.0 +twisted==24.11.0 types-pyOpenSSL==24.1.0.20240722 types-requests==2.32.0.20241016 From 00fbc3c6a6ca104c4d016b2341e42d7637ff171b Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 12 Dec 2024 15:38:07 -0800 Subject: [PATCH 008/117] fix(webSocketRoute): allow no trailing slash in route matching (#2687) --- playwright/_impl/_helper.py | 6 ++++- tests/async/test_route_web_socket.py | 33 ++++++++++++++++++++++++++++ tests/sync/test_route_web_socket.py | 31 ++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index d0737be07..538d5533a 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -34,7 +34,7 @@ Union, cast, ) -from urllib.parse import urljoin +from urllib.parse import urljoin, urlparse from playwright._impl._api_structures import NameValue from playwright._impl._errors import ( @@ -157,6 +157,10 @@ def url_matches( base_url = re.sub(r"^http", "ws", base_url) if base_url: match = urljoin(base_url, match) + parsed = urlparse(match) + if parsed.path == "": + parsed = parsed._replace(path="/") + match = parsed.geturl() if isinstance(match, str): match = glob_to_regex(match) if isinstance(match, Pattern): diff --git a/tests/async/test_route_web_socket.py b/tests/async/test_route_web_socket.py index 2ebda4b9e..465832adf 100644 --- a/tests/async/test_route_web_socket.py +++ b/tests/async/test_route_web_socket.py @@ -346,3 +346,36 @@ async def _handle_ws(ws: WebSocketRoute) -> None: f"message: data=echo origin=ws://localhost:{server.PORT} lastEventId=", ], ) + + +async def test_should_work_with_no_trailing_slash(page: Page, server: Server) -> None: + log: list[str] = [] + + async def handle_ws(ws: WebSocketRoute) -> None: + def on_message(message: Union[str, bytes]) -> None: + assert isinstance(message, str) + log.append(message) + ws.send("response") + + ws.on_message(on_message) + + # No trailing slash in the route pattern + await page.route_web_socket(f"ws://localhost:{server.PORT}", handle_ws) + + await page.goto("about:blank") + await page.evaluate( + """({ port }) => { + window.log = []; + // No trailing slash in WebSocket URL + window.ws = new WebSocket('ws://localhost:' + port); + window.ws.addEventListener('message', event => window.log.push(event.data)); + }""", + {"port": server.PORT}, + ) + + await assert_equal( + lambda: page.evaluate("window.ws.readyState"), 1 # WebSocket.OPEN + ) + await page.evaluate("window.ws.send('query')") + await assert_equal(lambda: log, ["query"]) + await assert_equal(lambda: page.evaluate("window.log"), ["response"]) diff --git a/tests/sync/test_route_web_socket.py b/tests/sync/test_route_web_socket.py index a22a6e883..2e97ebd8d 100644 --- a/tests/sync/test_route_web_socket.py +++ b/tests/sync/test_route_web_socket.py @@ -340,3 +340,34 @@ def _handle_ws(ws: WebSocketRoute) -> None: f"message: data=echo origin=ws://localhost:{server.PORT} lastEventId=", ], ) + + +def test_should_work_with_no_trailing_slash(page: Page, server: Server) -> None: + log: list[str] = [] + + async def handle_ws(ws: WebSocketRoute) -> None: + def on_message(message: Union[str, bytes]) -> None: + assert isinstance(message, str) + log.append(message) + ws.send("response") + + ws.on_message(on_message) + + # No trailing slash in the route pattern + page.route_web_socket(f"ws://localhost:{server.PORT}", handle_ws) + + page.goto("about:blank") + page.evaluate( + """({ port }) => { + window.log = []; + // No trailing slash in WebSocket URL + window.ws = new WebSocket('ws://localhost:' + port); + window.ws.addEventListener('message', event => window.log.push(event.data)); + }""", + {"port": server.PORT}, + ) + + assert_equal(lambda: page.evaluate("window.ws.readyState"), 1) # WebSocket.OPEN + page.evaluate("window.ws.send('query')") + assert_equal(lambda: log, ["query"]) + assert_equal(lambda: page.evaluate("window.log"), ["response"]) From 70c5031cc78439ae6ca6d03984a7de0d0eac7290 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:03:29 +0100 Subject: [PATCH 009/117] build(deps): bump pytest-asyncio from 0.24.0 to 0.25.0 (#2690) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 5aa0b0fc4..10dbe6eee 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -10,7 +10,7 @@ pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==24.3.0 pytest==8.3.4 -pytest-asyncio==0.24.0 +pytest-asyncio==0.25.0 pytest-cov==6.0.0 pytest-repeat==0.9.3 pytest-timeout==2.3.1 From 6d777fedc2926452978d52ba3af3fe8328c4d2bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 11:45:43 +0100 Subject: [PATCH 010/117] build(deps): bump mypy from 1.13.0 to 1.14.0 (#2695) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 10dbe6eee..4f458d4a5 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -3,7 +3,7 @@ black==24.8.0 build==1.2.2.post1 flake8==7.1.1 flaky==3.8.1 -mypy==1.13.0 +mypy==1.14.0 objgraph==3.6.2 Pillow==10.4.0 pixelmatch==0.3.0 From 4ae12bd37016d7fe927076befdd974137fd69704 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:26:59 +0300 Subject: [PATCH 011/117] build(deps): bump pytest-asyncio from 0.25.0 to 0.25.1 (#2711) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 4f458d4a5..043bd5a31 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -10,7 +10,7 @@ pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==24.3.0 pytest==8.3.4 -pytest-asyncio==0.25.0 +pytest-asyncio==0.25.1 pytest-cov==6.0.0 pytest-repeat==0.9.3 pytest-timeout==2.3.1 From dffa098606633b6ca4573c4ab12ba7808337ae07 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 22 Jan 2025 16:34:49 +0100 Subject: [PATCH 012/117] fix(webError): fix WebError when using sync API (#2721) --- playwright/_impl/_browser_context.py | 5 ++++- playwright/_impl/_web_error.py | 9 +++++++-- playwright/async_api/__init__.py | 2 ++ playwright/sync_api/__init__.py | 2 ++ tests/async/test_browsercontext_events.py | 10 +++++++++- tests/sync/test_browsercontext_events.py | 10 +++++++++- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index f415d5900..e5a9b14fd 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -692,7 +692,10 @@ def _on_dialog(self, dialog: Dialog) -> None: asyncio.create_task(dialog.dismiss()) def _on_page_error(self, error: Error, page: Optional[Page]) -> None: - self.emit(BrowserContext.Events.WebError, WebError(self._loop, page, error)) + self.emit( + BrowserContext.Events.WebError, + WebError(self._loop, self._dispatcher_fiber, page, error), + ) if page: page.emit(Page.Events.PageError, error) diff --git a/playwright/_impl/_web_error.py b/playwright/_impl/_web_error.py index eb1b51948..345f95b8f 100644 --- a/playwright/_impl/_web_error.py +++ b/playwright/_impl/_web_error.py @@ -13,7 +13,7 @@ # limitations under the License. from asyncio import AbstractEventLoop -from typing import Optional +from typing import Any, Optional from playwright._impl._helper import Error from playwright._impl._page import Page @@ -21,9 +21,14 @@ class WebError: def __init__( - self, loop: AbstractEventLoop, page: Optional[Page], error: Error + self, + loop: AbstractEventLoop, + dispatcher_fiber: Any, + page: Optional[Page], + error: Error, ) -> None: self._loop = loop + self._dispatcher_fiber = dispatcher_fiber self._page = page self._error = error diff --git a/playwright/async_api/__init__.py b/playwright/async_api/__init__.py index a64a066c2..be918f53c 100644 --- a/playwright/async_api/__init__.py +++ b/playwright/async_api/__init__.py @@ -60,6 +60,7 @@ Selectors, Touchscreen, Video, + WebError, WebSocket, WebSocketRoute, Worker, @@ -190,6 +191,7 @@ def __call__( "Touchscreen", "Video", "ViewportSize", + "WebError", "WebSocket", "WebSocketRoute", "Worker", diff --git a/playwright/sync_api/__init__.py b/playwright/sync_api/__init__.py index 80eaf71db..136433982 100644 --- a/playwright/sync_api/__init__.py +++ b/playwright/sync_api/__init__.py @@ -60,6 +60,7 @@ Selectors, Touchscreen, Video, + WebError, WebSocket, WebSocketRoute, Worker, @@ -190,6 +191,7 @@ def __call__( "Touchscreen", "Video", "ViewportSize", + "WebError", "WebSocket", "WebSocketRoute", "Worker", diff --git a/tests/async/test_browsercontext_events.py b/tests/async/test_browsercontext_events.py index a0a3b90eb..8ae14def6 100644 --- a/tests/async/test_browsercontext_events.py +++ b/tests/async/test_browsercontext_events.py @@ -17,7 +17,7 @@ import pytest -from playwright.async_api import Page +from playwright.async_api import BrowserContext, Page from tests.utils import must from ..server import Server, TestServerRequest @@ -198,3 +198,11 @@ async def test_page_error_event_should_work(page: Page) -> None: page_error = await page_error_info.value assert page_error.page == page assert "boom" in page_error.error.stack + + +async def test_weberror_event_should_work(context: BrowserContext, page: Page) -> None: + async with context.expect_event("weberror") as error_info: + await page.goto('data:text/html,') + error = await error_info.value + assert error.page == page + assert error.error.message == "Test" diff --git a/tests/sync/test_browsercontext_events.py b/tests/sync/test_browsercontext_events.py index 315fff0dc..6e44b76d5 100644 --- a/tests/sync/test_browsercontext_events.py +++ b/tests/sync/test_browsercontext_events.py @@ -16,7 +16,7 @@ import pytest -from playwright.sync_api import Dialog, Page +from playwright.sync_api import BrowserContext, Dialog, Page from ..server import Server, TestServerRequest @@ -198,3 +198,11 @@ def test_console_event_should_work_with_context_manager(page: Page) -> None: message = cm_info.value assert message.text == "hello" assert message.page == page + + +def test_weberror_event_should_work(context: BrowserContext, page: Page) -> None: + with context.expect_event("weberror") as error_info: + page.goto('data:text/html,') + error = error_info.value + assert error.page == page + assert error.error.message == "Test" From b74a3dc17472aa9562d998a50cc3d36dc90af198 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 07:58:00 +0100 Subject: [PATCH 013/117] build(deps): bump mypy from 1.14.0 to 1.14.1 (#2703) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 043bd5a31..d0fc629c9 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -3,7 +3,7 @@ black==24.8.0 build==1.2.2.post1 flake8==7.1.1 flaky==3.8.1 -mypy==1.14.0 +mypy==1.14.1 objgraph==3.6.2 Pillow==10.4.0 pixelmatch==0.3.0 From 84e7e156e0acedf04120081aecf90b97e5d4a122 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 07:58:07 +0100 Subject: [PATCH 014/117] build(deps): bump auditwheel from 6.1.0 to 6.2.0 (#2709) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b4de55327..74484b0ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools==75.6.0", "setuptools-scm==8.1.0", "wheel==0.45.1", "auditwheel==6.1.0"] +requires = ["setuptools==75.6.0", "setuptools-scm==8.1.0", "wheel==0.45.1", "auditwheel==6.2.0"] build-backend = "setuptools.build_meta" [project] From 9010889cd6e2292e9bb6bdf1d75cf443d52c4edf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 07:58:22 +0100 Subject: [PATCH 015/117] build(deps): bump pillow from 10.4.0 to 11.1.0 (#2710) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index d0fc629c9..2610edb4f 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -5,7 +5,7 @@ flake8==7.1.1 flaky==3.8.1 mypy==1.14.1 objgraph==3.6.2 -Pillow==10.4.0 +Pillow==11.1.0 pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==24.3.0 From 4ecf61e18bec0de89d1eb540ad2ae1edb4ceffcc Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 27 Jan 2025 09:39:55 +0100 Subject: [PATCH 016/117] fix(assertions): allow tuple as valid input type for expected text values (#2723) --- playwright/_impl/_assertions.py | 2 +- tests/async/test_assertions.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/playwright/_impl/_assertions.py b/playwright/_impl/_assertions.py index fce405da7..b226e241f 100644 --- a/playwright/_impl/_assertions.py +++ b/playwright/_impl/_assertions.py @@ -874,7 +874,7 @@ def to_expected_text_values( ignoreCase: Optional[bool] = None, ) -> Sequence[ExpectedTextValue]: out: List[ExpectedTextValue] = [] - assert isinstance(items, list) + assert isinstance(items, (list, tuple)) for item in items: if isinstance(item, str): o = ExpectedTextValue( diff --git a/tests/async/test_assertions.py b/tests/async/test_assertions.py index 88b9c1b4f..dc0a1e615 100644 --- a/tests/async/test_assertions.py +++ b/tests/async/test_assertions.py @@ -274,6 +274,10 @@ async def test_assertions_locator_to_have_text(page: Page, server: Server) -> No await expect(page.locator("div")).to_have_text( ["Text 1", re.compile(r"Text \d+a")] ) + # Should work with a tuple + await expect(page.locator("div")).to_have_text( + ("Text 1", re.compile(r"Text \d+a")) + ) @pytest.mark.parametrize( From 9ab78abb3c72c6182051bcb9bcad543a71e0c08c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 27 Jan 2025 16:18:56 +0100 Subject: [PATCH 017/117] chore: relax dependency versions (#2698) --- .azure-pipelines/publish.yml | 1 + .github/workflows/ci.yml | 3 +++ .github/workflows/publish_docker.yml | 1 + .github/workflows/test_docker.yml | 2 ++ meta.yaml | 5 +++-- pyproject.toml | 7 +++++-- requirements.txt | 8 ++++++++ 7 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 requirements.txt diff --git a/.azure-pipelines/publish.yml b/.azure-pipelines/publish.yml index 6674eaae2..0076089ab 100644 --- a/.azure-pipelines/publish.yml +++ b/.azure-pipelines/publish.yml @@ -38,6 +38,7 @@ extends: - script: | python -m pip install --upgrade pip pip install -r local-requirements.txt + pip install -r requirements.txt pip install -e . for wheel in $(python setup.py --list-wheels); do PLAYWRIGHT_TARGET_WHEEL=$wheel python -m build --wheel diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 624269f05..929b05b8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r local-requirements.txt + pip install -r requirements.txt pip install -e . python -m build --wheel python -m playwright install --with-deps @@ -88,6 +89,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r local-requirements.txt + pip install -r requirements.txt pip install -e . python -m build --wheel python -m playwright install --with-deps ${{ matrix.browser }} @@ -134,6 +136,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r local-requirements.txt + pip install -r requirements.txt pip install -e . python -m build --wheel python -m playwright install ${{ matrix.browser-channel }} --with-deps diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index 99ac96c7f..7d83136bc 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -36,5 +36,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -r local-requirements.txt + pip install -r requirements.txt pip install -e . - run: ./utils/docker/publish_docker.sh stable diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml index 9d70ae303..573370f13 100644 --- a/.github/workflows/test_docker.yml +++ b/.github/workflows/test_docker.yml @@ -36,6 +36,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r local-requirements.txt + pip install -r requirements.txt pip install -e . - name: Build Docker image run: bash utils/docker/build.sh --amd64 ${{ matrix.docker-image-variant }} playwright-python:localbuild-${{ matrix.docker-image-variant }} @@ -45,6 +46,7 @@ jobs: # Fix permissions for Git inside the container docker exec "${CONTAINER_ID}" chown -R root:root /root/playwright docker exec "${CONTAINER_ID}" pip install -r local-requirements.txt + docker exec "${CONTAINER_ID}" pip install -r requirements.txt docker exec "${CONTAINER_ID}" pip install -e . docker exec "${CONTAINER_ID}" python -m build --wheel docker exec "${CONTAINER_ID}" xvfb-run pytest -vv tests/sync/ diff --git a/meta.yaml b/meta.yaml index f9fc9d5ba..f78f0e90f 100644 --- a/meta.yaml +++ b/meta.yaml @@ -26,8 +26,9 @@ requirements: - setuptools_scm run: - python >=3.9 - - greenlet ==3.1.1 - - pyee ==12.1.1 + # This should be the same as the dependencies in pyproject.toml + - greenlet>=3.1.1,<4.0.0 + - pyee>=12,<13 test: # [build_platform == target_platform] requires: diff --git a/pyproject.toml b/pyproject.toml index 74484b0ca..8c66a788a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,9 +12,12 @@ readme = "README.md" license = {text = "Apache-2.0"} dynamic = ["version"] requires-python = ">=3.9" +# Please when changing dependencies run the following commands to update requirements.txt: +# - pip install uv==0.5.4 +# - uv pip compile pyproject.toml -o requirements.txt dependencies = [ - "greenlet==3.1.1", - "pyee==12.1.1", + "pyee>=12,<13", + "greenlet>=3.1.1,<4.0.0" ] classifiers = [ "Topic :: Software Development :: Testing", diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..eaa753330 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o requirements.txt +greenlet==3.1.1 + # via playwright (pyproject.toml) +pyee==12.1.1 + # via playwright (pyproject.toml) +typing-extensions==4.12.2 + # via pyee From 4712d3f4cebf8096d2b1c8125067ad99595996ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:58:45 +0100 Subject: [PATCH 018/117] build(deps): bump pytest-asyncio from 0.25.1 to 0.25.2 (#2724) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 2610edb4f..7134a315e 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -10,7 +10,7 @@ pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==24.3.0 pytest==8.3.4 -pytest-asyncio==0.25.1 +pytest-asyncio==0.25.2 pytest-cov==6.0.0 pytest-repeat==0.9.3 pytest-timeout==2.3.1 From fb271bd2e919fba429fec35c0ee2cfe1136a5111 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Jan 2025 14:06:41 +0100 Subject: [PATCH 019/117] chore(roll): roll Playwright to v1.50 (#2726) --- README.md | 4 +- playwright/_impl/_assertions.py | 54 +++++++++-- playwright/_impl/_network.py | 2 + playwright/async_api/_generated.py | 141 ++++++++++++++++++++++----- playwright/sync_api/_generated.py | 147 ++++++++++++++++++++++++----- setup.py | 2 +- tests/async/test_assertions.py | 83 +++++++++++++++- tests/async/test_locators.py | 18 +++- tests/async/test_tracing.py | 2 - tests/sync/test_assertions.py | 139 ++++++++++++++++++++++++++- tests/sync/test_locators.py | 18 +++- tests/sync/test_tracing.py | 2 - 12 files changed, 538 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 1efcead54..9a5529b13 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 131.0.6778.33 | ✅ | ✅ | ✅ | +| Chromium 133.0.6943.16 | ✅ | ✅ | ✅ | | WebKit 18.2 | ✅ | ✅ | ✅ | -| Firefox 132.0 | ✅ | ✅ | ✅ | +| Firefox 134.0 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_assertions.py b/playwright/_impl/_assertions.py index b226e241f..8ec657531 100644 --- a/playwright/_impl/_assertions.py +++ b/playwright/_impl/_assertions.py @@ -525,14 +525,22 @@ async def to_be_checked( self, timeout: float = None, checked: bool = None, + indeterminate: bool = None, ) -> None: __tracebackhide__ = True - if checked is None: - checked = True - checked_string = "checked" if checked else "unchecked" + expected_value = {} + if indeterminate is not None: + expected_value["indeterminate"] = indeterminate + if checked is not None: + expected_value["checked"] = checked + checked_string: str + if indeterminate: + checked_string = "indeterminate" + else: + checked_string = "unchecked" if checked is False else "checked" await self._expect_impl( - ("to.be.checked" if checked else "to.be.unchecked"), - FrameExpectOptions(timeout=timeout), + "to.be.checked", + FrameExpectOptions(timeout=timeout, expectedValue=expected_value), None, f"Locator expected to be {checked_string}", ) @@ -726,7 +734,9 @@ async def to_have_accessible_description( timeout: float = None, ) -> None: __tracebackhide__ = True - expected_values = to_expected_text_values([description], ignoreCase=ignoreCase) + expected_values = to_expected_text_values( + [description], ignoreCase=ignoreCase, normalize_white_space=True + ) await self._expect_impl( "to.have.accessible.description", FrameExpectOptions(expectedText=expected_values, timeout=timeout), @@ -750,7 +760,9 @@ async def to_have_accessible_name( timeout: float = None, ) -> None: __tracebackhide__ = True - expected_values = to_expected_text_values([name], ignoreCase=ignoreCase) + expected_values = to_expected_text_values( + [name], ignoreCase=ignoreCase, normalize_white_space=True + ) await self._expect_impl( "to.have.accessible.name", FrameExpectOptions(expectedText=expected_values, timeout=timeout), @@ -779,6 +791,34 @@ async def to_have_role(self, role: AriaRole, timeout: float = None) -> None: "Locator expected to have accessible role", ) + async def to_have_accessible_error_message( + self, + errorMessage: Union[str, Pattern[str]], + ignoreCase: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + expected_values = to_expected_text_values( + [errorMessage], ignoreCase=ignoreCase, normalize_white_space=True + ) + await self._expect_impl( + "to.have.accessible.error.message", + FrameExpectOptions(expectedText=expected_values, timeout=timeout), + None, + "Locator expected to have accessible error message", + ) + + async def not_to_have_accessible_error_message( + self, + errorMessage: Union[str, Pattern[str]], + ignoreCase: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_accessible_error_message( + errorMessage=errorMessage, ignoreCase=ignoreCase, timeout=timeout + ) + async def not_to_have_role(self, role: AriaRole, timeout: float = None) -> None: __tracebackhide__ = True await self._not.to_have_role(role, timeout) diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 97bb049e3..4b15531af 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -131,6 +131,7 @@ def __init__( self, parent: ChannelOwner, type: str, guid: str, initializer: Dict ) -> None: super().__init__(parent, type, guid, initializer) + self._channel.mark_as_internal_type() self._redirected_from: Optional["Request"] = from_nullable_channel( initializer.get("redirectedFrom") ) @@ -767,6 +768,7 @@ def __init__( self, parent: ChannelOwner, type: str, guid: str, initializer: Dict ) -> None: super().__init__(parent, type, guid, initializer) + self._channel.mark_as_internal_type() self._request: Request = from_channel(self._initializer["request"]) timing = self._initializer["timing"] self._request._timing["startTime"] = timing["startTime"] diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index e1480f5bf..7b92fbafb 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -6879,6 +6879,18 @@ async def pause_at(self, time: typing.Union[float, str, datetime.datetime]) -> N await page.clock.pause_at(\"2020-02-02\") ``` + For best results, install the clock before navigating the page and set it to a time slightly before the intended + test time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. + Once the page has fully loaded, you can safely use `clock.pause_at()` to pause the clock. + + ```py + # Initialize clock with some time before the test time and let the page load + # naturally. `Date.now` will progress as the timers fire. + await page.clock.install(time=datetime.datetime(2024, 12, 10, 8, 0, 0)) + await page.goto(\"http://localhost:3333\") + await page.clock.pause_at(datetime.datetime(2024, 12, 10, 10, 0, 0)) + ``` + Parameters ---------- time : Union[datetime.datetime, float, str] @@ -8036,7 +8048,7 @@ def set_default_timeout(self, timeout: float) -> None: Parameters ---------- timeout : float - Maximum time in milliseconds + Maximum time in milliseconds. Pass `0` to disable timeout. """ return mapping.from_maybe_impl( @@ -11497,8 +11509,6 @@ async def pdf( Returns the PDF buffer. - **NOTE** Generating a pdf is currently only supported in Chromium headless. - `page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call `page.emulate_media()` before calling `page.pdf()`: @@ -12750,7 +12760,7 @@ def set_default_timeout(self, timeout: float) -> None: Parameters ---------- timeout : float - Maximum time in milliseconds + Maximum time in milliseconds. Pass `0` to disable timeout. """ return mapping.from_maybe_impl( @@ -12858,9 +12868,13 @@ async def grant_permissions( Parameters ---------- permissions : Sequence[str] - A permission or an array of permissions to grant. Permissions can be one of the following values: + A list of permissions to grant. + + **NOTE** Supported permissions differ between browsers, and even between different versions of the same browser. + Any permission may stop working after an update. + + Here are some permissions that may be supported by some browsers: - `'accelerometer'` - - `'accessibility-events'` - `'ambient-light-sensor'` - `'background-sync'` - `'camera'` @@ -14161,9 +14175,9 @@ async def close(self, *, reason: typing.Optional[str] = None) -> None: In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the browser server. - **NOTE** This is similar to force quitting the browser. Therefore, you should call `browser_context.close()` - on any `BrowserContext`'s you explicitly created earlier with `browser.new_context()` **before** calling - `browser.close()`. + **NOTE** This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close + events, call `browser_context.close()` on any `BrowserContext` instances you explicitly created earlier + using `browser.new_context()` **before** calling `browser.close()`. The `Browser` object itself is considered to be disposed and cannot be used anymore. @@ -14346,7 +14360,7 @@ async def launch( channel : Union[str, None] Browser distribution channel. - Use "chromium" to [opt in to new headless mode](../browsers.md#opt-in-to-new-headless-mode). + Use "chromium" to [opt in to new headless mode](../browsers.md#chromium-new-headless-mode). Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to use branded [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge). @@ -14504,7 +14518,7 @@ async def launch_persistent_context( channel : Union[str, None] Browser distribution channel. - Use "chromium" to [opt in to new headless mode](../browsers.md#opt-in-to-new-headless-mode). + Use "chromium" to [opt in to new headless mode](../browsers.md#chromium-new-headless-mode). Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to use branded [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge). @@ -15522,7 +15536,6 @@ async def dispatch_event( You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: ```py - # note you can only create data_transfer in chromium and firefox data_transfer = await page.evaluate_handle(\"new DataTransfer()\") await locator.dispatch_event(\"#source\", \"dragstart\", {\"dataTransfer\": data_transfer}) ``` @@ -16445,18 +16458,22 @@ def or_(self, locator: "Locator") -> "Locator": Creates a locator matching all elements that match one or both of the two locators. - Note that when both locators match something, the resulting locator will have multiple matches and violate - [locator strictness](https://playwright.dev/python/docs/locators#strictness) guidelines. + Note that when both locators match something, the resulting locator will have multiple matches, potentially causing + a [locator strictness](https://playwright.dev/python/docs/locators#strictness) violation. **Usage** Consider a scenario where you'd like to click on a \"New email\" button, but sometimes a security settings dialog shows up instead. In this case, you can wait for either a \"New email\" button, or a dialog and act accordingly. + **NOTE** If both \"New email\" button and security dialog appear on screen, the \"or\" locator will match both of them, + possibly throwing the [\"strict mode violation\" error](https://playwright.dev/python/docs/locators#strictness). In this case, you can use + `locator.first()` to only match one of them. + ```py new_email = page.get_by_role(\"button\", name=\"New\") dialog = page.get_by_text(\"Confirm security settings\") - await expect(new_email.or_(dialog)).to_be_visible() + await expect(new_email.or_(dialog).first).to_be_visible() if (await dialog.is_visible()): await page.get_by_role(\"button\", name=\"Dismiss\").click() await new_email.click() @@ -16877,7 +16894,9 @@ async def is_disabled(self, *, timeout: typing.Optional[float] = None) -> bool: async def is_editable(self, *, timeout: typing.Optional[float] = None) -> bool: """Locator.is_editable - Returns whether the element is [editable](https://playwright.dev/python/docs/actionability#editable). + Returns whether the element is [editable](https://playwright.dev/python/docs/actionability#editable). If the target element is not an ``, + `