playwright-python Documentation

Python version of the Playwright testing and automation library.

15,048 stars Python Python 2 files ยท ~25,628 tokens #chromium#firefox#playwright#webkit
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy playwright-python on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name playwright-python --restart always microsoft/playwright-python:latest

1. Project Overview & Quickstart (microsoft/playwright-python)

File: README.md

๐ŸŽญ [Playwright](https://playwright.dev) for Python [](https://pypi.python.org/pypi/playwright/) [](https://aka.ms/playwright/discord)

Playwright is a Python library to automate Chromium, Firefox and WebKit browsers with a single API. Playwright delivers automation that is ever-green, capable, reliable and fast. See how Playwright is better.

Linux macOS Windows
Chromium 156.0.8078.12 โœ… โœ… โœ…
WebKit 27.2 โœ… โœ… โœ…
Firefox 157.0 โœ… โœ… โœ…

Documentation

https://playwright.dev/python/docs/intro

API Reference

https://playwright.dev/python/docs/api/class-playwright

Example

py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    for browser_type in [p.chromium, p.firefox, p.webkit]:
        browser = browser_type.launch()
        page = browser.new_page()
        page.goto('http://playwright.dev')
        page.screenshot(path=f'example-{browser_type.name}.png')
        browser.close()
py
import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        for browser_type in [p.chromium, p.firefox, p.webkit]:
            browser = await browser_type.launch()
            page = await browser.new_page()
            await page.goto('http://playwright.dev')
            await page.screenshot(path=f'example-{browser_type.name}.png')
            await browser.close()

asyncio.run(main())

Other languages

More comfortable in another programming language? Playwright is also available in


File: 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/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 from the upstream tests. Most new tests are added to the async file with a sync mirror. See Tests.
  • 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.
  • 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) 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.
  • 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):

sh
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]. 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/). Keep them close to upstream, so that each upstream test has one place in this repo.

First rule: follow the upstream names and files.

  • When a change needs a test, find the upstream test for the same behavior and port it. Do not write a new test when an upstream test exists.
  • Put the test in the Python file that matches the upstream file: tests/page/expect-misc.spec.ts becomes tests/async/test_expect_misc.py. If that file does not exist, create it.
  • Name the test after the upstream title, in snake_case, with the describe title in front: test('should support pseudo element') in test.describe('toHaveCSS') becomes test_to_have_css_should_support_pseudo_element.
  • Add the sync mirror in tests/sync/, with the same file name and test name.

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

bash
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-

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.


File: CODE_OF_CONDUCT.md

Microsoft Open Source Code of Conduct

This project has adopted the Microsoft Open Source Code of Conduct.

Resources:


File: CONTRIBUTING.md

Contributing

Choosing an Issue

To maintain project quality and focus, Playwright requires a corresponding issue for every contribution, with the exception of minor documentation fixes.

If you would like to address a bug or feature that isn't currently listed, please file a new issue first. This allows the community and maintainers to provide early feedback and facilitates a discussion before you invest time in developing a pull request.

When submitting an issue, please state clearly if you intend to work on it. Once triaged and approved, the maintainers will determine the best path forwardโ€”whether the task should be handled by the core team, an automated agent, or a community contributor. If the issue is assigned to you, you may then proceed with your changes and submit a PR.

Submission Policy

To ensure the maintainability of the project, please note the following:

  • Unsolicited PRs: Pull requests submitted without a linked issue or prior approval will be closed.
  • Low-Quality AI Contributions: PRs that do not meet our quality standards or lack human oversight (including low-quality agentic submissions) will be closed without explanation.
  • Approval Required: Only proceed with a PR once the issue has been officially assigned to you or approved for community contribution.

How to Contribute

Configuring python environment

The project development requires Python version 3.10+. To set it as default in the environment run the following commands:

sh
# You may need to install python 3.10 venv if it's missing, on Ubuntu just run `sudo apt-get install python3.10-venv`
python3.10 -m venv env
source ./env/bin/activate

Install required dependencies:

sh
python -m pip install --upgrade pip
pip install -r local-requirements.txt

Build and install drivers:

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

sh
pip install -e .
python -m build --wheel

Install the browsers to test against:

sh
playwright install chromium

Run tests:

sh
pytest --browser chromium

Checking for typing errors

sh
mypy playwright

Format the code

sh
pre-commit install
pre-commit run --all-files

For more details look at the CI configuration.

Regenerating APIs

update_api.sh generates playwright/{async,sync}_api/_generated.py and the
public TypedDicts in playwright/_impl/_api_structures.py from the API docs in
a separate microsoft/playwright checkout on the exact release branch for
DRIVER_VERSION (for example release-1.64); point PW_SRC_DIR at it. The
generator is
scripts/codegen/generate_api.js; Python facts that the docs do not contain
live in scripts/codegen/python_rules.js.

bash
PW_SRC_DIR=../playwright-for-python ./scripts/update_api.sh
pre-commit run --all-files

Contributor License Agreement

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.

Code of Conduct

This project has adopted the Microsoft Open Source Code of Conduct.
For more information see the Code of Conduct FAQ or
contact [email protected] with any additional questions or comments.


File: local-requirements.txt

asyncio-atexit==1.0.1
autobahn==24.4.2
build==1.6.1
mypy==2.4.0
objgraph==3.6.2
Pillow==12.3.0
pixelmatch==0.3.0
pre-commit==3.5.0
pyOpenSSL==26.0.0
pytest==9.0.3
pytest-asyncio==1.4.0
pytest-rerunfailures==16.7
pytest-timeout==2.4.0
PyYAML==6.0.3
service_identity==24.2.0
twisted==25.5.0
types-pyOpenSSL==24.1.0.20240722
types-PyYAML==6.0.12.20260906


File: requirements.txt

This file was autogenerated by uv via the following command:

uv pip compile pyproject.toml -o requirements.txt

greenlet==3.5.6
# via playwright (pyproject.toml)
pyee==13.0.1
# via playwright (pyproject.toml)
typing-extensions==4.16.0
# via pyee


File: ROLLING.md

Rolling Playwright-Python to the latest Playwright driver

  • checkout repo: git clone https://github.com/microsoft/playwright-python
  • make sure local python is 3.10 or newer
    • create virtual environment, if don't have one: python -m venv env
  • activate venv: source env/bin/activate
  • install all deps:
text
python -m pip install --upgrade pip
pip install -r local-requirements.txt
pre-commit install
pip install -e .
  • 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
  • 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).
  • 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
  • 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.
  • commit changes & send PR
  • wait for bots to pass & merge the PR

File: SECURITY.md

Security

Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include Microsoft, Azure, DotNet, AspNet, Xamarin, and our GitHub organizations.

If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's definition of a security vulnerability, please report it to us as described below.

Reporting Security Issues

Please do not report security vulnerabilities through public GitHub issues.

Instead, please report them to the Microsoft Security Response Center (MSRC) at https://msrc.microsoft.com/create-report.

If you prefer to submit without logging in, send email to [email protected]. If possible, encrypt your message with our PGP key; please download it from the Microsoft Security Response Center PGP Key page.

You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at microsoft.com/msrc.

Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:

  • Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
  • Full paths of source file(s) related to the manifestation of the issue
  • The location of the affected source code (tag/branch/commit or direct URL)
  • Any special configuration required to reproduce the issue
  • Step-by-step instructions to reproduce the issue
  • Proof-of-concept or exploit code (if possible)
  • Impact of the issue, including how an attacker might exploit the issue

This information will help us triage your report more quickly.

If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our Microsoft Bug Bounty Program page for more details about our active programs.

Preferred Languages

We prefer all communications to be in English.

Policy

Microsoft follows the principle of Coordinated Vulnerability Disclosure.


File: SUPPORT.md

Support

How to file issues and get help

This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new ones to avoid duplicates. For new issues, file your bug or feature request as a new issue using corresponding template.

For help and questions about using this project, please see the docs site for Playwright for Python.

Join our community Discord Server to connect with other developers using Playwright and ask questions in our 'help-playwright' forum.

Microsoft Support Policy

Support for Playwright for Python is limited to the resources listed above.


File: .pre-commit-config.yaml

See https://pre-commit.com for more information

See https://pre-commit.com/hooks.html for more hooks

repos:

  • repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
    • id: trailing-whitespace
    • id: end-of-file-fixer
      exclude: tests/assets/har-sha1-main-response.txt
    • id: check-yaml
    • id: check-toml
    • id: requirements-txt-fixer
    • id: check-ast
    • id: check-builtin-literals
    • id: check-executables-have-shebangs
    • id: check-merge-conflict
  • repo: https://github.com/psf/black
    rev: 25.1.0
    hooks:
    • id: black
  • repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.17.0
    hooks:
    • id: mypy
      additional_dependencies: [types-pyOpenSSL==24.1.0.20240722, types-PyYAML==6.0.12.20260906]
  • repo: https://github.com/pycqa/flake8
    rev: 7.3.0
    hooks:
    • id: flake8
  • repo: https://github.com/pycqa/isort
    rev: 6.0.1
    hooks:
    • id: isort
  • repo: local
    hooks:
    • id: pyright
      name: pyright
      entry: pyright
      language: node
      pass_filenames: false
      types: [python]
      additional_dependencies: ["[email protected]"]
  • repo: local
    hooks:
    • id: check-license-header
      name: Check License Header
      entry: ./utils/linting/check_file_header.py
      language: python
      types: [python]

File: examples/todomvc/requirements.txt

pytest-playwright


File: tests/assets/file-to-upload-2.txt

contents of the file


File: tests/assets/file-to-upload.txt

contents of the file


File: tests/assets/har-sha1-main-response.txt

Hello, world


File: tests/assets/simple.json

{"foo": "bar"}


File: tests/assets/client-certificates/README.md

Client Certificate test-certificates

Regenerate all certificates by running:

text
bash generate.sh

File: tests/assets/extension-mv3-simple/manifest.json

{
"name": "Simple extension",
"version": "0.1",
"background": {
"service_worker": "index.js"
},
"content_scripts": [{
"matches": [""],
"css": [],
"js": ["content-script.js"]
}],
"permissions": ["background", "activeTab"],
"manifest_version": 3
}


File: tests/assets/extension-mv3-with-logging/manifest.json

{
"manifest_version": 3,
"name": "Console Log Extension",
"version": "1.0",
"background": {
"service_worker": "background.js"
},
"permissions": [
"tabs"
],
"content_scripts": [
{
"matches": [""],
"js": ["content.js"]
}
]
}


File: .azure-pipelines/publish-docker.yml

Trigger: any `v*` release tag (e.g. v1.40.0).

Can also be queued manually from the ADO UI.

trigger:
tags:
include:
- v*

pr: none

parameters:

  • name: releaseChannel
    displayName: "IMPORTANT: set this to 'canary' when triggering manually"
    type: string
    default: stable
    values:
    • stable
    • canary

resources:
repositories:

  • repository: 1esPipelines
    type: git
    name: 1ESPipelineTemplates/1ESPipelineTemplates
    ref: refs/tags/release

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
parameters:
pool:
name: DevDivPlaywrightAzurePipelinesUbuntu2204
os: linux
sdl:
sourceAnalysisPool:
name: DevDivPlaywrightAzurePipelinesWindows2022
# The image must be windows-based due to restrictions of the SDL tools. See: https://aka.ms/AAo6v8e
os: windows
suppression:
suppressionFile: $(Build.SourcesDirectory).azure-pipelines\guardian\SDL.gdnsuppress
stages:
- stage: Publish
jobs:
- job: PublishDocker
displayName: "Publish Docker images to ACR"
# arm64 images are cross-built under QEMU emulation, which is slow.
timeoutInMinutes: 360
steps:
# Full history and tags: setuptools-scm derives the package version
# (and thus the image tags) from the release tag.
- checkout: self
fetchDepth: 0
fetchTags: true
displayName: "Checkout code"

text
- task: UsePythonVersion@0
      inputs:
        versionSpec: '3.10'
      displayName: "Use Python"

    # Resolve pip installs through the DevDiv_PublicPackages feed. The task exports
    # an authenticated PIP_INDEX_URL, which build.sh also forwards to the image builds.
    - task: PipAuthenticate@1
      inputs:
        artifactFeeds: DevDiv/DevDiv_PublicPackages
      displayName: "Authenticate pip to DevDiv_PublicPackages feed"

    - task: UseNode@1
      inputs:
        version: '24.x'
      displayName: "Install Node.js"

    # Relocate the Docker data-root to the large /mnt volume: this job builds
    # 6 images (jammy/noble/resolute x amd64/arm64) and `docker system prune`s
    # between them, so the default disk fills up.
    - task: Bash@3
      displayName: "Setup docker"
      inputs:
        targetType: "inline"
        script: |
          set -x
          sudo service docker stop
          sudo mkdir -p /etc/docker
          echo '{ "data-root": "/mnt/docker" }' | sudo tee /etc/docker/daemon.json
          sudo service docker start

    # scripts/build_driver.py fetches playwright-core with `npm pack`, which
    # picks up the registry and credentials from this .npmrc.
    - task: Bash@3
      displayName: "setup .npmrc"
      inputs:
        targetType: "inline"
        script: echo "registry=https://devdiv.pkgs.visualstudio.com/DevDiv/_packaging/DevDiv_PublicPackages/npm/registry/" >> .npmrc

    - task: npmAuthenticate@0
      displayName: "authenticate the private npm registry"
      inputs:
        workingFile: .npmrc

    - script: |
        python -m pip install --upgrade pip --disable-pip-version-check
        pip install -r local-requirements.txt --disable-pip-version-check
        pip install -r requirements.txt --disable-pip-version-check
        pip install -e . --disable-pip-version-check
      displayName: "Install dependencies"

    - task: AzureCLI@2
      displayName: "Login to ACR via OIDC"
      inputs:
        azureSubscription: "Playwright-CDN"
        scriptType: "bash"
        scriptLocation: "inlineScript"
        inlineScript: "az acr login --name playwright"

    - task: Bash@3
      displayName: "Register QEMU (binfmt) for arm64 cross-build"
      inputs:
        targetType: "inline"
        script: "docker run --rm --privileged ${ACR_CACHE_PREFIX}tonistiigi/binfmt --install arm64"
      env:
        ACR_CACHE_PREFIX: "playwright.azurecr.io/cached/"

    - task: Bash@3
      displayName: "Build & publish Docker images"
      inputs:
        targetType: "inline"
        script: "./utils/docker/publish_docker.sh ${{ parameters.releaseChannel }}"
      env:
        ACR_CACHE_PREFIX: "playwright.azurecr.io/cached/"
        UBUNTU_MIRROR_PREFIX: "azure."

File: .claude/skills/playwright-release/SKILL.md


name: playwright-release

Preparing a Playwright Python release

In this skill, 1.xx is the release (for example 1.64), and v1.xx.0 is its tag.
$PW_SRC_DIR is the roll checkout of microsoft/playwright (see the
playwright-roll skill, step 2).

Remotes: find the remote for microsoft/playwright-python with git remote -v.
The commands below call it upstream.

What you may push:

  • The user's request to prepare the release is the instruction to open the two PRs
    in steps 2 and 3.
  • Cut the release branch (step 4) only when the user tells you to.
  • Never publish the release.

1. Preconditions

Check that the bugs are closed. The bug and regression issue templates give
these title prefixes:

sh
gh issue list --repo microsoft/playwright-python --state open \
  --search 'in:title "[Bug]" OR in:title "[Regression]"'

If any are open, list them for the user and ask whether to continue. Do not
close or comment on issues.

Check that the driver is rolled. DRIVER_VERSION must be the driver for the
release: a 1.xx.y build, or a 1.xx.0-beta-โ€ฆ build from release-1.xx, as the
user says. If it is not, roll with the playwright-roll skill. This is often
already done. The roll commit goes into the release commit PR (step 3). When the
API roll for 1.xx is already on main, that PR only moves DRIVER_VERSION.

2. Release notes (microsoft/playwright)

Write the notes in the roll checkout, on a branch from origin/main:

sh
git -C "$PW_SRC_DIR" fetch origin main
git -C "$PW_SRC_DIR" checkout -b release-notes-1.xx-python origin/main

In docs/src/release-notes-python.md, add a ## Version 1.xx section above the
previous version. Translate it from the ## Version 1.xx section of
docs/src/release-notes-js.md. Compare the Version 1.63 sections of the two
files to see what a past translation dropped and changed.

  • Keep the structure: highlights with emoji headings, ### New APIs with
    #### groups, ### Announcements, ### Browser Versions.

  • Remove what Python does not have: the test runner (@playwright/test,
    test.*, config, fixtures, projects, reporters), UI mode, the HTML report,
    component testing, the VS Code extension, and npx playwright test options.
    Remove every API whose langs excludes Python. To check an API, look for it in
    playwright/async_api/_generated.py, or check langs in api.json (see
    "Verifying classifications" in the playwright-roll skill).

  • Rewrite code samples in Python, with the sync API:
    page.get_by_role("button").click(). Use snake_case names and Python values
    (True, None).

  • Keep API links as docs link macros: [`method: Page.frameLocator`],
    [`option: Tracing.start.ariaSnapshots`],
    [`event: Page.dialogClosed`]. The docs site shows them with Python names.
    In plain text, use Python names.

  • Commands: write playwright install, not npx playwright install.

  • Lean towards Python. List the playwright-python commits since the previous
    release branch:

    sh
    gh api repos/microsoft/playwright-python/compare/release-1.<xx-1>...main \
      --jq '.commits[].commit.message | split("\n")[0]'

    If some of them change what users see (for example, a different exception
    type), list them for the user and ask whether to add them to the notes.

Commit, push to the user's fork of microsoft/playwright, and open the PR. If
the checkout has no remote for the fork, add one. gh api user --jq .login
gives the user's login.

sh
git -C "$PW_SRC_DIR" commit -am "docs: release notes for v1.xx Python"
git -C "$PW_SRC_DIR" push <fork-remote> release-notes-1.xx-python
gh pr create --repo microsoft/playwright --head <login>:release-notes-1.xx-python \
  --title "docs: release notes for v1.xx Python" \
  --body "- Add \`Version 1.xx\` to the Python release notes, translated from the Node.js notes"

Then put the roll checkout back on the release branch. ./scripts/update_api.sh
reads the docs from it. Run the checkout checks from step 2 of the
playwright-roll skill.

sh
git -C "$PW_SRC_DIR" checkout release-1.xx

3. Release commit (playwright-python)

Open a PR from the branch with the roll commit:

  • Title: chore: roll Playwright to v1.xx.y, where v1.xx.y is the Python
    release version, even when DRIVER_VERSION is a beta build.
  • Description: the release notes from step 2, in GitHub markdown (see below).
    The user copies this description into the GitHub release, so for this PR it
    replaces the "keep PR descriptions short" rule in AGENTS.md.

Convert the notes to GitHub markdown yourself:

  • Leave out the ## Version 1.xx heading. Move every other heading up one level
    (### becomes ##, #### becomes ###).

  • Replace each link macro with a full playwright.dev link. Use the Python name as
    the link text:

    Macro GitHub markdown
    [`method: Page.frameLocator`] [page.frame_locator()](https://playwright.dev/python/docs/api/class-page#page-frame-locator)
    [`method: Locator.visible`] (a property in Python) [locator.visible](https://playwright.dev/python/docs/api/class-locator#locator-visible)
    [`option: Browser.newContext.httpCredentials`] [`http_credentials`](https://playwright.dev/python/docs/api/class-browser#browser-new-context-option-http-credentials)
    [`event: BrowserContext.dialogClosed`] [browser_context.on("dialogclosed")](https://playwright.dev/python/docs/api/class-browsercontext#browser-context-event-dialog-closed)

    The page is class- and the class name in lower case. The anchor is the class
    and member names in kebab-case. Options add -option-<name>, and events add
    -event-<name>. A member is a property in Python if _generated.py declares
    it with @property.

Wait for the user to review and merge the PR. Do not merge it yourself.

4. Release (only when the user tells you to)

Cut release-1.xx from main and push it to upstream:

sh
git fetch upstream main
git show FETCH_HEAD:DRIVER_VERSION       # the release driver
git log -1 --oneline FETCH_HEAD          # the release commit from step 3
git ls-remote --exit-code upstream refs/heads/release-1.xx   # must fail: the branch must not exist yet
git push upstream FETCH_HEAD:refs/heads/release-1.xx

Save the release notes as a draft release that targets the branch. Take the
notes from the merged PR description, so that edits from the review are
included:

sh
gh pr view <release-pr> --repo microsoft/playwright-python --json body --jq .body > <notes-file>
gh release create v1.xx.0 --repo microsoft/playwright-python --draft \
  --target release-1.xx --title v1.xx.0 --notes-file <notes-file>
gh release list --repo microsoft/playwright-python --limit 3   # v1.xx.0 shows as Draft

Give the user the URL of the draft. Your work ends here. Do not publish the
release: never run gh release create without --draft, and never run
gh release edit --draft=false. Publishing creates the tag and starts
.github/workflows/publish.yml, which builds the wheels and uploads them to PyPI
after a reviewer approves the pypi environment. Never run that workflow, and
never approve the environment.


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


name: playwright-roll

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 and mypy playwright run 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/codegen/generate_api.js) that:

  1. parses the upstream docs ($PW_SRC_DIR/docs/src/api) with the upstream parser and filters them for Python,
  2. applies the Python facts that the docs do not contain, from scripts/codegen/python_rules.js, and
  3. emits the whole public API into playwright/{async,sync}_api/_generated.py: signatures, types, docstrings, event overloads, and the call into _impl/ for every member.

The generator never reads _impl/. A new member or option in the docs therefore shows up in _generated.py right away, as a call into _impl/ that does not exist yet. Each generated class types its _impl_obj, so mypy playwright lists every such gap as an error in _generated.py. Three resolutions:

  • PORT โ€” the new API is intended for Python (no langs.only filter, or langs.only includes "python"). Implement it in _impl/ until mypy is clean.
  • MISMATCH โ€” the shipped Python API differs from the docs (a callback signature uses unions, a kwarg uses a legacy name, etc.). The shipped implementation wins. Add an entry to scripts/codegen/python_rules.js with a comment explaining why, and propose the upstream docs change that would make the entry unnecessary.
  • 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 generator already leaves it out.

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: {} in api.json and were real Python APIs. Sounding tooling-y is not a langs filter. The langs field on the member in api.json is 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.10 isn't available, use python3 (3.12 is fine).
  • 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.
  • Always activate the venv before any pip, pytest, mypy, or pre-commit invocation.

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

The docs walk and codegen read the upstream docs from a microsoft/playwright
checkout at $PW_SRC_DIR. Use a separate checkout that exists only for
rolls, on the exact release branch you roll to
: release-<new>, the
major.minor of the new DRIVER_VERSION (the branch has the release's docs
plus any cherry-picks). Never point PW_SRC_DIR at a checkout that someone
works in: on main or a feature branch the docs differ, and the generator
silently emits that API instead of the release's.

sh
export PW_SRC_DIR=../playwright-for-python      # the roll checkout, nothing else
# First roll on this machine: clone it (no npm install needed).
git clone https://github.com/microsoft/playwright.git "$PW_SRC_DIR"
# Every roll: move it to the release branch.
git -C "$PW_SRC_DIR" fetch origin
git -C "$PW_SRC_DIR" checkout release-<new>     # e.g. release-1.62
git -C "$PW_SRC_DIR" merge --ff-only origin/release-<new>

Check the checkout before you generate, and again before you commit:

sh
git -C "$PW_SRC_DIR" rev-parse --abbrev-ref HEAD                  # must print release-<new>
git -C "$PW_SRC_DIR" status --porcelain                           # must print nothing
git -C "$PW_SRC_DIR" rev-parse HEAD origin/release-<new>          # both lines must be equal

Then bump the pins and assemble the driver:

sh
# 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

# The walk below inspects `langs` from api.json. Codegen does not need it.
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/. The docs are the
one piece not shipped in the bundle โ€” ./scripts/update_api.sh reads them from
$PW_SRC_DIR. /tmp/api.json is only a convenient dump of the same docs for
the walk.

3. Identify the commit range

Use the roll checkout at $PW_SRC_DIR (from step 2).
Bring it up to date and ensure release branches/tags are present before walking
the range:

sh
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-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.
    sh
    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/:

sh
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:

sh
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.md files (whole new classes โ€” usually langs: 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:

python
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:

python
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 empty langs: {}, 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.size is langs: js while Screencast.start itself 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 generator makes argument-less sync members properties โ€” see isPythonProperty in scripts/codegen/generate_api.js โ€” and reads self._impl_obj.foo without calling it. mypy does not catch a plain method here; the test does.)
await this._channel.foo({ a, b }, this._timeout(options)) await self._channel.foo(a=a, b=b, timeout=self._timeout(timeout)). The generated channel method takes every protocol parameter, with no defaults; an absent option is None. The implementation method's own parameters use the Python (snake_case) names, with no defaults.
(await this._channel.foo()).value (await self._channel.foo())["value"]. The result dict has every top-level field; a field that the driver left out is None.
this._channel.on('foo', ({ a }) => ...) self._channel.onFoo(lambda event: ...); event["a"] is None when the driver left the optional field out.
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, which the generator writes. It names the struct from the docs: "Name" <[Object]>, or an * alias-python: line under it. 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)):

  1. Register it in playwright/_impl/_object_factory.py:create_remote_object โ€” otherwise the guid resolves to DummyObject and downstream code breaks mysteriously.
  2. Add it to playwright/_impl/_api_classes.py (the import and __all__). The generator emits every Python class in the docs, and the generated API imports each implementation from that file. If the class is missing there, _generated.py fails to import.

When implementing a non-ChannelOwner wrapper class (a plain class that holds a Page/Context reference, like Screencast, Clock):

  • Add it to playwright/_impl/_api_classes.py, as for a ChannelOwner subclass.
  • Set self._loop = parent._loop and self._dispatcher_fiber = parent._dispatcher_fiber in __init__. The generated AsyncBase/SyncBase wrappers read these; missing them gives AttributeError: 'X' object has no attribute '_loop' at first use.

When the docs add a new struct (an inline [Object] with named fields):

  • The generator writes its TypedDict into _api_structures.py. Never edit that file by hand; internal types go in _internal_structures.py.
  • It needs a Python name in the docs: a name before the type (- `size` "ScreenSize" <[Object]>), or an * alias-python: line under the type. The generator fails with "No Python name for the struct at โ€ฆ" until one exists. Nested structs need names too.
  • Re-export it from both playwright/async_api/__init__.py and playwright/sync_api/__init__.py: assignment line plus an entry in __all__. Same pattern as ViewportSize, RemoteAddr.

If the new API was previously recorded in python_rules.js (for example in extraMembers), remove that entry when implementing it. The generator fails on entries that no longer match anything, so stale entries cannot pile up.

If a doc rename involves a positional parameter (no default, before any *), users almost certainly call it positionally โ€” you can rename freely. The 1.59 BrowserType.connect.wsEndpoint โ†’ endpoint is the canonical example. Don't suppress this kind of rename; just rename in _impl/. Important corollary: when docs rename a param, the wire-protocol field usually also changed in packages/protocol/spec/*.yml and the server-side dispatcher in packages/playwright-core/src/server/dispatchers/*Dispatcher.ts. If so, you must also update the channel-send dict key (e.g. {"wsEndpoint": โ€ฆ} โ†’ {"endpoint": โ€ฆ}). Keeping the old name in Python for a renamed param hides a wire-protocol bug.

MISMATCH

A MISMATCH is a divergence between the docs and the shipped Python surface. The shipped implementation wins. Use it sparingly โ€” most apparent mismatches turn out to be PORTs you skipped. Fix a type, a name or a position in the upstream docs: declare the param or option for Python only, with * langs: python, and limit the shared declaration to the other languages. Example from class-page.md, where Python accepts a callback with one or two arguments:

md
### param: Page.route.handler
* since: v1.8
* langs: python
- `handler` <[function]\([Route]\)|[function]\([Route], [Request]\)>

scripts/codegen/python_rules.js only holds what the docs cannot say, for example hidden internal kwargs (extraOptions: Browser.new_context(default_browser_type=)). Add an entry to the right table, with a one-line // comment above it explaining why. Keys use docs names (Class.member.arg, camelCase).

The generator fails with "the entry โ€ฆ no longer matches anything; remove it" when an upstream change makes an entry unnecessary. Delete that entry.

Do not suppress these โ€” they're PORTs in disguise:

  • "Internal tooling" classes/methods whose langs field is empty (Screencast.*, Debugger.*, Page.pick_locator, BrowserContext.debugger, Browser.bind/unbind, Page.{clear,console}_*). The 1.59 roll suppressed all of these initially, then had to walk every one back. Verify langs first.
  • A renamed positional parameter (mypy: unexpected keyword argument newName for the impl method). Just rename in _impl/; if the wire name changed too, mypy reports the generated channel call.
  • A struct return the generator cannot name ("No Python name for the struct at โ€ฆ"). Name it, so that the generator writes a TypedDict โ€” don't type it Dict.

N/A

Common N/A flavors:

  • Whole new class with langs: js (Inspector/Screencast, Debugger, Overlay).
  • Members with langs: js (most "tooling" / MCP / agentic features).
  • Doc-only edits (typo fixes, "Improve not property sections", etc.).
  • Reverts that cancel an earlier add in the same range (always check the rest of the range before porting something that gets reverted).
  • Java/C# langs: blocks.
  • Electron-only changes (docs/src/api/class-electron.md).

Tick the box in /tmp/roll-<new>-commits.md with one line: [x] <sha> <subject> โ€” <classification>: <one-liner>.

5. Regenerate

sh
./scripts/update_api.sh   # uses PW_SRC_DIR from step 2; run the checkout checks there first

The script, for sync and then async:

  1. runs node scripts/codegen/generate_api.js --flavor <flavor> (reads the docs from $PW_SRC_DIR) straight into playwright/<flavor>_api/_generated.py,
  2. invokes pre-commit run --files on the generated file.

It then runs python scripts/codegen/generate_protocol.py, which reads packages/protocol/spec/*.yml from $PW_SRC_DIR and writes the command parameter tables to playwright/_impl/_protocol.py, the message types to playwright/_impl/_protocol_types.py and the typed channels to playwright/_impl/_protocol_channels.py.

Then run mypy playwright and work through the errors in _generated.py โ€” each one is a call into _impl/ that the implementation does not support yet.

To see exactly what a regeneration changed in the public API, compare against the committed file:

sh
git show HEAD:playwright/async_api/_generated.py > /tmp/old.py
python scripts/codegen/compare_api.py /tmp/old.py playwright/async_api/_generated.py --show all

CI no longer verifies that _generated.py is in sync (the Lint job dropped the
"Verify generated API is up to date" step so it needn't check out upstream). So
regenerating here and committing the result is on you โ€” don't skip it.

Failure modes and fixes:

Symptom Cause Fix
mypy: "XImpl" has no attribute "y" in _generated.py The docs have a new member X.y; _impl/ does not. PORT it.
mypy: Unexpected keyword argument "z" for "y" of "XImpl" New parameter or option on an existing method. Add the parameter in _impl/ under its Python (snake_case) name, as Optional[X] with no default, and pass it in the channel call. mypy then reports each caller that must pass it. If Python must not have it, fix the docs: * langs: on the option.
mypy: Missing named argument "z" for "y" of "XChannel" The protocol added a parameter to a command. Pass the value in the channel call. If the implementation method needs a new parameter for it, add it as above.
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).
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]>.
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.
_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.
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.
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.
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.
pyright โ€ฆ reportInconsistentOverload (single-event class) A class gained its first event, so there is one Literal[โ€ฆ] overload + impl, which pyright wants โ‰ฅ2 of. The generator already handles this โ€” emitEvents adds a second @typing.overload with event: str when a class has exactly one event.
pre-commit keeps reformatting _generated.py on each run First run after regen always reformats once; rerun until idle. pre-commit run --all-files to settle.
A kwarg in _impl/ that the docs don't mention (e.g. legacy name from a doc rename) Nothing fails: the generator simply never passes it. If the param is positional with no default, just rename it in _impl/. Check packages/protocol/spec/*.yml and the server dispatcher โ€” if the wire field renamed too, also update the channel-send dict key. Only use extraOptions for genuinely hidden internal kwargs (default_browser_type).
AttributeError: 'X' object has no attribute '_loop' (or _dispatcher_fiber) at first use of a new wrapper class The non-ChannelOwner wrapper isn't initializing the fields the generated AsyncBase/SyncBase reads. In the wrapper's __init__, set self._loop = parent._loop and self._dispatcher_fiber = parent._dispatcher_fiber.
'NoneType' object has no attribute 'get' after send_return_as_dict Method's protocol response carries no fields and send_return_as_dict returned None. (result or {}).get(...).
A test fails at teardown with Protocol violations: X.y: params.z: unknown (or missing, or expected โ€ฆ) The client sends a message that the protocol spec does not allow. The driver would drop an unknown parameter without an error. Fix the send in _impl/: rename the key after a wire rename, or remove a client-side option before the send.
Frame/buffer payload arrives as a str instead of bytes Protocol binary fields cross the wire as base64. base64.b64decode(value) in the impl before exposing.

After the script settles, run pre-commit run --all-files once more to confirm everything is idle.

6. Add tests

For each PORT, port the upstream test that covers it, as an async test and a matching sync test. Conventions:

  • The upstream file decides the Python file: tests/page/expect-misc.spec.ts becomes tests/async/test_expect_misc.py and tests/sync/test_expect_misc.py. Create the file if it does not exist.
  • The upstream title decides the test name: the describe title and the test title, in snake_case. test('should support pseudo element') in test.describe('toHaveCSS') becomes test_to_have_css_should_support_pseudo_element.
  • When no upstream test covers the change, extend an existing test before you add a new one.
  • Use from playwright.async_api import โ€ฆ, not from playwright._impl._page import Page (the impl class doesn't have the public wrappers like expect_console_message).
  • For event-info objects, await message_info.value (it's an async property).
  • Don't write tests that hang the page (e.g. page.evaluate(... fetch slow ...) followed by page.close() from a fixture) โ€” the request task gets a TargetClosedError. Use page.on("event", handler) to capture state at event time instead.
  • playwright install chromium (no --with-deps) is sufficient for the test suite under sandbox.

7. Update existing high-touch artifacts

  • DRIVER_VERSION and NODE_VERSION: already done in step 2.
  • 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.
  • 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.

8. Final verification

Repeat the checkout checks from step 2: the committed _generated.py must come from release-<new>.

sh
pre-commit run --all-files
mypy playwright                 # 2 pre-existing errors in _json_pipe.py and _artifact.py are unrelated
pytest --browser chromium tests/async/<files-you-touched> tests/sync/<files-you-touched>

Then a smoke regression on a few neighboring suites (tests/async/test_browser*.py, test_cdp_session.py, test_tracing.py, test_dialog.py, test_page_*.py) to make sure nothing inherent to the port shifted.

Reference: `python_rules.js` tables

All keys use docs names: Class.member or Class.member.arg, with the upstream PascalCase class and camelCase member/arg names. * matches one name segment.

Table Use
handWrittenClasses Classes in the Python docs that are written by hand (Error, TimeoutError, FormData).
contextManagerClasses, tracebackHideClasses Base-class and __tracebackhide__ choices.
extraMembers, extraOptions Python-only members and hidden options; generated without docstrings.
positionalOptions Option names that are positional everywhere (predicate).
structTotal total= of a TypedDict, where it differs from "total=False if any field is optional".

Tips & gotchas

  • langs.only is your filter โ€” and the only filter. Don't classify by name (Screencast, Debugger, pickLocator) or by intuition ("looks like internal tooling"). Always check langs in api.json. The 1.59 roll cost two extra audit passes by trusting names over langs.
  • Audit your own classifications a second time. After the first walk through the commit range, before opening the PR, re-read every entry in python_rules.js you added during this roll and ask "is this divergence justified, or did I skip a port?" Run the langs-dump snippet on each suspicious entry. The 1.59 roll's first PR had ~20 wrong suppressions; the second pass cut them to 0.
  • A cluster of rules on the same class is a smell. If you're about to add five entries for Foo.*, you're almost certainly looking at a class that needs to be implemented. Implement the whole thing once and the entries disappear.
  • 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.
  • Watch for rename-revert pairs. 1.59 had Locator.normalize โ†’ Locator.toCode (#39648) โ†’ Locator.normalize (#39754). Final state wins; only port the last.
  • 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/spec/ and the corresponding *Dispatcher.ts file. If the wire field changed too, the regenerated _protocol_channels.py renames the argument of the channel method, and mypy reports each call in _impl/ that passes the old name.
  • TypedDicts beat Dict[str, X] for any structured return. When the docs describe a return as [Object] with named fields, name the struct for the generator, which writes its TypedDict into _api_structures.py, and re-export it from both public __init__.py files. Zero runtime cost (it's still a dict), and the docstrings expand its fields from the docs.
  • 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.
  • 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.
  • The docs parser has quirks. In a type expression, everything after a function's : is its return type, so [function]([A]):[any]|[B] is one function returning any|B. Put a function with a return type last in a union. And when a ### param: header name differs from the backticked name on its type line, the header name is the docs name and the type-line name becomes the alias.
  • Don't edit _generated.py to fix lint or typing. Fix _impl/, scripts/codegen/generate_api.js, or python_rules.js instead. Hand-editing the generated file is reverted on the next regen.
  • /tmp/roll-<new>-commits.md is a working artifact, not a deliverable. Don't commit it. The commit message and PR description are where the audit summary belongs.

File: .github/dependabot.yml

version: 2
updates:

  • package-ecosystem: "pip"
    directory: "/"
    schedule:
    interval: "weekly"
  • package-ecosystem: "github-actions"
    directory: "/"
    schedule:
    interval: "weekly"
    groups:
    actions:
    patterns:
    - "*"

    cooldown:
    default-days: 7

File: .github/ISSUE_TEMPLATE/bug.yml

name: Bug Report ๐Ÿชฒ
title: '[Bug]: '
body:

  • type: markdown
    attributes:
    value: |
    # Please follow these steps first:
  • type: markdown
    attributes:
    value: |
    ## Troubleshoot
    If Playwright is not behaving the way you expect, we'd ask you to look at the documentation and search the issue tracker for evidence supporting your expectation.
    Please make reasonable efforts to troubleshoot and rule out issues with your code, the configuration, or any 3rd party libraries you might be using.
    Playwright offers several debugging tools that you can use to troubleshoot your issues.
  • type: markdown
    attributes:
    value: |
    ## Ask for help through appropriate channels
    If you feel unsure about the cause of the problem, consider asking for help on for example StackOverflow or our Discord channel before posting a bug report. The issue tracker is not a help forum.
  • type: markdown
    attributes:
    value: |
    ## Make a minimal reproduction
    To file the report, you will need a GitHub repository with a minimal (but complete) example and simple/clear steps on how to reproduce the bug.
    The simpler you can make it, the more likely we are to successfully verify and fix the bug.
  • type: markdown
    attributes:
    value: |
    > [!IMPORTANT]
    > Bug reports without a minimal reproduction will be rejected.

    ---
  • type: input
    id: version
    attributes:
    label: Version
    The version of Playwright you are using.
    Is it the latest? Test and see if the bug has already been fixed.

    placeholder: ex. 1.41.1
    validations:
    required: true
  • type: textarea
    id: reproduction
    attributes:
    label: Steps to reproduce
    value: |
    Example steps (replace with your own):
    1. Clone my repo at https://github.com//example
    2. pip install -r requirements.txt
    3. python test.py
    4. You should see the error come up

    validations:
    required: true
  • type: textarea
    id: expected
    attributes:
    label: Expected behavior
    placeholder: I expect to see X or Y
    validations:
    required: true
  • type: textarea
    id: what-happened
    attributes:
    label: Actual behavior
    A clear and concise description of the unexpected behavior.
    Please include any relevant output here, especially any error messages.

    placeholder: A bug happened!
    validations:
    required: true
  • type: textarea
    id: context
    attributes:
    label: Additional context
    validations:
    required: false
  • type: textarea
    id: envinfo
    attributes:
    label: Environment
    Please provide information about the environment you are running in.

    value: |
    - Operating System: [Ubuntu 22.04]
    - CPU: [arm64]
    - Browser: [All, Chromium, Firefox, WebKit]
    - Python Version: [3.12]
    - Other info:

    render: Text
    validations:
    required: true

File: .github/ISSUE_TEMPLATE/config.yml

blank_issues_enabled: false
contact_links:


File: .github/ISSUE_TEMPLATE/documentation.yml

name: Documentation ๐Ÿ“–
title: 'Docs: '
labels: ['Documentation :book:']
body:

  • type: markdown
    attributes:
    value: |
    ### Thank you for helping us improve our documentation!
    Please be sure you are looking at the Next version of the documentation before opening an issue here.
  • type: textarea
    id: links
    attributes:
    label: Page(s)
    Links to one or more documentation pages that should be modified.
    If you are reporting an issue with a specific section of a page, try to link directly to the nearest anchor.
    If you are suggesting that a new page be created, link to the parent of the proposed page.

    validations:
    required: true
  • type: textarea
    id: description
    attributes:
    label: Description
    Describe the change you are requesting.
    If the issue pertains to a single function or matcher, be sure to specify the entire call signature.

    validations:
    required: true

File: .github/ISSUE_TEMPLATE/feature.yml

name: Feature Request ๐Ÿš€
title: '[Feature]: '
body:

  • type: markdown
    attributes:
    value: |
    ### Thank you for taking the time to suggest a new feature!
  • type: textarea
    id: description
    attributes:
    label: '๐Ÿš€ Feature Request'
    validations:
    required: true
  • type: textarea
    id: example
    attributes:
    label: Example
    validations:
    required: false
  • type: textarea
    id: motivation
    attributes:
    label: Motivation
    Outline your motivation for the proposal. How will it make Playwright better?

    validations:
    required: true

File: .github/ISSUE_TEMPLATE/question.yml

name: 'Questions / Help ๐Ÿ’ฌ'
title: '[Please read the message below]'
labels: [':speech_balloon: Question']
body:

  • type: markdown
    attributes:
    value: |
    ## Questions and Help ๐Ÿ’ฌ

    This issue tracker is reserved for bug reports and feature requests.

    For anything else, such as questions or getting help, please see:

    - The Playwright documentation
    - Our Discord server
  • type: checkboxes
    id: no-post
    attributes:
    label: |
    Please do not submit this issue.
    > [!IMPORTANT]
    > This issue will be closed.

    options:
    - label: I understand
    required: true

File: .github/ISSUE_TEMPLATE/regression.yml

name: Report regression
title: "[Regression]: "

body:

  • type: markdown
    attributes:
    value: |
    # Please follow these steps first:
  • type: markdown
    attributes:
    value: |
    ## Make a minimal reproduction
    To file the report, you will need a GitHub repository with a minimal (but complete) example and simple/clear steps on how to reproduce the regression.
    The simpler you can make it, the more likely we are to successfully verify and fix the regression.
  • type: markdown
    attributes:
    value: |
    > [!IMPORTANT]
    > Regression reports without a minimal reproduction will be rejected.

    ---
  • type: input
    id: goodVersion
    attributes:
    label: Last Good Version
    Last version of Playwright where the feature was working.

    placeholder: ex. 1.40.1
    validations:
    required: true
  • type: input
    id: badVersion
    attributes:
    label: First Bad Version
    First version of Playwright where the feature was broken.
    Is it the latest? Test and see if the regression has already been fixed.

    placeholder: ex. 1.41.1
    validations:
    required: true
  • type: textarea
    id: reproduction
    attributes:
    label: Steps to reproduce
    value: |
    Example steps (replace with your own):
    1. Clone my repo at https://github.com//example
    2. pip -r requirements.txt
    3. python test.py
    4. You should see the error come up

    validations:
    required: true
  • type: textarea
    id: expected
    attributes:
    label: Expected behavior
    placeholder: I expect to see X or Y
    validations:
    required: true
  • type: textarea
    id: what-happened
    attributes:
    label: Actual behavior
    placeholder: A bug happened!
    validations:
    required: true
  • type: textarea
    id: context
    attributes:
    label: Additional context
    validations:
    required: false
  • type: textarea
    id: envinfo
    attributes:
    label: Environment
    Please provide information about the environment you are running in.

    value: |
    - Operating System: [Ubuntu 22.04]
    - CPU: [arm64]
    - Browser: [All, Chromium, Firefox, WebKit]
    - Python Version: [3.12]
    - Other info:

    render: Text
    validations:
    required: true

File: .github/workflows/ci.yml

name: CI

on:
push:
branches:
- main
- release-*
pull_request:
branches:
- main
- release-*

concurrency:

For pull requests, cancel all currently-running jobs for this workflow

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency

group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
infra:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r local-requirements.txt
pip install -r requirements.txt
pip install -e .
- name: Lint
run: pre-commit run --show-diff-on-failure --color=always --all-files

build:
name: Build
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.14']
browser: [chromium, firefox, webkit]
exclude:
# WebKit on standard macOS-latest (currently macos-15-arm64) is unstable;
# upstream pins paid macos-15-xlarge for cross-browser webkit too.
- os: macos-latest
browser: webkit
include:
- os: windows-latest
python-version: '3.10'
browser: chromium
- os: macos-latest
python-version: '3.10'
browser: chromium
- os: ubuntu-latest
python-version: '3.10'
browser: chromium
- os: macos-15-xlarge
python-version: '3.14'
browser: webkit
- os: windows-latest
python-version: '3.11'
browser: chromium
- os: macos-latest
python-version: '3.11'
browser: chromium
- os: ubuntu-latest
python-version: '3.11'
browser: chromium
- os: windows-latest
python-version: '3.12'
browser: chromium
- os: macos-latest
python-version: '3.12'
browser: chromium
- os: ubuntu-latest
python-version: '3.12'
browser: chromium
- os: windows-latest
python-version: '3.13'
browser: chromium
- os: macos-latest
python-version: '3.13'
browser: chromium
- os: ubuntu-latest
python-version: '3.13'
browser: chromium
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies & browsers
run: |
python -m pip install --upgrade pip
pip install -r local-requirements.txt
pip install -r requirements.txt
pip install -e .
python -m build --wheel
python -m playwright install --with-deps ${{ matrix.browser }}
- name: Common Tests
run: pytest tests/common --browser=${{ matrix.browser }} --timeout 90
- name: Test Reference count
run: pytest tests/test_reference_count_async.py --browser=${{ matrix.browser }}
- name: Test Wheel Installation
run: pytest tests/test_installation.py --browser=${{ matrix.browser }}
- name: Test Sync API
if: matrix.os != 'ubuntu-latest'
run: pytest tests/sync --browser=${{ matrix.browser }} --timeout 90
- name: Test Sync API
if: matrix.os == 'ubuntu-latest'
run: xvfb-run pytest tests/sync --browser=${{ matrix.browser }} --timeout 90
- name: Test Async API
if: matrix.os != 'ubuntu-latest'
run: pytest tests/async --browser=${{ matrix.browser }} --timeout 90
- name: Test Async API
if: matrix.os == 'ubuntu-latest'
run: xvfb-run pytest tests/async --browser=${{ matrix.browser }} --timeout 90

test-stable:
name: Stable
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
browser-channel: [chrome]
include:
- os: windows-latest
browser-channel: msedge
- os: macos-latest
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"
- name: Install dependencies & browsers
run: |
python -m pip install --upgrade pip
pip install -r local-requirements.txt
pip install -r requirements.txt
pip install -e .
python -m build --wheel
python -m playwright install ${{ matrix.browser-channel }} --with-deps
- name: Common Tests
run: pytest tests/common --browser=chromium --browser-channel=${{ matrix.browser-channel }} --timeout 90
- name: Test Sync API
if: matrix.os != 'ubuntu-latest'
run: pytest tests/sync --browser=chromium --browser-channel=${{ matrix.browser-channel }} --timeout 90
- name: Test Sync API
if: matrix.os == 'ubuntu-latest'
run: xvfb-run pytest tests/sync --browser=chromium --browser-channel=${{ matrix.browser-channel }} --timeout 90
- name: Test Async API
if: matrix.os != 'ubuntu-latest'
run: pytest tests/async --browser=chromium --browser-channel=${{ matrix.browser-channel }} --timeout 90
- name: Test Async API
if: matrix.os == 'ubuntu-latest'
run: xvfb-run pytest tests/async --browser=chromium --browser-channel=${{ matrix.browser-channel }} --timeout 90

test_examples:
name: Examples
runs-on: ubuntu-22.04
defaults:
run:
working-directory: examples/todomvc/
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.10'
- name: Install dependencies & browsers
run: |
pip install -r requirements.txt
python -m playwright install --with-deps chromium
- name: Common Tests
run: pytest


File: .github/workflows/publish.yml

name: Publish

Builds the wheels of a release tag and uploads them to PyPI. Uses PyPI trusted

publishing: PyPI trusts this workflow file in the "pypi" environment, so no

PyPI token is stored. Runs when a release is published. To publish an existing

tag, run it by hand with the tag name.

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
required: true

permissions:
contents: read

jobs:
build:
name: Build wheels
runs-on: ubuntu-latest
env:
TAG: ${{ inputs.tag || github.event.release.tag_name }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ env.TAG }}
# setuptools-scm reads the version from the tag.
fetch-depth: 0
- name: Check the tag
run: |
if [[ ! "$TAG" =~ ^v[0-9]+.[0-9]+.[0-9]+$ ]]; then
echo "Can only publish a release tag (vX.Y.Z), got: $TAG"
exit 1
fi
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"
- name: Set up Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '24'
- name: Install dependencies
run: python -m pip install "$(grep '^build==' local-requirements.txt)"
- name: Build wheels
run: |
for wheel in $(python setup.py --list-wheels); do
PLAYWRIGHT_TARGET_WHEEL=$wheel python -m build --wheel --outdir dist
done
- name: Check the wheels
run: |
ls -l dist
test "$(ls dist/.whl | wc -l)" -eq "$(python setup.py --list-wheels | wc -l)"
for file in dist/
; do
if [[ "$(basename "$file")" != "playwright-${TAG#v}-"*.whl ]]; then
echo "Unexpected file for $TAG: $file"
exit 1
fi
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: wheels
path: dist/
if-no-files-found: error

publish:
name: Upload to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/playwright/
permissions:
# Lets the job get the GitHub OIDC token that PyPI trusted publishing needs.
id-token: write
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: wheels
path: dist/
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2


File: .github/workflows/test_docker.yml

name: Test Docker
on:
push:
paths:
- '.github/workflows/test_docker.yml'
- 'setup.py'
- '/Dockerfile.'
branches:
- main
- release-

pull_request:
paths:
- '.github/workflows/test_docker.yml'
- 'setup.py'
- '
/Dockerfile.'
branches:
- main
- release-

jobs:
build:
timeout-minutes: 120
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
docker-image-variant:
- jammy
- noble
- resolute
runs-on:
- ubuntu-24.04
- ubuntu-24.04-arm
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"
- name: Set up Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '24'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r local-requirements.txt
pip install -r requirements.txt
pip install -e .
- name: Build Docker image
run: |
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
bash utils/docker/build.sh --$ARCH ${{ matrix.docker-image-variant }} playwright-python:localbuild-${{ matrix.docker-image-variant }}
- name: Test
run: |
CONTAINER_ID="$(docker run --rm -e CI -v $(pwd):/root/playwright --name playwright-docker-test --workdir /root/playwright/ -d -t playwright-python:localbuild-${{ matrix.docker-image-variant }} /bin/bash)"
# Fix permissions for Git inside the container
docker exec "${CONTAINER_ID}" chown -R root:root /root/playwright
docker exec "${CONTAINER_ID}" pip install -r local-requirements.txt
docker exec "${CONTAINER_ID}" pip install -r requirements.txt
docker exec "${CONTAINER_ID}" pip install -e .
# build.sh (above) already built the driver bundles into driver/ on the
# host. The repo is bind-mounted into the container, so this in-container
# wheel build reuses those bundles (setup.py skips the source build when
# the zip already exists) and therefore needs no Node.js in the image.
docker exec "${CONTAINER_ID}" python -m build --wheel
docker exec "${CONTAINER_ID}" xvfb-run pytest tests/sync/
docker exec "${CONTAINER_ID}" xvfb-run pytest tests/async/

2. Official Technical Reference & Guides (microsoft/microsoft.github.io)

File: README.md

[microsoft.github.io](https://microsoft.github.io)

Many open source projects released by Microsoft products, services and teams also have
deployments powered by GitHub Pages on this domain.

To discover Microsoft-released open source projects, learn about Microsoft's open source
approach and other information, check us out online at opensource.microsoft.com.

If we can help answer questions about the open source program at Microsoft, you can
contact opensource at microsoft com - although we are not able to provide support
for projects through this mechanism.

Microsoft GitHub resources

Microsoft open source resources

Resources for Microsoft employees

How to identify an official Microsoft organization on GitHub

  • Official Microsoft GitHub orgs have a professional avatar and include a web address at Microsoft.com or an associated official domain
  • Official Microsoft orgs are Verified on GitHub - look for the domain verification indicator on GitHub

File: SECURITY.MD

IMPORTANT: THIS IS NOT THE SOURCE OF TRUTH FOR THIS FILE

The SECURITY.MD file used by default in new Microsoft GitHub repos can be found
in the repo microsoft/repo-templates at https://github.com/microsoft/repo-templates.

Please contribute any fixes there.


Security

Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include Microsoft, Azure, DotNet, AspNet, Xamarin, and our GitHub organizations.

If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's definition of a security vulnerability, please report it to us as described below.

Reporting Security Issues

Please do not report security vulnerabilities through public GitHub issues.

Instead, please report them to the Microsoft Security Response Center (MSRC) at https://msrc.microsoft.com/create-report.

If you prefer to submit without logging in, send email to [email protected]. If possible, encrypt your message with our PGP key; please download it from the Microsoft Security Response Center PGP Key page.

You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at microsoft.com/msrc.

Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:

  • Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
  • Full paths of source file(s) related to the manifestation of the issue
  • The location of the affected source code (tag/branch/commit or direct URL)
  • Any special configuration required to reproduce the issue
  • Step-by-step instructions to reproduce the issue
  • Proof-of-concept or exploit code (if possible)
  • Impact of the issue, including how an attacker might exploit the issue

This information will help us triage your report more quickly.

If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our Microsoft Bug Bounty Program page for more details about our active programs.

Preferred Languages

We prefer all communications to be in English.

Policy

Microsoft follows the principle of Coordinated Vulnerability Disclosure.


File: codeofconduct/index.md

Microsoft Open Source Code of Conduct

This code of conduct outlines expectations for participation in Microsoft-managed open source communities, as well as steps for reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all. People violating this code of conduct may be banned from the community.

Our open source communities strive to:

  • Be friendly and patient: Remember you might not be communicating in someone else's primary spoken or programming language, and others may not have your level of understanding.
  • Be welcoming: Our communities welcome and support people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
  • Be respectful: We are a world-wide community of professionals, and we conduct ourselves professionally. Disagreement is no excuse for poor behavior and poor manners. Disrespectful and unacceptable behavior includes, but is not limited to:
    • Violent threats or language.
    • Discriminatory or derogatory jokes and language.
    • Posting sexually explicit or violent material.
    • Posting, or threatening to post, people's personally identifying information ("doxing").
    • Insults, especially those using discriminatory terms or slurs.
    • Behavior that could be perceived as sexual attention.
    • Advocating for or encouraging any of the above behaviors.
  • Understand disagreements: Disagreements, both social and technical, are useful learning opportunities. Seek to understand the other viewpoints and resolve differences constructively.
  • This code is not exhaustive or complete. It serves to capture our common understanding of a productive, collaborative environment. We expect the code to be followed in spirit as much as in the letter.

Scope

This code of conduct applies to all repos and communities for Microsoft-managed open source projects regardless of whether or not the repo explicitly calls out its use of this code. The code also applies in public spaces when an individual is representing a project or its community. Examples include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Note: Some Microsoft-managed communities have codes of conduct that pre-date this document and issue resolution process. While communities are not required to change their code, they are expected to use the resolution process outlined here. The review team will coordinate with the communities involved to address your concerns.

Reporting Code of Conduct Issues

We encourage all communities to resolve issues on their own whenever possible. This builds a broader and deeper understanding and ultimately a healthier interaction. In the event that an issue cannot be resolved locally, please feel free to report your concerns by contacting [email protected]. Your report will be handled in accordance with the issue resolution process described in the Code of Conduct FAQ.

In your report please include:

  • Your contact information.
  • Names (real, usernames or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well.
  • Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public chat log), please include a link or attachment.
  • Any additional information that may be helpful.

All reports will be reviewed by a multi-person team and will result in a response that is deemed necessary and appropriate to the circumstances. Where additional perspectives are needed, the team may seek insight from others with relevant expertise or experience. The confidentiality of the person reporting the incident will be kept at all times. Involved parties are never part of the review team.

Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the review team may take any action they deem appropriate, including a permanent ban from the community.

This code of conduct is based on the template established by the TODO Group and used by numerous other large communities (e.g., Facebook, Twitter, GitHub and the Scope section from the Contributor Covenant version 1.4.


File: codeofconduct/faq/index.md

Microsoft Open Source Code of Conduct FAQ

Contents

Why have a Code of Conduct?

People and communities are the foundation of open source. Communities thrive through diversity of thought and the safety of its members. Microsoft has a deep-seated commitment to diversity and inclusion. All Microsoft employees enjoy a safe work environment and a culture of mutual respect and responsibility. Our team members and open source partners should enjoy the same environment when collaborating on open source projects.

While conduct issues seldom arise, when they do, they are often very public and very passionate. It is best for the community to be prepared with:

  • A clear and accessible Code of Conduct stating the norms under which projects operate
  • An Issue Resolution Process for addressing cases where these norms are not being maintained

Why this Code of Conduct?

The Microsoft Open Source Code of Conduct is an instantiation of the TODO Group Code of Conduct template, that captures the Microsoft culture of equality, respect and inclusion. This same template is used by industry colleagues such as Facebook, Twitter, GitHub, Yahoo and others in their open source projects.

Why do this across all repos?

Having one code and process is both efficient and consistent — community members can participate in any Microsoft-driven project and be confident that they know the norms and that they are supported by a robust process.

How does a project adopt the code and process?

All Microsoft projects are automatically covered by the Code of Conduct and the Issue Resolution Process. However, it is critical that everyone in the communities be aware of the code and process. For that reason, all projects must link to the Code of Conduct in their README and/or CONTRIBUTING files using the following markdown.

text
This project has adopted the [Microsoft Open Source Code of
Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [[email protected]](mailto:[email protected])
with any additional questions or comments.

What if my Microsoft project already has a Code of Conduct?

Thanks for being proactive in this important area. You are welcome to continue using your existing Code of Conduct, or you may change to the Microsoft Open Source Code of Conduct. Either way, all Microsoft projects must use the same issue resolution process. Please ensure that your documentation and guidance is updated to point people at the process outlined in this FAQ (see below).

Projects in the .NET Foundation are already covered by the .NET Foundation Contributors Code of Conduct. As such, there is no need for .NET Foundation projects to make any changes.

How do I raise a concern?

If you have witnessed or been subjected to a violation of the Code of Conduct, please send an email to [email protected]. Your message will be handled in a secure and confidential manner. This email address is monitored by people who are not active in open source projects or communities — so you can be sure that you are not communicating with a person involved in the issue you are reporting. You will receive a response within one business day acknowledging receipt of your email and describing the process for its resolution. (See below for more details.)

What is the process for addressing issues that arise?

Emails sent to [email protected] kick off the following process:

  • Your message will be acknowledged within one business day.
  • Within the following business day, a small, 2-3, person team will be assembled from a pool of Microsoft employee volunteers to review your concern. This team will be as diverse as possible given its size and will pull in additional people as needed to gain further insight and provide guidance. The team will not include anyone directly involved in the issue that has been raised.
  • From there team will work with you and the others involved to come to a conclusion. While issue complexity varies, the goal is to resolve issues within five working days.
  • All communication will be confidential with very limited circulation.

How does the resolution team work?

Beyond the norms and values set out in the Code of Conduct, issue review teams operate under the following principles:

  • Less is more — As much as possible, let the community work it out. It is much better to have communities self-correct than to have outsiders come in and โ€œfixโ€ problems.
  • Equality — Contribution value or status in the community are not relevant to the review. Key people do not have more rights (either to abuse or be protected from abuse) than others in the community.
  • Independence — Reviewers must act and be seen to act with independence from the project(s) in question and from Microsoft.

Can I use this Code of Conduct in my projects?

Yes. The document is licenced under CC BY 4.0. You will of course need to update the document to suit the specifics of your projects, but by all means use this however you can to drive great communities.