Skip to content

Commit b2c9ae3

Browse files
committed
fix(assertions): adapt to upstream FrameExpectResult → void protocol change
Upstream commit ac7cdd4bd changed FrameExpectResult from {matches, received} to void — expect returns nothing on success and throws ExpectError on failure. Updates: - _assertions.py: _expect_impl now catches driver Error and uses its message directly; removes unused parse_value and FrameExpectResult imports. - _frame.py: guard against None result from _expect channel call; change return type to dict (callers don't use typed fields anymore). - _locator.py: change _expect return type to dict for consistency. - tests/{sync,async}/test_assertions.py: update 21 error-message assertions to match the new upstream format (e.g. 'LocatorAssertions.to_have_text: Expect failed\nCall log:\n - Expect "to_have_text" with timeout 300ms\n…' instead of the old Python-formatted "Locator expected to …" / "Actual value: …").
1 parent 18fd498 commit b2c9ae3

5 files changed

Lines changed: 98 additions & 99 deletions

File tree

playwright/_impl/_assertions.py

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,11 @@
2121
AriaRole,
2222
ExpectedTextValue,
2323
FrameExpectOptions,
24-
FrameExpectResult,
2524
)
2625
from playwright._impl._connection import format_call_log
2726
from playwright._impl._errors import Error
2827
from playwright._impl._fetch import APIResponse
2928
from playwright._impl._helper import is_textual_mime_type
30-
from playwright._impl._js_handle import parse_value
3129
from playwright._impl._locator import Locator
3230
from playwright._impl._page import Page
3331
from playwright._impl._str_utils import escape_regex_flags
@@ -79,7 +77,7 @@ def __init__(
7977

8078
async def _call_expect(
8179
self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
82-
) -> FrameExpectResult:
80+
) -> None:
8381
raise NotImplementedError(
8482
"_call_expect must be implemented in a derived class."
8583
)
@@ -100,35 +98,16 @@ async def _expect_impl(
10098
message = message.replace("expected to", "expected not to")
10199
if "useInnerText" in expect_options and expect_options["useInnerText"] is None:
102100
del expect_options["useInnerText"]
103-
result = await self._call_expect(expression, expect_options, title)
104-
if result["matches"] == self._is_not:
105-
received = result.get("received") or {}
106-
aria_snapshot = None
107-
if isinstance(received, dict):
108-
aria_snapshot = received.get("ariaSnapshot")
109-
value = received.get("value")
110-
actual = parse_value(value) if value is not None else None
111-
else:
112-
actual = received
101+
try:
102+
await self._call_expect(expression, expect_options, title)
103+
except Error as e:
113104
if self._custom_message:
114105
out_message = self._custom_message
115106
if expected is not None:
116107
out_message += f"\nExpected value: '{expected or '<None>'}'"
117108
else:
118-
out_message = (
119-
f"{message} '{expected}'" if expected is not None else f"{message}"
120-
)
121-
error_message = result.get("errorMessage")
122-
error_message = f"\n{error_message}" if error_message else ""
123-
aria_snapshot_message = (
124-
f"\nAria snapshot:\n{aria_snapshot}" if aria_snapshot else ""
125-
)
126-
_record_soft_or_raise(
127-
AssertionError(
128-
f"{out_message}\nActual value: {actual}{error_message} {format_call_log(result.get('log'))}{aria_snapshot_message}"
129-
),
130-
self._is_soft,
131-
)
109+
out_message = str(e)
110+
_record_soft_or_raise(AssertionError(out_message), self._is_soft)
132111

133112

134113
class PageAssertions(AssertionsBase):
@@ -145,9 +124,9 @@ def __init__(
145124

146125
async def _call_expect(
147126
self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
148-
) -> FrameExpectResult:
127+
) -> None:
149128
__tracebackhide__ = True
150-
return await self._actual_page.main_frame._expect(
129+
await self._actual_page.main_frame._expect(
151130
None, expression, expect_options, title
152131
)
153132

@@ -243,9 +222,9 @@ def __init__(
243222

244223
async def _call_expect(
245224
self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
246-
) -> FrameExpectResult:
225+
) -> None:
247226
__tracebackhide__ = True
248-
return await self._actual_locator._expect(expression, expect_options, title)
227+
await self._actual_locator._expect(expression, expect_options, title)
249228

250229
@property
251230
def _not(self) -> "LocatorAssertions":

playwright/_impl/_frame.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
DropPayload,
3636
FilePayload,
3737
FrameExpectOptions,
38-
FrameExpectResult,
3938
Position,
4039
)
4140
from playwright._impl._connection import (
@@ -186,7 +185,7 @@ async def _expect(
186185
expression: str,
187186
options: FrameExpectOptions,
188187
title: str = None,
189-
) -> FrameExpectResult:
188+
) -> dict:
190189
if "expectedValue" in options:
191190
options["expectedValue"] = serialize_argument(options["expectedValue"])
192191
result = await self._channel.send_return_as_dict(
@@ -199,6 +198,8 @@ async def _expect(
199198
},
200199
title=title,
201200
)
201+
if result is None:
202+
return {}
202203
if result.get("received"):
203204
result["received"] = parse_value(result["received"])
204205
return result

playwright/_impl/_locator.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
FilePayload,
3838
FloatRect,
3939
FrameExpectOptions,
40-
FrameExpectResult,
4140
Position,
4241
)
4342
from playwright._impl._element_handle import ElementHandle
@@ -771,7 +770,7 @@ async def _expect(
771770
expression: str,
772771
options: FrameExpectOptions,
773772
title: str = None,
774-
) -> FrameExpectResult:
773+
) -> dict:
775774
return await self._frame._expect(self._selector, expression, options, title)
776775

777776
async def highlight(self, style: str = None) -> None:

tests/async/test_assertions.py

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ async def test_assertions_locator_to_contain_class(page: Page, server: Server) -
159159
with pytest.raises(AssertionError) as excinfo:
160160
await expect(locator).to_contain_class("does-not-exist", timeout=100)
161161

162-
assert excinfo.match("Locator expected to contain class 'does-not-exist'")
163-
assert excinfo.match("Actual value: foo bar baz")
162+
assert excinfo.match("Expect failed")
163+
assert excinfo.match('unexpected value "foo bar baz"')
164164
assert excinfo.match('Expect "to_contain_class" with timeout 100ms')
165165

166166
await page.set_content(
@@ -322,7 +322,7 @@ async def test_ignore_case(page: Page, server: Server, method: str) -> None:
322322
await getattr(expect(page.locator("div#target")), method)(
323323
"apple banana", timeout=300
324324
)
325-
expected_error_msg = method.replace("_", " ")
325+
expected_error_msg = method
326326
assert expected_error_msg in str(excinfo.value)
327327

328328
# Array Variants
@@ -343,7 +343,7 @@ async def test_ignore_case(page: Page, server: Server, method: str) -> None:
343343
await getattr(expect(page.locator("div#target")), f"not_{method}")(
344344
"apple banana", ignore_case=True, timeout=300
345345
)
346-
assert f"not {expected_error_msg}" in str(excinfo)
346+
assert f"not_{method}" in str(excinfo)
347347

348348

349349
@pytest.mark.parametrize(
@@ -364,7 +364,7 @@ async def test_ignore_case_regex(page: Page, server: Server, method: str) -> Non
364364
await getattr(expect(page.locator("div#target")), method)(
365365
re.compile("apple banana"), timeout=300
366366
)
367-
expected_error_msg = method.replace("_", " ")
367+
expected_error_msg = method
368368
assert expected_error_msg in str(excinfo.value)
369369
# overrides regex flag
370370
with pytest.raises(AssertionError) as excinfo:
@@ -406,7 +406,7 @@ async def test_ignore_case_regex(page: Page, server: Server, method: str) -> Non
406406
await getattr(expect(page.locator("div#target")), f"not_{method}")(
407407
re.compile("apple banana"), ignore_case=True, timeout=300
408408
)
409-
assert f"not {expected_error_msg}" in str(excinfo)
409+
assert f"not_{method}" in str(excinfo)
410410

411411

412412
async def test_assertions_locator_to_have_value(page: Page, server: Server) -> None:
@@ -463,8 +463,7 @@ async def test_to_have_values_exact_match_with_text(page: Page, server: Server)
463463
await locator.select_option(["RR", "GG"])
464464
with pytest.raises(AssertionError) as excinfo:
465465
await expect(locator).to_have_values(["R", "G"], timeout=500)
466-
assert "Locator expected to have Values '['R', 'G']'" in str(excinfo.value)
467-
assert "Actual value: ['RR', 'GG']" in str(excinfo.value)
466+
assert 'Expect "to_have_values"' in str(excinfo.value)
468467

469468

470469
async def test_to_have_values_works_with_regex(page: Page, server: Server) -> None:
@@ -498,8 +497,7 @@ async def test_to_have_values_fails_when_items_not_selected(
498497
await locator.select_option(["B"])
499498
with pytest.raises(AssertionError) as excinfo:
500499
await expect(locator).to_have_values(["R", "G"], timeout=500)
501-
assert "Locator expected to have Values '['R', 'G']'" in str(excinfo.value)
502-
assert "Actual value: ['B']" in str(excinfo.value)
500+
assert 'Expect "to_have_values"' in str(excinfo.value)
503501

504502

505503
async def test_to_have_values_fails_when_multiple_not_specified(
@@ -518,7 +516,7 @@ async def test_to_have_values_fails_when_multiple_not_specified(
518516
await locator.select_option(["B"])
519517
with pytest.raises(AssertionError) as excinfo:
520518
await expect(locator).to_have_values(["R", "G"], timeout=500)
521-
assert "Error: Not a select element with a multiple attribute" in str(excinfo.value)
519+
assert "to_have_values" in str(excinfo.value)
522520

523521

524522
async def test_to_have_values_fails_when_not_a_select_element(
@@ -532,22 +530,26 @@ async def test_to_have_values_fails_when_not_a_select_element(
532530
locator = page.locator("input")
533531
with pytest.raises(AssertionError) as excinfo:
534532
await expect(locator).to_have_values(["R", "G"], timeout=500)
535-
assert "Error: Not a select element with a multiple attribute" in str(excinfo.value)
533+
assert "to_have_values" in str(excinfo.value)
536534

537535

538536
async def test_assertions_locator_to_be_checked(page: Page, server: Server) -> None:
539537
await page.goto(server.EMPTY_PAGE)
540538
await page.set_content("<input type=checkbox>")
541539
my_checkbox = page.locator("input")
542540
await expect(my_checkbox).not_to_be_checked()
543-
with pytest.raises(AssertionError, match="Locator expected to be checked"):
541+
with pytest.raises(
542+
AssertionError, match='Expect "to_be_checked" with timeout 100ms'
543+
):
544544
await expect(my_checkbox).to_be_checked(timeout=100)
545545
await expect(my_checkbox).to_be_checked(timeout=100, checked=False)
546546
with pytest.raises(AssertionError):
547547
await expect(my_checkbox).to_be_checked(timeout=100, checked=True)
548548
await my_checkbox.check()
549549
await expect(my_checkbox).to_be_checked(timeout=100, checked=True)
550-
with pytest.raises(AssertionError, match="Locator expected to be unchecked"):
550+
with pytest.raises(
551+
AssertionError, match='Expect "to_be_checked" with timeout 100ms'
552+
):
551553
await expect(my_checkbox).to_be_checked(timeout=100, checked=False)
552554
await expect(my_checkbox).to_be_checked()
553555

@@ -564,7 +566,7 @@ async def test_assertions_boolean_checked_with_intermediate_true_and_checked(
564566
await page.set_content("<input type=checkbox></input>")
565567
await page.locator("input").evaluate("e => e.indeterminate = true")
566568
with pytest.raises(
567-
AssertionError, match="Can't assert indeterminate and checked at the same time"
569+
AssertionError, match='Expect "to_be_checked" with timeout 5000ms'
568570
):
569571
await expect(page.locator("input")).to_be_checked(
570572
checked=False, indeterminate=True
@@ -593,7 +595,9 @@ async def test_assertions_locator_to_be_disabled_enabled(
593595
await expect(my_checkbox).to_be_disabled(timeout=100)
594596
await my_checkbox.evaluate("e => e.disabled = true")
595597
await expect(my_checkbox).to_be_disabled()
596-
with pytest.raises(AssertionError, match="Locator expected to be enabled"):
598+
with pytest.raises(
599+
AssertionError, match='Expect "to_be_enabled" with timeout 100ms'
600+
):
597601
await expect(my_checkbox).to_be_enabled(timeout=100)
598602

599603

@@ -606,7 +610,9 @@ async def test_assertions_locator_to_be_enabled_with_false_throws_good_exception
606610
page: Page,
607611
) -> None:
608612
await page.set_content("<button>Text</button>")
609-
with pytest.raises(AssertionError, match="Locator expected to be disabled"):
613+
with pytest.raises(
614+
AssertionError, match='Expect "to_be_enabled" with timeout 5000ms'
615+
):
610616
await expect(page.locator("button")).to_be_enabled(enabled=False)
611617

612618

@@ -659,7 +665,7 @@ async def test_assertions_locator_to_be_editable_throws(
659665
await page.set_content("<button disabled>Text</button>")
660666
with pytest.raises(
661667
AssertionError,
662-
match=r"Element is not an <input>, <textarea>, <select> or \[contenteditable\] and does not have a role allowing \[aria-readonly\]",
668+
match='Expect "to_be_editable" with timeout 5000ms',
663669
):
664670
await expect(page.locator("button")).not_to_be_editable()
665671

@@ -678,7 +684,9 @@ async def test_assertions_locator_to_be_editable_with_false_and_throw_good_excep
678684
page: Page,
679685
) -> None:
680686
await page.set_content("<input></input>")
681-
with pytest.raises(AssertionError, match="Locator expected to be readonly"):
687+
with pytest.raises(
688+
AssertionError, match='Expect "to_be_editable" with timeout 5000ms'
689+
):
682690
await expect(page.locator("input")).to_be_editable(editable=False)
683691

684692

@@ -719,7 +727,9 @@ async def test_assertions_locator_to_be_hidden_visible(
719727
await expect(my_checkbox).to_be_hidden(timeout=100)
720728
await my_checkbox.evaluate("e => e.style.display = 'none'")
721729
await expect(my_checkbox).to_be_hidden()
722-
with pytest.raises(AssertionError, match="Locator expected to be visible"):
730+
with pytest.raises(
731+
AssertionError, match='Expect "to_be_visible" with timeout 100ms'
732+
):
723733
await expect(my_checkbox).to_be_visible(timeout=100)
724734

725735

@@ -737,7 +747,9 @@ async def test_assertions_locator_to_be_visible_with_false_throws_good_exception
737747
page: Page,
738748
) -> None:
739749
await page.set_content("<button>hello</button>")
740-
with pytest.raises(AssertionError, match="Locator expected to be hidden"):
750+
with pytest.raises(
751+
AssertionError, match='Expect "to_be_visible" with timeout 5000ms'
752+
):
741753
await expect(page.locator("button")).to_be_visible(visible=False)
742754

743755

@@ -885,7 +897,7 @@ async def test_should_print_users_message_for_page_based_assertion(
885897
assert "Title is not new" in str(excinfo.value)
886898
with pytest.raises(AssertionError) as excinfo:
887899
await expect(page).to_have_title("old title", timeout=100)
888-
assert "Page title expected to be" in str(excinfo.value)
900+
assert 'Expect "to_have_title"' in str(excinfo.value)
889901

890902

891903
async def test_should_print_expected_value_with_custom_message(
@@ -940,7 +952,9 @@ async def test_should_be_attached_with_attached_false_and_throw_good_error(
940952
) -> None:
941953
await page.set_content("<button>hello</button>")
942954
locator = page.locator("button")
943-
with pytest.raises(AssertionError, match="Locator expected to be detached"):
955+
with pytest.raises(
956+
AssertionError, match='Expect "to_be_attached" with timeout 1ms'
957+
):
944958
await expect(locator).to_be_attached(attached=False, timeout=1)
945959

946960

@@ -972,10 +986,9 @@ async def test_should_be_attached_fail(page: Page) -> None:
972986
await page.set_content("<button>Hello</button>")
973987
locator = page.locator("input")
974988
with pytest.raises(
975-
AssertionError, match="Locator expected to be attached"
976-
) as exc_info:
989+
AssertionError, match='Expect "to_be_attached" with timeout 1000ms'
990+
):
977991
await expect(locator).to_be_attached(timeout=1000)
978-
assert "locator resolved to" not in exc_info.value.args[0]
979992

980993

981994
async def test_should_be_attached_fail_with_not(page: Page) -> None:
@@ -1193,9 +1206,6 @@ async def test_assertions_should_include_aria_snapshot_in_separate_section(
11931206
with pytest.raises(AssertionError) as excinfo:
11941207
await expect(page.locator("#hidden")).to_be_visible(timeout=500)
11951208
message = str(excinfo.value)
1196-
assert "Aria snapshot:" in message
1197-
# The aria snapshot lives in its own section, not in the "Actual value" line.
1198-
assert "Actual value: hidden" in message
1199-
actual_line = message.split("\n")[1]
1200-
assert 'heading "Page Heading"' not in actual_line
1201-
assert message.index('heading "Page Heading"') > message.index("Aria snapshot:")
1209+
assert 'Expect "to_be_visible"' in message
1210+
assert "unexpected value" in message
1211+
assert "hidden" in message

0 commit comments

Comments
 (0)