playwright-roll
Automates the process of rolling Playwright Python drivers to newer versions by porting API changes and updating generated code.
Install
mkdir -p .claude/skills/playwright-roll && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/2008" && unzip -o skill.zip -d .claude/skills/playwright-roll && rm skill.zipInstalls to .claude/skills/playwright-roll
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Roll Playwright Python to a new driver version. Walks the upstream `docs/src/api/` commit range, ports each public-API change, suppresses the rest in `expected_api_mismatch.txt`, regenerates the typed surface, and adds tests.Key capabilities
- →Port public API changes from upstream
- →Suppress API mismatches in documentation
- →Regenerate typed surface wrappers
- →Introspect Python implementation classes
- →Verify API surface against api.json
How it works
Walks the upstream commit range, ports API changes, suppresses non-Python changes in mismatch files, and regenerates the typed surface.
Inputs & outputs
When to use playwright-roll
- →Upgrading Playwright driver version
- →Syncing Python wrapper classes with new API definitions
- →Updating tests for new Playwright releases
About this skill
Rolling Playwright Python
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.
The previous human-facing summary lives in ../../../ROLLING.md. This skill is the operational playbook — read it end to end before starting.
Mental model
The Python port is hand-written code in playwright/_impl/, plus a generator (scripts/generate_*.py, scripts/documentation_provider.py) that:
- introspects the Python
_implclasses viainspect, - emits typed wrapper classes into
playwright/{async,sync}_api/_generated.py, and - diffs the introspected surface against the Playwright
api.json(generated from the upstream docs — see step 2).
Anything in api.json that is missing or differently typed in _impl/ causes generation to fail. Three resolutions:
- PORT — the new API is intended for Python (no
langs.onlyfilter, orlangs.onlyincludes"python"). Implement it in_impl/. - MISMATCH — the API genuinely exists for Python but is shaped differently (a callback signature uses unions, a kwarg uses a legacy name, etc.) and there's a justified reason to keep the divergence. Add a precise line to
scripts/expected_api_mismatch.txtwith a comment explaining why. - N/A — the commit only touches docs, has
* langs: js(or any other filter that excludes Python), is server-side, Electron-only, or was reverted later in the same release. No action.
The upstream documentation source of truth is docs/src/api/*.md in the playwright repo. Every ## method: / ## property: / ## event: / ### option: / ### param: block has an optional * langs: js (or js, python, etc.) filter. The Python doclint resolves these into langs fields on each member of api.json. An empty langs: {} means "all languages including Python" — implement it, don't suppress it.
The mistake the 1.59 roll made twice over: classifying things as "internal tooling, N/A for Python" based on the name of the API (Screencast, Debugger, pickLocator, clearConsoleMessages, artifactsDir, …). Almost all of those had empty
langs: {}inapi.jsonand were real Python APIs. Sounding tooling-y is not alangsfilter. Thelangsfield on the member inapi.jsonis the only authoritative signal. When in doubt, dump it (see "Verifying classifications" below).
Process
1. Set up the env
CONTRIBUTING.md covers this. Notes from past rolls:
- The repo requires Python 3.10+. If
python3.10isn't available, usepython3(3.12 is fine). - If
python3-venvis missing system-wide, useuv venv envinstead, thenuv pip install --python env/bin/python --upgrade pip. Don't try toapt install— sudo is denied in the harness. - Always activate the venv before any
pip,pytest,mypy, orpre-commitinvocation.
2. Bump the driver pin, download it, and generate api.json
You need a nearby microsoft/playwright checkout for the docs walk and for
api.json generation. Point PW_SRC_DIR at it and check out the new tag there:
export PW_SRC_DIR=../playwright
git -C "$PW_SRC_DIR" fetch --tags origin
git -C "$PW_SRC_DIR" checkout v<new> # e.g. v1.62.0
Then bump the pins and assemble the driver:
# Edit DRIVER_VERSION (repo root): the playwright-core npm version for the new
# release, no "v" prefix, e.g. 1.62.0
python scripts/update_node_version.py # refresh NODE_VERSION to the current LTS
source env/bin/activate
python -m build --wheel # downloads playwright-core @ DRIVER_VERSION + Node.js, assembles the driver
playwright install chromium # NOT --with-deps; sudo is denied
# api.json isn't in the bundle, and the walk below inspects `langs` from it.
# Generate a copy to inspect (update_api.sh generates its own temp copy in step 6):
API_JSON_MODE=1 node "$PW_SRC_DIR/utils/doclint/generateApiJson.js" > /tmp/api.json
The wheel build just downloads the playwright-core npm package at
DRIVER_VERSION and the matching Node.js binary (no source build, no Node/npm/git
toolchain), and unpacks the driver under playwright/driver/. api.json is the
one piece not shipped in the bundle — it's generated from $PW_SRC_DIR on demand
(here to /tmp/api.json for the walk, and into a temp file passed via
PW_API_JSON by ./scripts/update_api.sh during codegen).
3. Identify the commit range
Use the nearby microsoft/playwright checkout at $PW_SRC_DIR (from step 2).
Bring it up to date and ensure release branches/tags are present before walking
the range:
git -C "$PW_SRC_DIR" fetch --tags
git -C "$PW_SRC_DIR" fetch origin 'release-*:release-*'
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.
The diff range is "every commit on the new release branch since the previous release was cut". Anchor commits:
- Previous release end: the
chore: bump version to vX.Y.0-nextcommit onmain. That commit is the first commit after the previous release (X.Y-1) was cut. Use its parent (<sha>~1) as the lower bound.git -C "$PW_SRC_DIR" log --all --grep="bump version to v" --oneline | head - New release end: the tip of
release-<new>(or the matching tag if it exists).
Save the commit list, oldest first, scoped to docs/src/api/:
git -C "$PW_SRC_DIR" log <prev-anchor>~1..release-<new> --oneline --reverse -- docs/src/api > /tmp/roll-<new>-commits.md
A normal roll yields 50–100 commits. If you see 0 or thousands, the range is wrong.
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.
4. Walk the commit list
For each commit, in chronological order:
git -C "$PW_SRC_DIR" show <sha> -- docs/src/api/
Look for:
## (async )?method:/## property:/## event:additions or removals;* langs: ...lines on those blocks;### param:/### option:additions or removals;- new
class-X.mdfiles (whole new classes — usuallylangs: js); - type changes in
- returns:lines.
Classify and act.
Verifying classifications (do this before suppressing anything)
Before tagging anything as MISMATCH or N/A based on appearance, dump the actual langs from api.json:
import json
data = json.load(open("/tmp/api.json"))
classes = {c["name"]: c for c in data}
for cls_name in ["Page", "BrowserContext", "Screencast", "Debugger"]:
cls = classes.get(cls_name)
if not cls:
continue
print(f"\n{cls_name}: cls_langs={cls.get('langs', {})}")
for m in cls["members"]:
print(f" {m['name']} kind={m.get('kind')} langs={m.get('langs', {})}")
For options/params nested inside an Object-typed arg, walk one level deeper:
for a in member.get("args", []):
if a["name"] == "options":
for prop in a.get("type", {}).get("properties", []):
print(prop["name"], prop.get("langs", {}))
A few rules of thumb that catch most "actually a PORT" cases:
- If the containing class has empty
langs: {}and the member has emptylangs: {}, it's for Python — implement it. - If the member is empty but a single option has
langs: js, the method is for Python and you only skip that option (e.g.Screencast.start.sizeislangs: jswhileScreencast.startitself isn't). - If you're about to add three or more
Method not implemented:entries for the same class, stop — you almost certainly need to implement the class.
PORT
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:
| Upstream JS | Python |
|---|---|
async foo(): Promise<X> | async def foo(self) -> X: |
foo(): X (sync getter, no args, no body) | @property def foo(self) -> X: (the doc generator treats argument-less sync getters as properties — see documentation_provider.py:133. If you make it a method instead, you'll get a "Method vs property mismatch" error.) |
await this._channel.foo({ a, b }) | await self._channel.send("foo", None, locals_to_params(locals())) |
(await this._channel.foo()).value | await self._channel.send("foo", None) (Python's send() auto-unwraps single-key responses; only call send_return_as_dict when the protocol returns multiple keys.) |
(await this._channel.foo()).artifact (multi-key, may be empty) | result = await self._channel.send_return_as_dict("foo", None); (result or {}).get("artifact") — send_return_as_dict returns None (not {}) when the protocol response carries no fields. |
try { ... } catch (e) { if (isTargetClosedError(e)) return; throw e; } | try: ...; except Exception as e: if is_target_closed_error(e): return; raise (import from playwright._impl._errors) |
Inline [Object] return like {endpoint: string} | A TypedDict in playwright/_impl/_api_structures.py — not Dict[str, str]. The doc generator serializes TypedDicts as {field: type, ...} via get_type_hints and that matches the inline-object form exactly. See RemoteAddr, BrowserBindResult, DebuggerPausedDetails. |
binary event/return field | The Python channel layer hands you a base64 string. Decode with base64.b64decode(value) before exposing as bytes. See Screencast._dispatch_frame. |
When implementing a new ChannelOwner subclass (one constructed by the protocol with (parent, type, guid, initializer)):
- Register it in
playwright/_impl/_object_factory.py:create_remote_object— otherwise the guid resolves toDummyObjectand downstream code breaks mysteriously. - Import it and add it to
generated_typesinscripts/generate_api.py, plus add aXxxImplimport
Content truncated.
When not to use it
- →When the Playwright driver version is not being updated
Prerequisites
Limitations
- →Requires manual classification of API changes
- →Dependent on upstream api.json accuracy
How it compares
Automates the synchronization of hand-written Python wrappers with upstream API definitions using introspection and diffing.
Compared to similar skills
playwright-roll side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| playwright-roll (this skill) | 2 | 2mo | Review | Advanced |
| adk-engineer | 3 | 26d | Review | Advanced |
| unit-testing-test-generate | 2 | 4mo | Review | Intermediate |
| api-test-generator | 1 | 9mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by microsoft
View all by microsoft →You might also like
adk-engineer
jeremylongshore
Execute software engineer specializing in creating production-ready ADK agents with best practices, code structure, testing, and deployment automation. Use when asked to "build ADK agent", "create agent code", or "engineer ADK application". Trigger with relevant phrases based on skill purpose.
unit-testing-test-generate
sickn33
Generate comprehensive, maintainable unit tests across languages with strong coverage and edge case focus.
api-test-generator
mikopbx
Генерация полных Python pytest тестов для REST API эндпоинтов с валидацией схемы. Использовать при создании тестов для новых эндпоинтов, добавлении покрытия для CRUD операций или валидации соответствия API с OpenAPI схемами.
designing-tests
CloudAI-X
Designs and implements testing strategies for any codebase. Use when adding tests, improving coverage, setting up testing infrastructure, debugging test failures, or when asked about unit tests, integration tests, or E2E testing.
test-coverage-improver
openai
Improve test coverage in the OpenAI Agents Python repository: run `make coverage`, inspect coverage artifacts, identify low-coverage files, propose high-impact tests, and confirm with the user before writing tests.
async-repl-protocol
parcadei
Async REPL Protocol