Skip to content

Commit 875b380

Browse files
authored
feat(expect): add expect_navigation, expect_load_state, navigation tests (microsoft#118)
1 parent c36ccc4 commit 875b380

16 files changed

+1000
-305
lines changed

playwright/async_api.py

Lines changed: 110 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@
5050
from playwright.network import Route as RouteImpl
5151
from playwright.page import BindingCall as BindingCallImpl
5252
from playwright.page import Page as PageImpl
53+
from playwright.page import Worker as WorkerImpl
5354
from playwright.playwright import Playwright as PlaywrightImpl
5455
from playwright.selectors import Selectors as SelectorsImpl
55-
from playwright.worker import Worker as WorkerImpl
5656

5757
NoneType = type(None)
5858

@@ -1622,9 +1622,9 @@ async def goto(
16221622

16231623
async def waitForNavigation(
16241624
self,
1625-
timeout: int = None,
1626-
waitUntil: Literal["load", "domcontentloaded", "networkidle"] = None,
16271625
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None,
1626+
waitUntil: Literal["load", "domcontentloaded", "networkidle"] = None,
1627+
timeout: int = None,
16281628
) -> typing.Union["Response", NoneType]:
16291629
"""Frame.waitForNavigation
16301630
@@ -1634,15 +1634,15 @@ async def waitForNavigation(
16341634
16351635
Parameters
16361636
----------
1637-
timeout : Optional[int]
1638-
Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
1637+
url : Optional[str, typing.Pattern, typing.Callable[[str], bool]]
1638+
URL string, URL regex pattern or predicate receiving URL to match while waiting for the navigation.
16391639
waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']]
16401640
When to consider navigation succeeded, defaults to `load`. Events can be either:
16411641
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
16421642
- `'load'` - consider navigation to be finished when the `load` event is fired.
16431643
- `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
1644-
url : Optional[str, typing.Pattern, typing.Callable[[str], bool]]
1645-
URL string, URL regex pattern or predicate receiving URL to match while waiting for the navigation.
1644+
timeout : Optional[int]
1645+
Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
16461646
16471647
Returns
16481648
-------
@@ -1651,7 +1651,7 @@ async def waitForNavigation(
16511651
"""
16521652
return mapping.from_impl_nullable(
16531653
await self._impl_obj.waitForNavigation(
1654-
timeout=timeout, waitUntil=waitUntil, url=self._wrap_handler(url)
1654+
url=self._wrap_handler(url), waitUntil=waitUntil, timeout=timeout
16551655
)
16561656
)
16571657

@@ -2597,6 +2597,23 @@ async def title(self) -> str:
25972597
"""
25982598
return mapping.from_maybe_impl(await self._impl_obj.title())
25992599

2600+
def expect_load_state(
2601+
self,
2602+
state: Literal["load", "domcontentloaded", "networkidle"] = None,
2603+
timeout: int = None,
2604+
) -> AsyncEventContextManager[typing.Union["Response", NoneType]]:
2605+
return AsyncEventContextManager(self._impl_obj.waitForLoadState(state, timeout))
2606+
2607+
def expect_navigation(
2608+
self,
2609+
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None,
2610+
waitUntil: Literal["load", "domcontentloaded", "networkidle"] = None,
2611+
timeout: int = None,
2612+
) -> AsyncEventContextManager[typing.Union["Response", NoneType]]:
2613+
return AsyncEventContextManager(
2614+
self._impl_obj.waitForNavigation(url, waitUntil, timeout)
2615+
)
2616+
26002617

26012618
mapping.register(FrameImpl, Frame)
26022619

@@ -3577,9 +3594,9 @@ async def waitForLoadState(
35773594

35783595
async def waitForNavigation(
35793596
self,
3580-
timeout: int = None,
3597+
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None,
35813598
waitUntil: Literal["load", "domcontentloaded", "networkidle"] = None,
3582-
url: str = None,
3599+
timeout: int = None,
35833600
) -> typing.Union["Response", NoneType]:
35843601
"""Page.waitForNavigation
35853602
@@ -3590,15 +3607,15 @@ async def waitForNavigation(
35903607
35913608
Parameters
35923609
----------
3593-
timeout : Optional[int]
3594-
Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
3610+
url : Optional[str, typing.Pattern, typing.Callable[[str], bool]]
3611+
A glob pattern, regex pattern or predicate receiving URL to match while waiting for the navigation.
35953612
waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']]
35963613
When to consider navigation succeeded, defaults to `load`. Events can be either:
35973614
- `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired.
35983615
- `'load'` - consider navigation to be finished when the `load` event is fired.
35993616
- `'networkidle'` - consider navigation to be finished when there are no network connections for at least `500` ms.
3600-
url : Optional[str]
3601-
A glob pattern, regex pattern or predicate receiving URL to match while waiting for the navigation.
3617+
timeout : Optional[int]
3618+
Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
36023619
36033620
Returns
36043621
-------
@@ -3607,7 +3624,7 @@ async def waitForNavigation(
36073624
"""
36083625
return mapping.from_impl_nullable(
36093626
await self._impl_obj.waitForNavigation(
3610-
timeout=timeout, waitUntil=waitUntil, url=url
3627+
url=self._wrap_handler(url), waitUntil=waitUntil, timeout=timeout
36113628
)
36123629
)
36133630

@@ -4634,63 +4651,112 @@ async def pdf(
46344651
)
46354652
)
46364653

4654+
def expect_event(
4655+
self,
4656+
event: str,
4657+
predicate: typing.Union[typing.Callable[[typing.Any], bool]] = None,
4658+
timeout: int = None,
4659+
) -> AsyncEventContextManager:
4660+
return AsyncEventContextManager(
4661+
self._impl_obj.waitForEvent(event, predicate, timeout)
4662+
)
4663+
46374664
def expect_console_message(
46384665
self,
46394666
predicate: typing.Union[typing.Callable[["ConsoleMessage"], bool]] = None,
46404667
timeout: int = None,
46414668
) -> AsyncEventContextManager["ConsoleMessage"]:
4642-
return AsyncEventContextManager(self, "console", predicate, timeout)
4669+
event = "console"
4670+
return AsyncEventContextManager(
4671+
self._impl_obj.waitForEvent(event, predicate, timeout)
4672+
)
46434673

46444674
def expect_dialog(
46454675
self,
46464676
predicate: typing.Union[typing.Callable[["Dialog"], bool]] = None,
46474677
timeout: int = None,
46484678
) -> AsyncEventContextManager["Dialog"]:
4649-
return AsyncEventContextManager(self, "dialog", predicate, timeout)
4679+
event = "dialog"
4680+
return AsyncEventContextManager(
4681+
self._impl_obj.waitForEvent(event, predicate, timeout)
4682+
)
46504683

46514684
def expect_download(
46524685
self,
46534686
predicate: typing.Union[typing.Callable[["Download"], bool]] = None,
46544687
timeout: int = None,
46554688
) -> AsyncEventContextManager["Download"]:
4656-
return AsyncEventContextManager(self, "download", predicate, timeout)
4689+
event = "download"
4690+
return AsyncEventContextManager(
4691+
self._impl_obj.waitForEvent(event, predicate, timeout)
4692+
)
46574693

46584694
def expect_file_chooser(
46594695
self,
46604696
predicate: typing.Union[typing.Callable[["FileChooser"], bool]] = None,
46614697
timeout: int = None,
46624698
) -> AsyncEventContextManager["FileChooser"]:
4663-
return AsyncEventContextManager(self, "filechooser", predicate, timeout)
4699+
event = "filechooser"
4700+
return AsyncEventContextManager(
4701+
self._impl_obj.waitForEvent(event, predicate, timeout)
4702+
)
46644703

4665-
def expect_request(
4704+
def expect_load_state(
46664705
self,
4667-
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None,
4668-
predicate: typing.Union[typing.Callable[["Request"], bool]] = None,
4706+
state: Literal["load", "domcontentloaded", "networkidle"] = None,
46694707
timeout: int = None,
4670-
) -> AsyncEventContextManager["Request"]:
4671-
return AsyncEventContextManager(self, "request", predicate, timeout)
4708+
) -> AsyncEventContextManager[typing.Union["Response", NoneType]]:
4709+
return AsyncEventContextManager(self._impl_obj.waitForLoadState(state, timeout))
46724710

4673-
def expect_response(
4711+
def expect_navigation(
46744712
self,
46754713
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None,
4676-
predicate: typing.Union[typing.Callable[["Response"], bool]] = None,
4714+
waitUntil: Literal["load", "domcontentloaded", "networkidle"] = None,
46774715
timeout: int = None,
4678-
) -> AsyncEventContextManager["Response"]:
4679-
return AsyncEventContextManager(self, "response", predicate, timeout)
4716+
) -> AsyncEventContextManager[typing.Union["Response", NoneType]]:
4717+
return AsyncEventContextManager(
4718+
self._impl_obj.waitForNavigation(url, waitUntil, timeout)
4719+
)
46804720

46814721
def expect_popup(
46824722
self,
46834723
predicate: typing.Union[typing.Callable[["Page"], bool]] = None,
46844724
timeout: int = None,
46854725
) -> AsyncEventContextManager["Page"]:
4686-
return AsyncEventContextManager(self, "popup", predicate, timeout)
4726+
event = "popup"
4727+
return AsyncEventContextManager(
4728+
self._impl_obj.waitForEvent(event, predicate, timeout)
4729+
)
4730+
4731+
def expect_request(
4732+
self,
4733+
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None,
4734+
predicate: typing.Union[typing.Callable[["Request"], bool]] = None,
4735+
timeout: int = None,
4736+
) -> AsyncEventContextManager["Request"]:
4737+
return AsyncEventContextManager(
4738+
self._impl_obj.waitForRequest(url, predicate, timeout)
4739+
)
4740+
4741+
def expect_response(
4742+
self,
4743+
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] = None,
4744+
predicate: typing.Union[typing.Callable[["Request"], bool]] = None,
4745+
timeout: int = None,
4746+
) -> AsyncEventContextManager["Response"]:
4747+
return AsyncEventContextManager(
4748+
self._impl_obj.waitForResponse(url, predicate, timeout)
4749+
)
46874750

46884751
def expect_worker(
46894752
self,
46904753
predicate: typing.Union[typing.Callable[["Worker"], bool]] = None,
46914754
timeout: int = None,
46924755
) -> AsyncEventContextManager["Worker"]:
4693-
return AsyncEventContextManager(self, "worker", predicate, timeout)
4756+
event = "worker"
4757+
return AsyncEventContextManager(
4758+
self._impl_obj.waitForEvent(event, predicate, timeout)
4759+
)
46944760

46954761

46964762
mapping.register(PageImpl, Page)
@@ -5036,12 +5102,25 @@ async def close(self) -> NoneType:
50365102
"""
50375103
return mapping.from_maybe_impl(await self._impl_obj.close())
50385104

5105+
def expect_event(
5106+
self,
5107+
event: str,
5108+
predicate: typing.Union[typing.Callable[[typing.Any], bool]] = None,
5109+
timeout: int = None,
5110+
) -> AsyncEventContextManager:
5111+
return AsyncEventContextManager(
5112+
self._impl_obj.waitForEvent(event, predicate, timeout)
5113+
)
5114+
50395115
def expect_page(
50405116
self,
50415117
predicate: typing.Union[typing.Callable[["Page"], bool]] = None,
50425118
timeout: int = None,
50435119
) -> AsyncEventContextManager["Page"]:
5044-
return AsyncEventContextManager(self, "page", predicate, timeout)
5120+
event = "page"
5121+
return AsyncEventContextManager(
5122+
self._impl_obj.waitForEvent(event, predicate, timeout)
5123+
)
50455124

50465125

50475126
mapping.register(BrowserContextImpl, BrowserContext)

playwright/async_base.py

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@
1313
# limitations under the License.
1414

1515
import asyncio
16-
from typing import Any, Callable, Generic, Optional, TypeVar, cast
16+
from typing import Any, Callable, Coroutine, Generic, Optional, TypeVar, cast
1717

1818
from playwright.impl_to_api_mapping import ImplToApiMapping, ImplWrapper
19-
from playwright.wait_helper import WaitHelper
2019

2120
mapping = ImplToApiMapping()
2221

@@ -25,39 +24,22 @@
2524

2625

2726
class AsyncEventInfo(Generic[T]):
28-
def __init__(
29-
self,
30-
async_base: "AsyncBase",
31-
event: str,
32-
predicate: Callable[[T], bool] = None,
33-
timeout: int = None,
34-
) -> None:
27+
def __init__(self, coroutine: Coroutine) -> None:
3528
self._value: Optional[T] = None
36-
37-
wait_helper = WaitHelper(async_base._loop)
38-
wait_helper.reject_on_timeout(
39-
timeout or 30000, f'Timeout while waiting for event "${event}"'
40-
)
41-
self._future = asyncio.get_event_loop().create_task(
42-
wait_helper.wait_for_event(async_base._impl_obj, event, predicate)
43-
)
29+
self._future = asyncio.get_event_loop().create_task(coroutine)
30+
self._done = False
4431

4532
@property
4633
async def value(self) -> T:
47-
if not self._value:
34+
if not self._done:
4835
self._value = mapping.from_maybe_impl(await self._future)
36+
self._done = True
4937
return cast(T, self._value)
5038

5139

5240
class AsyncEventContextManager(Generic[T]):
53-
def __init__(
54-
self,
55-
async_base: "AsyncBase",
56-
event: str,
57-
predicate: Callable[[T], bool] = None,
58-
timeout: int = None,
59-
) -> None:
60-
self._event = AsyncEventInfo(async_base, event, predicate, timeout)
41+
def __init__(self, coroutine: Coroutine) -> None:
42+
self._event: AsyncEventInfo = AsyncEventInfo(coroutine)
6143

6244
async def __aenter__(self) -> AsyncEventInfo[T]:
6345
return self._event
@@ -90,8 +72,3 @@ def once(self, event_name: str, handler: Any) -> None:
9072

9173
def remove_listener(self, event_name: str, handler: Any) -> None:
9274
self._impl_obj.remove_listener(event_name, handler)
93-
94-
def expect_event(
95-
self, event: str, predicate: Callable[[Any], bool] = None, timeout: int = None,
96-
) -> AsyncEventContextManager:
97-
return AsyncEventContextManager(self, event, predicate, timeout)

playwright/browser_context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,9 @@ async def close(self) -> None:
216216
def expect_event(
217217
self, event: str, predicate: Callable[[Any], bool] = None, timeout: int = None,
218218
) -> EventContextManagerImpl:
219-
return EventContextManagerImpl(self, event, predicate, timeout)
219+
return EventContextManagerImpl(self.waitForEvent(event, predicate, timeout))
220220

221221
def expect_page(
222222
self, predicate: Callable[[Page], bool] = None, timeout: int = None,
223223
) -> EventContextManagerImpl[Page]:
224-
return EventContextManagerImpl(self, "page", predicate, timeout)
224+
return EventContextManagerImpl(self.waitForEvent("page", predicate, timeout))

0 commit comments

Comments
 (0)