Skip to content

Commit 5cf76bd

Browse files
authored
chore: roll driver to 1.65.0-alpha-2026-10-19 (#3273)
1 parent 3e7abc4 commit 5cf76bd

16 files changed

Lines changed: 159 additions & 69 deletions

File tree

‎.claude/skills/playwright-release/SKILL.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ gh issue list --repo microsoft/playwright-python --state open \
3131
If any are open, list them for the user and ask whether to continue. Do not
3232
close or comment on issues.
3333

34-
Check that the driver is rolled. `DRIVER_VERSION` must be the driver for the
35-
release: a `1.xx.y` build, or a `1.xx.0-beta-…` build from `release-1.xx`, as the
36-
user says. If it is not, roll with the `playwright-roll` skill. This is often
34+
Check that the driver is rolled. `DRIVER_VERSION` (`npm=`) must be the driver
35+
for the release: a `1.xx.y` build, or a `1.xx.0-beta-…` build from `release-1.xx`,
36+
as the user says, and `rev=` the commit that it is built from. If it is not, roll with the `playwright-roll` skill. This is often
3737
already done. The roll commit goes into the release commit PR (step 3). When the
3838
API roll for 1.xx is already on `main`, that PR only moves `DRIVER_VERSION`.
3939

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

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -39,37 +39,37 @@ The upstream documentation source of truth is `docs/src/api/*.md` in the playwri
3939

4040
### 2. Bump the driver pin, download it, and generate api.json
4141

42+
`DRIVER_VERSION` has two lines: `npm=`, the `playwright-core` npm version
43+
(a release such as `1.62.0`, a beta, or a nightly such as
44+
`1.65.0-alpha-2026-10-19`), and `rev=`, the `microsoft/playwright` commit that
45+
the package is built from. npm records that commit:
46+
47+
```sh
48+
npm view playwright-core@<version> gitHead
49+
```
50+
4251
The docs walk and codegen read the upstream docs from a `microsoft/playwright`
4352
checkout at `$PW_SRC_DIR`. **Use a separate checkout that exists only for
44-
rolls, on the exact release branch you roll to**: `release-<new>`, the
45-
`major.minor` of the new `DRIVER_VERSION` (the branch has the release's docs
46-
plus any cherry-picks). Never point `PW_SRC_DIR` at a checkout that someone
47-
works in: on `main` or a feature branch the docs differ, and the generator
48-
silently emits that API instead of the release's.
53+
rolls, at exactly the `rev=` commit.** Never point `PW_SRC_DIR` at a checkout
54+
that someone works in: on any other commit the docs differ, and the generator
55+
silently emits that API instead of the driver's. `./scripts/update_api.sh`
56+
refuses to run when the checkout is not at `rev=` or has local changes.
4957

5058
```sh
5159
export PW_SRC_DIR=../playwright-for-python # the roll checkout, nothing else
5260
# First roll on this machine: clone it (no npm install needed).
5361
git clone https://github.com/microsoft/playwright.git "$PW_SRC_DIR"
54-
# Every roll: move it to the release branch.
62+
# Every roll: move it to the new rev.
5563
git -C "$PW_SRC_DIR" fetch origin
56-
git -C "$PW_SRC_DIR" checkout release-<new> # e.g. release-1.62
57-
git -C "$PW_SRC_DIR" merge --ff-only origin/release-<new>
58-
```
59-
60-
Check the checkout before you generate, and again before you commit:
61-
62-
```sh
63-
git -C "$PW_SRC_DIR" rev-parse --abbrev-ref HEAD # must print release-<new>
64-
git -C "$PW_SRC_DIR" status --porcelain # must print nothing
65-
git -C "$PW_SRC_DIR" rev-parse HEAD origin/release-<new> # both lines must be equal
64+
git -C "$PW_SRC_DIR" checkout --detach <rev>
6665
```
6766

6867
Then bump the pins and assemble the driver:
6968

7069
```sh
71-
# Edit DRIVER_VERSION (repo root): the playwright-core npm version for the new
72-
# release, no "v" prefix, e.g. 1.62.0
70+
# Edit DRIVER_VERSION (repo root), for example:
71+
# npm=1.62.0
72+
# rev=<npm view playwright-core@1.62.0 gitHead>
7373
python scripts/update_node_version.py # refresh NODE_VERSION to the current LTS
7474

7575
source env/bin/activate
@@ -89,32 +89,18 @@ the walk.
8989

9090
### 3. Identify the commit range
9191

92-
Use the roll checkout at `$PW_SRC_DIR` (from step 2).
93-
Bring it up to date and ensure release branches/tags are present before walking
94-
the range:
95-
96-
```sh
97-
git -C "$PW_SRC_DIR" fetch --tags
98-
git -C "$PW_SRC_DIR" fetch origin 'release-*:release-*'
99-
```
100-
101-
There is sometimes no `vX.Y.0` tag for the latest release (the bots cut release branches first and tag later). Anchor on commits, not tags.
102-
103-
The diff range is "every commit on the new release branch since the previous release was cut". Anchor commits:
104-
105-
- **Previous release end**: the `chore: bump version to vX.Y.0-next` commit on `main`. That commit is the first commit *after* the previous release (X.Y-1) was cut. Use its parent (`<sha>~1`) as the lower bound.
106-
```sh
107-
git -C "$PW_SRC_DIR" log --all --grep="bump version to v" --oneline | head
108-
```
109-
- **New release end**: the tip of `release-<new>` (or the matching tag if it exists).
110-
111-
Save the commit list, oldest first, scoped to `docs/src/api/`:
92+
The range is every commit between the old `rev=` (in `DRIVER_VERSION` before the
93+
bump) and the new one. Save the commit list, oldest first, scoped to
94+
`docs/src/api/`:
11295

11396
```sh
114-
git -C "$PW_SRC_DIR" log <prev-anchor>~1..release-<new> --oneline --reverse -- docs/src/api > /tmp/roll-<new>-commits.md
97+
git -C "$PW_SRC_DIR" log <old-rev>..<new-rev> --oneline --reverse -- docs/src/api > /tmp/roll-<new>-commits.md
11598
```
11699

117-
A normal roll yields 50–100 commits. If you see 0 or thousands, the range is wrong.
100+
If the old `DRIVER_VERSION` has no `rev=` line (rolls before the two-line
101+
format), take the old commit from npm: `npm view playwright-core@<old version>
102+
gitHead`. A release-to-release roll yields 50–100 commits; a nightly-to-nightly
103+
roll a handful. If you see thousands, the range is wrong.
118104

119105
Format the file as a markdown checklist and add the standard preamble (status legend, where to look up `api.json` etc.) — see the file from the 1.58→1.59 roll for the template.
120106

@@ -266,7 +252,7 @@ Failure modes and fixes:
266252
| mypy: incompatible argument type in `_generated.py` | The docs type changed, or `_impl/` declares a narrower type. | Match the type in `_impl/`, or fix the docs with a Python-only declaration (`* langs: python`). |
267253
| generator: `No Python name for the struct at X.y.z` | A new inline `[Object]` with no name in the docs. | Name it in the docs: `` "Name" <[Object]> ``. |
268254
| generator: `The struct X has different fields at A and B` | Two places in the docs share one struct name, but their fields differ. | Give one place its own name in the docs. |
269-
| `_generated.py` gains or loses members that the commit walk never mentioned | `PW_SRC_DIR` is not on `release-<new>` (for example on `main` or a feature branch), or is behind `origin/release-<new>`. | Run the checkout checks from step 2, fix the checkout, regenerate. |
255+
| `_generated.py` gains or loses members that the commit walk never mentioned | `PW_SRC_DIR` is not at the `rev=` commit, or the range of the walk is wrong. | Check out `rev=` (step 2), regenerate, and recompute the range (step 3). |
270256
| generator: `Unknown docs type "T" at X.y` | A docs type the type table in `generate_api.js` (`toPy`) does not know. | Add it to `toPy`. |
271257
| generator: `required parameter z follows an optional one` | The docs order params so that Python would need a default before a non-default. | Fix the docs, for example with a Python-only `### param:` or `### option:`, as `select_option(value)` has. |
272258
| generator: `the entry "…" no longer matches anything; remove it` | An upstream change made a rules entry unnecessary (or renamed what it points at). | Delete it, or re-key it to the new docs name. |
@@ -300,7 +286,7 @@ For each PORT, port the upstream test that covers it, as an async test and a mat
300286

301287
### 8. Final verification
302288

303-
Repeat the checkout checks from step 2: the committed `_generated.py` must come from `release-<new>`.
289+
Check that `$PW_SRC_DIR` is still at the `rev=` commit, with no local changes: the committed `_generated.py` must come from it.
304290

305291
```sh
306292
pre-commit run --all-files

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ Python bindings for [Playwright](https://playwright.dev). The Python client talk
1818
- `_protocol.py` has the parameters of every command. With `PW_CHECK_PROTOCOL=1`, `playwright/_impl/_protocol_check.py` checks each message that the client sends against it. The test suite sets this, and fails a test that sends an unknown command, an unknown or missing parameter, or a value of the wrong type.
1919
- `scripts/codegen/compare_api.py` — compares two generated files (signatures, annotations, call bodies, docstrings) and lists every difference. Use it to check that a generator change does not change the API.
2020
- `tests/async/`, `tests/sync/` — pytest suites, ported from the upstream tests. Most new tests are added to the async file with a sync mirror. See [Tests](#tests).
21-
- `DRIVER_VERSION` — the single source of truth for which Playwright release the driver is assembled from (one line, the `playwright-core` npm version, e.g. `1.61.0`, no `v` prefix). Read by `setup.py`, `scripts/build_driver.py`, and CI. The wheel build downloads `playwright-core` at this version from npm plus the matching Node.js binary and assembles the per-platform bundles — no source build. The version is baked into the staged bundle filenames (`driver/playwright-<version>-<suffix>.zip`), so it doubles as the build cache key.
21+
- `DRIVER_VERSION` — the single source of truth for which Playwright build the driver is assembled from. Two lines: `npm=<version>`, the `playwright-core` npm version (e.g. `npm=1.61.0`, no `v` prefix), and `rev=<sha>`, the `microsoft/playwright` commit that the package is built from (`npm view playwright-core@<version> gitHead`). Read by `setup.py`, `scripts/build_driver.py`, `scripts/update_api.sh`, and CI (through `setup.py`). The wheel build downloads `playwright-core` at this version from npm plus the matching Node.js binary and assembles the per-platform bundles — no source build. The version is baked into the staged bundle filenames (`driver/playwright-<version>-<suffix>.zip`), so it doubles as the build cache key.
2222
- `NODE_VERSION` — the Node.js version bundled with the driver (one line, e.g. `24.16.0`). Maintained at roll time by `scripts/update_node_version.py` (latest LTS, mirroring upstream's `utils/build/update-playwright-node.mjs`).
2323
- `scripts/build_driver.py` — assembles the per-platform driver bundles into `driver/` by downloading the `playwright-core` npm package (`DRIVER_VERSION`) and the official Node.js binaries (`NODE_VERSION`). Fetches `playwright-core` with `npm pack` (needs Node.js/npm on PATH; honours a root `.npmrc`) and the Node.js binaries over plain HTTP; invoked from `setup.py`'s `bdist_wheel` with the target platform's suffix (no arg builds all six).
24-
- The API docs are **not** shipped in the bundle. `scripts/update_api.sh` reads them from a separate `microsoft/playwright` checkout (`$PW_SRC_DIR`) on the exact release branch for `DRIVER_VERSION`, never from a checkout on `main` or a feature branch. Needed only when regenerating the API, never at runtime.
24+
- The API docs are **not** shipped in the bundle. `scripts/update_api.sh` reads them from a separate `microsoft/playwright` checkout (`$PW_SRC_DIR`) at the exact commit in `DRIVER_VERSION` (`rev=`), and refuses to run on any other commit or on local changes. Needed only when regenerating the API, never at runtime.
2525
- `ROLLING.md`, `CONTRIBUTING.md` — human-facing setup and roll docs.
2626

2727
## Setup

‎CONTRIBUTING.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pip install -r local-requirements.txt
3737
Build and install drivers:
3838

3939
The driver is assembled from published artifacts — the `playwright-core` npm
40-
package (version pinned in `DRIVER_VERSION`) and the official Node.js binary
40+
package (version pinned in `DRIVER_VERSION`, `npm=`) and the official Node.js binary
4141
(pinned in `NODE_VERSION`). Building a wheel downloads them with `npm pack` and
4242
plain HTTP, so Node.js/npm must be installed; no git or source build is needed.
4343

@@ -77,8 +77,9 @@ For more details look at the [CI configuration](./.github/workflows/ci.yml).
7777

7878
`update_api.sh` generates `playwright/{async,sync}_api/_generated.py` and the
7979
public TypedDicts in `playwright/_impl/_api_structures.py` from the API docs in
80-
a separate `microsoft/playwright` checkout on the exact release branch for
81-
`DRIVER_VERSION` (for example `release-1.64`); point `PW_SRC_DIR` at it. The
80+
a separate `microsoft/playwright` checkout at the commit in `DRIVER_VERSION`
81+
(`rev=`, the commit that the `npm=` package is built from); point `PW_SRC_DIR`
82+
at it. The
8283
generator is
8384
`scripts/codegen/generate_api.js`; Python facts that the docs do not contain
8485
live in `scripts/codegen/python_rules.js`.

‎DRIVER_VERSION‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
1.65.0-alpha-1792021904000
1+
npm=1.65.0-alpha-2026-10-19
2+
rev=b335271cdc2b57cac2be8ad37c5dd283ac6b8938

‎ROLLING.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ pip install -r local-requirements.txt
1111
pre-commit install
1212
pip install -e .
1313
```
14-
* change the driver pin in `DRIVER_VERSION` (the `playwright-core` npm version, e.g. `1.61.0`) and refresh `NODE_VERSION`: `python scripts/update_node_version.py`
14+
* change the driver pin in `DRIVER_VERSION`: `npm=` the `playwright-core` npm version (e.g. `1.61.0`) and `rev=` the commit it is built from (`npm view playwright-core@<version> gitHead`); refresh `NODE_VERSION`: `python scripts/update_node_version.py`
1515
* download the new driver: `python -m build --wheel` (fetches `playwright-core` via `npm pack` + the matching Node.js binary and assembles the bundle; no source build).
16-
* generate API from the docs. This needs a separate `microsoft/playwright` checkout used only for rolls, on the exact release branch for the new version (e.g. `release-1.64`), clean and up to date with `origin`: `PW_SRC_DIR=../playwright-for-python ./scripts/update_api.sh`
16+
* generate API from the docs. This needs a separate `microsoft/playwright` checkout used only for rolls, at the `rev=` commit of `DRIVER_VERSION` and clean (`git -C ../playwright-for-python fetch origin && git -C ../playwright-for-python checkout --detach <rev>`): `PW_SRC_DIR=../playwright-for-python ./scripts/update_api.sh`
1717
* run `mypy playwright`: every error in `_generated.py` points at an implementation method in `playwright/_impl/` that lacks a new member or option. Implement it there, or record the difference in `scripts/codegen/python_rules.js`.
1818
* commit changes & send PR
1919
* wait for bots to pass & merge the PR

‎playwright/_impl/_cdp_session.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,15 @@ def _on_event(self, event: "channels.CDPSessionEventEvent") -> None:
4444
{key: value for key, value in event.items() if value is not None},
4545
)
4646

47-
async def send(self, method: str, params: Optional[Dict]) -> Dict:
48-
return (await self._channel.send(method=method, params=params))["result"]
47+
async def send(
48+
self, method: str, params: Optional[Dict], timeout: Optional[float]
49+
) -> Dict:
50+
# No timeout by default, as send() in client/cdpSession.ts.
51+
return (
52+
await self._channel.send(
53+
method=method, params=params, timeout=0 if timeout is None else timeout
54+
)
55+
)["result"]
4956

5057
async def detach(self) -> None:
5158
await self._channel.detach()

‎playwright/async_api/_generated.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4622,7 +4622,11 @@ def once(
46224622
return super().once(event=event, f=f)
46234623

46244624
async def send(
4625-
self, method: str, params: typing.Optional[typing.Dict] = None
4625+
self,
4626+
method: str,
4627+
params: typing.Optional[typing.Dict] = None,
4628+
*,
4629+
timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None,
46264630
) -> typing.Dict:
46274631
"""CDPSession.send
46284632

@@ -4632,14 +4636,20 @@ async def send(
46324636
Protocol method name.
46334637
params : Union[Dict, None]
46344638
Optional method parameters.
4639+
timeout : Union[float, None]
4640+
Maximum time in milliseconds to wait for the response. Defaults to `0` - no timeout.
46354641

46364642
Returns
46374643
-------
46384644
Dict
46394645
"""
46404646

46414647
return mapping.from_maybe_impl(
4642-
await self._impl_obj.send(method=method, params=mapping.to_impl(params))
4648+
await self._impl_obj.send(
4649+
method=method,
4650+
params=mapping.to_impl(params),
4651+
timeout=to_milliseconds(timeout),
4652+
)
46434653
)
46444654

46454655

‎playwright/sync_api/_generated.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4530,7 +4530,11 @@ def once(self, event: str, f: typing.Callable[..., None]) -> None:
45304530
return super().once(event=event, f=f)
45314531

45324532
def send(
4533-
self, method: str, params: typing.Optional[typing.Dict] = None
4533+
self,
4534+
method: str,
4535+
params: typing.Optional[typing.Dict] = None,
4536+
*,
4537+
timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None,
45344538
) -> typing.Dict:
45354539
"""CDPSession.send
45364540

@@ -4540,6 +4544,8 @@ def send(
45404544
Protocol method name.
45414545
params : Union[Dict, None]
45424546
Optional method parameters.
4547+
timeout : Union[float, None]
4548+
Maximum time in milliseconds to wait for the response. Defaults to `0` - no timeout.
45434549

45444550
Returns
45454551
-------
@@ -4548,7 +4554,11 @@ def send(
45484554

45494555
return mapping.from_maybe_impl(
45504556
self._sync(
4551-
self._impl_obj.send(method=method, params=mapping.to_impl(params))
4557+
self._impl_obj.send(
4558+
method=method,
4559+
params=mapping.to_impl(params),
4560+
timeout=to_milliseconds(timeout),
4561+
)
45524562
)
45534563
)
45544564

‎scripts/build_driver.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ def read_pin(name: str) -> str:
8484
return value
8585

8686

87+
def read_driver_version() -> str:
88+
# DRIVER_VERSION has two lines: npm=<playwright-core version> and
89+
# rev=<the microsoft/playwright commit that the package is built from>.
90+
pins = dict(
91+
line.split("=", 1)
92+
for line in read_pin("DRIVER_VERSION").splitlines()
93+
if line.strip()
94+
)
95+
if "npm" not in pins:
96+
raise SystemExit(
97+
f"DRIVER_VERSION has no npm= line at {REPO_ROOT / 'DRIVER_VERSION'}"
98+
)
99+
return pins["npm"].strip()
100+
101+
87102
def download(url: str, destination: Path) -> None:
88103
last_error: Exception = RuntimeError("no attempt made")
89104
for attempt in range(1, 6):
@@ -219,7 +234,7 @@ def assemble(
219234

220235

221236
def build(suffixes: Set[str]) -> None:
222-
version = read_pin("DRIVER_VERSION")
237+
version = read_driver_version()
223238
node_version = read_pin("NODE_VERSION")
224239
DRIVER_DIR.mkdir(exist_ok=True)
225240

0 commit comments

Comments
 (0)