You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/skills/playwright-roll/SKILL.md
+47-30Lines changed: 47 additions & 30 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,7 +5,7 @@ description: Roll Playwright Python to a new driver version. Walks the upstream
5
5
6
6
# Rolling Playwright Python
7
7
8
-
The goal of a roll is to move the driver pin in `DRIVER_SHA` to a new release, port every public API change introduced upstream during that interval, and suppress the rest, so that `./scripts/update_api.sh` runs clean and the test suite still passes.
8
+
The goal of a roll is to move the driver pin in `DRIVER_VERSION` to a new release, port every public API change introduced upstream during that interval, and suppress the rest, so that `./scripts/update_api.sh` runs clean and the test suite still passes.
9
9
10
10
The previous human-facing summary lives in `../../../ROLLING.md`. This skill is the operational playbook — read it end to end before starting.
11
11
@@ -15,7 +15,7 @@ The Python port is hand-written code in `playwright/_impl/`, plus a generator (`
15
15
16
16
1. introspects the Python `_impl` classes via `inspect`,
17
17
2. emits typed wrapper classes into `playwright/{async,sync}_api/_generated.py`, and
18
-
3. diffs the introspected surface against `playwright/driver/package/api.json` (built into the new driver from source).
18
+
3. diffs the introspected surface against the Playwright `api.json` (generated from the upstream docs — see step 2).
19
19
20
20
Anything in `api.json` that is missing or differently typed in `_impl/` causes generation to fail. Three resolutions:
21
21
@@ -37,37 +37,49 @@ The upstream documentation source of truth is `docs/src/api/*.md` in the playwri
37
37
- If `python3-venv` is missing system-wide, use `uv venv env` instead, then `uv pip install --python env/bin/python --upgrade pip`. Don't try to `apt install` — sudo is denied in the harness.
38
38
- Always activate the venv before any `pip`, `pytest`, `mypy`, or `pre-commit` invocation.
39
39
40
-
### 2. Bump the driver and build it from source
40
+
### 2. Bump the driver pin, download it, and generate api.json
41
+
42
+
You need a nearby `microsoft/playwright` checkout for the docs walk and for
43
+
`api.json` generation. Point `PW_SRC_DIR` at it and check out the new tag there:
41
44
42
45
```sh
43
-
# Edit DRIVER_SHA (repo root): replace with the microsoft/playwright commit SHA
44
-
# for the new release, e.g. the commit that v<new> points at.
45
-
# 87bb9ddbd78f329df18c2b24847bc9409240cd07
46
-
# Update the "# microsoft/playwright @ v<new>" comment in scripts/build_driver.sh too.
46
+
export PW_SRC_DIR=../playwright
47
+
git -C "$PW_SRC_DIR" fetch --tags origin
48
+
git -C "$PW_SRC_DIR" checkout v<new># e.g. v1.62.0
49
+
```
50
+
51
+
Then bump the pins and assemble the driver:
52
+
53
+
```sh
54
+
# Edit DRIVER_VERSION (repo root): the playwright-core npm version for the new
55
+
# release, no "v" prefix, e.g. 1.62.0
56
+
python scripts/update_node_version.py # refresh NODE_VERSION to the current LTS
47
57
48
58
source env/bin/activate
49
-
python -m build --wheel #clones microsoft/playwright @ DRIVER_SHA and builds the driver from source
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.
@@ -76,14 +88,14 @@ The diff range is "every commit on the new release branch since the previous rel
76
88
77
89
-**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.
78
90
```sh
79
-
git -C driver/playwright-src log --all --grep="bump version to v" --oneline | head
91
+
git -C "$PW_SRC_DIR" log --all --grep="bump version to v" --oneline | head
80
92
```
81
93
-**New release end**: the tip of `release-<new>` (or the matching tag if it exists).
82
94
83
95
Save the commit list, oldest first, scoped to `docs/src/api/`:
A normal roll yields 50–100 commits. If you see 0 or thousands, the range is wrong.
@@ -95,7 +107,7 @@ Format the file as a markdown checklist and add the standard preamble (status le
95
107
For each commit, in chronological order:
96
108
97
109
```sh
98
-
git -C driver/playwright-src show <sha> -- docs/src/api/
110
+
git -C "$PW_SRC_DIR" show <sha> -- docs/src/api/
99
111
```
100
112
101
113
Look for:
@@ -113,7 +125,7 @@ Before tagging anything as MISMATCH or N/A based on appearance, dump the actual
113
125
114
126
```python
115
127
import json
116
-
data = json.load(open("playwright/driver/package/api.json"))
128
+
data = json.load(open("/tmp/api.json"))
117
129
classes = {c["name"]: c for c in data}
118
130
for cls_name in ["Page", "BrowserContext", "Screencast", "Debugger"]:
119
131
cls= classes.get(cls_name)
@@ -140,7 +152,7 @@ A few rules of thumb that catch most "actually a PORT" cases:
140
152
141
153
#### PORT
142
154
143
-
Implement the change in `playwright/_impl/<module>.py`. Use the upstream JS implementation as a reference: `driver/playwright-src/packages/playwright-core/src/client/<module>.ts`. Translate idioms:
155
+
Implement the change in `playwright/_impl/<module>.py`. Use the upstream JS implementation as a reference: `$PW_SRC_DIR/packages/playwright-core/src/client/<module>.ts`. Translate idioms:
144
156
145
157
| Upstream JS | Python |
146
158
|---|---|
@@ -205,13 +217,18 @@ Tick the box in `/tmp/roll-<new>-commits.md` with one line: `[x] <sha> <subject>
205
217
### 5. Regenerate
206
218
207
219
```sh
208
-
./scripts/update_api.sh
220
+
PW_SRC_DIR=../playwright ./scripts/update_api.sh# PW_SRC_DIR already exported in step 2
209
221
```
210
222
211
223
The script does, in order:
212
-
1.`git checkout HEAD -- playwright/{async,sync}_api/_generated.py` (resets to last committed),
213
-
2. runs `scripts/generate_{sync,async}_api.py` which dumps to `.x` then renames into place,
214
-
3. invokes `pre-commit run --files` on the generated files.
224
+
1. generates `api.json` from `$PW_SRC_DIR` into a temp file and exports `PW_API_JSON`,
225
+
2.`git checkout HEAD -- playwright/{async,sync}_api/_generated.py` (resets to last committed),
226
+
3. runs `scripts/generate_{sync,async}_api.py` (they read `api.json` via `PW_API_JSON`), dumping to `.x` then renaming into place,
227
+
4. invokes `pre-commit run --files` on the generated files.
228
+
229
+
**CI no longer verifies that `_generated.py` is in sync** (the Lint job dropped the
230
+
"Verify generated API is up to date" step so it needn't check out upstream). So
231
+
regenerating here and committing the result is on you — don't skip it.
215
232
216
233
Failure modes and fixes:
217
234
@@ -245,7 +262,7 @@ For each PORT, add one async test and a matching sync test. Conventions:
245
262
246
263
### 7. Update existing high-touch artifacts
247
264
248
-
-`DRIVER_SHA` (and the version comment in `scripts/build_driver.sh`): already done in step 2.
265
+
-`DRIVER_VERSION`and `NODE_VERSION`: already done in step 2.
249
266
-`README.md`: gets the chromium/firefox/webkit version table updated automatically by `scripts/update_versions.py` (called from `update_api.sh`). Don't edit by hand.
250
267
- The "Backport changes" tracking issue on GitHub (filed by `microsoft-playwright-automation`) is the *intent* tracker, but it's frequently out of sync with what's actually been ported. Treat it as a starting point, not the source of truth — the `docs/src/api/` commit walk is authoritative.
251
268
@@ -281,7 +298,7 @@ Class names use the upstream PascalCase (`BrowserContext`, `BrowserType`); metho
281
298
-**A cluster of suppressions on the same class is a smell.** If you're about to add five `Method not implemented: Foo.*` lines, you're almost certainly looking at a class that needs to be implemented. Implement the whole thing once and the suppressions disappear.
282
299
-**Watch for revert pairs in the same range.** 1.59 added and reverted `Browser.isRemote` (#39613 / #39620) inside the same release. Walking chronologically lets you skip the add when you see the revert later.
283
300
-**Watch for rename-revert pairs.** 1.59 had `Locator.normalize` → `Locator.toCode` (#39648) → `Locator.normalize` (#39754). Final state wins; only port the last.
284
-
-**Doc renames almost always include a wire-protocol rename.** Whenever you see `### param: X.y.oldName` → `### param: X.y.newName` in a doc commit, also `git -C driver/playwright-src show <sha> -- packages/protocol/src/protocol.yml` and the corresponding `*Dispatcher.ts` file. If the wire field changed too, the channel-send dict key in `_impl/` must change. Suppressing the doc-side mismatch is hiding a real bug — the previous Python code is silently sending an unknown field that the new server ignores.
301
+
-**Doc renames almost always include a wire-protocol rename.** Whenever you see `### param: X.y.oldName` → `### param: X.y.newName` in a doc commit, also `git -C "$PW_SRC_DIR" show <sha> -- packages/protocol/src/protocol.yml` and the corresponding `*Dispatcher.ts` file. If the wire field changed too, the channel-send dict key in `_impl/` must change. Suppressing the doc-side mismatch is hiding a real bug — the previous Python code is silently sending an unknown field that the new server ignores.
285
302
-**TypedDicts beat `Dict[str, X]` for any structured return.** When the docs describe a return as `[Object]` with named fields (or even `[Object=Foo]`), define a `TypedDict` in `_api_structures.py`, re-export from both public `__init__.py` files, and use it. Zero runtime cost (it's still a `dict`), and the doc generator's type comparator matches by structure via `get_type_hints`.
286
303
-**Positional renames are free.** A param with no default before any `*` separator is positional-or-keyword in Python, but realistic call sites pass it positionally. Renaming such a param doesn't break callers.
287
304
-**The "Backport changes" GitHub issue can be misleading.** In the 1.59 roll its checkboxes were all marked `[x]` with annotations like "✅ IMPLEMENTED", but several of those features had not actually been merged into the Python port. Trust the `docs/src/api/` walk over the issue.
Copy file name to clipboardExpand all lines: CLAUDE.md
+7-5Lines changed: 7 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,11 +10,13 @@ Python bindings for [Playwright](https://playwright.dev). The Python client talk
10
10
11
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
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.
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 Playwright's `api.json` (provided via the `PW_API_JSON` env var; see `scripts/update_api.sh`) and abort if either side is out of sync.
14
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
15
-`tests/async/`, `tests/sync/` — pytest suites. Most new tests are added to the async file with a sync mirror.
16
-
-`DRIVER_SHA` — the single source of truth for which Playwright commit the driver is built from (one line, the 40-char `microsoft/playwright` commit SHA). Read by `setup.py`, `scripts/build_driver.sh`, and CI. The wheel build clones `microsoft/playwright` at this commit and builds the driver from source (via `scripts/build_driver.sh` + upstream's `utils/build/build-playwright-driver.sh`). The SHA is baked into the staged bundle filenames (`driver/playwright-<sha>-<suffix>.zip`), so it doubles as the build cache key.
17
-
-`scripts/build_driver.sh` — clones and builds the upstream driver bundles into `driver/`. A portable bash script (shareable with the other language forks) that needs Node.js, npm, git and bash; invoked from `setup.py`'s `bdist_wheel`. Reads the pin from `DRIVER_SHA`; takes no arguments.
16
+
-`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.
17
+
-`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`).
18
+
-`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`). Pure Python stdlib (no Node/npm/git); invoked from `setup.py`'s `bdist_wheel` with the target platform's suffix (no arg builds all six).
19
+
-`api.json` is **not** shipped in the bundle and is never written into the driver — `scripts/update_api.sh` generates it from a nearby `microsoft/playwright` checkout (`$PW_SRC_DIR`) into a temp file and passes it to codegen via `PW_API_JSON` (read by `scripts/documentation_provider.py`). Needed only when regenerating the API, never at runtime.
18
20
-`ROLLING.md`, `CONTRIBUTING.md` — human-facing setup and roll docs.
python -m build --wheel #clones microsoft/playwright @ DRIVER_SHA and builds the driver from source
31
+
python -m build --wheel #downloads playwright-core @ DRIVER_VERSION + Node.js and assembles the driver
30
32
pre-commit install
31
33
```
32
34
@@ -39,7 +41,7 @@ If the system lacks `python3-venv`, `uv venv env` is an acceptable substitute (t
39
41
- Type-check: `mypy playwright`.
40
42
- Run tests: `pytest --browser chromium [-k name]`. Browsers are installed via `playwright install chromium` (do **not** use `--with-deps`, which requires sudo).
41
43
42
-
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`.
44
+
When changing public API, edit `_impl/`, then run `./scripts/update_api.sh`. The script regenerates `_generated.py` and validates against Playwright's `api.json` (which it generates from `$PW_SRC_DIR`). If validation fails, fix the mismatch in `_impl/`, in `expected_api_mismatch.txt`, or in `documentation_provider.py` — not by hand-editing `_generated.py`.
0 commit comments