Skip to content

Commit 29fe478

Browse files
sawt00thmxschmitt
andauthored
feat: add support for custom expect message (microsoft#1737)
Co-authored-by: Max Schmitt <[email protected]>
1 parent 51c1504 commit 29fe478

File tree

6 files changed

+87
-32
lines changed

6 files changed

+87
-32
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ playwright/driver/
44
playwright.egg-info/
55
build/
66
dist/
7+
venv/
8+
.idea/
79
**/*.pyc
810
env/
911
htmlcov/

playwright/_impl/_assertions.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424

2525

2626
class AssertionsBase:
27-
def __init__(self, locator: Locator, is_not: bool = False) -> None:
27+
def __init__(
28+
self, locator: Locator, is_not: bool = False, message: Optional[str] = None
29+
) -> None:
2830
self._actual_locator = locator
2931
self._loop = locator._loop
3032
self._dispatcher_fiber = locator._dispatcher_fiber
3133
self._is_not = is_not
34+
self._custom_message = message
3235

3336
async def _expect_impl(
3437
self,
@@ -51,21 +54,27 @@ async def _expect_impl(
5154
log = "\n".join(result.get("log", "")).strip()
5255
if log:
5356
log = "\nCall log:\n" + log
54-
if expected is not None:
55-
raise AssertionError(
56-
f"{message} '{expected}'\nActual value: {actual} {log}"
57+
if self._custom_message:
58+
out_message = (
59+
f"{self._custom_message}\nExpected value: {expected or '<None>'}"
60+
)
61+
else:
62+
out_message = (
63+
f"{message} '{expected}'" if expected is not None else f"{message}"
5764
)
58-
raise AssertionError(f"{message}\nActual value: {actual} {log}")
65+
raise AssertionError(f"{out_message}\nActual value: {actual} {log}")
5966

6067

6168
class PageAssertions(AssertionsBase):
62-
def __init__(self, page: Page, is_not: bool = False) -> None:
63-
super().__init__(page.locator(":root"), is_not)
69+
def __init__(
70+
self, page: Page, is_not: bool = False, message: Optional[str] = None
71+
) -> None:
72+
super().__init__(page.locator(":root"), is_not, message)
6473
self._actual_page = page
6574

6675
@property
6776
def _not(self) -> "PageAssertions":
68-
return PageAssertions(self._actual_page, not self._is_not)
77+
return PageAssertions(self._actual_page, not self._is_not, self._custom_message)
6978

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

111120

112121
class LocatorAssertions(AssertionsBase):
113-
def __init__(self, locator: Locator, is_not: bool = False) -> None:
114-
super().__init__(locator, is_not)
122+
def __init__(
123+
self, locator: Locator, is_not: bool = False, message: Optional[str] = None
124+
) -> None:
125+
super().__init__(locator, is_not, message)
115126
self._actual_locator = locator
116127

117128
@property
118129
def _not(self) -> "LocatorAssertions":
119-
return LocatorAssertions(self._actual_locator, not self._is_not)
130+
return LocatorAssertions(
131+
self._actual_locator, not self._is_not, self._custom_message
132+
)
120133

121134
async def to_contain_text(
122135
self,
@@ -639,15 +652,20 @@ async def not_to_be_in_viewport(
639652

640653

641654
class APIResponseAssertions:
642-
def __init__(self, response: APIResponse, is_not: bool = False) -> None:
655+
def __init__(
656+
self, response: APIResponse, is_not: bool = False, message: Optional[str] = None
657+
) -> None:
643658
self._loop = response._loop
644659
self._dispatcher_fiber = response._dispatcher_fiber
645660
self._is_not = is_not
646661
self._actual = response
662+
self._custom_message = message
647663

648664
@property
649665
def _not(self) -> "APIResponseAssertions":
650-
return APIResponseAssertions(self._actual, not self._is_not)
666+
return APIResponseAssertions(
667+
self._actual, not self._is_not, self._custom_message
668+
)
651669

652670
async def to_be_ok(
653671
self,
@@ -658,18 +676,19 @@ async def to_be_ok(
658676
message = f"Response status expected to be within [200..299] range, was '{self._actual.status}'"
659677
if self._is_not:
660678
message = message.replace("expected to", "expected not to")
679+
out_message = self._custom_message or message
661680
log_list = await self._actual._fetch_log()
662681
log = "\n".join(log_list).strip()
663682
if log:
664-
message += f"\n Call log:\n{log}"
683+
out_message += f"\n Call log:\n{log}"
665684

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

672-
raise AssertionError(message)
691+
raise AssertionError(out_message)
673692

674693
async def not_to_be_ok(self) -> None:
675694
__tracebackhide__ = True

playwright/async_api/__init__.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
web automation that is ever-green, capable, reliable and fast.
1919
"""
2020

21-
from typing import Union, overload
21+
from typing import Optional, Union, overload
2222

2323
import playwright._impl._api_structures
2424
import playwright._impl._api_types
@@ -88,29 +88,33 @@ def async_playwright() -> PlaywrightContextManager:
8888

8989

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

9494

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

9999

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

104104

105105
def expect(
106-
actual: Union[Page, Locator, APIResponse]
106+
actual: Union[Page, Locator, APIResponse], message: Optional[str] = None
107107
) -> Union[PageAssertions, LocatorAssertions, APIResponseAssertions]:
108108
if isinstance(actual, Page):
109-
return PageAssertions(PageAssertionsImpl(actual._impl_obj))
109+
return PageAssertions(PageAssertionsImpl(actual._impl_obj, message=message))
110110
elif isinstance(actual, Locator):
111-
return LocatorAssertions(LocatorAssertionsImpl(actual._impl_obj))
111+
return LocatorAssertions(
112+
LocatorAssertionsImpl(actual._impl_obj, message=message)
113+
)
112114
elif isinstance(actual, APIResponse):
113-
return APIResponseAssertions(APIResponseAssertionsImpl(actual._impl_obj))
115+
return APIResponseAssertions(
116+
APIResponseAssertionsImpl(actual._impl_obj, message=message)
117+
)
114118
raise ValueError(f"Unsupported type: {type(actual)}")
115119

116120

playwright/sync_api/__init__.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
web automation that is ever-green, capable, reliable and fast.
1919
"""
2020

21-
from typing import Union, overload
21+
from typing import Optional, Union, overload
2222

2323
import playwright._impl._api_structures
2424
import playwright._impl._api_types
@@ -88,29 +88,33 @@ def sync_playwright() -> PlaywrightContextManager:
8888

8989

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

9494

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

9999

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

104104

105105
def expect(
106-
actual: Union[Page, Locator, APIResponse]
106+
actual: Union[Page, Locator, APIResponse], message: Optional[str] = None
107107
) -> Union[PageAssertions, LocatorAssertions, APIResponseAssertions]:
108108
if isinstance(actual, Page):
109-
return PageAssertions(PageAssertionsImpl(actual._impl_obj))
109+
return PageAssertions(PageAssertionsImpl(actual._impl_obj, message=message))
110110
elif isinstance(actual, Locator):
111-
return LocatorAssertions(LocatorAssertionsImpl(actual._impl_obj))
111+
return LocatorAssertions(
112+
LocatorAssertionsImpl(actual._impl_obj, message=message)
113+
)
112114
elif isinstance(actual, APIResponse):
113-
return APIResponseAssertions(APIResponseAssertionsImpl(actual._impl_obj))
115+
return APIResponseAssertions(
116+
APIResponseAssertionsImpl(actual._impl_obj, message=message)
117+
)
114118
raise ValueError(f"Unsupported type: {type(actual)}")
115119

116120

tests/async/test_assertions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,3 +658,16 @@ async def test_should_print_response_with_text_content_type_if_to_be_ok_fails(
658658
assert "← 404 Not Found" in error_message
659659
assert "Response Text:" not in error_message
660660
assert "Image content type error" not in error_message
661+
662+
663+
async def test_should_print_users_message_for_page_based_assertion(
664+
page: Page, server: Server
665+
) -> None:
666+
await page.goto(server.EMPTY_PAGE)
667+
await page.set_content("<title>new title</title>")
668+
with pytest.raises(AssertionError) as excinfo:
669+
await expect(page, "Title is not new").to_have_title("old title", timeout=100)
670+
assert "Title is not new" in str(excinfo.value)
671+
with pytest.raises(AssertionError) as excinfo:
672+
await expect(page).to_have_title("old title", timeout=100)
673+
assert "Page title expected to be" in str(excinfo.value)

tests/sync/test_assertions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,16 @@ def test_should_print_response_with_text_content_type_if_to_be_ok_fails(
746746
assert "← 404 Not Found" in error_message
747747
assert "Response Text:" not in error_message
748748
assert "Image content type error" not in error_message
749+
750+
751+
def test_should_print_users_message_for_page_based_assertion(
752+
page: Page, server: Server
753+
) -> None:
754+
page.goto(server.EMPTY_PAGE)
755+
page.set_content("<title>new title</title>")
756+
with pytest.raises(AssertionError) as excinfo:
757+
expect(page, "Title is not new").to_have_title("old title", timeout=100)
758+
assert "Title is not new" in str(excinfo.value)
759+
with pytest.raises(AssertionError) as excinfo:
760+
expect(page).to_have_title("old title", timeout=100)
761+
assert "Page title expected to be" in str(excinfo.value)

0 commit comments

Comments
 (0)