Skip to content

Commit 6e586ed

Browse files
authored
chore(roll): roll to Playwright 1.41.0-beta-1705101589000 (#2225)
1 parent f2640a3 commit 6e586ed

24 files changed

+1558
-403
lines changed

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H
44

55
| | Linux | macOS | Windows |
66
| :--- | :---: | :---: | :---: |
7-
| Chromium <!-- GEN:chromium-version -->120.0.6099.28<!-- GEN:stop --> ||||
7+
| Chromium <!-- GEN:chromium-version -->121.0.6167.57<!-- GEN:stop --> ||||
88
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> ||||
9-
| Firefox <!-- GEN:firefox-version -->119.0<!-- GEN:stop --> ||||
9+
| Firefox <!-- GEN:firefox-version -->121.0<!-- GEN:stop --> ||||
1010

1111
## Documentation
1212

Diff for: playwright/_impl/_browser_context.py

+45-7
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ def __init__(
196196
self.Events.Close, lambda context: self._closed_future.set_result(True)
197197
)
198198
self._close_reason: Optional[str] = None
199+
self._har_routers: List[HarRouter] = []
199200
self._set_event_to_subscription_mapping(
200201
{
201202
BrowserContext.Events.Console: "console",
@@ -219,10 +220,16 @@ def _on_page(self, page: Page) -> None:
219220

220221
async def _on_route(self, route: Route) -> None:
221222
route._context = self
223+
page = route.request._safe_page()
222224
route_handlers = self._routes.copy()
223225
for route_handler in route_handlers:
226+
# If the page or the context was closed we stall all requests right away.
227+
if (page and page._close_was_called) or self._close_was_called:
228+
return
224229
if not route_handler.matches(route.request.url):
225230
continue
231+
if route_handler not in self._routes:
232+
continue
226233
if route_handler.will_expire:
227234
self._routes.remove(route_handler)
228235
try:
@@ -236,7 +243,12 @@ async def _on_route(self, route: Route) -> None:
236243
)
237244
if handled:
238245
return
239-
await route._internal_continue(is_internal=True)
246+
try:
247+
# If the page is closed or unrouteAll() was called without waiting and interception disabled,
248+
# the method will throw an error - silence it.
249+
await route._internal_continue(is_internal=True)
250+
except Exception:
251+
pass
240252

241253
def _on_binding(self, binding_call: BindingCall) -> None:
242254
func = self._bindings.get(binding_call._initializer["name"])
@@ -361,13 +373,37 @@ async def route(
361373
async def unroute(
362374
self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None
363375
) -> None:
364-
self._routes = list(
365-
filter(
366-
lambda r: r.matcher.match != url or (handler and r.handler != handler),
367-
self._routes,
368-
)
369-
)
376+
removed = []
377+
remaining = []
378+
for route in self._routes:
379+
if route.matcher.match != url or (handler and route.handler != handler):
380+
remaining.append(route)
381+
else:
382+
removed.append(route)
383+
await self._unroute_internal(removed, remaining, "default")
384+
385+
async def _unroute_internal(
386+
self,
387+
removed: List[RouteHandler],
388+
remaining: List[RouteHandler],
389+
behavior: Literal["default", "ignoreErrors", "wait"] = None,
390+
) -> None:
391+
self._routes = remaining
370392
await self._update_interception_patterns()
393+
if behavior is None or behavior == "default":
394+
return
395+
await asyncio.gather(*map(lambda router: router.stop(behavior), removed)) # type: ignore
396+
397+
def _dispose_har_routers(self) -> None:
398+
for router in self._har_routers:
399+
router.dispose()
400+
self._har_routers = []
401+
402+
async def unroute_all(
403+
self, behavior: Literal["default", "ignoreErrors", "wait"] = None
404+
) -> None:
405+
await self._unroute_internal(self._routes, [], behavior)
406+
self._dispose_har_routers()
371407

372408
async def _record_into_har(
373409
self,
@@ -419,6 +455,7 @@ async def route_from_har(
419455
not_found_action=notFound or "abort",
420456
url_matcher=url,
421457
)
458+
self._har_routers.append(router)
422459
await router.add_context_route(self)
423460

424461
async def _update_interception_patterns(self) -> None:
@@ -450,6 +487,7 @@ def _on_close(self) -> None:
450487
if self._browser:
451488
self._browser._contexts.remove(self)
452489

490+
self._dispose_har_routers()
453491
self.emit(BrowserContext.Events.Close, self)
454492

455493
async def close(self, reason: str = None) -> None:

Diff for: playwright/_impl/_element_handle.py

+1
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ async def screenshot(
298298
scale: Literal["css", "device"] = None,
299299
mask: Sequence["Locator"] = None,
300300
maskColor: str = None,
301+
style: str = None,
301302
) -> bytes:
302303
params = locals_to_params(locals())
303304
if "path" in params:

Diff for: playwright/_impl/_har_router.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,14 @@ async def add_context_route(self, context: "BrowserContext") -> None:
102102
url=self._options_url_match or "**/*",
103103
handler=lambda route, _: asyncio.create_task(self._handle(route)),
104104
)
105-
context.once("close", lambda _: self._dispose())
106105

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

114-
def _dispose(self) -> None:
112+
def dispose(self) -> None:
115113
asyncio.create_task(
116114
self._local_utils._channel.send("harClose", {"harId": self._har_id})
117115
)

Diff for: playwright/_impl/_helper.py

+52-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import asyncio
15-
import inspect
1615
import math
1716
import os
1817
import re
@@ -25,11 +24,11 @@
2524
TYPE_CHECKING,
2625
Any,
2726
Callable,
28-
Coroutine,
2927
Dict,
3028
List,
3129
Optional,
3230
Pattern,
31+
Set,
3332
TypeVar,
3433
Union,
3534
cast,
@@ -257,6 +256,15 @@ def monotonic_time() -> int:
257256
return math.floor(time.monotonic() * 1000)
258257

259258

259+
class RouteHandlerInvocation:
260+
complete: "asyncio.Future"
261+
route: "Route"
262+
263+
def __init__(self, complete: "asyncio.Future", route: "Route") -> None:
264+
self.complete = complete
265+
self.route = route
266+
267+
260268
class RouteHandler:
261269
def __init__(
262270
self,
@@ -270,32 +278,57 @@ def __init__(
270278
self._times = times if times else math.inf
271279
self._handled_count = 0
272280
self._is_sync = is_sync
281+
self._ignore_exception = False
282+
self._active_invocations: Set[RouteHandlerInvocation] = set()
273283

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

277287
async def handle(self, route: "Route") -> bool:
288+
handler_invocation = RouteHandlerInvocation(
289+
asyncio.get_running_loop().create_future(), route
290+
)
291+
self._active_invocations.add(handler_invocation)
292+
try:
293+
return await self._handle_internal(route)
294+
except Exception as e:
295+
# If the handler was stopped (without waiting for completion), we ignore all exceptions.
296+
if self._ignore_exception:
297+
return False
298+
raise e
299+
finally:
300+
handler_invocation.complete.set_result(None)
301+
self._active_invocations.remove(handler_invocation)
302+
303+
async def _handle_internal(self, route: "Route") -> bool:
278304
handled_future = route._start_handling()
279-
handler_task = []
280-
281-
def impl() -> None:
282-
self._handled_count += 1
283-
result = cast(
284-
Callable[["Route", "Request"], Union[Coroutine, Any]], self.handler
285-
)(route, route.request)
286-
if inspect.iscoroutine(result):
287-
handler_task.append(asyncio.create_task(result))
288-
289-
# As with event handlers, each route handler is a potentially blocking context
290-
# so it needs a fiber.
305+
306+
self._handled_count += 1
291307
if self._is_sync:
292-
g = greenlet(impl)
308+
# As with event handlers, each route handler is a potentially blocking context
309+
# so it needs a fiber.
310+
g = greenlet(lambda: self.handler(route, route.request)) # type: ignore
293311
g.switch()
294312
else:
295-
impl()
296-
297-
[handled, *_] = await asyncio.gather(handled_future, *handler_task)
298-
return handled
313+
coro_or_future = self.handler(route, route.request) # type: ignore
314+
if coro_or_future:
315+
# separate task so that we get a proper stack trace for exceptions / tracing api_name extraction
316+
await asyncio.ensure_future(coro_or_future)
317+
return await handled_future
318+
319+
async def stop(self, behavior: Literal["ignoreErrors", "wait"]) -> None:
320+
# When a handler is manually unrouted or its page/context is closed we either
321+
# - wait for the current handler invocations to finish
322+
# - or do not wait, if the user opted out of it, but swallow all exceptions
323+
# that happen after the unroute/close.
324+
if behavior == "ignoreErrors":
325+
self._ignore_exception = True
326+
else:
327+
tasks = []
328+
for activation in self._active_invocations:
329+
if not activation.route._did_throw:
330+
tasks.append(activation.complete)
331+
await asyncio.gather(*tasks)
299332

300333
@property
301334
def will_expire(self) -> bool:

Diff for: playwright/_impl/_locator.py

+1
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ async def screenshot(
523523
scale: Literal["css", "device"] = None,
524524
mask: Sequence["Locator"] = None,
525525
maskColor: str = None,
526+
style: str = None,
526527
) -> bytes:
527528
params = locals_to_params(locals())
528529
return await self._with_element(

Diff for: playwright/_impl/_network.py

+48-19
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ def _target_closed_future(self) -> asyncio.Future:
267267
return asyncio.Future()
268268
return page._closed_or_crashed_future
269269

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

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

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

300304
async def abort(self, errorCode: str = None) -> None:
301-
self._check_not_handled()
302-
await self._race_with_page_close(
303-
self._channel.send(
304-
"abort",
305-
{
306-
"errorCode": errorCode,
307-
"requestUrl": self.request._initializer["url"],
308-
},
305+
await self._handle_route(
306+
lambda: self._race_with_page_close(
307+
self._channel.send(
308+
"abort",
309+
{
310+
"errorCode": errorCode,
311+
"requestUrl": self.request._initializer["url"],
312+
},
313+
)
309314
)
310315
)
311-
self._report_handled(True)
312316

313317
async def fulfill(
314318
self,
@@ -320,7 +324,22 @@ async def fulfill(
320324
contentType: str = None,
321325
response: "APIResponse" = None,
322326
) -> None:
323-
self._check_not_handled()
327+
await self._handle_route(
328+
lambda: self._inner_fulfill(
329+
status, headers, body, json, path, contentType, response
330+
)
331+
)
332+
333+
async def _inner_fulfill(
334+
self,
335+
status: int = None,
336+
headers: Dict[str, str] = None,
337+
body: Union[str, bytes] = None,
338+
json: Any = None,
339+
path: Union[str, Path] = None,
340+
contentType: str = None,
341+
response: "APIResponse" = None,
342+
) -> None:
324343
params = locals_to_params(locals())
325344

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

377396
await self._race_with_page_close(self._channel.send("fulfill", params))
378-
self._report_handled(True)
397+
398+
async def _handle_route(self, callback: Callable) -> None:
399+
self._check_not_handled()
400+
try:
401+
await callback()
402+
self._report_handled(True)
403+
except Exception as e:
404+
self._did_throw = True
405+
raise e
379406

380407
async def fetch(
381408
self,
@@ -418,10 +445,12 @@ async def continue_(
418445
postData: Union[Any, str, bytes] = None,
419446
) -> None:
420447
overrides = cast(FallbackOverrideParameters, locals_to_params(locals()))
421-
self._check_not_handled()
422-
self.request._apply_fallback_overrides(overrides)
423-
await self._internal_continue()
424-
self._report_handled(True)
448+
449+
async def _inner() -> None:
450+
self.request._apply_fallback_overrides(overrides)
451+
await self._internal_continue()
452+
453+
return await self._handle_route(_inner)
425454

426455
def _internal_continue(
427456
self, is_internal: bool = False
@@ -458,11 +487,11 @@ async def continue_route() -> None:
458487
return continue_route()
459488

460489
async def _redirected_navigation_request(self, url: str) -> None:
461-
self._check_not_handled()
462-
await self._race_with_page_close(
463-
self._channel.send("redirectNavigationRequest", {"url": url})
490+
await self._handle_route(
491+
lambda: self._race_with_page_close(
492+
self._channel.send("redirectNavigationRequest", {"url": url})
493+
)
464494
)
465-
self._report_handled(True)
466495

467496
async def _race_with_page_close(self, future: Coroutine) -> None:
468497
fut = asyncio.create_task(future)

0 commit comments

Comments
 (0)