diff --git a/README.md b/README.md index b3ce572b8..3c0a612e3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 96.0.4652.0 | ✅ | ✅ | ✅ | +| Chromium 96.0.4655.0 | ✅ | ✅ | ✅ | | WebKit 15.0 | ✅ | ✅ | ✅ | | Firefox 92.0 | ✅ | ✅ | ✅ | diff --git a/playwright/_impl/_async_base.py b/playwright/_impl/_async_base.py index 8c15f6944..b2f02e386 100644 --- a/playwright/_impl/_async_base.py +++ b/playwright/_impl/_async_base.py @@ -93,12 +93,12 @@ async def __aenter__(self: Self) -> Self: return self async def __aexit__( - self: Self, + self, exc_type: Type[BaseException], exc_val: BaseException, traceback: TracebackType, ) -> None: await self.close() - async def close(self: Self) -> None: + async def close(self) -> None: ... diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index 44e311e79..f5b03e7ee 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -67,7 +67,7 @@ class ErrorPayload(TypedDict, total=False): message: str name: str stack: str - value: Any + value: Optional[Any] class ContinueParameters(TypedDict, total=False): @@ -159,7 +159,9 @@ def navigation_timeout(self) -> float: def serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload: - return dict(message=str(ex), name="Error", stack="".join(traceback.format_tb(tb))) + return ErrorPayload( + message=str(ex), name="Error", stack="".join(traceback.format_tb(tb)) + ) def parse_error(error: ErrorPayload) -> Error: diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index cad755dd7..8cc0b7a36 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -24,7 +24,6 @@ Optional, TypeVar, Union, - cast, ) from playwright._impl._api_structures import FilePayload, FloatRect, Position @@ -173,12 +172,11 @@ async def element_handle( timeout: float = None, ) -> ElementHandle: params = locals_to_params(locals()) - return cast( - ElementHandle, - await self._frame.wait_for_selector( - self._selector, strict=True, state="attached", **params - ), + handle = await self._frame.wait_for_selector( + self._selector, strict=True, state="attached", **params ) + assert handle + return handle async def element_handles(self) -> List[ElementHandle]: return await self._frame.query_selector_all(self._selector) @@ -201,7 +199,7 @@ async def focus(self, timeout: float = None) -> None: async def count( self, ) -> int: - return cast(int, await self.evaluate_all("ee => ee.length")) + return int(await self.evaluate_all("ee => ee.length")) async def get_attribute(self, name: str, timeout: float = None) -> Optional[str]: params = locals_to_params(locals()) @@ -439,6 +437,15 @@ async def all_text_contents( self._selector, "ee => ee.map(e => e.textContent || '')" ) + async def wait_for( + self, + timeout: float = None, + state: Literal["attached", "detached", "hidden", "visible"] = None, + ) -> None: + await self._frame.wait_for_selector( + self._selector, strict=True, timeout=timeout, state=state + ) + async def set_checked( self, checked: bool, diff --git a/playwright/_impl/_sync_base.py b/playwright/_impl/_sync_base.py index 93d0784f1..36877d4b1 100644 --- a/playwright/_impl/_sync_base.py +++ b/playwright/_impl/_sync_base.py @@ -167,12 +167,12 @@ def __enter__(self: Self) -> Self: return self def __exit__( - self: Self, + self, exc_type: Type[BaseException], exc_val: BaseException, traceback: TracebackType, ) -> None: self.close() - def close(self: Self) -> None: + def close(self) -> None: ... diff --git a/playwright/_impl/_transport.py b/playwright/_impl/_transport.py index 5c7d6fc41..c6c333f4c 100644 --- a/playwright/_impl/_transport.py +++ b/playwright/_impl/_transport.py @@ -23,6 +23,7 @@ from typing import Callable, Dict, Optional, Union import websockets +import websockets.exceptions from pyee import AsyncIOEventEmitter from websockets.client import connect as websocket_connect diff --git a/playwright/async_api/__init__.py b/playwright/async_api/__init__.py index 046755ea7..f10058795 100644 --- a/playwright/async_api/__init__.py +++ b/playwright/async_api/__init__.py @@ -76,7 +76,6 @@ def async_playwright() -> PlaywrightContextManager: __all__ = [ "async_playwright", "Accessibility", - "BindingCall", "Browser", "BrowserContext", "BrowserType", @@ -110,7 +109,6 @@ def async_playwright() -> PlaywrightContextManager: "Selectors", "SourceLocation", "StorageState", - "sync_playwright", "TimeoutError", "Touchscreen", "Video", diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 9b760c1b9..5dab86b9c 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -11497,7 +11497,8 @@ async def launch_persistent_context( Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for [Chromium](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction) and [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile). Note that Chromium's user - data directory is the **parent** directory of the "Profile Path" seen at `chrome://version`. + data directory is the **parent** directory of the "Profile Path" seen at `chrome://version`. Pass an empty string to use + a temporary directory instead. channel : Union[str, NoneType] Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary". Read more about using @@ -13345,6 +13346,46 @@ async def all_text_contents(self) -> typing.List[str]: ) ) + async def wait_for( + self, + *, + timeout: float = None, + state: Literal["attached", "detached", "hidden", "visible"] = None + ) -> NoneType: + """Locator.wait_for + + Returns when element specified by locator satisfies the `state` option. + + If target element already satisfies the condition, the method returns immediately. Otherwise, waits for up to `timeout` + milliseconds until the condition is met. + + ```py + order_sent = page.locator(\"#order-sent\") + await order_sent.wait_for() + ``` + + Parameters + ---------- + timeout : Union[float, NoneType] + Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by + using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. + state : Union["attached", "detached", "hidden", "visible", NoneType] + Defaults to `'visible'`. Can be either: + - `'attached'` - wait for element to be present in DOM. + - `'detached'` - wait for element to not be present in DOM. + - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element without + any content or with `display:none` has an empty bounding box and is not considered visible. + - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or `visibility:hidden`. + This is opposite to the `'visible'` option. + """ + + return mapping.from_maybe_impl( + await self._async( + "locator.wait_for", + self._impl_obj.wait_for(timeout=timeout, state=state), + ) + ) + async def set_checked( self, checked: bool, diff --git a/playwright/sync_api/__init__.py b/playwright/sync_api/__init__.py index c7c5b284b..33d06016a 100644 --- a/playwright/sync_api/__init__.py +++ b/playwright/sync_api/__init__.py @@ -74,9 +74,7 @@ def sync_playwright() -> PlaywrightContextManager: __all__ = [ - "async_playwright", "Accessibility", - "BindingCall", "Browser", "BrowserContext", "BrowserType", diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index b1b141229..486970bae 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -11244,7 +11244,8 @@ def launch_persistent_context( Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for [Chromium](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction) and [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile). Note that Chromium's user - data directory is the **parent** directory of the "Profile Path" seen at `chrome://version`. + data directory is the **parent** directory of the "Profile Path" seen at `chrome://version`. Pass an empty string to use + a temporary directory instead. channel : Union[str, NoneType] Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary". Read more about using @@ -13074,6 +13075,46 @@ def all_text_contents(self) -> typing.List[str]: self._sync("locator.all_text_contents", self._impl_obj.all_text_contents()) ) + def wait_for( + self, + *, + timeout: float = None, + state: Literal["attached", "detached", "hidden", "visible"] = None + ) -> NoneType: + """Locator.wait_for + + Returns when element specified by locator satisfies the `state` option. + + If target element already satisfies the condition, the method returns immediately. Otherwise, waits for up to `timeout` + milliseconds until the condition is met. + + ```py + order_sent = page.locator(\"#order-sent\") + order_sent.wait_for() + ``` + + Parameters + ---------- + timeout : Union[float, NoneType] + Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by + using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. + state : Union["attached", "detached", "hidden", "visible", NoneType] + Defaults to `'visible'`. Can be either: + - `'attached'` - wait for element to be present in DOM. + - `'detached'` - wait for element to not be present in DOM. + - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element without + any content or with `display:none` has an empty bounding box and is not considered visible. + - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or `visibility:hidden`. + This is opposite to the `'visible'` option. + """ + + return mapping.from_maybe_impl( + self._sync( + "locator.wait_for", + self._impl_obj.wait_for(timeout=timeout, state=state), + ) + ) + def set_checked( self, checked: bool, diff --git a/setup.py b/setup.py index f10a5343b..d7c380434 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ InWheel = None from wheel.bdist_wheel import bdist_wheel as BDistWheelCommand -driver_version = "1.16.0-next-1632717011000" +driver_version = "1.16.0-next-1632960932000" def extractall(zip: zipfile.ZipFile, path: str) -> None: diff --git a/tests/async/test_locators.py b/tests/async/test_locators.py index 0e89b0e5d..12b6be300 100644 --- a/tests/async/test_locators.py +++ b/tests/async/test_locators.py @@ -465,3 +465,12 @@ async def test_locators_set_checked(page: Page): assert await page.evaluate("checkbox.checked") await locator.set_checked(False) assert await page.evaluate("checkbox.checked") is False + + +async def test_locators_wait_for(page: Page) -> None: + await page.set_content("
") + locator = page.locator("div") + task = locator.wait_for() + await page.eval_on_selector("div", "div => div.innerHTML = 'target'") + await task + assert await locator.text_content() == "target"