Skip to content
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->96.0.4652.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->96.0.4655.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->15.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->92.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

Expand Down
4 changes: 2 additions & 2 deletions playwright/_impl/_async_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
...
6 changes: 4 additions & 2 deletions playwright/_impl/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
21 changes: 14 additions & 7 deletions playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
Optional,
TypeVar,
Union,
cast,
)

from playwright._impl._api_structures import FilePayload, FloatRect, Position
Expand Down Expand Up @@ -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)
Expand All @@ -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())
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions playwright/_impl/_sync_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
...
1 change: 1 addition & 0 deletions playwright/_impl/_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 0 additions & 2 deletions playwright/async_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ def async_playwright() -> PlaywrightContextManager:
__all__ = [
"async_playwright",
"Accessibility",
"BindingCall",
"Browser",
"BrowserContext",
"BrowserType",
Expand Down Expand Up @@ -110,7 +109,6 @@ def async_playwright() -> PlaywrightContextManager:
"Selectors",
"SourceLocation",
"StorageState",
"sync_playwright",
"TimeoutError",
"Touchscreen",
"Video",
Expand Down
43 changes: 42 additions & 1 deletion playwright/async_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 0 additions & 2 deletions playwright/sync_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ def sync_playwright() -> PlaywrightContextManager:


__all__ = [
"async_playwright",
"Accessibility",
"BindingCall",
"Browser",
"BrowserContext",
"BrowserType",
Expand Down
43 changes: 42 additions & 1 deletion playwright/sync_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions tests/async/test_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("<div></div>")
locator = page.locator("div")
task = locator.wait_for()
await page.eval_on_selector("div", "div => div.innerHTML = '<span>target</span>'")
await task
assert await locator.text_content() == "target"