Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .azure-pipelines/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ extends:
targetPath: $(Build.ArtifactStagingDirectory)/esrp-build
steps:
- checkout: none
- task: EsrpRelease@9
- task: EsrpRelease@11
inputs:
connectedservicename: 'Playwright-ESRP-PME'
usemanagedidentity: true
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down Expand Up @@ -91,7 +91,7 @@ jobs:
browser: chromium
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down Expand Up @@ -138,7 +138,7 @@ jobs:
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down Expand Up @@ -181,11 +181,11 @@ jobs:
# where the default shell is pwsh and skips the activation hooks.
shell: bash -el {0}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get conda
uses: conda-incubator/setup-miniconda@v3
uses: conda-incubator/setup-miniconda@v4
with:
python-version: '3.12'
channels: conda-forge
Expand All @@ -202,7 +202,7 @@ jobs:
run:
working-directory: examples/todomvc/
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ jobs:
runs-on: ${{ matrix.os }}
defaults:
run:
# Required for conda-incubator/setup-miniconda@v3
# Required for conda-incubator/setup-miniconda@v4
shell: bash -el {0}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get conda
uses: conda-incubator/setup-miniconda@v3
uses: conda-incubator/setup-miniconda@v4
with:
python-version: '3.12'
channels: conda-forge
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ jobs:
contents: read # This is required for actions/checkout to succeed
environment: Docker
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Azure login
uses: azure/login@v2
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_DOCKER_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_DOCKER_TENANT_ID }}
Expand All @@ -29,7 +29,7 @@ jobs:
with:
python-version: "3.10"
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
with:
platforms: arm64
- name: Install dependencies & browsers
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- ubuntu-24.04
- ubuntu-24.04-arm
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down
41 changes: 41 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,51 @@ This is the recurring high-stakes task. Use the dedicated skill:

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 `expected_api_mismatch.txt` minimal — every entry needs a one-line rationale comment above it.
- Prefer `locals_to_params(locals())` for forwarding optional kwargs to channel sends, matching the rest of the codebase.

## 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-<issue-number>`

**Never `git push` without an explicit instruction to push.** Applies even when a PR is already open for the branch — additional commits are immediately visible to reviewers. Commit locally, report what was committed, and wait. Only push when the user's message contains "push", "upload", "create PR", "ship it", or equivalent.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->147.0.7727.15<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->148.0.7778.96<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->150.0.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
9 changes: 5 additions & 4 deletions local-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
asyncio-atexit==1.0.1
autobahn==23.1.2
black==25.1.0
build==1.3.0
flake8==7.2.0
flake8==7.3.0
mypy==1.17.1
objgraph==3.6.2
Pillow==11.3.0
pixelmatch==0.3.0
pre-commit==3.5.0
pyOpenSSL==25.1.0
pyOpenSSL==26.0.0
pytest==8.4.1
pytest-asyncio==1.1.0
pytest-cov==6.3.0
pytest-cov==7.1.0
pytest-repeat==0.9.4
pytest-rerunfailures==15.1
pytest-timeout==2.4.0
Expand All @@ -19,4 +20,4 @@ requests==2.32.5
service_identity==24.2.0
twisted==25.5.0
types-pyOpenSSL==24.1.0.20240722
types-requests==2.32.4.20250809
types-requests==2.32.4.20260107
18 changes: 18 additions & 0 deletions playwright/_impl/_api_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,31 @@ class ViewportSize(TypedDict):

class SourceLocation(TypedDict):
url: str
line: int
column: int
lineNumber: int
columnNumber: int


class WebErrorLocation(TypedDict):
url: str
line: int
column: int


class FilePayload(TypedDict):
name: str
mimeType: str
buffer: bytes


class DropPayload(TypedDict, total=False):
files: Optional[
Union[str, Path, FilePayload, Sequence[Union[str, Path]], Sequence[FilePayload]]
]
data: Optional[Dict[str, str]]


class RemoteAddr(TypedDict):
ipAddress: str
port: int
Expand Down Expand Up @@ -216,6 +231,7 @@ class FrameExpectOptions(TypedDict, total=False):
useInnerText: Optional[bool]
isNot: bool
timeout: Optional[float]
pseudo: Optional[str]


class FrameExpectResult(TypedDict):
Expand Down Expand Up @@ -330,3 +346,5 @@ class DebuggerPausedDetails(TypedDict):

class ScreencastFrame(TypedDict):
data: bytes
viewportWidth: int
viewportHeight: int
36 changes: 33 additions & 3 deletions playwright/_impl/_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

import collections.abc
from typing import Any, List, Optional, Pattern, Sequence, Union
from typing import Any, List, Literal, Optional, Pattern, Sequence, Union
from urllib.parse import urljoin

from playwright._impl._api_structures import (
Expand All @@ -26,6 +26,7 @@
from playwright._impl._errors import Error
from playwright._impl._fetch import APIResponse
from playwright._impl._helper import is_textual_mime_type
from playwright._impl._js_handle import parse_value
from playwright._impl._locator import Locator
from playwright._impl._page import Page
from playwright._impl._str_utils import escape_regex_flags
Expand Down Expand Up @@ -71,7 +72,14 @@ async def _expect_impl(
del expect_options["useInnerText"]
result = await self._call_expect(expression, expect_options, title)
if result["matches"] == self._is_not:
actual = result.get("received")
received = result.get("received") or {}
if isinstance(received, dict):
if "value" in received and received["value"] is not None:
actual = parse_value(received["value"])
else:
actual = received.get("ariaSnapshot")
else:
actual = received
if self._custom_message:
out_message = self._custom_message
if expected is not None:
Expand Down Expand Up @@ -161,6 +169,24 @@ async def not_to_have_url(
__tracebackhide__ = True
await self._not.to_have_url(urlOrRegExp, timeout, ignoreCase)

async def to_match_aria_snapshot(
self, expected: str, timeout: float = None
) -> None:
__tracebackhide__ = True
await self._expect_impl(
"to.match.aria",
FrameExpectOptions(expectedValue=expected, timeout=timeout),
expected,
"Page expected to match Aria snapshot",
'Expect "to_match_aria_snapshot"',
)

async def not_to_match_aria_snapshot(
self, expected: str, timeout: float = None
) -> None:
__tracebackhide__ = True
await self._not.to_match_aria_snapshot(expected, timeout)


class LocatorAssertions(AssertionsBase):
def __init__(
Expand Down Expand Up @@ -400,13 +426,17 @@ async def to_have_css(
name: str,
value: Union[str, Pattern[str]],
timeout: float = None,
pseudo: Literal["after", "before"] = None,
) -> None:
__tracebackhide__ = True
expected_text = to_expected_text_values([value])
await self._expect_impl(
"to.have.css",
FrameExpectOptions(
expressionArg=name, expectedText=expected_text, timeout=timeout
expressionArg=name,
expectedText=expected_text,
timeout=timeout,
pseudo=pseudo,
),
value,
"Locator expected to have CSS",
Expand Down
2 changes: 2 additions & 0 deletions playwright/_impl/_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@

class Browser(ChannelOwner):
Events = SimpleNamespace(
Context="context",
Disconnected="disconnected",
)

Expand Down Expand Up @@ -104,6 +105,7 @@ def _did_create_context(self, context: BrowserContext) -> None:
# and will be configured later in `ConnectToBrowserType`.
if self._browser_type:
self._setup_browser_context(context)
self.emit(Browser.Events.Context, context)

def _setup_browser_context(self, context: BrowserContext) -> None:
context._tracing._traces_dir = self._traces_dir
Expand Down
Loading