agentleFS
MicrosoftKnown publishermicrosoft/playwright-python/AGENTS.md

playwright-python

AGENTS.md15k starsChanged 12 days agoApache-2.0View on GitHub

Guidance for coding agents working in this repository. Python bindings for Playwright. 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/spec/*.yml.

  • Installs packages
  • Commits and pushes

We split this AGENTS.md into the rules it gives an agent

11 rules. About 0 are common, 2 are distinctive, and 9 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    If the system lacks python3-venv, uv venv env is an acceptable substitute (then uv pip install --python env/bin/python --upgrade pip).

    line 41
  2. Very specific:

    Run tests: pytest --browser chromium [-k name] (v1), PW_IMPL=v2 pytest tests/async --browser chromium (v2), or ./test.sh [browser] for both.

    line 48
  3. Very specific:

    Do not write a new test when an upstream test exists.

    line 63
  4. Distinctive:

    Never post comments, replies, or reviews on GitHub PRs/issues under my account without my explicit approval.

    line 81
  5. Distinctive:

    Don't hand-edit generated files.

    line 85
  6. Very specific:

    Don't add # type: ignore or modify _generated.py to silence pyright.

    line 86
  7. Very specific:

    Keep scripts/codegen/python_rules.js minimal — every entry needs a one-line rationale comment above it.

    line 88
  8. Very specific:

    Before committing, run mypy playwright and fix errors.

    line 94
  9. Very specific:

    Never add Co-Authored-By agents in commit message.

    line 122
  10. Very specific:

    Never add "Generated with" in commit message.

    line 123
  11. Very specific:

    Never add test plan to PR description.

    line 124

≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

AGENTS.md

Guidance for coding agents working in this repository.

What this is

Python bindings for Playwright. 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/spec/*.yml.

Layout

  • 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. Internal types are in _internal_structures.py.
  • playwright/_impl_v2/ — the second implementation, a line-by-line port of upstream's JS client (packages/playwright-core/src/client/*.ts), one module per client file in snake_case (browserContext.ts is browser_context.py), members in the JS order. It has only the async API; PW_IMPL=v2 switches the async API to it (playwright/_impl/_api_classes.py). Each difference from the JS code has a # Python: comment; see its __init__.py for the rules. isomorphic/, protocol/ and utils/ hold its own ports of the parts of packages/isomorphic, packages/protocol and packages/utils that it uses; from playwright/_impl it uses only the public error classes, FormData, the generated API types, the driver location and the protocol check. protocol_channels.py, protocol_types.py and protocol/validator.py are generated by scripts/codegen/generate_protocol.py.
  • playwright/async_api/_generated.py, playwright/sync_api/_generated.py, and the public TypedDicts in playwright/_impl/_api_structures.py — auto-generated from the upstream API docs. Never edit by hand; rerun ./scripts/update_api.sh after a roll or after changing scripts/codegen/.
  • scripts/codegen/generate_api.js — the generator. A Node.js script that loads the upstream doc parser (utils/doclint/api_parser.js) from $PW_SRC_DIR, filters the docs for Python, and writes the whole public API: signatures, types, docstrings, event overloads, the calls into _impl/, and the TypedDicts. It does not read _impl/.
  • scripts/codegen/python_rules.js — the Python facts that the docs do not contain (positional options, struct total=, hidden members and options). Policy: the shipped Python API wins; each entry is a candidate for an upstream docs fix, and the generator fails on entries that no longer match anything. Every entry needs a one-line rationale comment above it.
  • playwright/_impl/_protocol.py, _protocol_types.py and _protocol_channels.py — auto-generated by scripts/codegen/generate_protocol.py (run by ./scripts/update_api.sh) from the upstream protocol spec.
    • _protocol_channels.py has a typed channel class for each interface (PageChannel, FrameChannel, …), like upstream's channels.d.ts. Each command is an async method with the protocol name and parameters (await self._channel.goto(url=..., waitUntil=..., referer=..., timeout=...)); each event has on<Event> / off<Event> methods. A result or event payload has every top-level field; a field that the driver left out is None.
    • _protocol_types.py has the TypedDicts for initializers, results, events and nested types, named as upstream's utils/generate_channels.js names them (PageInitializer, FrameGotoResult, PageRouteEvent, …).
    • _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.
  • 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.
  • tests/async/, tests/sync/ — pytest suites, ported test for test from the upstream tests, which tests/upstream_registry.md lists by identifier. See Tests. playwright/_impl (v1, the default) runs the older tests in tests/{async,sync}/legacy/ and the other tests outside of tests/async and tests/sync; PW_IMPL=v2 runs playwright/_impl_v2 on all of tests/async (tests/conftest.py). CI runs v2 on Ubuntu 22.04 with Chromium, Firefox and WebKit. An older test that checks a behavior where v2 follows upstream and v1 does not is marked @only_v1("<tests of v2>") (tests/utils.py), naming the upstream port that covers v2, or the Python-only test of v2 in tests/async/python/, which v1 does not run.
  • 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.
  • 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).
  • 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).
  • 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.
  • ROLLING.md, CONTRIBUTING.md — human-facing setup and roll docs.

Setup

CONTRIBUTING.md has the full sequence. The short version (needs Node.js and npm for the driver build):

python3 -m venv env && source env/bin/activate
pip install --upgrade pip
pip install -r local-requirements.txt
pip install -e .
python -m build --wheel        # downloads playwright-core @ DRIVER_VERSION + Node.js and assembles the driver
pre-commit install

If the system lacks python3-venv, uv venv env is an acceptable substitute (then uv pip install --python env/bin/python --upgrade pip).

Common commands

  • Regenerate _generated.py: ./scripts/update_api.sh (runs codegen + pre-commit on the generated files).
  • Lint everything: pre-commit run --all-files.
  • Type-check: mypy playwright.
  • Run tests: pytest --browser chromium [-k name] (v1), PW_IMPL=v2 pytest tests/async --browser chromium (v2), or ./test.sh [browser] for both. Browsers are installed via playwright install chromium (do not use --with-deps, which requires sudo).

The public API comes from the upstream docs. After ./scripts/update_api.sh, run mypy playwright: the generated classes type their _impl_obj, so every new member or option that _impl/ does not implement yet is a mypy error in _generated.py. Fix it in _impl/, or record a justified difference in scripts/codegen/python_rules.js — not by hand-editing _generated.py.

Tests

The tests are a port of the upstream tests in microsoft/playwright (tests/page/, tests/library/), test for test. Upstream gives each test an identifier, the first token of its title: it('CH-001 should check the box', ...). tests/upstream_registry.md is a copy of upstream's tests/registry.md, which lists every identifier with its file and title.

First rule: every upstream test is ported, or listed as not ported.

  • Port each upstream test twice, to tests/async/ and to tests/sync/, in the file that matches the upstream file: tests/page/page-check.spec.ts becomes tests/async/page/test_page_check.py and tests/sync/page/test_page_check.py; tests/library/chromium/oopif.spec.ts becomes tests/async/library/chromium/test_oopif.py.
  • Name the test test_<ID>_<title>: the identifier with _, then the describe titles and the title in snake_case. test('EX-181 toHaveCount pass') in test.describe('toHaveCount') becomes test_EX_181_to_have_count_to_have_count_pass. A test that a loop declares several times is one function with @pytest.mark.parametrize.
  • Port faithfully: the same steps, assertions and skip conditions. A test that fails because of a bug in the Python client stays, marked @pytest.mark.xfail(reason="python: ...", strict=True).
  • A test that can not exist in Python (a JavaScript-only API, Node.js client internals, tools that run in the driver, @playwright/test features) goes in tests/upstream_not_ported.txt, with the reason.
  • python scripts/check_test_registry.py checks all of this. --missing <code or file glob> prints the expected names of the tests still to port; --report prints the coverage per subsystem.
  • Do not write a new test when an upstream test exists. tests/async/legacy/ and tests/sync/legacy/ hold older ports named after old upstream titles; do not add tests there.

Second rule: keep the number of tests small. This applies to every test, and most of all to Python-only behavior that has no upstream test (the sync API, asyncio, the driver).

  • Extend before you add: before you write a new test, look for an existing test that covers the same feature or code path. If one exists, add the new edge case or assertion to it. Create a new test only when no existing test is a good fit, or when the new scenario needs a different setup.
  • New features: cover the mainstream scenario and the important edge cases only. Do not create matrices of tests for every scenario, option, or aspect.
  • Regressions: generalize the provided regression cases into meaningful tests, and stop there. Do not add more tests around them.

Rolling Playwright to a new version

This is the recurring high-stakes task. Use the dedicated skill:

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

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.

Working on PRs

  • Never post comments, replies, or reviews on GitHub PRs/issues under my account without my explicit approval. Draft the proposed text and wait for me to approve before sending.

House style

  • Don't hand-edit generated files.
  • Don't add # type: ignore or modify _generated.py to silence pyright; fix the source of the mismatch.
  • New public methods on impl classes need a sync test mirror under tests/sync/.
  • Keep scripts/codegen/python_rules.js minimal — every entry needs a one-line rationale comment above it. Prefer fixing the upstream docs.
  • Implementation methods take the Python (snake_case) names that the generated API passes. Their options have no default: an option that can be absent is Optional[X] without = None. So mypy reports each caller that does not pass a new option.
  • Send messages through the generated channel methods, never by name: await self._channel.click(selector=selector, ..., clickCount=click_count, timeout=self._timeout(timeout)). Pass every parameter, with the protocol (camelCase) name; an absent option is None, and the connection drops it. Declare the channel type on each ChannelOwner subclass (_channel: "PageChannel"). Listen to events with self._channel.on<Event>(handler).

Commit Convention

Before committing, run mypy playwright and fix errors.

Semantic commit messages: label(scope): description

Labels: fix, feat, chore, docs, test, devops

git checkout -b fix-12345
# ... make changes ...
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix(asyncio): do not deadlock in atexit handler

Fixes: https://github.com/microsoft/playwright-python/issues/12345
EOF
)"
git push origin fix-12345
gh pr create --repo microsoft/playwright-python --head username:fix-12345 \
  --title "fix(asyncio): do not deadlock in atexit handler" \
  --body "$(cat <<'EOF'
## Summary
- <describe the change very! briefly>

Fixes https://github.com/microsoft/playwright-python/issues/12345
EOF
)"

Never add Co-Authored-By agents in commit message. Never add "Generated with" in commit message. Never add test plan to PR description. Keep PR description short — a few bullet points at most. Branch naming for issue fixes: fix-<issue-number>

Never git push without an explicit instruction to push. Applies even when a PR is already open for the branch — additional commits are immediately visible to reviewers. Commit locally, report what was committed, and wait. Only push when the user's message contains "push", "upload", "create PR", "ship it", or equivalent.

Download now (ZIP)

Trending

Steady: 0 new projects took it in the last 4 weeks, none the 4 weeks before.thin data

Week of Aug 30: 0 new projectsWeek of Sep 6: 0 new projectsWeek of Sep 13: 0 new projectsWeek of Sep 20: 0 new projectsWeek of Sep 27: 0 new projectsWeek of Oct 4: 0 new projectsWeek of Oct 11: 0 new projectsWeek of Oct 18: 0 new projects
Aug 2026Sep 2026Oct 2026
New projects taking a version of this file each week, over the 8 weeks our latest run read: only 0 in all, so this is a hint, not a trend.

Who's talking about microsoft/playwright-python

The biggest 5 of 72 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 72

Talked about on GitHub, Stack Overflow, YouTube, the web and Hacker News (72 times).

On GitHub · 50

45 more on GitHub

On Stack Overflow · 11

6 more on Stack Overflow

On YouTube · 5

On the web · 4

On Hacker News · 2

  • Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.

    Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.

  • In this repository2 other files it gives its agents.

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.