Skip to content

Commit

Permalink
chore(roll): roll to Playwright 1.41.0-beta-1705101589000 (#2225)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxschmitt authored Jan 16, 2024
1 parent f2640a3 commit 6e586ed
Show file tree
Hide file tree
Showing 24 changed files with 1,558 additions and 403 deletions.
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 -->120.0.6099.28<!-- GEN:stop --> ||||
| Chromium <!-- GEN:chromium-version -->121.0.6167.57<!-- GEN:stop --> ||||
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->119.0<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->121.0<!-- GEN:stop --> ||||

## Documentation

Expand Down
52 changes: 45 additions & 7 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def __init__(
self.Events.Close, lambda context: self._closed_future.set_result(True)
)
self._close_reason: Optional[str] = None
self._har_routers: List[HarRouter] = []
self._set_event_to_subscription_mapping(
{
BrowserContext.Events.Console: "console",
Expand All @@ -219,10 +220,16 @@ def _on_page(self, page: Page) -> None:

async def _on_route(self, route: Route) -> None:
route._context = self
page = route.request._safe_page()
route_handlers = self._routes.copy()
for route_handler in route_handlers:
# If the page or the context was closed we stall all requests right away.
if (page and page._close_was_called) or self._close_was_called:
return
if not route_handler.matches(route.request.url):
continue
if route_handler not in self._routes:
continue
if route_handler.will_expire:
self._routes.remove(route_handler)
try:
Expand All @@ -236,7 +243,12 @@ async def _on_route(self, route: Route) -> None:
)
if handled:
return
await route._internal_continue(is_internal=True)
try:
# If the page is closed or unrouteAll() was called without waiting and interception disabled,
# the method will throw an error - silence it.
await route._internal_continue(is_internal=True)
except Exception:
pass

def _on_binding(self, binding_call: BindingCall) -> None:
func = self._bindings.get(binding_call._initializer["name"])
Expand Down Expand Up @@ -361,13 +373,37 @@ async def route(
async def unroute(
self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None
) -> None:
self._routes = list(
filter(
lambda r: r.matcher.match != url or (handler and r.handler != handler),
self._routes,
)
)
removed = []
remaining = []
for route in self._routes:
if route.matcher.match != url or (handler and route.handler != handler):
remaining.append(route)
else:
removed.append(route)
await self._unroute_internal(removed, remaining, "default")

async def _unroute_internal(
self,
removed: List[RouteHandler],
remaining: List[RouteHandler],
behavior: Literal["default", "ignoreErrors", "wait"] = None,
) -> None:
self._routes = remaining
await self._update_interception_patterns()
if behavior is None or behavior == "default":
return
await asyncio.gather(*map(lambda router: router.stop(behavior), removed)) # type: ignore

def _dispose_har_routers(self) -> None:
for router in self._har_routers:
router.dispose()
self._har_routers = []

async def unroute_all(
self, behavior: Literal["default", "ignoreErrors", "wait"] = None
) -> None:
await self._unroute_internal(self._routes, [], behavior)
self._dispose_har_routers()

async def _record_into_har(
self,
Expand Down Expand Up @@ -419,6 +455,7 @@ async def route_from_har(
not_found_action=notFound or "abort",
url_matcher=url,
)
self._har_routers.append(router)
await router.add_context_route(self)

async def _update_interception_patterns(self) -> None:
Expand Down Expand Up @@ -450,6 +487,7 @@ def _on_close(self) -> None:
if self._browser:
self._browser._contexts.remove(self)

self._dispose_har_routers()
self.emit(BrowserContext.Events.Close, self)

async def close(self, reason: str = None) -> None:
Expand Down
1 change: 1 addition & 0 deletions playwright/_impl/_element_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ async def screenshot(
scale: Literal["css", "device"] = None,
mask: Sequence["Locator"] = None,
maskColor: str = None,
style: str = None,
) -> bytes:
params = locals_to_params(locals())
if "path" in params:
Expand Down
4 changes: 1 addition & 3 deletions playwright/_impl/_har_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,14 @@ async def add_context_route(self, context: "BrowserContext") -> None:
url=self._options_url_match or "**/*",
handler=lambda route, _: asyncio.create_task(self._handle(route)),
)
context.once("close", lambda _: self._dispose())

async def add_page_route(self, page: "Page") -> None:
await page.route(
url=self._options_url_match or "**/*",
handler=lambda route, _: asyncio.create_task(self._handle(route)),
)
page.once("close", lambda _: self._dispose())

def _dispose(self) -> None:
def dispose(self) -> None:
asyncio.create_task(
self._local_utils._channel.send("harClose", {"harId": self._har_id})
)
71 changes: 52 additions & 19 deletions playwright/_impl/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import inspect
import math
import os
import re
Expand All @@ -25,11 +24,11 @@
TYPE_CHECKING,
Any,
Callable,
Coroutine,
Dict,
List,
Optional,
Pattern,
Set,
TypeVar,
Union,
cast,
Expand Down Expand Up @@ -257,6 +256,15 @@ def monotonic_time() -> int:
return math.floor(time.monotonic() * 1000)


class RouteHandlerInvocation:
complete: "asyncio.Future"
route: "Route"

def __init__(self, complete: "asyncio.Future", route: "Route") -> None:
self.complete = complete
self.route = route


class RouteHandler:
def __init__(
self,
Expand All @@ -270,32 +278,57 @@ def __init__(
self._times = times if times else math.inf
self._handled_count = 0
self._is_sync = is_sync
self._ignore_exception = False
self._active_invocations: Set[RouteHandlerInvocation] = set()

def matches(self, request_url: str) -> bool:
return self.matcher.matches(request_url)

async def handle(self, route: "Route") -> bool:
handler_invocation = RouteHandlerInvocation(
asyncio.get_running_loop().create_future(), route
)
self._active_invocations.add(handler_invocation)
try:
return await self._handle_internal(route)
except Exception as e:
# If the handler was stopped (without waiting for completion), we ignore all exceptions.
if self._ignore_exception:
return False
raise e
finally:
handler_invocation.complete.set_result(None)
self._active_invocations.remove(handler_invocation)

async def _handle_internal(self, route: "Route") -> bool:
handled_future = route._start_handling()
handler_task = []

def impl() -> None:
self._handled_count += 1
result = cast(
Callable[["Route", "Request"], Union[Coroutine, Any]], self.handler
)(route, route.request)
if inspect.iscoroutine(result):
handler_task.append(asyncio.create_task(result))

# As with event handlers, each route handler is a potentially blocking context
# so it needs a fiber.

self._handled_count += 1
if self._is_sync:
g = greenlet(impl)
# As with event handlers, each route handler is a potentially blocking context
# so it needs a fiber.
g = greenlet(lambda: self.handler(route, route.request)) # type: ignore
g.switch()
else:
impl()

[handled, *_] = await asyncio.gather(handled_future, *handler_task)
return handled
coro_or_future = self.handler(route, route.request) # type: ignore
if coro_or_future:
# separate task so that we get a proper stack trace for exceptions / tracing api_name extraction
await asyncio.ensure_future(coro_or_future)
return await handled_future

async def stop(self, behavior: Literal["ignoreErrors", "wait"]) -> None:
# When a handler is manually unrouted or its page/context is closed we either
# - wait for the current handler invocations to finish
# - or do not wait, if the user opted out of it, but swallow all exceptions
# that happen after the unroute/close.
if behavior == "ignoreErrors":
self._ignore_exception = True
else:
tasks = []
for activation in self._active_invocations:
if not activation.route._did_throw:
tasks.append(activation.complete)
await asyncio.gather(*tasks)

@property
def will_expire(self) -> bool:
Expand Down
1 change: 1 addition & 0 deletions playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ async def screenshot(
scale: Literal["css", "device"] = None,
mask: Sequence["Locator"] = None,
maskColor: str = None,
style: str = None,
) -> bytes:
params = locals_to_params(locals())
return await self._with_element(
Expand Down
67 changes: 48 additions & 19 deletions playwright/_impl/_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ def _target_closed_future(self) -> asyncio.Future:
return asyncio.Future()
return page._closed_or_crashed_future

def _safe_page(self) -> "Optional[Page]":
return cast("Frame", from_channel(self._initializer["frame"]))._page


class Route(ChannelOwner):
def __init__(
Expand All @@ -275,6 +278,7 @@ def __init__(
super().__init__(parent, type, guid, initializer)
self._handling_future: Optional[asyncio.Future["bool"]] = None
self._context: "BrowserContext" = cast("BrowserContext", None)
self._did_throw = False

def _start_handling(self) -> "asyncio.Future[bool]":
self._handling_future = asyncio.Future()
Expand All @@ -298,17 +302,17 @@ def request(self) -> Request:
return from_channel(self._initializer["request"])

async def abort(self, errorCode: str = None) -> None:
self._check_not_handled()
await self._race_with_page_close(
self._channel.send(
"abort",
{
"errorCode": errorCode,
"requestUrl": self.request._initializer["url"],
},
await self._handle_route(
lambda: self._race_with_page_close(
self._channel.send(
"abort",
{
"errorCode": errorCode,
"requestUrl": self.request._initializer["url"],
},
)
)
)
self._report_handled(True)

async def fulfill(
self,
Expand All @@ -320,7 +324,22 @@ async def fulfill(
contentType: str = None,
response: "APIResponse" = None,
) -> None:
self._check_not_handled()
await self._handle_route(
lambda: self._inner_fulfill(
status, headers, body, json, path, contentType, response
)
)

async def _inner_fulfill(
self,
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,
) -> None:
params = locals_to_params(locals())

if json is not None:
Expand Down Expand Up @@ -375,7 +394,15 @@ async def fulfill(
params["requestUrl"] = self.request._initializer["url"]

await self._race_with_page_close(self._channel.send("fulfill", params))
self._report_handled(True)

async def _handle_route(self, callback: Callable) -> None:
self._check_not_handled()
try:
await callback()
self._report_handled(True)
except Exception as e:
self._did_throw = True
raise e

async def fetch(
self,
Expand Down Expand Up @@ -418,10 +445,12 @@ async def continue_(
postData: Union[Any, str, bytes] = None,
) -> None:
overrides = cast(FallbackOverrideParameters, locals_to_params(locals()))
self._check_not_handled()
self.request._apply_fallback_overrides(overrides)
await self._internal_continue()
self._report_handled(True)

async def _inner() -> None:
self.request._apply_fallback_overrides(overrides)
await self._internal_continue()

return await self._handle_route(_inner)

def _internal_continue(
self, is_internal: bool = False
Expand Down Expand Up @@ -458,11 +487,11 @@ async def continue_route() -> None:
return continue_route()

async def _redirected_navigation_request(self, url: str) -> None:
self._check_not_handled()
await self._race_with_page_close(
self._channel.send("redirectNavigationRequest", {"url": url})
await self._handle_route(
lambda: self._race_with_page_close(
self._channel.send("redirectNavigationRequest", {"url": url})
)
)
self._report_handled(True)

async def _race_with_page_close(self, future: Coroutine) -> None:
fut = asyncio.create_task(future)
Expand Down
Loading

0 comments on commit 6e586ed

Please sign in to comment.