diff --git a/.claude/skills/playwright-roll/SKILL.md b/.claude/skills/playwright-roll/SKILL.md new file mode 100644 index 000000000..ece920353 --- /dev/null +++ b/.claude/skills/playwright-roll/SKILL.md @@ -0,0 +1,23 @@ +--- +name: playwright-roll +description: Roll Playwright Python to a new version +--- + +Help the user roll to a new version of Playwright. +../../../ROLLING.md contains general instructions and scripts. + +Start with updating the version and generating the API to see the state of things. + +Afterwards, work through the list of changes that need to be backported. +You can find a list of pull requests that might need to be taking into account in the issue titled "Backport changes". +Work through them one-by-one and check off the items that you have handled. +Not all of them will be relevant, some might have partially been reverted, etc. - so feel free to check with the upstream release branch. + +Rolling includes: +- updating client implementation to match changes in the upstream JS implementation (see ../playwright/packages/playwright-core/src/client) +- adding a couple of new tests to verify new/changed functionality + +## Tips & Tricks +- Project checkouts are in the parent directory (`../`). +- when updating checkboxes, store the issue content into /tmp and edit it there, then update the issue based on the file +- use the "gh" cli to interact with GitHub diff --git a/README.md b/README.md index c1797dfe0..c5e2d70b0 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 143.0.7499.4 | ✅ | ✅ | ✅ | +| Chromium 145.0.7632.6 | ✅ | ✅ | ✅ | | WebKit 26.0 | ✅ | ✅ | ✅ | -| Firefox 144.0.2 | ✅ | ✅ | ✅ | +| Firefox 146.0.1 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py index 93173160c..3aef7dd6d 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -82,7 +82,6 @@ async def launch( timeout: float = None, env: Env = None, headless: bool = None, - devtools: bool = None, proxy: ProxySettings = None, downloadsPath: Union[str, Path] = None, slowMo: float = None, @@ -118,7 +117,6 @@ async def launch_persistent_context( timeout: float = None, env: Env = None, headless: bool = None, - devtools: bool = None, proxy: ProxySettings = None, downloadsPath: Union[str, Path] = None, slowMo: float = None, @@ -200,6 +198,7 @@ async def connect_over_cdp( timeout: float = None, slowMo: float = None, headers: Dict[str, str] = None, + isLocal: bool = None, ) -> Browser: params = locals_to_params(locals()) if params.get("headers"): diff --git a/playwright/_impl/_console_message.py b/playwright/_impl/_console_message.py index 7866df2ae..f37e3dd4d 100644 --- a/playwright/_impl/_console_message.py +++ b/playwright/_impl/_console_message.py @@ -57,6 +57,7 @@ def type(self) -> Union[ Literal["startGroup"], Literal["startGroupCollapsed"], Literal["table"], + Literal["time"], Literal["timeEnd"], Literal["trace"], Literal["warning"], diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 469046b21..bd694886d 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -946,9 +946,12 @@ async def handle(route, request): `route.continue_()` will immediately send the request to the network, other matching handlers won't be invoked. Use `route.fallback()` If you want next matching handler in the chain to be invoked. - **NOTE** The `Cookie` header cannot be overridden using this method. If a value is provided, it will be ignored, - and the cookie will be loaded from the browser's cookie store. To set custom cookies, use - `browser_context.add_cookies()`. + **NOTE** Some request headers are **forbidden** and cannot be overridden (for example, `Cookie`, `Host`, + `Content-Length` and others, see + [this MDN page](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header) for full list). If an + override is provided for a forbidden header, it will be ignored and the original request header will be used. + + To set custom cookies, use `browser_context.add_cookies()`. Parameters ---------- @@ -7039,6 +7042,7 @@ def type( Literal["startGroup"], Literal["startGroupCollapsed"], Literal["table"], + Literal["time"], Literal["timeEnd"], Literal["trace"], Literal["warning"], @@ -7047,7 +7051,7 @@ def type( Returns ------- - Union["assert", "clear", "count", "debug", "dir", "dirxml", "endGroup", "error", "info", "log", "profile", "profileEnd", "startGroup", "startGroupCollapsed", "table", "timeEnd", "trace", "warning"] + Union["assert", "clear", "count", "debug", "dir", "dirxml", "endGroup", "error", "info", "log", "profile", "profileEnd", "startGroup", "startGroupCollapsed", "table", "time", "timeEnd", "trace", "warning"] """ return mapping.from_maybe_impl(self._impl_obj.type) @@ -14440,7 +14444,6 @@ async def launch( timeout: typing.Optional[float] = None, env: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, headless: typing.Optional[bool] = None, - devtools: typing.Optional[bool] = None, proxy: typing.Optional[ProxySettings] = None, downloads_path: typing.Optional[typing.Union[pathlib.Path, str]] = None, slow_mo: typing.Optional[float] = None, @@ -14514,12 +14517,7 @@ async def launch( headless : Union[bool, None] Whether to run browser in headless mode. More details for [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and - [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true` unless the - `devtools` option is `true`. - devtools : Union[bool, None] - **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the - `headless` option will be set `false`. - Deprecated: Use [debugging tools](../debug.md) instead. + [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true`. proxy : Union[{server: str, bypass: Union[str, None], username: Union[str, None], password: Union[str, None]}, None] Network proxy settings. downloads_path : Union[pathlib.Path, str, None] @@ -14557,7 +14555,6 @@ async def launch( timeout=timeout, env=mapping.to_impl(env), headless=headless, - devtools=devtools, proxy=proxy, downloadsPath=downloads_path, slowMo=slow_mo, @@ -14583,7 +14580,6 @@ async def launch_persistent_context( timeout: typing.Optional[float] = None, env: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, headless: typing.Optional[bool] = None, - devtools: typing.Optional[bool] = None, proxy: typing.Optional[ProxySettings] = None, downloads_path: typing.Optional[typing.Union[pathlib.Path, str]] = None, slow_mo: typing.Optional[float] = None, @@ -14691,12 +14687,7 @@ async def launch_persistent_context( headless : Union[bool, None] Whether to run browser in headless mode. More details for [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and - [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true` unless the - `devtools` option is `true`. - devtools : Union[bool, None] - **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the - `headless` option will be set `false`. - Deprecated: Use [debugging tools](../debug.md) instead. + [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true`. proxy : Union[{server: str, bypass: Union[str, None], username: Union[str, None], password: Union[str, None]}, None] Network proxy settings. downloads_path : Union[pathlib.Path, str, None] @@ -14858,7 +14849,6 @@ async def launch_persistent_context( timeout=timeout, env=mapping.to_impl(env), headless=headless, - devtools=devtools, proxy=proxy, downloadsPath=downloads_path, slowMo=slow_mo, @@ -14908,6 +14898,7 @@ async def connect_over_cdp( timeout: typing.Optional[float] = None, slow_mo: typing.Optional[float] = None, headers: typing.Optional[typing.Dict[str, str]] = None, + is_local: typing.Optional[bool] = None, ) -> "Browser": """BrowserType.connect_over_cdp @@ -14942,6 +14933,9 @@ async def connect_over_cdp( on. Defaults to 0. headers : Union[Dict[str, str], None] Additional HTTP headers to be sent with connect request. Optional. + is_local : Union[bool, None] + Tells Playwright that it runs on the same host as the CDP server. It will enable certain optimizations that rely + upon the file system being the same between Playwright and the Browser. Returns ------- @@ -14954,6 +14948,7 @@ async def connect_over_cdp( timeout=timeout, slowMo=slow_mo, headers=mapping.to_impl(headers), + isLocal=is_local, ) ) @@ -15397,8 +15392,7 @@ def description(self) -> typing.Optional[str]: """Locator.description Returns locator description previously set with `locator.describe()`. Returns `null` if no custom - description has been set. Prefer `Locator.toString()` for a human-readable representation, as it uses the - description when available. + description has been set. **Usage** @@ -19176,7 +19170,7 @@ async def to_contain_text( from playwright.async_api import expect # ✓ Contains the right items in the right order - await expect(page.locator(\"ul > li\")).to_contain_text([\"Text 1\", \"Text 3\", \"Text 4\"]) + await expect(page.locator(\"ul > li\")).to_contain_text([\"Text 1\", \"Text 3\"]) # ✖ Wrong order await expect(page.locator(\"ul > li\")).to_contain_text([\"Text 3\", \"Text 2\"]) diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 82c7b983f..0df2087df 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -960,9 +960,12 @@ def handle(route, request): `route.continue_()` will immediately send the request to the network, other matching handlers won't be invoked. Use `route.fallback()` If you want next matching handler in the chain to be invoked. - **NOTE** The `Cookie` header cannot be overridden using this method. If a value is provided, it will be ignored, - and the cookie will be loaded from the browser's cookie store. To set custom cookies, use - `browser_context.add_cookies()`. + **NOTE** Some request headers are **forbidden** and cannot be overridden (for example, `Cookie`, `Host`, + `Content-Length` and others, see + [this MDN page](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header) for full list). If an + override is provided for a forbidden header, it will be ignored and the original request header will be used. + + To set custom cookies, use `browser_context.add_cookies()`. Parameters ---------- @@ -7129,6 +7132,7 @@ def type( Literal["startGroup"], Literal["startGroupCollapsed"], Literal["table"], + Literal["time"], Literal["timeEnd"], Literal["trace"], Literal["warning"], @@ -7137,7 +7141,7 @@ def type( Returns ------- - Union["assert", "clear", "count", "debug", "dir", "dirxml", "endGroup", "error", "info", "log", "profile", "profileEnd", "startGroup", "startGroupCollapsed", "table", "timeEnd", "trace", "warning"] + Union["assert", "clear", "count", "debug", "dir", "dirxml", "endGroup", "error", "info", "log", "profile", "profileEnd", "startGroup", "startGroupCollapsed", "table", "time", "timeEnd", "trace", "warning"] """ return mapping.from_maybe_impl(self._impl_obj.type) @@ -14459,7 +14463,6 @@ def launch( timeout: typing.Optional[float] = None, env: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, headless: typing.Optional[bool] = None, - devtools: typing.Optional[bool] = None, proxy: typing.Optional[ProxySettings] = None, downloads_path: typing.Optional[typing.Union[pathlib.Path, str]] = None, slow_mo: typing.Optional[float] = None, @@ -14533,12 +14536,7 @@ def launch( headless : Union[bool, None] Whether to run browser in headless mode. More details for [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and - [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true` unless the - `devtools` option is `true`. - devtools : Union[bool, None] - **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the - `headless` option will be set `false`. - Deprecated: Use [debugging tools](../debug.md) instead. + [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true`. proxy : Union[{server: str, bypass: Union[str, None], username: Union[str, None], password: Union[str, None]}, None] Network proxy settings. downloads_path : Union[pathlib.Path, str, None] @@ -14577,7 +14575,6 @@ def launch( timeout=timeout, env=mapping.to_impl(env), headless=headless, - devtools=devtools, proxy=proxy, downloadsPath=downloads_path, slowMo=slow_mo, @@ -14604,7 +14601,6 @@ def launch_persistent_context( timeout: typing.Optional[float] = None, env: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, headless: typing.Optional[bool] = None, - devtools: typing.Optional[bool] = None, proxy: typing.Optional[ProxySettings] = None, downloads_path: typing.Optional[typing.Union[pathlib.Path, str]] = None, slow_mo: typing.Optional[float] = None, @@ -14712,12 +14708,7 @@ def launch_persistent_context( headless : Union[bool, None] Whether to run browser in headless mode. More details for [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and - [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true` unless the - `devtools` option is `true`. - devtools : Union[bool, None] - **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the - `headless` option will be set `false`. - Deprecated: Use [debugging tools](../debug.md) instead. + [Firefox](https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/). Defaults to `true`. proxy : Union[{server: str, bypass: Union[str, None], username: Union[str, None], password: Union[str, None]}, None] Network proxy settings. downloads_path : Union[pathlib.Path, str, None] @@ -14880,7 +14871,6 @@ def launch_persistent_context( timeout=timeout, env=mapping.to_impl(env), headless=headless, - devtools=devtools, proxy=proxy, downloadsPath=downloads_path, slowMo=slow_mo, @@ -14931,6 +14921,7 @@ def connect_over_cdp( timeout: typing.Optional[float] = None, slow_mo: typing.Optional[float] = None, headers: typing.Optional[typing.Dict[str, str]] = None, + is_local: typing.Optional[bool] = None, ) -> "Browser": """BrowserType.connect_over_cdp @@ -14965,6 +14956,9 @@ def connect_over_cdp( on. Defaults to 0. headers : Union[Dict[str, str], None] Additional HTTP headers to be sent with connect request. Optional. + is_local : Union[bool, None] + Tells Playwright that it runs on the same host as the CDP server. It will enable certain optimizations that rely + upon the file system being the same between Playwright and the Browser. Returns ------- @@ -14978,6 +14972,7 @@ def connect_over_cdp( timeout=timeout, slowMo=slow_mo, headers=mapping.to_impl(headers), + isLocal=is_local, ) ) ) @@ -15423,8 +15418,7 @@ def description(self) -> typing.Optional[str]: """Locator.description Returns locator description previously set with `locator.describe()`. Returns `null` if no custom - description has been set. Prefer `Locator.toString()` for a human-readable representation, as it uses the - description when available. + description has been set. **Usage** @@ -19289,7 +19283,7 @@ def to_contain_text( from playwright.sync_api import expect # ✓ Contains the right items in the right order - expect(page.locator(\"ul > li\")).to_contain_text([\"Text 1\", \"Text 3\", \"Text 4\"]) + expect(page.locator(\"ul > li\")).to_contain_text([\"Text 1\", \"Text 3\"]) # ✖ Wrong order expect(page.locator(\"ul > li\")).to_contain_text([\"Text 3\", \"Text 2\"]) diff --git a/setup.py b/setup.py index fa81e5e85..5a8229db1 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.57.0-beta-1764944708000" +driver_version = "1.58.0" base_wheel_bundles = [ { diff --git a/tests/async/test_expect_misc.py b/tests/async/test_expect_misc.py index 9c6a8aa01..2148b0d9e 100644 --- a/tests/async/test_expect_misc.py +++ b/tests/async/test_expect_misc.py @@ -58,7 +58,7 @@ async def test_to_be_in_viewport_should_have_good_stack( page: Page, server: Server ) -> None: with pytest.raises(AssertionError) as exc_info: - await expect(page.locator("body")).not_to_be_in_viewport(timeout=100) + await expect(page.locator("body")).not_to_be_in_viewport(timeout=1000) assert 'unexpected value "viewport ratio' in str(exc_info.value) diff --git a/tests/async/test_worker.py b/tests/async/test_worker.py index cd6828053..4c2e4ebeb 100644 --- a/tests/async/test_worker.py +++ b/tests/async/test_worker.py @@ -189,7 +189,7 @@ async def test_workers_should_report_network_activity_on_worker_creation( async def test_workers_should_format_number_using_context_locale( - browser: Browser, server: Server + browser: Browser, server: Server, browser_name: str ) -> None: context = await browser.new_context(locale="ru-RU") page = await context.new_page() @@ -199,7 +199,9 @@ async def test_workers_should_format_number_using_context_locale( "() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))" ) worker = await worker_info.value - assert await worker.evaluate("() => (10000.20).toLocaleString()") == "10\u00a0000,2" + # https://github.com/microsoft/playwright/issues/38919 + expected = "10,000.2" if browser_name == "firefox" else "10\u00a0000,2" + assert await worker.evaluate("() => (10000.20).toLocaleString()") == expected await context.close() diff --git a/tests/sync/test_expect_misc.py b/tests/sync/test_expect_misc.py index 042929fde..4d8b68cc0 100644 --- a/tests/sync/test_expect_misc.py +++ b/tests/sync/test_expect_misc.py @@ -56,7 +56,7 @@ def test_to_be_in_viewport_should_respect_ratio_option( def test_to_be_in_viewport_should_have_good_stack(page: Page, server: Server) -> None: with pytest.raises(AssertionError) as exc_info: - expect(page.locator("body")).not_to_be_in_viewport(timeout=100) + expect(page.locator("body")).not_to_be_in_viewport(timeout=1000) assert 'unexpected value "viewport ratio' in str(exc_info.value)