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
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()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 inplaywright/_impl/_api_structures.pyโ auto-generated from the upstream API docs. Never edit by hand; rerun./scripts/update_api.shafter a roll or after changingscripts/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, structtotal=, 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.pyand_protocol_channels.pyโ auto-generated byscripts/codegen/generate_protocol.py(run by./scripts/update_api.sh) from the upstream protocol spec._protocol_channels.pyhas a typed channel class for each interface (PageChannel,FrameChannel, โฆ), like upstream'schannels.d.ts. Each command is an async method with the protocol name and parameters (await self._channel.goto(url=..., waitUntil=..., referer=..., timeout=...)); each event hason<Event>/off<Event>methods. A result or event payload has every top-level field; a field that the driver left out isNone._protocol_types.pyhas the TypedDicts for initializers, results, events and nested types, named as upstream'sutils/generate_channels.jsnames them (PageInitializer,FrameGotoResult,PageRouteEvent, โฆ)._protocol.pyhas the parameters of every command. WithPW_CHECK_PROTOCOL=1,playwright/_impl/_protocol_check.pychecks 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, theplaywright-corenpm version, e.g.1.61.0, novprefix). Read bysetup.py,scripts/build_driver.py, and CI. The wheel build downloadsplaywright-coreat 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 byscripts/update_node_version.py(latest LTS, mirroring upstream'sutils/build/update-playwright-node.mjs).scripts/build_driver.pyโ assembles the per-platform driver bundles intodriver/by downloading theplaywright-corenpm package (DRIVER_VERSION) and the official Node.js binaries (NODE_VERSION). Fetchesplaywright-corewithnpm pack(needs Node.js/npm on PATH; honours a root.npmrc) and the Node.js binaries over plain HTTP; invoked fromsetup.py'sbdist_wheelwith the target platform's suffix (no arg builds all six).- The API docs are not shipped in the bundle.
scripts/update_api.shreads them from a separatemicrosoft/playwrightcheckout ($PW_SRC_DIR) on the exact release branch forDRIVER_VERSION, never from a checkout onmainor 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):
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 installIf 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 viaplaywright 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.tsbecomestests/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
describetitle in front:test('should support pseudo element')intest.describe('toHaveCSS')becomestest_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: ignoreor modify_generated.pyto 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.jsminimal โ 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 isNone, and the connection drops it. Declare the channel type on each ChannelOwner subclass (_channel: "PageChannel"). Listen to events withself._channel.on<Event>(handler).
Commit Convention
Before committing, run mypy playwright and fix errors.
Semantic commit messages: label(scope): description
Labels: fix, feat, chore, docs, test, devops
git checkout -b fix-12345
# ... make changes ...
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix(asyncio): do not deadlock in atexit handler
Fixes: https://github.com/microsoft/playwright-python/issues/12345
EOF
)"
git push origin fix-12345
gh pr create --repo microsoft/playwright-python --head username:fix-12345 \
--title "fix(asyncio): do not deadlock in atexit handler" \
--body "$(cat <<'EOF'
## Summary
- <describe the change very! briefly>
Fixes https://github.com/microsoft/playwright-python/issues/12345
EOF
)"Never add Co-Authored-By agents in commit message.
Never add "Generated with" in commit message.
Never add test plan to PR description. Keep PR description short โ a few bullet points at most.
Branch naming for issue fixes: fix-
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:
- Microsoft Open Source Code of Conduct
- Microsoft Code of Conduct FAQ
- Contact [email protected] with questions or concerns
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:
# 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/activateInstall required dependencies:
python -m pip install --upgrade pip
pip install -r local-requirements.txtBuild 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.
pip install -e .
python -m build --wheelInstall the browsers to test against:
playwright install chromiumRun tests:
pytest --browser chromiumChecking for typing errors
mypy playwrightFormat the code
pre-commit install
pre-commit run --all-filesFor 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 forDRIVER_VERSION (for example release-1.64); point PW_SRC_DIR at it. The
generator isscripts/codegen/generate_api.js; Python facts that the docs do not contain
live in scripts/codegen/python_rules.js.
PW_SRC_DIR=../playwright-for-python ./scripts/update_api.sh
pre-commit run --all-filesContributor 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
- create virtual environment, if don't have one:
- activate venv:
source env/bin/activate - install all deps:
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(theplaywright-corenpm version, e.g.1.61.0) and refreshNODE_VERSION:python scripts/update_node_version.py - download the new driver:
python -m build --wheel(fetchesplaywright-corevianpm pack+ the matching Node.js binary and assembles the bundle; no source build). - generate API from the docs. This needs a separate
microsoft/playwrightcheckout used only for rolls, on the exact release branch for the new version (e.g.release-1.64), clean and up to date withorigin:PW_SRC_DIR=../playwright-for-python ./scripts/update_api.sh - run
mypy playwright: every error in_generated.pypoints at an implementation method inplaywright/_impl/that lacks a new member or option. Implement it there, or record the difference inscripts/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]
- id: mypy
- 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]"]
- id: pyright
- repo: local
hooks:- id: check-license-header
name: Check License Header
entry: ./utils/linting/check_file_header.py
language: python
types: [python]
- id: check-license-header
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:
bash generate.shFile: 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"
- 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 theplaywright-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:
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:
git -C "$PW_SRC_DIR" fetch origin main
git -C "$PW_SRC_DIR" checkout -b release-notes-1.xx-python origin/mainIn docs/src/release-notes-python.md, add a ## Version 1.xx section above the
previous version. Translate it from the ## Version 1.xx section ofdocs/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 APIswith####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, andnpx playwright testoptions.
Remove every API whoselangsexcludes Python. To check an API, look for it inplaywright/async_api/_generated.py, or checklangsinapi.json(see
"Verifying classifications" in theplaywright-rollskill).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, notnpx playwright install.Lean towards Python. List the playwright-python commits since the previous
release branch:shgh 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.
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 theplaywright-roll skill.
git -C "$PW_SRC_DIR" checkout release-1.xx3. Release commit (playwright-python)
Open a PR from the branch with the roll commit:
- Title:
chore: roll Playwright to v1.xx.y, wherev1.xx.yis the Python
release version, even whenDRIVER_VERSIONis 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 inAGENTS.md.
Convert the notes to GitHub markdown yourself:
Leave out the
## Version 1.xxheading. 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.pydeclares
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:
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.xxSave 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:
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 DraftGive 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 rungh 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:
- parses the upstream docs (
$PW_SRC_DIR/docs/src/api) with the upstream parser and filters them for Python, - applies the Python facts that the docs do not contain, from
scripts/codegen/python_rules.js, and - 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.onlyfilter, orlangs.onlyincludes"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.jswith 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: {}inapi.jsonand were real Python APIs. Sounding tooling-y is not alangsfilter. Thelangsfield on the member inapi.jsonis the only authoritative signal. When in doubt, dump it (see "Verifying classifications" below).
Process
1. Set up the env
CONTRIBUTING.md covers this. Notes from past rolls:
- The repo requires Python 3.10+. If
python3.10isn't available, usepython3(3.12 is fine). - If
python3-venvis missing system-wide, useuv venv envinstead, thenuv pip install --python env/bin/python --upgrade pip. Don't try toapt installโ sudo is denied in the harness. - Always activate the venv before any
pip,pytest,mypy, orpre-commitinvocation.
2. Bump the driver pin, download it, and generate api.json
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>, themajor.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.
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:
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 equalThen bump the pins and assemble the driver:
# Edit DRIVER_VERSION (repo root): the playwright-core npm version for the new
# release, no "v" prefix, e.g. 1.62.0
python scripts/update_node_version.py # refresh NODE_VERSION to the current LTS
source env/bin/activate
python -m build --wheel # downloads playwright-core @ DRIVER_VERSION + Node.js, assembles the driver
playwright install chromium # NOT --with-deps; sudo is denied
# The walk below inspects `langs` from api.json. Codegen does not need it.
node "$PW_SRC_DIR/utils/doclint/generateApiJson.js" > /tmp/api.jsonThe wheel build just downloads the playwright-core npm package atDRIVER_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:
git -C "$PW_SRC_DIR" fetch --tags
git -C "$PW_SRC_DIR" fetch origin 'release-*:release-*'There is sometimes no vX.Y.0 tag for the latest release (the bots cut release branches first and tag later). Anchor on commits, not tags.
The diff range is "every commit on the new release branch since the previous release was cut". Anchor commits:
- Previous release end: the
chore: bump version to vX.Y.0-nextcommit onmain. That commit is the first commit after the previous release (X.Y-1) was cut. Use its parent (<sha>~1) as the lower bound.shgit -C "$PW_SRC_DIR" log --all --grep="bump version to v" --oneline | head - New release end: the tip of
release-<new>(or the matching tag if it exists).
Save the commit list, oldest first, scoped to docs/src/api/:
git -C "$PW_SRC_DIR" log <prev-anchor>~1..release-<new> --oneline --reverse -- docs/src/api > /tmp/roll-<new>-commits.mdA normal roll yields 50โ100 commits. If you see 0 or thousands, the range is wrong.
Format the file as a markdown checklist and add the standard preamble (status legend, where to look up api.json etc.) โ see the file from the 1.58โ1.59 roll for the template.
4. Walk the commit list
For each commit, in chronological order:
git -C "$PW_SRC_DIR" show <sha> -- docs/src/api/Look for:
## (async )?method:/## property:/## event:additions or removals;* langs: ...lines on those blocks;### param:/### option:additions or removals;- new
class-X.mdfiles (whole new classes โ usuallylangs: js); - type changes in
- returns:lines.
Classify and act.
Verifying classifications (do this before suppressing anything)
Before tagging anything as MISMATCH or N/A based on appearance, dump the actual langs from api.json:
import json
data = json.load(open("/tmp/api.json"))
classes = {c["name"]: c for c in data}
for cls_name in ["Page", "BrowserContext", "Screencast", "Debugger"]:
cls = classes.get(cls_name)
if not cls:
continue
print(f"\n{cls_name}: cls_langs={cls.get('langs', {})}")
for m in cls["members"]:
print(f" {m['name']} kind={m.get('kind')} langs={m.get('langs', {})}")For options/params nested inside an Object-typed arg, walk one level deeper:
for a in member.get("args", []):
if a["name"] == "options":
for prop in a.get("type", {}).get("properties", []):
print(prop["name"], prop.get("langs", {}))A few rules of thumb that catch most "actually a PORT" cases:
- If the containing class has empty
langs: {}and the member has emptylangs: {}, it's for Python โ implement it. - If the member is empty but a single option has
langs: js, the method is for Python and you only skip that option (e.g.Screencast.start.sizeislangs: jswhileScreencast.startitself isn't). - If you're about to add three or more
Method not implemented:entries for the same class, stop โ you almost certainly need to implement the class.
PORT
Implement the change in playwright/_impl/<module>.py. Use the upstream JS implementation as a reference: $PW_SRC_DIR/packages/playwright-core/src/client/<module>.ts. Translate idioms:
| Upstream JS | Python |
|---|---|
async foo(): Promise<X> |
async def foo(self) -> X: |
foo(): X (sync getter, no args, no body) |
@property def foo(self) -> X: (the 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)):
- Register it in
playwright/_impl/_object_factory.py:create_remote_objectโ otherwise the guid resolves toDummyObjectand downstream code breaks mysteriously. - 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.pyfails 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._loopandself._dispatcher_fiber = parent._dispatcher_fiberin__init__. The generatedAsyncBase/SyncBasewrappers read these; missing them givesAttributeError: '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__.pyandplaywright/sync_api/__init__.py: assignment line plus an entry in__all__. Same pattern asViewportSize,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:
### 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
langsfield 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. Verifylangsfirst. - A renamed positional parameter (mypy: unexpected keyword argument
newNamefor 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
notproperty 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
./scripts/update_api.sh # uses PW_SRC_DIR from step 2; run the checkout checks there firstThe script, for sync and then async:
- runs
node scripts/codegen/generate_api.js --flavor <flavor>(reads the docs from$PW_SRC_DIR) straight intoplaywright/<flavor>_api/_generated.py, - invokes
pre-commit run --fileson 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:
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 allCI 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.tsbecomestests/async/test_expect_misc.pyandtests/sync/test_expect_misc.py. Create the file if it does not exist. - The upstream title decides the test name: the
describetitle and the test title, in snake_case.test('should support pseudo element')intest.describe('toHaveCSS')becomestest_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 โฆ, notfrom playwright._impl._page import Page(the impl class doesn't have the public wrappers likeexpect_console_message). - For event-info objects,
await message_info.value(it's anasyncproperty). - Don't write tests that hang the page (e.g.
page.evaluate(... fetch slow ...)followed bypage.close()from a fixture) โ the request task gets aTargetClosedError. Usepage.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_VERSIONandNODE_VERSION: already done in step 2.README.md: gets the chromium/firefox/webkit version table updated automatically byscripts/update_versions.py(called fromupdate_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 โ thedocs/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>.
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.onlyis your filter โ and the only filter. Don't classify by name (Screencast,Debugger,pickLocator) or by intuition ("looks like internal tooling"). Always checklangsinapi.json. The 1.59 roll cost two extra audit passes by trusting names overlangs.- 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.jsyou added during this roll and ask "is this divergence justified, or did I skip a port?" Run thelangs-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.newNamein a doc commit, alsogit -C "$PW_SRC_DIR" show <sha> -- packages/protocol/spec/and the corresponding*Dispatcher.tsfile. If the wire field changed too, the regenerated_protocol_channels.pyrenames 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 itsTypedDictinto_api_structures.py, and re-export it from both public__init__.pyfiles. Zero runtime cost (it's still adict), 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 thedocs/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 returningany|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.pyto fix lint or typing. Fix_impl/,scripts/codegen/generate_api.js, orpython_rules.jsinstead. Hand-editing the generated file is reverted on the next regen. /tmp/roll-<new>-commits.mdis 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:
- name: Join our Discord Server
url: https://aka.ms/playwright/discord
about: Ask questions and discuss with other community members
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
- Default repo templates including
SECURITY.MDand code of conduct files: microsoft/repo-templates - Code of Conduct (FAQ)
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?
- Why this Code of Conduct?
- Why do this across all repos?
- How does a project adopt the code and process?
- What if my Microsoft project already has a Code of Conduct?
- How do I raise a concern?
- What is the process for addressing issues that arise?
- How does the resolution team work?
- Can I use this Code of Conduct in my projects?
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.
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.