Skip to content

Commit 581316c

Browse files
pavelfeldmanclaude
andauthored
chore: roll to 1.59.1 (#3050)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3aecfcf commit 581316c

51 files changed

Lines changed: 2506 additions & 198 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/playwright-roll/SKILL.md

Lines changed: 277 additions & 15 deletions
Large diffs are not rendered by default.

.github/workflows/ci.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,18 +162,25 @@ jobs:
162162
matrix:
163163
os: [ubuntu-22.04, macos-13, windows-2022]
164164
runs-on: ${{ matrix.os }}
165+
defaults:
166+
run:
167+
# `setup-miniconda` activates the env only for login shells; using
168+
# `bash -el` (recommended by the action) ensures `conda` and the
169+
# installed `conda-build` are on PATH on every OS, including Windows
170+
# where the default shell is pwsh and skips the activation hooks.
171+
shell: bash -el {0}
165172
steps:
166173
- uses: actions/checkout@v5
167174
with:
168175
fetch-depth: 0
169176
- name: Get conda
170177
uses: conda-incubator/setup-miniconda@v3
171178
with:
172-
python-version: 3.9
179+
python-version: '3.12'
173180
channels: conda-forge
174181
miniconda-version: latest
175182
- name: Prepare
176-
run: conda install conda-build conda-verify
183+
run: conda install -n base "conda-build>=26" conda-verify
177184
- name: Build
178185
run: conda build .
179186

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ jobs:
3131
- name: Get conda
3232
uses: conda-incubator/setup-miniconda@v3
3333
with:
34-
python-version: 3.9
34+
python-version: '3.12'
3535
channels: conda-forge
3636
miniconda-version: latest
3737
- name: Prepare
38-
run: conda install anaconda-client conda-build conda-verify
38+
run: conda install -n base anaconda-client "conda-build>=26" conda-verify
3939
- name: Build and Upload
4040
env:
4141
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}

CLAUDE.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# CLAUDE.md
2+
3+
Guidance for Claude when working in this repository.
4+
5+
## What this is
6+
7+
Python bindings for [Playwright](https://playwright.dev). The Python client talks JSON over a pipe to the Node-based driver bundled in `playwright/driver/`. The pipe protocol is defined upstream in `packages/protocol/src/protocol.yml`.
8+
9+
## Layout
10+
11+
- `playwright/_impl/` — hand-written client implementation (one module per object: `_browser.py`, `_page.py`, `_locator.py`, `_network.py`, etc.). Edit these to add or change behavior.
12+
- `playwright/async_api/_generated.py`, `playwright/sync_api/_generated.py`**auto-generated**. Never edit by hand; rerun `./scripts/update_api.sh` after changing `_impl/` or the driver.
13+
- `scripts/generate_api.py`, `scripts/generate_async_api.py`, `scripts/generate_sync_api.py`, `scripts/documentation_provider.py` — codegen and validation. They diff the Python implementation against the driver's `playwright/driver/package/api.json` and abort if either side is out of sync.
14+
- `scripts/expected_api_mismatch.txt` — explicit allowlist of "documented in JS, not in Python" or "named differently in Python" gaps. Lines that no longer apply must be removed.
15+
- `tests/async/`, `tests/sync/` — pytest suites. Most new tests are added to the async file with a sync mirror.
16+
- `setup.py``driver_version = "X.Y.Z"` is the source of truth for which driver build is downloaded from `cdn.playwright.dev`.
17+
- `ROLLING.md`, `CONTRIBUTING.md` — human-facing setup and roll docs.
18+
19+
## Setup
20+
21+
`CONTRIBUTING.md` has the full sequence. The short version:
22+
23+
```sh
24+
python3 -m venv env && source env/bin/activate
25+
pip install --upgrade pip
26+
pip install -r local-requirements.txt
27+
pip install -e .
28+
python -m build --wheel # downloads the driver listed in setup.py
29+
pre-commit install
30+
```
31+
32+
If the system lacks `python3-venv`, `uv venv env` is an acceptable substitute (then `uv pip install --python env/bin/python --upgrade pip`).
33+
34+
## Common commands
35+
36+
- Regenerate `_generated.py`: `./scripts/update_api.sh` (runs codegen + pre-commit on the generated files).
37+
- Lint everything: `pre-commit run --all-files`.
38+
- Type-check: `mypy playwright`.
39+
- Run tests: `pytest --browser chromium [-k name]`. Browsers are installed via `playwright install chromium` (do **not** use `--with-deps`, which requires sudo).
40+
41+
When changing public API, edit `_impl/`, then run `./scripts/update_api.sh`. The script regenerates `_generated.py` and validates against the driver's `api.json`. If validation fails, fix the mismatch in `_impl/`, in `expected_api_mismatch.txt`, or in `documentation_provider.py` — not by hand-editing `_generated.py`.
42+
43+
## Rolling Playwright to a new version
44+
45+
This is the recurring high-stakes task. Use the dedicated skill:
46+
47+
**[`.claude/skills/playwright-roll/SKILL.md`](.claude/skills/playwright-roll/SKILL.md)**
48+
49+
It documents the full process: the upstream commit-range diff over `docs/src/api/`, how to classify each commit (PORT / MISMATCH / N/A), how to handle the `langs:` filter, the recurring failure modes, and the tests/sync-mirroring conventions.
50+
51+
## House style
52+
53+
- Don't hand-edit generated files.
54+
- Don't add `# type: ignore` or modify `_generated.py` to silence pyright; fix the source of the mismatch.
55+
- New public methods on impl classes need a sync test mirror under `tests/sync/`.
56+
- Keep `expected_api_mismatch.txt` minimal — every entry needs a one-line rationale comment above it.
57+
- Prefer `locals_to_params(locals())` for forwarding optional kwargs to channel sends, matching the rest of the codebase.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H
44

55
| | Linux | macOS | Windows |
66
| :--- | :---: | :---: | :---: |
7-
| Chromium <!-- GEN:chromium-version -->145.0.7632.6<!-- GEN:stop --> ||||
8-
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> ||||
9-
| Firefox <!-- GEN:firefox-version -->146.0.1<!-- GEN:stop --> ||||
7+
| Chromium <!-- GEN:chromium-version -->147.0.7727.15<!-- GEN:stop --> ||||
8+
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> ||||
9+
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> ||||
1010

1111
## Documentation
1212

playwright/_impl/_api_structures.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ class RemoteAddr(TypedDict):
166166
port: int
167167

168168

169+
class BrowserBindResult(TypedDict):
170+
endpoint: str
171+
172+
169173
class SecurityDetails(TypedDict):
170174
issuer: Optional[str]
171175
protocol: Optional[str]
@@ -311,3 +315,18 @@ class TracingGroupLocation(TypedDict):
311315
file: str
312316
line: Optional[int]
313317
column: Optional[int]
318+
319+
320+
class DebuggerLocation(TypedDict):
321+
file: str
322+
line: Optional[int]
323+
column: Optional[int]
324+
325+
326+
class DebuggerPausedDetails(TypedDict):
327+
location: DebuggerLocation
328+
title: str
329+
330+
331+
class ScreencastFrame(TypedDict):
332+
data: bytes

playwright/_impl/_browser.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
)
2828

2929
from playwright._impl._api_structures import (
30+
BrowserBindResult,
3031
ClientCertificate,
3132
Geolocation,
3233
HttpCredentials,
@@ -247,6 +248,20 @@ def version(self) -> str:
247248
async def new_browser_cdp_session(self) -> CDPSession:
248249
return from_channel(await self._channel.send("newBrowserCDPSession", None))
249250

251+
async def bind(
252+
self,
253+
title: str,
254+
workspaceDir: str = None,
255+
host: str = None,
256+
port: int = None,
257+
) -> BrowserBindResult:
258+
return await self._channel.send_return_as_dict(
259+
"startServer", None, locals_to_params(locals())
260+
)
261+
262+
async def unbind(self) -> None:
263+
await self._channel.send("stopServer", None)
264+
250265
async def start_tracing(
251266
self,
252267
page: Page = None,

playwright/_impl/_browser_context.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from_nullable_channel,
4747
)
4848
from playwright._impl._console_message import ConsoleMessage
49+
from playwright._impl._debugger import Debugger
4950
from playwright._impl._dialog import Dialog
5051
from playwright._impl._errors import Error, TargetClosedError
5152
from playwright._impl._event_context_manager import EventContextManagerImpl
@@ -122,6 +123,7 @@ def __init__(
122123
self._base_url: Optional[str] = self._options.get("baseURL")
123124
self._videos_dir: Optional[str] = self._options.get("recordVideo")
124125
self._tracing = cast(Tracing, from_channel(initializer["tracing"]))
126+
self._debugger: Debugger = cast(Debugger, from_channel(initializer["debugger"]))
125127
self._har_recorders: Dict[str, HarRecordingMetadata] = {}
126128
self._request: APIRequestContext = from_channel(initializer["requestContext"])
127129
self._request._timeout_settings = self._timeout_settings
@@ -582,6 +584,9 @@ def _on_close(self) -> None:
582584
self._tracing._reset_stack_counter()
583585
self.emit(BrowserContext.Events.Close, self)
584586

587+
def is_closed(self) -> bool:
588+
return self._closing_or_closed
589+
585590
async def close(self, reason: str = None) -> None:
586591
if self._closing_or_closed:
587592
return
@@ -627,6 +632,15 @@ async def storage_state(
627632
await async_writefile(path, json.dumps(result))
628633
return result
629634

635+
async def set_storage_state(
636+
self, storageState: Union[StorageState, str, Path]
637+
) -> None:
638+
if isinstance(storageState, (str, Path)):
639+
state = json.loads(await async_readfile(storageState))
640+
else:
641+
state = storageState
642+
await self._channel.send("setStorageState", None, {"storageState": state})
643+
630644
def _effective_close_reason(self) -> Optional[str]:
631645
if self._close_reason:
632646
return self._close_reason
@@ -753,6 +767,10 @@ async def new_cdp_session(self, page: Union[Page, Frame]) -> CDPSession:
753767
def tracing(self) -> Tracing:
754768
return self._tracing
755769

770+
@property
771+
def debugger(self) -> Debugger:
772+
return self._debugger
773+
756774
@property
757775
def request(self) -> "APIRequestContext":
758776
return self._request

playwright/_impl/_browser_type.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ async def launch(
8686
downloadsPath: Union[str, Path] = None,
8787
slowMo: float = None,
8888
tracesDir: Union[pathlib.Path, str] = None,
89+
artifactsDir: Union[pathlib.Path, str] = None,
8990
chromiumSandbox: bool = None,
9091
firefoxUserPrefs: Dict[str, Union[str, float, bool]] = None,
9192
) -> Browser:
@@ -143,6 +144,7 @@ async def launch_persistent_context(
143144
contrast: Contrast = None,
144145
acceptDownloads: bool = None,
145146
tracesDir: Union[pathlib.Path, str] = None,
147+
artifactsDir: Union[pathlib.Path, str] = None,
146148
chromiumSandbox: bool = None,
147149
firefoxUserPrefs: Dict[str, Union[str, float, bool]] = None,
148150
recordHarPath: Union[Path, str] = None,
@@ -213,7 +215,7 @@ async def connect_over_cdp(
213215

214216
async def connect(
215217
self,
216-
wsEndpoint: str,
218+
endpoint: str,
217219
timeout: float = None,
218220
slowMo: float = None,
219221
headers: Dict[str, str] = None,
@@ -229,7 +231,7 @@ async def connect(
229231
"connect",
230232
None,
231233
{
232-
"wsEndpoint": wsEndpoint,
234+
"endpoint": endpoint,
233235
"headers": headers,
234236
"slowMo": slowMo,
235237
"timeout": timeout if timeout is not None else 0,
@@ -361,3 +363,5 @@ def normalize_launch_params(params: Dict) -> None:
361363
params["downloadsPath"] = str(Path(params["downloadsPath"]))
362364
if "tracesDir" in params:
363365
params["tracesDir"] = str(Path(params["tracesDir"]))
366+
if "artifactsDir" in params:
367+
params["artifactsDir"] = str(Path(params["artifactsDir"]))

playwright/_impl/_cdp_session.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,29 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from types import SimpleNamespace
1516
from typing import Any, Dict
1617

1718
from playwright._impl._connection import ChannelOwner
1819
from playwright._impl._helper import locals_to_params
1920

2021

2122
class CDPSession(ChannelOwner):
23+
Events = SimpleNamespace(
24+
Event="event",
25+
Close="close",
26+
)
27+
2228
def __init__(
2329
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
2430
) -> None:
2531
super().__init__(parent, type, guid, initializer)
2632
self._channel.on("event", lambda params: self._on_event(params))
33+
self._channel.on("close", lambda _: self.emit(CDPSession.Events.Close, self))
2734

2835
def _on_event(self, params: Any) -> None:
2936
self.emit(params["method"], params.get("params"))
37+
self.emit(CDPSession.Events.Event, params)
3038

3139
async def send(self, method: str, params: Dict = None) -> Dict:
3240
return await self._channel.send("send", None, locals_to_params(locals()))

0 commit comments

Comments
 (0)