Skip to content
Merged
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 DRIVER_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.63.0
1.64.0
6 changes: 3 additions & 3 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 -->153.0.8010.12<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.6<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->155.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->156.0.8078.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->27.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->157.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
20 changes: 20 additions & 0 deletions playwright/_impl/_api_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,32 @@ class ScreencastSize(TypedDict):
height: int


class ScreencastActionStyle(TypedDict, total=False):
point: Optional[str]
highlight: Optional[str]
title: Optional[str]


class VirtualCredential(TypedDict):
id: str
rpId: str
userHandle: str
privateKey: str
publicKey: str
signCount: int


class WebMCPToolAnnotations(TypedDict, total=False):
readOnly: Optional[bool]
untrustedContent: Optional[bool]
consequential: Optional[bool]


class WebMCPTool(TypedDict, total=False):
name: str
description: str
inputSchema: Optional[Any]
annotations: Optional[WebMCPToolAnnotations]


class ScreencastFrame(TypedDict):
Expand Down
2 changes: 2 additions & 0 deletions playwright/_impl/_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ async def new_context(
recordHarMode: HarMode = None,
recordHarContent: HarContentPolicy = None,
clientCertificates: List[ClientCertificate] = None,
recordVideoFps: int = None,
) -> BrowserContext:
params = locals_to_params(locals())
assert self._browser_type is not None
Expand Down Expand Up @@ -221,6 +222,7 @@ async def new_page(
recordHarMode: HarMode = None,
recordHarContent: HarContentPolicy = None,
clientCertificates: List[ClientCertificate] = None,
recordVideoFps: int = None,
) -> Page:
params = locals_to_params(locals())

Expand Down
24 changes: 2 additions & 22 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@
WebSocketRoute,
WebSocketRouteHandler,
serialize_headers,
to_clear_cookies_params,
)
from playwright._impl._page import BindingCall, Page, Worker
from playwright._impl._str_utils import escape_regex_flags
from playwright._impl._tracing import Tracing
from playwright._impl._waiter import Waiter
from playwright._impl._web_error import WebError
Expand Down Expand Up @@ -369,27 +369,7 @@ async def clear_cookies(
path: Union[str, Pattern[str]] = None,
) -> None:
await self._channel.send(
"clearCookies",
None,
{
"name": name if isinstance(name, str) else None,
"nameRegexSource": name.pattern if isinstance(name, Pattern) else None,
"nameRegexFlags": (
escape_regex_flags(name) if isinstance(name, Pattern) else None
),
"domain": domain if isinstance(domain, str) else None,
"domainRegexSource": (
domain.pattern if isinstance(domain, Pattern) else None
),
"domainRegexFlags": (
escape_regex_flags(domain) if isinstance(domain, Pattern) else None
),
"path": path if isinstance(path, str) else None,
"pathRegexSource": path.pattern if isinstance(path, Pattern) else None,
"pathRegexFlags": (
escape_regex_flags(path) if isinstance(path, Pattern) else None
),
},
"clearCookies", None, to_clear_cookies_params(name, domain, path)
)

async def grant_permissions(
Expand Down
4 changes: 4 additions & 0 deletions playwright/_impl/_browser_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ async def launch_persistent_context(
recordHarMode: HarMode = None,
recordHarContent: HarContentPolicy = None,
clientCertificates: List[ClientCertificate] = None,
recordVideoFps: int = None,
) -> BrowserContext:
userDataDir = self._user_data_dir(userDataDir)
params = locals_to_params(locals())
Expand Down Expand Up @@ -313,7 +314,10 @@ async def _prepare_browser_context_params(self, params: Dict) -> None:
if "recordVideoSize" in params:
params["recordVideo"]["size"] = params["recordVideoSize"]
del params["recordVideoSize"]
if "recordVideoFps" in params:
params["recordVideo"]["fps"] = params["recordVideoFps"]
del params["recordVideoDir"]
params.pop("recordVideoFps", None)
if "storageState" in params:
storageState = params["storageState"]
if not isinstance(storageState, dict):
Expand Down
10 changes: 7 additions & 3 deletions playwright/_impl/_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import contextvars
import datetime
import inspect
import random
import string
import sys
import traceback
from pathlib import Path
Expand Down Expand Up @@ -250,7 +252,7 @@ def remove_listener(self, event: str, f: Any) -> None:

class ProtocolCallback:
def __init__(
self, loop: asyncio.AbstractEventLoop, id: int, no_reply: bool = False
self, loop: asyncio.AbstractEventLoop, id: str, no_reply: bool = False
) -> None:
self.id = id
self.stack_trace: traceback.StackSummary
Expand Down Expand Up @@ -305,9 +307,11 @@ def __init__(
self._dispatcher_fiber = dispatcher_fiber
self._transport = transport
self._transport.on_message = lambda msg: self.dispatch(msg)
# Call ids double as trace call ids, so they must be unique across connections.
self._call_id_prefix = "".join(random.choices(string.ascii_lowercase, k=4))
self._last_id = 0
self._objects: Dict[str, ChannelOwner] = {}
self._callbacks: Dict[int, ProtocolCallback] = {}
self._callbacks: Dict[str, ProtocolCallback] = {}
self._object_factory = object_factory
self._is_sync = False
self._child_ws_connections: List["Connection"] = []
Expand Down Expand Up @@ -403,7 +407,7 @@ def _send_message_to_server(
"The object has been collected to prevent unbounded heap growth."
)
self._last_id += 1
id = self._last_id
id = f"{self._call_id_prefix}@{self._last_id}"
callback = ProtocolCallback(self._loop, id, no_reply=no_reply)
task = asyncio.current_task(self._loop)
callback.stack_trace = cast(
Expand Down
1 change: 1 addition & 0 deletions playwright/_impl/_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async def create(
userHandle: str = None,
privateKey: str = None,
publicKey: str = None,
signCount: int = None,
) -> VirtualCredential:
result = await self._browser_context._channel.send_return_as_dict(
"credentialsCreate", None, locals_to_params(locals())
Expand Down
25 changes: 24 additions & 1 deletion playwright/_impl/_fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
import pathlib
import typing
from pathlib import Path
from typing import Any, Dict, List, Optional, Sequence, Union, cast
from typing import Any, Dict, List, Optional, Pattern, Sequence, Union, cast

import playwright._impl._network as network
from playwright._impl._api_structures import (
ClientCertificate,
Cookie,
FilePayload,
FormField,
Headers,
Expand All @@ -32,6 +33,7 @@
ResourceTiming,
SecurityDetails,
ServerFilePayload,
SetCookieParam,
StorageState,
)
from playwright._impl._connection import ChannelOwner, from_channel
Expand All @@ -51,6 +53,7 @@
)
from playwright._impl._network import (
serialize_headers,
to_clear_cookies_params,
to_client_certificates_protocol,
to_http_credentials_protocol,
)
Expand Down Expand Up @@ -136,6 +139,26 @@ async def dispose(self, reason: str = None) -> None:
def tracing(self) -> Tracing:
return self._tracing

async def cookies(self, urls: Union[str, Sequence[str]] = None) -> List[Cookie]:
if urls is None:
urls = []
if isinstance(urls, str):
urls = [urls]
return await self._channel.send("cookies", None, dict(urls=urls))

async def add_cookies(self, cookies: Sequence[SetCookieParam]) -> None:
await self._channel.send("addCookies", None, dict(cookies=cookies))

async def clear_cookies(
self,
name: Union[str, Pattern[str]] = None,
domain: Union[str, Pattern[str]] = None,
path: Union[str, Pattern[str]] = None,
) -> None:
await self._channel.send(
"clearCookies", None, to_clear_cookies_params(name, domain, path)
)

async def delete(
self,
url: str,
Expand Down
10 changes: 8 additions & 2 deletions playwright/_impl/_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
from playwright._impl._network import Response
from playwright._impl._set_input_files_helpers import convert_input_files
from playwright._impl._waiter import Waiter
from playwright._impl._webmcp import WebMCP

if TYPE_CHECKING: # pragma: no cover
from playwright._impl._page import Page
Expand All @@ -102,6 +103,7 @@ def __init__(
self._page: Optional[Page] = None
self._load_states: Set[str] = set(initializer["loadStates"])
self._event_emitter = EventEmitter()
self._webmcp = WebMCP(self)
self._channel.on(
"loadstate",
lambda params: self._on_load_state(params.get("add"), params.get("remove")),
Expand Down Expand Up @@ -497,8 +499,8 @@ async def eval_on_selector_all(
)
)

async def content(self) -> str:
return await self._channel.send("content", None)
async def content(self, includeShadow: bool = None) -> str:
return await self._channel.send("content", None, locals_to_params(locals()))

async def set_content(
self,
Expand All @@ -518,6 +520,10 @@ def name(self) -> str:
def url(self) -> str:
return self._url or ""

@property
def webmcp(self) -> WebMCP:
return self._webmcp

@property
def parent_frame(self) -> Optional["Frame"]:
return self._parent_frame
Expand Down
2 changes: 1 addition & 1 deletion playwright/_impl/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class ParsedMessageParams(TypedDict):


class ParsedMessagePayload(TypedDict, total=False):
id: int
id: str
guid: str
method: str
params: ParsedMessageParams
Expand Down
2 changes: 1 addition & 1 deletion playwright/_impl/_local_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async def tracing_started(
async def trace_discarded(self, stacks_id: str) -> None:
return await self._channel.send("traceDiscarded", None, {"stacksId": stacks_id})

def add_stack_to_tracing_no_reply(self, id: int, frames: List[StackFrame]) -> None:
def add_stack_to_tracing_no_reply(self, id: str, frames: List[StackFrame]) -> None:
self._channel.send_no_reply(
"addStackToTracingNoReply",
None,
Expand Down
18 changes: 15 additions & 3 deletions playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ def __init__(
self._selector = selector
self._loop = frame._loop
self._dispatcher_fiber = frame._connection._dispatcher_fiber
# Python code for this locator, known only for normalized locators.
self._locator_code: Optional[str] = None

if has_text:
self._selector += f" >> internal:has-text={escape_for_text_selector(has_text, exact=False)}"
Expand All @@ -104,6 +106,11 @@ def __init__(
def __repr__(self) -> str:
return f"<Locator frame={self._frame!r} selector={self._selector!r}>"

def __str__(self) -> str:
if self._locator_code is not None:
return self._locator_code
return self.__repr__()

async def _with_element(
self,
task: Callable[[ElementHandle, float], Awaitable[T]],
Expand Down Expand Up @@ -404,6 +411,9 @@ def and_(self, locator: "Locator") -> "Locator":
self._selector + " >> internal:and=" + json.dumps(locator._selector),
)

def within(self, locator: "Locator") -> "Locator":
return locator.locator(self)

async def focus(self, timeout: float = None) -> None:
params = locals_to_params(locals())
return await self._frame.focus(self._selector, strict=True, **params)
Expand Down Expand Up @@ -608,12 +618,14 @@ async def aria_snapshot(
)

async def normalize(self) -> "Locator":
result = await self._frame._channel.send(
result = await self._frame._channel.send_return_as_dict(
"resolveSelector",
None,
{"selector": self._selector},
{"selector": self._selector, "sdkLanguage": "python"},
)
return Locator(self._frame, result)
locator = Locator(self._frame, result["resolvedSelector"])
locator._locator_code = result.get("locatorCode")
return locator

async def scroll_into_view_if_needed(
self,
Expand Down
25 changes: 25 additions & 0 deletions playwright/_impl/_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
Dict,
List,
Optional,
Pattern,
Sequence,
TypedDict,
Union,
Expand Down Expand Up @@ -142,6 +143,30 @@ def to_http_credentials_protocol(
] or None


def to_clear_cookies_params(
name: Optional[Union[str, Pattern[str]]],
domain: Optional[Union[str, Pattern[str]]],
path: Optional[Union[str, Pattern[str]]],
) -> Dict[str, Optional[str]]:
return {
"name": name if isinstance(name, str) else None,
"nameRegexSource": name.pattern if isinstance(name, Pattern) else None,
"nameRegexFlags": (
escape_regex_flags(name) if isinstance(name, Pattern) else None
),
"domain": domain if isinstance(domain, str) else None,
"domainRegexSource": (domain.pattern if isinstance(domain, Pattern) else None),
"domainRegexFlags": (
escape_regex_flags(domain) if isinstance(domain, Pattern) else None
),
"path": path if isinstance(path, str) else None,
"pathRegexSource": path.pattern if isinstance(path, Pattern) else None,
"pathRegexFlags": (
escape_regex_flags(path) if isinstance(path, Pattern) else None
),
}


class Request(ChannelOwner):
def __init__(
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
Expand Down
Loading
Loading