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 Playwright 1.41.0-beta-1705101589000 #2225

Merged
merged 6 commits into from
Jan 16, 2024
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 -->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
Loading