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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ playwright/driver/
playwright.egg-info/
build/
dist/
venv/
.idea/
**/*.pyc
env/
htmlcov/
Expand Down
51 changes: 35 additions & 16 deletions playwright/_impl/_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@


class AssertionsBase:
def __init__(self, locator: Locator, is_not: bool = False) -> None:
def __init__(
self, locator: Locator, is_not: bool = False, message: Optional[str] = None
) -> None:
self._actual_locator = locator
self._loop = locator._loop
self._dispatcher_fiber = locator._dispatcher_fiber
self._is_not = is_not
self._custom_message = message

async def _expect_impl(
self,
Expand All @@ -51,21 +54,27 @@ async def _expect_impl(
log = "\n".join(result.get("log", "")).strip()
if log:
log = "\nCall log:\n" + log
if expected is not None:
raise AssertionError(
f"{message} '{expected}'\nActual value: {actual} {log}"
if self._custom_message:
out_message = (
f"{self._custom_message}\nExpected value: {expected or '<None>'}"
)
else:
out_message = (
f"{message} '{expected}'" if expected is not None else f"{message}"
)
raise AssertionError(f"{message}\nActual value: {actual} {log}")
raise AssertionError(f"{out_message}\nActual value: {actual} {log}")


class PageAssertions(AssertionsBase):
def __init__(self, page: Page, is_not: bool = False) -> None:
super().__init__(page.locator(":root"), is_not)
def __init__(
self, page: Page, is_not: bool = False, message: Optional[str] = None
) -> None:
super().__init__(page.locator(":root"), is_not, message)
self._actual_page = page

@property
def _not(self) -> "PageAssertions":
return PageAssertions(self._actual_page, not self._is_not)
return PageAssertions(self._actual_page, not self._is_not, self._custom_message)

async def to_have_title(
self, title_or_reg_exp: Union[Pattern[str], str], timeout: float = None
Expand Down Expand Up @@ -110,13 +119,17 @@ async def not_to_have_url(


class LocatorAssertions(AssertionsBase):
def __init__(self, locator: Locator, is_not: bool = False) -> None:
super().__init__(locator, is_not)
def __init__(
self, locator: Locator, is_not: bool = False, message: Optional[str] = None
) -> None:
super().__init__(locator, is_not, message)
self._actual_locator = locator

@property
def _not(self) -> "LocatorAssertions":
return LocatorAssertions(self._actual_locator, not self._is_not)
return LocatorAssertions(
self._actual_locator, not self._is_not, self._custom_message
)

async def to_contain_text(
self,
Expand Down Expand Up @@ -639,15 +652,20 @@ async def not_to_be_in_viewport(


class APIResponseAssertions:
def __init__(self, response: APIResponse, is_not: bool = False) -> None:
def __init__(
self, response: APIResponse, is_not: bool = False, message: Optional[str] = None
) -> None:
self._loop = response._loop
self._dispatcher_fiber = response._dispatcher_fiber
self._is_not = is_not
self._actual = response
self._custom_message = message

@property
def _not(self) -> "APIResponseAssertions":
return APIResponseAssertions(self._actual, not self._is_not)
return APIResponseAssertions(
self._actual, not self._is_not, self._custom_message
)

async def to_be_ok(
self,
Expand All @@ -658,18 +676,19 @@ async def to_be_ok(
message = f"Response status expected to be within [200..299] range, was '{self._actual.status}'"
if self._is_not:
message = message.replace("expected to", "expected not to")
out_message = self._custom_message or message
log_list = await self._actual._fetch_log()
log = "\n".join(log_list).strip()
if log:
message += f"\n Call log:\n{log}"
out_message += f"\n Call log:\n{log}"

content_type = self._actual.headers.get("content-type")
is_text_encoding = content_type and is_textual_mime_type(content_type)
text = await self._actual.text() if is_text_encoding else None
if text is not None:
message += f"\n Response Text:\n{text[:1000]}"
out_message += f"\n Response Text:\n{text[:1000]}"

raise AssertionError(message)
raise AssertionError(out_message)

async def not_to_be_ok(self) -> None:
__tracebackhide__ = True
Expand Down
20 changes: 12 additions & 8 deletions playwright/async_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
web automation that is ever-green, capable, reliable and fast.
"""

from typing import Union, overload
from typing import Optional, Union, overload

import playwright._impl._api_structures
import playwright._impl._api_types
Expand Down Expand Up @@ -88,29 +88,33 @@ def async_playwright() -> PlaywrightContextManager:


@overload
def expect(actual: Page) -> PageAssertions:
def expect(actual: Page, message: Optional[str] = None) -> PageAssertions:
...


@overload
def expect(actual: Locator) -> LocatorAssertions:
def expect(actual: Locator, message: Optional[str] = None) -> LocatorAssertions:
...


@overload
def expect(actual: APIResponse) -> APIResponseAssertions:
def expect(actual: APIResponse, message: Optional[str] = None) -> APIResponseAssertions:
...


def expect(
actual: Union[Page, Locator, APIResponse]
actual: Union[Page, Locator, APIResponse], message: Optional[str] = None
) -> Union[PageAssertions, LocatorAssertions, APIResponseAssertions]:
if isinstance(actual, Page):
return PageAssertions(PageAssertionsImpl(actual._impl_obj))
return PageAssertions(PageAssertionsImpl(actual._impl_obj, message=message))
elif isinstance(actual, Locator):
return LocatorAssertions(LocatorAssertionsImpl(actual._impl_obj))
return LocatorAssertions(
LocatorAssertionsImpl(actual._impl_obj, message=message)
)
elif isinstance(actual, APIResponse):
return APIResponseAssertions(APIResponseAssertionsImpl(actual._impl_obj))
return APIResponseAssertions(
APIResponseAssertionsImpl(actual._impl_obj, message=message)
)
raise ValueError(f"Unsupported type: {type(actual)}")


Expand Down
20 changes: 12 additions & 8 deletions playwright/sync_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
web automation that is ever-green, capable, reliable and fast.
"""

from typing import Union, overload
from typing import Optional, Union, overload

import playwright._impl._api_structures
import playwright._impl._api_types
Expand Down Expand Up @@ -88,29 +88,33 @@ def sync_playwright() -> PlaywrightContextManager:


@overload
def expect(actual: Page) -> PageAssertions:
def expect(actual: Page, message: Optional[str] = None) -> PageAssertions:
...


@overload
def expect(actual: Locator) -> LocatorAssertions:
def expect(actual: Locator, message: Optional[str] = None) -> LocatorAssertions:
...


@overload
def expect(actual: APIResponse) -> APIResponseAssertions:
def expect(actual: APIResponse, message: Optional[str] = None) -> APIResponseAssertions:
...


def expect(
actual: Union[Page, Locator, APIResponse]
actual: Union[Page, Locator, APIResponse], message: Optional[str] = None
) -> Union[PageAssertions, LocatorAssertions, APIResponseAssertions]:
if isinstance(actual, Page):
return PageAssertions(PageAssertionsImpl(actual._impl_obj))
return PageAssertions(PageAssertionsImpl(actual._impl_obj, message=message))
elif isinstance(actual, Locator):
return LocatorAssertions(LocatorAssertionsImpl(actual._impl_obj))
return LocatorAssertions(
LocatorAssertionsImpl(actual._impl_obj, message=message)
)
elif isinstance(actual, APIResponse):
return APIResponseAssertions(APIResponseAssertionsImpl(actual._impl_obj))
return APIResponseAssertions(
APIResponseAssertionsImpl(actual._impl_obj, message=message)
)
raise ValueError(f"Unsupported type: {type(actual)}")


Expand Down
13 changes: 13 additions & 0 deletions tests/async/test_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,3 +658,16 @@ async def test_should_print_response_with_text_content_type_if_to_be_ok_fails(
assert "← 404 Not Found" in error_message
assert "Response Text:" not in error_message
assert "Image content type error" not in error_message


async def test_should_print_users_message_for_page_based_assertion(
page: Page, server: Server
) -> None:
await page.goto(server.EMPTY_PAGE)
await page.set_content("<title>new title</title>")
with pytest.raises(AssertionError) as excinfo:
await expect(page, "Title is not new").to_have_title("old title", timeout=100)
assert "Title is not new" in str(excinfo.value)
with pytest.raises(AssertionError) as excinfo:
await expect(page).to_have_title("old title", timeout=100)
assert "Page title expected to be" in str(excinfo.value)
13 changes: 13 additions & 0 deletions tests/sync/test_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,3 +746,16 @@ def test_should_print_response_with_text_content_type_if_to_be_ok_fails(
assert "← 404 Not Found" in error_message
assert "Response Text:" not in error_message
assert "Image content type error" not in error_message


def test_should_print_users_message_for_page_based_assertion(
page: Page, server: Server
) -> None:
page.goto(server.EMPTY_PAGE)
page.set_content("<title>new title</title>")
with pytest.raises(AssertionError) as excinfo:
expect(page, "Title is not new").to_have_title("old title", timeout=100)
assert "Title is not new" in str(excinfo.value)
with pytest.raises(AssertionError) as excinfo:
expect(page).to_have_title("old title", timeout=100)
assert "Page title expected to be" in str(excinfo.value)