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): roll to 1.31.0-beta-1676906983000 #1775

Merged
merged 2 commits into from
Feb 21, 2023
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
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 -->110.0.5481.38<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->111.0.5563.19<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->108.0.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->109.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
2 changes: 1 addition & 1 deletion playwright/_impl/_api_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class ExpectedTextValue(TypedDict, total=False):
class FrameExpectOptions(TypedDict, total=False):
expressionArg: Any
expectedText: Optional[List[ExpectedTextValue]]
expectedNumber: Optional[int]
expectedNumber: Optional[float]
expectedValue: Optional[Any]
useInnerText: Optional[bool]
isNot: bool
Expand Down
19 changes: 19 additions & 0 deletions playwright/_impl/_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,25 @@ async def not_to_be_focused(
__tracebackhide__ = True
await self._not.to_be_focused(timeout)

async def to_be_in_viewport(
self,
ratio: float = None,
timeout: float = None,
) -> None:
__tracebackhide__ = True
await self._expect_impl(
"to.be.in.viewport",
FrameExpectOptions(timeout=timeout, expectedNumber=ratio),
None,
"Locator expected to be in viewport",
)

async def not_to_be_in_viewport(
self, ratio: float = None, timeout: float = None
) -> None:
__tracebackhide__ = True
await self._not.to_be_in_viewport(ratio=ratio, timeout=timeout)


class APIResponseAssertions:
def __init__(self, response: APIResponse, is_not: bool = False) -> None:
Expand Down
21 changes: 12 additions & 9 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,11 @@ async def _on_route(self, route: Route) -> None:
handled = await route_handler.handle(route)
finally:
if len(self._routes) == 0:
asyncio.create_task(self._disable_interception())
asyncio.create_task(
self._connection.wrap_api_call(
lambda: self._update_interception_patterns(), True
)
)
if handled:
return
await route._internal_continue(is_internal=True)
Expand Down Expand Up @@ -304,10 +308,7 @@ async def route(
times,
),
)
if len(self._routes) == 1:
await self._channel.send(
"setNetworkInterceptionEnabled", dict(enabled=True)
)
await self._update_interception_patterns()

async def unroute(
self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None
Expand All @@ -318,8 +319,7 @@ async def unroute(
self._routes,
)
)
if len(self._routes) == 0:
await self._disable_interception()
await self._update_interception_patterns()

async def _record_into_har(
self,
Expand Down Expand Up @@ -360,8 +360,11 @@ async def route_from_har(
)
await router.add_context_route(self)

async def _disable_interception(self) -> None:
await self._channel.send("setNetworkInterceptionEnabled", dict(enabled=False))
async def _update_interception_patterns(self) -> None:
patterns = RouteHandler.prepare_interception_patterns(self._routes)
await self._channel.send(
"setNetworkInterceptionPatterns", {"patterns": patterns}
)

def expect_event(
self,
Expand Down
22 changes: 22 additions & 0 deletions playwright/_impl/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,28 @@ def impl() -> None:
def will_expire(self) -> bool:
return self._handled_count + 1 >= self._times

@staticmethod
def prepare_interception_patterns(
handlers: List["RouteHandler"],
) -> List[Dict[str, str]]:
patterns = []
all = False
for handler in handlers:
if isinstance(handler.matcher.match, str):
patterns.append({"glob": handler.matcher.match})
elif isinstance(handler.matcher._regex_obj, re.Pattern):
patterns.append(
{
"regexSource": handler.matcher._regex_obj.pattern,
"regexFlags": escape_regex_flags(handler.matcher._regex_obj),
}
)
else:
all = True
if all:
return [{"glob": "**/*"}]
return patterns


def is_safe_close_error(error: Exception) -> bool:
message = str(error)
Expand Down
2 changes: 1 addition & 1 deletion playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ async def _expect(
{
"selector": self._selector,
"expression": expression,
**options,
**({k: v for k, v in options.items() if v is not None}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ratio is None which is default, then expectedNumber is None which is null during the protocol serialisation what our protocol does not like.

Any opinions?

},
)
if result.get("received"):
Expand Down
63 changes: 37 additions & 26 deletions playwright/_impl/_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ async def _actual_headers(self) -> "RawHeaders":
self._all_headers_future.set_result(RawHeaders(headers))
return await self._all_headers_future

def _target_closed_future(self) -> asyncio.Future:
if not hasattr(self.frame, "_page"):
return asyncio.Future()
return self.frame._page._closed_or_crashed_future


class Route(ChannelOwner):
def __init__(
Expand Down Expand Up @@ -348,10 +353,11 @@ async def fetch(
method: str = None,
headers: Dict[str, str] = None,
postData: Union[Any, str, bytes] = None,
maxRedirects: int = None,
) -> "APIResponse":
page = self.request.frame._page
return await page.context.request._inner_fetch(
self.request, url, method, headers, postData
self.request, url, method, headers, postData, maxRedirects=maxRedirects
)

async def fallback(
Expand Down Expand Up @@ -419,30 +425,22 @@ async def _redirected_navigation_request(self, url: str) -> None:
self._report_handled(True)

async def _race_with_page_close(self, future: Coroutine) -> None:
if hasattr(self.request.frame, "_page"):
page = self.request.frame._page
# When page closes or crashes, we catch any potential rejects from this Route.
# Note that page could be missing when routing popup's initial request that
# does not have a Page initialized just yet.
fut = asyncio.create_task(future)
# Rewrite the user's stack to the new task which runs in the background.
setattr(
fut,
"__pw_stack__",
getattr(
asyncio.current_task(self._loop), "__pw_stack__", inspect.stack()
),
)
await asyncio.wait(
[fut, page._closed_or_crashed_future],
return_when=asyncio.FIRST_COMPLETED,
)
if fut.done() and fut.exception():
raise cast(BaseException, fut.exception())
if page._closed_or_crashed_future.done():
await asyncio.gather(fut, return_exceptions=True)
else:
await future
fut = asyncio.create_task(future)
# Rewrite the user's stack to the new task which runs in the background.
setattr(
fut,
"__pw_stack__",
getattr(asyncio.current_task(self._loop), "__pw_stack__", inspect.stack()),
)
target_closed_future = self.request._target_closed_future()
await asyncio.wait(
[fut, target_closed_future],
return_when=asyncio.FIRST_COMPLETED,
)
if fut.done() and fut.exception():
raise cast(BaseException, fut.exception())
if target_closed_future.done():
await asyncio.gather(fut, return_exceptions=True)


class Response(ChannelOwner):
Expand Down Expand Up @@ -522,7 +520,20 @@ async def security_details(self) -> Optional[SecurityDetails]:
return await self._channel.send("securityDetails")

async def finished(self) -> None:
await self._finished_future
async def on_finished() -> None:
await self._request._target_closed_future()
raise Error("Target closed")

on_finished_task = asyncio.create_task(on_finished())
await asyncio.wait(
cast(
List[Union[asyncio.Task, asyncio.Future]],
[self._finished_future, on_finished_task],
),
return_when=asyncio.FIRST_COMPLETED,
)
if on_finished_task.done():
await on_finished_task

async def body(self) -> bytes:
binary = await self._channel.send("body")
Expand Down
21 changes: 12 additions & 9 deletions playwright/_impl/_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,11 @@ async def _on_route(self, route: Route) -> None:
handled = await route_handler.handle(route)
finally:
if len(self._routes) == 0:
asyncio.create_task(self._disable_interception())
asyncio.create_task(
self._connection.wrap_api_call(
lambda: self._update_interception_patterns(), True
)
)
if handled:
return
await self._browser_context._on_route(route)
Expand Down Expand Up @@ -594,10 +598,7 @@ async def route(
times,
),
)
if len(self._routes) == 1:
await self._channel.send(
"setNetworkInterceptionEnabled", dict(enabled=True)
)
await self._update_interception_patterns()

async def unroute(
self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None
Expand All @@ -608,8 +609,7 @@ async def unroute(
self._routes,
)
)
if len(self._routes) == 0:
await self._disable_interception()
await self._update_interception_patterns()

async def route_from_har(
self,
Expand All @@ -629,8 +629,11 @@ async def route_from_har(
)
await router.add_page_route(self)

async def _disable_interception(self) -> None:
await self._channel.send("setNetworkInterceptionEnabled", dict(enabled=False))
async def _update_interception_patterns(self) -> None:
patterns = RouteHandler.prepare_interception_patterns(self._routes)
await self._channel.send(
"setNetworkInterceptionPatterns", {"patterns": patterns}
)

async def screenshot(
self,
Expand Down
12 changes: 10 additions & 2 deletions playwright/_impl/_str_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,13 @@ def escape_for_text_selector(


def escape_for_attribute_selector(value: str, exact: bool = None) -> str:
suffix = "" if exact else "i"
return '"' + value.replace('"', '\\"') + '"' + suffix
# TODO: this should actually be
# cssEscape(value).replace(/\\ /g, ' ')
# However, our attribute selectors do not conform to CSS parsing spec,
# so we escape them differently.
return (
'"'
+ value.replace("\\", "\\\\").replace('"', '\\"')
+ '"'
+ ("s" if exact else "i")
)
Loading