From 4dbb9299931e23d4fc8b955db1ae3e021ef33c45 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 30 Apr 2026 17:28:03 +0100 Subject: [PATCH 1/2] fix(typing): add typed overloads for expect_event and wait_for_event Generates @typing.overload stubs keyed on Literal event names with their payload types from api.json, so pyright/mypy can narrow the return type at call sites instead of falling back to Any/Unknown. Fixes: https://github.com/microsoft/playwright-python/issues/3047 --- playwright/async_api/_generated.py | 729 +++++++++++++++++++++- playwright/sync_api/_generated.py | 729 +++++++++++++++++++++- scripts/documentation_provider.py | 49 ++ scripts/generate_async_api.py | 2 + scripts/generate_sync_api.py | 2 + tests/async/test_browsercontext_events.py | 2 +- tests/async/test_resource_timing.py | 5 +- tests/sync/test_resource_timing.py | 6 +- 8 files changed, 1488 insertions(+), 36 deletions(-) diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 130230390..9e8737c12 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -1120,6 +1120,55 @@ def url(self) -> str: """ return mapping.from_maybe_impl(self._impl_obj.url) + @typing.overload + def expect_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["WebSocket"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framereceived"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["typing.Union[bytes, str]"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framesent"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["typing.Union[bytes, str]"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["socketerror"], + predicate: typing.Optional[typing.Callable[["str"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["str"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager[typing.Any]: ... + def expect_event( self, event: str, @@ -1153,6 +1202,55 @@ def expect_event( ).future ) + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "WebSocket": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["framereceived"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "typing.Union[bytes, str]": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["framesent"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "typing.Union[bytes, str]": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["socketerror"], + predicate: typing.Optional[typing.Callable[["str"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "str": ... + + @typing.overload + async def wait_for_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> typing.Any: ... + async def wait_for_event( self, event: str, @@ -6737,6 +6835,33 @@ async def evaluate_handle( ) ) + @typing.overload + def expect_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Worker"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["ConsoleMessage"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager[typing.Any]: ... + def expect_event( self, event: str, @@ -9531,6 +9656,186 @@ async def wait_for_url( ) ) + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "ConsoleMessage": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["crash"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Dialog": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["domcontentloaded"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["download"], + predicate: typing.Optional[typing.Callable[["Download"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Download": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["filechooser"], + predicate: typing.Optional[typing.Callable[["FileChooser"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "FileChooser": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["frameattached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Frame": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["framedetached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Frame": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["framenavigated"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Frame": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["load"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["pageerror"], + predicate: typing.Optional[typing.Callable[["Error"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Error": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["popup"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Response": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["websocket"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "WebSocket": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["worker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Worker": ... + + @typing.overload + async def wait_for_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> typing.Any: ... + async def wait_for_event( self, event: str, @@ -12074,28 +12379,208 @@ async def pdf( ) ) + @typing.overload def expect_event( self, - event: str, - predicate: typing.Optional[typing.Callable] = None, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, *, timeout: typing.Optional[float] = None, - ) -> AsyncEventContextManager: - """Page.expect_event + ) -> AsyncEventContextManager["Page"]: ... - Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy - value. Will throw an error if the page is closed before the event is fired. Returns the event data value. + @typing.overload + def expect_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["ConsoleMessage"]: ... - **Usage** + @typing.overload + def expect_event( + self, + event: typing.Literal["crash"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Page"]: ... - ```py - async with page.expect_event(\"framenavigated\") as event_info: - await page.get_by_role(\"button\") - frame = await event_info.value - ``` + @typing.overload + def expect_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Dialog"]: ... - Parameters - ---------- + @typing.overload + def expect_event( + self, + event: typing.Literal["domcontentloaded"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["download"], + predicate: typing.Optional[typing.Callable[["Download"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Download"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["filechooser"], + predicate: typing.Optional[typing.Callable[["FileChooser"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["FileChooser"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["frameattached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Frame"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framedetached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Frame"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framenavigated"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Frame"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["load"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["pageerror"], + predicate: typing.Optional[typing.Callable[["Error"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Error"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["popup"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Response"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["websocket"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["WebSocket"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["worker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Worker"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager[typing.Any]: ... + + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager: + """Page.expect_event + + Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy + value. Will throw an error if the page is closed before the event is fired. Returns the event data value. + + **Usage** + + ```py + async with page.expect_event(\"framenavigated\") as event_info: + await page.get_by_role(\"button\") + frame = await event_info.value + ``` + + Parameters + ---------- event : str Event name, same one typically passed into `*.on(event)`. predicate : Union[Callable, None] @@ -13964,6 +14449,114 @@ async def route_from_har( ) ) + @typing.overload + def expect_event( + self, + event: typing.Literal["backgroundpage"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["BrowserContext"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["BrowserContext"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["ConsoleMessage"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Dialog"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["page"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["weberror"], + predicate: typing.Optional[typing.Callable[["WebError"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["WebError"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Response"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["serviceworker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager["Worker"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> AsyncEventContextManager[typing.Any]: ... + def expect_event( self, event: str, @@ -14089,6 +14682,114 @@ async def set_storage_state( await self._impl_obj.set_storage_state(storageState=storage_state) ) + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["backgroundpage"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["BrowserContext"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "BrowserContext": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "ConsoleMessage": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Dialog": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["page"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["weberror"], + predicate: typing.Optional[typing.Callable[["WebError"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "WebError": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Response": ... + + @typing.overload + async def wait_for_event( + self, + event: typing.Literal["serviceworker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Worker": ... + + @typing.overload + async def wait_for_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> typing.Any: ... + async def wait_for_event( self, event: str, diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index f7a8a2c24..13139abec 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -1112,6 +1112,55 @@ def url(self) -> str: """ return mapping.from_maybe_impl(self._impl_obj.url) + @typing.overload + def expect_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["WebSocket"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framereceived"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["typing.Union[bytes, str]"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framesent"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["typing.Union[bytes, str]"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["socketerror"], + predicate: typing.Optional[typing.Callable[["str"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["str"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager[typing.Any]: ... + def expect_event( self, event: str, @@ -1145,6 +1194,55 @@ def expect_event( ).future, ) + @typing.overload + def wait_for_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "WebSocket": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["framereceived"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "typing.Union[bytes, str]": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["framesent"], + predicate: typing.Optional[ + typing.Callable[["typing.Union[bytes, str]"], bool] + ] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "typing.Union[bytes, str]": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["socketerror"], + predicate: typing.Optional[typing.Callable[["str"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "str": ... + + @typing.overload + def wait_for_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> typing.Any: ... + def wait_for_event( self, event: str, @@ -6825,6 +6923,33 @@ def evaluate_handle( ) ) + @typing.overload + def expect_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Worker"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["ConsoleMessage"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager[typing.Any]: ... + def expect_event( self, event: str, @@ -9532,6 +9657,186 @@ def wait_for_url( ) ) + @typing.overload + def wait_for_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "ConsoleMessage": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["crash"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Dialog": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["domcontentloaded"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["download"], + predicate: typing.Optional[typing.Callable[["Download"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Download": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["filechooser"], + predicate: typing.Optional[typing.Callable[["FileChooser"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "FileChooser": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["frameattached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Frame": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["framedetached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Frame": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["framenavigated"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Frame": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["load"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["pageerror"], + predicate: typing.Optional[typing.Callable[["Error"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Error": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["popup"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Response": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["websocket"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "WebSocket": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["worker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Worker": ... + + @typing.overload + def wait_for_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> typing.Any: ... + def wait_for_event( self, event: str, @@ -12130,28 +12435,208 @@ def pdf( ) ) + @typing.overload def expect_event( self, - event: str, - predicate: typing.Optional[typing.Callable] = None, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, *, timeout: typing.Optional[float] = None, - ) -> EventContextManager: - """Page.expect_event + ) -> EventContextManager["Page"]: ... - Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy - value. Will throw an error if the page is closed before the event is fired. Returns the event data value. + @typing.overload + def expect_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["ConsoleMessage"]: ... - **Usage** + @typing.overload + def expect_event( + self, + event: typing.Literal["crash"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Page"]: ... - ```py - with page.expect_event(\"framenavigated\") as event_info: - page.get_by_role(\"button\") - frame = event_info.value - ``` + @typing.overload + def expect_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Dialog"]: ... - Parameters - ---------- + @typing.overload + def expect_event( + self, + event: typing.Literal["domcontentloaded"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["download"], + predicate: typing.Optional[typing.Callable[["Download"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Download"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["filechooser"], + predicate: typing.Optional[typing.Callable[["FileChooser"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["FileChooser"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["frameattached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Frame"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framedetached"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Frame"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["framenavigated"], + predicate: typing.Optional[typing.Callable[["Frame"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Frame"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["load"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["pageerror"], + predicate: typing.Optional[typing.Callable[["Error"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Error"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["popup"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Response"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["websocket"], + predicate: typing.Optional[typing.Callable[["WebSocket"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["WebSocket"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["worker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Worker"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager[typing.Any]: ... + + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager: + """Page.expect_event + + Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy + value. Will throw an error if the page is closed before the event is fired. Returns the event data value. + + **Usage** + + ```py + with page.expect_event(\"framenavigated\") as event_info: + page.get_by_role(\"button\") + frame = event_info.value + ``` + + Parameters + ---------- event : str Event name, same one typically passed into `*.on(event)`. predicate : Union[Callable, None] @@ -13967,6 +14452,114 @@ def route_from_har( ) ) + @typing.overload + def expect_event( + self, + event: typing.Literal["backgroundpage"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["BrowserContext"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["BrowserContext"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["ConsoleMessage"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Dialog"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["page"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Page"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["weberror"], + predicate: typing.Optional[typing.Callable[["WebError"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["WebError"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Request"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Response"]: ... + + @typing.overload + def expect_event( + self, + event: typing.Literal["serviceworker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager["Worker"]: ... + + @typing.overload + def expect_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> EventContextManager[typing.Any]: ... + def expect_event( self, event: str, @@ -14092,6 +14685,114 @@ def set_storage_state( self._sync(self._impl_obj.set_storage_state(storageState=storage_state)) ) + @typing.overload + def wait_for_event( + self, + event: typing.Literal["backgroundpage"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["close"], + predicate: typing.Optional[typing.Callable[["BrowserContext"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "BrowserContext": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["console"], + predicate: typing.Optional[typing.Callable[["ConsoleMessage"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "ConsoleMessage": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["dialog"], + predicate: typing.Optional[typing.Callable[["Dialog"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Dialog": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["page"], + predicate: typing.Optional[typing.Callable[["Page"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Page": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["weberror"], + predicate: typing.Optional[typing.Callable[["WebError"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "WebError": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["request"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["requestfailed"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["requestfinished"], + predicate: typing.Optional[typing.Callable[["Request"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Request": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["response"], + predicate: typing.Optional[typing.Callable[["Response"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Response": ... + + @typing.overload + def wait_for_event( + self, + event: typing.Literal["serviceworker"], + predicate: typing.Optional[typing.Callable[["Worker"], bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> "Worker": ... + + @typing.overload + def wait_for_event( + self, + event: str, + predicate: typing.Optional[typing.Callable[..., bool]] = None, + *, + timeout: typing.Optional[float] = None, + ) -> typing.Any: ... + def wait_for_event( self, event: str, diff --git a/scripts/documentation_provider.py b/scripts/documentation_provider.py index 4c2d9b95d..e3f1f1ebd 100644 --- a/scripts/documentation_provider.py +++ b/scripts/documentation_provider.py @@ -243,6 +243,55 @@ def print_events(self, class_name: str) -> None: doc.append(f" return super().{event_type}(event=event,f=f)") print("\n".join(doc)) + def print_event_overloads(self, class_name: str, method_name: str) -> None: + """Emit ``@typing.overload`` stubs for ``expect_event`` / ``wait_for_event`` + keyed on ``Literal`` event names with their payload types from api.json, + so pyright/mypy can narrow the return type at call sites. + Must be called right before the implementation signature is emitted. + """ + if class_name not in self.classes: + return + events = self.classes[class_name].get("events") or [] + if not events: + return + is_expect = method_name == "expect_event" + async_prefix = "async " if not is_expect and self.is_async else "" + if is_expect: + ctx_mgr = ( + "AsyncEventContextManager" if self.is_async else "EventContextManager" + ) + for event in events: + payload = self.serialize_doc_type(event["type"], "") + if payload.startswith("{"): + payload = "typing.Dict" + if "Union[" in payload: + payload = payload.replace("Union[", "typing.Union[") + return_type = f'{ctx_mgr}["{payload}"]' if is_expect else f'"{payload}"' + event_literal = event["name"].lower() + print(" @typing.overload") + print(f" {async_prefix}def {method_name}(") + print(" self,") + print(f' event: typing.Literal["{event_literal}"],') + print( + f' predicate: typing.Optional[typing.Callable[["{payload}"], bool]] = None,' + ) + print(" *,") + print(" timeout: typing.Optional[float] = None,") + print(f" ) -> {return_type}: ...") + print("") + # Catch-all overload for non-literal event names — keeps pyright happy + # with `event: str` callers without falling through to `Unknown`. + catchall_return = f"{ctx_mgr}[typing.Any]" if is_expect else "typing.Any" + print(" @typing.overload") + print(f" {async_prefix}def {method_name}(") + print(" self,") + print(" event: str,") + print(" predicate: typing.Optional[typing.Callable[..., bool]] = None,") + print(" *,") + print(" timeout: typing.Optional[float] = None,") + print(f" ) -> {catchall_return}: ...") + print("") + def indent_paragraph(self, p: str, indent: str) -> str: lines = p.split("\n") result = [lines[0]] diff --git a/scripts/generate_async_api.py b/scripts/generate_async_api.py index 4c0b4b655..e844bc400 100755 --- a/scripts/generate_async_api.py +++ b/scripts/generate_async_api.py @@ -92,6 +92,8 @@ def generate(t: Any) -> None: '"Disposable"', '"AsyncContextManager"' ).replace('"DisposableStub"', '"AsyncContextManager"') print("") + if name in ("expect_event", "wait_for_event"): + documentation_provider.print_event_overloads(class_name, name) async_prefix = "async " if is_async else "" print( f" {async_prefix}def {name}({signature(value, len(name) + 9)}) -> {return_type_value}:" diff --git a/scripts/generate_sync_api.py b/scripts/generate_sync_api.py index dbabce413..fa6ac8702 100755 --- a/scripts/generate_sync_api.py +++ b/scripts/generate_sync_api.py @@ -93,6 +93,8 @@ def generate(t: Any) -> None: '"Disposable"', '"SyncContextManager"' ).replace('"DisposableStub"', '"SyncContextManager"') print("") + if name in ("expect_event", "wait_for_event"): + documentation_provider.print_event_overloads(class_name, name) print( f" def {name}({signature(value, len(name) + 9)}) -> {return_type_value}:" ) diff --git a/tests/async/test_browsercontext_events.py b/tests/async/test_browsercontext_events.py index 8ae14def6..cfe585c77 100644 --- a/tests/async/test_browsercontext_events.py +++ b/tests/async/test_browsercontext_events.py @@ -197,7 +197,7 @@ async def test_page_error_event_should_work(page: Page) -> None: await page.set_content('') page_error = await page_error_info.value assert page_error.page == page - assert "boom" in page_error.error.stack + assert page_error.error.stack and "boom" in page_error.error.stack async def test_weberror_event_should_work(context: BrowserContext, page: Page) -> None: diff --git a/tests/async/test_resource_timing.py b/tests/async/test_resource_timing.py index a8481b8c8..e764e3159 100644 --- a/tests/async/test_resource_timing.py +++ b/tests/async/test_resource_timing.py @@ -12,11 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict import pytest -from playwright.async_api import Browser, Page +from playwright.async_api import Browser, Page, ResourceTiming from tests.server import Server @@ -95,7 +94,7 @@ def verify_timing_value(value: float, previous: float) -> None: assert value == -1 or value > 0 and value >= previous -def verify_connections_timing_consistency(timing: Dict) -> None: +def verify_connections_timing_consistency(timing: ResourceTiming) -> None: verify_timing_value(timing["domainLookupStart"], -1) verify_timing_value(timing["domainLookupEnd"], timing["domainLookupStart"]) verify_timing_value(timing["connectStart"], timing["domainLookupEnd"]) diff --git a/tests/sync/test_resource_timing.py b/tests/sync/test_resource_timing.py index a5bd8dd8a..1c6efab6a 100644 --- a/tests/sync/test_resource_timing.py +++ b/tests/sync/test_resource_timing.py @@ -12,11 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict - import pytest -from playwright.sync_api import Browser, Page +from playwright.sync_api import Browser, Page, ResourceTiming from tests.server import Server @@ -99,7 +97,7 @@ def verify_timing_value(value: float, previous: float) -> None: assert value == -1 or value > 0 and value >= previous -def verify_connections_timing_consistency(timing: Dict) -> None: +def verify_connections_timing_consistency(timing: ResourceTiming) -> None: verify_timing_value(timing["domainLookupStart"], -1) verify_timing_value(timing["domainLookupEnd"], timing["domainLookupStart"]) verify_timing_value(timing["connectStart"], timing["domainLookupEnd"]) From b430f0960656ce23cc8699cbe8ea46509b705846 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 30 Apr 2026 18:24:41 +0100 Subject: [PATCH 2/2] fix(tests): remove redundant cast after typed wait_for_event overloads --- tests/async/test_page_request_intercept.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/async/test_page_request_intercept.py b/tests/async/test_page_request_intercept.py index a9859e87b..d294031d5 100644 --- a/tests/async/test_page_request_intercept.py +++ b/tests/async/test_page_request_intercept.py @@ -13,7 +13,6 @@ # limitations under the License. import asyncio -from typing import cast import pytest @@ -98,4 +97,4 @@ async def route_handler(route: Route) -> None: [popup, _] = await asyncio.gather( page.wait_for_event("popup"), page.get_by_text("click me").click() ) - await expect(cast(Page, popup).locator("body")).to_have_text("hello") + await expect(popup.locator("body")).to_have_text("hello")