Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: roll to ToT #1684

Merged
merged 1 commit into from
Dec 16, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
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 -->108.0.5359.48<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->109.0.5414.46<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->106.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->107.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
2 changes: 1 addition & 1 deletion playwright/_impl/_element_handle.py
Expand Up @@ -392,7 +392,7 @@ def convert_select_option_values(
if value:
if not isinstance(value, list):
value = [value]
options = (options or []) + list(map(lambda e: dict(value=e), value))
options = (options or []) + list(map(lambda e: dict(valueOrLabel=e), value))
if index:
if not isinstance(index, list):
index = [index]
Expand Down
33 changes: 32 additions & 1 deletion playwright/_impl/_fetch.py
Expand Up @@ -273,6 +273,7 @@ async def fetch(
ignoreHTTPSErrors: bool = None,
maxRedirects: int = None,
) -> "APIResponse":
url = urlOrRequest if isinstance(urlOrRequest, str) else None
request = (
cast(network.Request, to_impl(urlOrRequest))
if isinstance(to_impl(urlOrRequest), network.Request)
Expand All @@ -281,13 +282,43 @@ async def fetch(
assert request or isinstance(
urlOrRequest, str
), "First argument must be either URL string or Request"
return await self._inner_fetch(
request,
url,
method,
headers,
data,
params,
form,
multipart,
timeout,
failOnStatusCode,
ignoreHTTPSErrors,
maxRedirects,
)

async def _inner_fetch(
self,
request: Optional[network.Request],
url: Optional[str],
method: str = None,
headers: Headers = None,
data: DataType = None,
params: ParamsType = None,
form: FormType = None,
multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None,
timeout: float = None,
failOnStatusCode: bool = None,
ignoreHTTPSErrors: bool = None,
maxRedirects: int = None,
) -> "APIResponse":
assert (
(1 if data else 0) + (1 if form else 0) + (1 if multipart else 0)
) <= 1, "Only one of 'data', 'form' or 'multipart' can be specified"
assert (
maxRedirects is None or maxRedirects >= 0
), "'max_redirects' must be greater than or equal to '0'"
url = request.url if request else urlOrRequest
url = url or (request.url if request else url)
method = method or (request.method if request else "GET")
# Cannot call allHeaders() here as the request may be paused inside route handler.
headers_obj = headers or (request.headers if request else None)
Expand Down
2 changes: 1 addition & 1 deletion playwright/_impl/_frame.py
Expand Up @@ -578,7 +578,7 @@ def get_by_role(
)
)

def get_by_test_id(self, testId: str) -> "Locator":
def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator":
return self.locator(get_by_test_id_selector(test_id_attribute_name(), testId))

def get_by_text(
Expand Down
7 changes: 0 additions & 7 deletions playwright/_impl/_helper.py
Expand Up @@ -79,13 +79,6 @@ class ErrorPayload(TypedDict, total=False):
value: Optional[Any]


class FallbackOverrideParameters(TypedDict, total=False):
url: Optional[str]
method: Optional[str]
headers: Optional[Dict[str, str]]
postData: Optional[Union[str, bytes]]


class HarRecordingMetadata(TypedDict, total=False):
path: str
content: Optional[HarContentPolicy]
Expand Down
18 changes: 15 additions & 3 deletions playwright/_impl/_locator.py
Expand Up @@ -261,7 +261,7 @@ def get_by_role(
)
)

def get_by_test_id(self, testId: str) -> "Locator":
def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator":
return self.locator(get_by_test_id_selector(test_id_attribute_name(), testId))

def get_by_text(
Expand Down Expand Up @@ -328,6 +328,14 @@ async def blur(self, timeout: float = None) -> None:
},
)

async def all(
self,
) -> List["Locator"]:
result = []
for index in range(await self.count()):
result.append(self.nth(index))
return result

async def count(
self,
) -> int:
Expand Down Expand Up @@ -709,7 +717,7 @@ def get_by_role(
)
)

def get_by_test_id(self, testId: str) -> "Locator":
def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator":
return self.locator(get_by_test_id_selector(test_id_attribute_name(), testId))

def get_by_text(
Expand Down Expand Up @@ -755,7 +763,11 @@ def set_test_id_attribute_name(attribute_name: str) -> None:
_test_id_attribute_name = attribute_name


def get_by_test_id_selector(test_id_attribute_name: str, test_id: str) -> str:
def get_by_test_id_selector(
test_id_attribute_name: str, test_id: Union[str, Pattern[str]]
) -> str:
if isinstance(test_id, Pattern):
return f"internal:testid=[{test_id_attribute_name}=/{test_id.pattern}/{escape_regex_flags(test_id)}]"
return f"internal:testid=[{test_id_attribute_name}={escape_for_attribute_selector(test_id, True)}]"


Expand Down
119 changes: 79 additions & 40 deletions playwright/_impl/_network.py
Expand Up @@ -17,6 +17,7 @@
import inspect
import json
import mimetypes
import sys
from collections import defaultdict
from pathlib import Path
from types import SimpleNamespace
Expand All @@ -31,6 +32,11 @@
Union,
cast,
)

if sys.version_info >= (3, 8): # pragma: no cover
from typing import TypedDict
else: # pragma: no cover
from typing_extensions import TypedDict
from urllib import parse

from playwright._impl._api_structures import (
Expand All @@ -48,7 +54,7 @@
from_nullable_channel,
)
from playwright._impl._event_context_manager import EventContextManagerImpl
from playwright._impl._helper import FallbackOverrideParameters, locals_to_params
from playwright._impl._helper import locals_to_params
from playwright._impl._wait_helper import WaitHelper

if TYPE_CHECKING: # pragma: no cover
Expand All @@ -57,6 +63,21 @@
from playwright._impl._page import Page


class FallbackOverrideParameters(TypedDict, total=False):
url: Optional[str]
method: Optional[str]
headers: Optional[Dict[str, str]]
postData: Optional[Union[str, bytes]]


class SerializedFallbackOverrides:
def __init__(self) -> None:
self.url: Optional[str] = None
self.method: Optional[str] = None
self.headers: Optional[Dict[str, str]] = None
self.post_data_buffer: Optional[bytes] = None


def serialize_headers(headers: Dict[str, str]) -> HeadersArray:
return [
{"name": name, "value": value}
Expand Down Expand Up @@ -90,31 +111,47 @@ def __init__(
}
self._provisional_headers = RawHeaders(self._initializer["headers"])
self._all_headers_future: Optional[asyncio.Future[RawHeaders]] = None
self._fallback_overrides: FallbackOverrideParameters = (
FallbackOverrideParameters()
self._fallback_overrides: SerializedFallbackOverrides = (
SerializedFallbackOverrides()
)
base64_post_data = initializer.get("postData")
if base64_post_data is not None:
self._fallback_overrides.post_data_buffer = base64.b64decode(
base64_post_data
)

def __repr__(self) -> str:
return f"<Request url={self.url!r} method={self.method!r}>"

def _apply_fallback_overrides(self, overrides: FallbackOverrideParameters) -> None:
self._fallback_overrides = cast(
FallbackOverrideParameters, {**self._fallback_overrides, **overrides}
self._fallback_overrides.url = overrides.get(
"url", self._fallback_overrides.url
)
self._fallback_overrides.method = overrides.get(
"method", self._fallback_overrides.method
)
self._fallback_overrides.headers = overrides.get(
"headers", self._fallback_overrides.headers
)
post_data = overrides.get("postData")
if isinstance(post_data, str):
self._fallback_overrides.post_data_buffer = post_data.encode()
elif isinstance(post_data, bytes):
self._fallback_overrides.post_data_buffer = post_data
elif post_data is not None:
self._fallback_overrides.post_data_buffer = json.dumps(post_data).encode()

@property
def url(self) -> str:
return cast(str, self._fallback_overrides.get("url", self._initializer["url"]))
return cast(str, self._fallback_overrides.url or self._initializer["url"])

@property
def resource_type(self) -> str:
return self._initializer["resourceType"]

@property
def method(self) -> str:
return cast(
str, self._fallback_overrides.get("method", self._initializer["method"])
)
return cast(str, self._fallback_overrides.method or self._initializer["method"])

async def sizes(self) -> RequestSizes:
response = await self.response()
Expand All @@ -124,7 +161,7 @@ async def sizes(self) -> RequestSizes:

@property
def post_data(self) -> Optional[str]:
data = self._fallback_overrides.get("postData", self.post_data_buffer)
data = self._fallback_overrides.post_data_buffer
if not data:
return None
return data.decode() if isinstance(data, bytes) else data
Expand All @@ -144,17 +181,7 @@ def post_data_json(self) -> Optional[Any]:

@property
def post_data_buffer(self) -> Optional[bytes]:
override = self._fallback_overrides.get("post_data")
if override:
return (
override.encode()
if isinstance(override, str)
else cast(bytes, override)
)
b64_content = self._initializer.get("postData")
if b64_content is None:
return None
return base64.b64decode(b64_content)
return self._fallback_overrides.post_data_buffer

async def response(self) -> Optional["Response"]:
return from_nullable_channel(await self._channel.send("response"))
Expand Down Expand Up @@ -189,7 +216,7 @@ def _set_response_end_timing(self, response_end_timing: float) -> None:

@property
def headers(self) -> Headers:
override = self._fallback_overrides.get("headers")
override = self._fallback_overrides.headers
if override:
return RawHeaders._from_headers_dict_lossy(override).headers()
return self._provisional_headers.headers()
Expand All @@ -204,7 +231,7 @@ async def header_value(self, name: str) -> Optional[str]:
return (await self._actual_headers()).get(name)

async def _actual_headers(self) -> "RawHeaders":
override = self._fallback_overrides.get("headers")
override = self._fallback_overrides.headers
if override:
return RawHeaders(serialize_headers(override))
if not self._all_headers_future:
Expand Down Expand Up @@ -254,6 +281,7 @@ async def fulfill(
status: int = None,
headers: Dict[str, str] = None,
body: Union[str, bytes] = None,
json: Any = None,
path: Union[str, Path] = None,
contentType: str = None,
response: "APIResponse" = None,
Expand All @@ -270,6 +298,8 @@ async def fulfill(
)
from playwright._impl._fetch import APIResponse

if json is not None:
body = json.dumps(json)
if body is None and path is None and isinstance(response, APIResponse):
if response._request._connection is self._connection:
params["fetchResponseUid"] = response._fetch_uid
Expand All @@ -295,6 +325,8 @@ async def fulfill(
headers = {k.lower(): str(v) for k, v in params.get("headers", {}).items()}
if params.get("contentType"):
headers["content-type"] = params["contentType"]
elif json:
headers["content-type"] = "application/json"
elif path:
headers["content-type"] = (
mimetypes.guess_type(str(Path(path)))[0] or "application/octet-stream"
Expand All @@ -305,12 +337,24 @@ async def fulfill(
await self._race_with_page_close(self._channel.send("fulfill", params))
self._report_handled(True)

async def fetch(
self,
url: str = None,
method: str = None,
headers: Dict[str, str] = None,
postData: Union[Any, str, bytes] = None,
) -> "APIResponse":
page = self.request.frame._page
return await page.context.request._inner_fetch(
self.request, url, method, headers, postData
)

async def fallback(
self,
url: str = None,
method: str = None,
headers: Dict[str, str] = None,
postData: Union[str, bytes] = None,
postData: Union[Any, str, bytes] = None,
) -> None:
overrides = cast(FallbackOverrideParameters, locals_to_params(locals()))
self._check_not_handled()
Expand All @@ -322,7 +366,7 @@ async def continue_(
url: str = None,
method: str = None,
headers: Dict[str, str] = None,
postData: Union[str, bytes] = None,
postData: Union[Any, str, bytes] = None,
) -> None:
overrides = cast(FallbackOverrideParameters, locals_to_params(locals()))
self._check_not_handled()
Expand All @@ -335,23 +379,18 @@ def _internal_continue(
) -> Coroutine[Any, Any, None]:
async def continue_route() -> None:
try:
post_data_for_wire: Optional[str] = None
post_data_from_overrides = self.request._fallback_overrides.get(
"postData"
)
if post_data_from_overrides is not None:
post_data_for_wire = (
base64.b64encode(post_data_from_overrides.encode()).decode()
if isinstance(post_data_from_overrides, str)
else base64.b64encode(post_data_from_overrides).decode()
)
params = locals_to_params(
cast(Dict[str, str], self.request._fallback_overrides)
)
params: Dict[str, Any] = {}
params["url"] = self.request._fallback_overrides.url
params["method"] = self.request._fallback_overrides.method
params["headers"] = self.request._fallback_overrides.headers
if self.request._fallback_overrides.post_data_buffer is not None:
params["postData"] = base64.b64encode(
self.request._fallback_overrides.post_data_buffer
).decode()
params = locals_to_params(params)

if "headers" in params:
params["headers"] = serialize_headers(params["headers"])
if post_data_for_wire is not None:
params["postData"] = post_data_for_wire
await self._connection.wrap_api_call(
lambda: self._race_with_page_close(
self._channel.send(
Expand Down
2 changes: 1 addition & 1 deletion playwright/_impl/_page.py
Expand Up @@ -788,7 +788,7 @@ def get_by_role(
exact=exact,
)

def get_by_test_id(self, testId: str) -> "Locator":
def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator":
return self._main_frame.get_by_test_id(testId)

def get_by_text(
Expand Down