From 89eeddfe47d68682576c4b78852a1a5053f0d020 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 22 Jan 2026 17:30:26 +0100 Subject: [PATCH 1/8] chore: roll to 1.58.0-beta-1769095880000 Co-Authored-By: Claude Opus 4.5 --- .claude/skills/playwright-roll/SKILL.md | 22 ++++++++++++++ README.md | 4 +-- playwright/_impl/_browser_type.py | 3 +- playwright/_impl/_console_message.py | 1 + playwright/async_api/_generated.py | 40 +++++++++++-------------- playwright/sync_api/_generated.py | 40 +++++++++++-------------- setup.py | 2 +- 7 files changed, 61 insertions(+), 51 deletions(-) create mode 100644 .claude/skills/playwright-roll/SKILL.md diff --git a/.claude/skills/playwright-roll/SKILL.md b/.claude/skills/playwright-roll/SKILL.md new file mode 100644 index 000000000..806a69c14 --- /dev/null +++ b/.claude/skills/playwright-roll/SKILL.md @@ -0,0 +1,22 @@ +--- +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 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..55807e473 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-beta-1769095880000" base_wheel_bundles = [ { From b989d51812a827e3be86ceef9a229690440153f7 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 08:37:46 +0100 Subject: [PATCH 2/8] locale --- tests/async/test_worker.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/async/test_worker.py b/tests/async/test_worker.py index cd6828053..02832abe2 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" + # Firefox 146 seems to have a locale bug + expected = "10.000,2" if browser_name == "firefox" else "10\u00a0000,2" + assert await worker.evaluate("() => (10000.20).toLocaleString()") == expected await context.close() From 93f4f3f61707f77ecb64e64674323dc48d9d871c Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 08:40:55 +0100 Subject: [PATCH 3/8] more tests --- tests/async/test_expect_misc.py | 1 + tests/sync/test_expect_misc.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/async/test_expect_misc.py b/tests/async/test_expect_misc.py index 9c6a8aa01..825630e02 100644 --- a/tests/async/test_expect_misc.py +++ b/tests/async/test_expect_misc.py @@ -54,6 +54,7 @@ async def test_to_be_in_viewport_should_respect_ratio_option( await expect(page.locator("div")).not_to_be_in_viewport(ratio=0.8) +@pytest.mark.skip_browser("webkit") async def test_to_be_in_viewport_should_have_good_stack( page: Page, server: Server ) -> None: diff --git a/tests/sync/test_expect_misc.py b/tests/sync/test_expect_misc.py index 042929fde..97d61edec 100644 --- a/tests/sync/test_expect_misc.py +++ b/tests/sync/test_expect_misc.py @@ -54,6 +54,7 @@ def test_to_be_in_viewport_should_respect_ratio_option( expect(page.locator("div")).not_to_be_in_viewport(ratio=0.8) +@pytest.mark.skip_browser("webkit") 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) From fd7572b0077705eeb226d3dd27be69ed0206fe5e Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 09:33:00 +0100 Subject: [PATCH 4/8] increase timeout instead of skipping --- tests/async/test_expect_misc.py | 3 +-- tests/sync/test_expect_misc.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/async/test_expect_misc.py b/tests/async/test_expect_misc.py index 825630e02..2148b0d9e 100644 --- a/tests/async/test_expect_misc.py +++ b/tests/async/test_expect_misc.py @@ -54,12 +54,11 @@ async def test_to_be_in_viewport_should_respect_ratio_option( await expect(page.locator("div")).not_to_be_in_viewport(ratio=0.8) -@pytest.mark.skip_browser("webkit") 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/sync/test_expect_misc.py b/tests/sync/test_expect_misc.py index 97d61edec..4d8b68cc0 100644 --- a/tests/sync/test_expect_misc.py +++ b/tests/sync/test_expect_misc.py @@ -54,10 +54,9 @@ def test_to_be_in_viewport_should_respect_ratio_option( expect(page.locator("div")).not_to_be_in_viewport(ratio=0.8) -@pytest.mark.skip_browser("webkit") 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) From fe04c3fa884b7c115183d81ba6036a2448dc2d75 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 10:59:38 +0100 Subject: [PATCH 5/8] link issue --- tests/async/test_worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/async/test_worker.py b/tests/async/test_worker.py index 02832abe2..e7fbf1804 100644 --- a/tests/async/test_worker.py +++ b/tests/async/test_worker.py @@ -199,7 +199,7 @@ 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 - # Firefox 146 seems to have a locale bug + # 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() From c0b434ba4d47c434f8143b0b812c3f57472d8f21 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 13:08:56 +0100 Subject: [PATCH 6/8] update to stable --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 55807e473..5a8229db1 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.58.0-beta-1769095880000" +driver_version = "1.58.0" base_wheel_bundles = [ { From 43844cc797472b7bedaa82e904fbaa41e1291999 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 13:09:41 +0100 Subject: [PATCH 7/8] ci has different locale --- tests/async/test_worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/async/test_worker.py b/tests/async/test_worker.py index e7fbf1804..4c2e4ebeb 100644 --- a/tests/async/test_worker.py +++ b/tests/async/test_worker.py @@ -200,7 +200,7 @@ async def test_workers_should_format_number_using_context_locale( ) worker = await worker_info.value # https://github.com/microsoft/playwright/issues/38919 - expected = "10.000,2" if browser_name == "firefox" else "10\u00a0000,2" + expected = "10,000.2" if browser_name == "firefox" else "10\u00a0000,2" assert await worker.evaluate("() => (10000.20).toLocaleString()") == expected await context.close() From c9fd6cc65a1bffdf9de0ef12994c0a4a7d39da48 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 13:13:38 +0100 Subject: [PATCH 8/8] amend skill --- .claude/skills/playwright-roll/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/skills/playwright-roll/SKILL.md b/.claude/skills/playwright-roll/SKILL.md index 806a69c14..ece920353 100644 --- a/.claude/skills/playwright-roll/SKILL.md +++ b/.claude/skills/playwright-roll/SKILL.md @@ -20,3 +20,4 @@ Rolling includes: ## 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