Chapter 5 · Web-First Assertions with expect
Chapter goals: Master the expect assertion family and auto-retry mechanism, distinguish between "retry assertions" and "instant queries", and learn page-level assertions and soft-assertion patterns.
5.1 Why expect Is "Web-First"
Frontend pages are asynchronous: after a click, data may take hundreds of milliseconds to return and the DOM to update. The traditional approach is "query once, judge immediately":
# ❌ Instant query: judges dead before rendering completes
assert page.get_by_text("Saved successfully").is_visible()Playwright's expect assertions hand the "expectation" to the framework, which retries within the timeout window until the condition is met:
from playwright.sync_api import expect
# ✅ Retry assertion: waits until it appears (default 5 seconds)
expect(page.get_by_text("Saved successfully")).to_be_visible()This is what "Web-First assertion" means — the assertion is aimed at "the state the page will eventually reach", naturally immune to race conditions.
5.2 Assertion Family Quick Reference
Most commonly used Locator assertions:
| Assertion | What It Verifies |
|---|---|
to_be_visible() / to_be_hidden() | Visible / hidden |
to_be_enabled() / to_be_disabled() | Enabled / disabled |
to_be_checked() | Checkbox is checked |
to_have_text("x") | Exact text match |
to_contain_text("x") | Contains text |
to_have_value("x") | Current input value |
to_have_count(n) | Number of matched elements |
to_have_attribute("href", "...") | Attribute value |
to_have_class(re.compile("active")) | Class (regex supported) |
Page-level assertions:
import re
expect(page).to_have_title(re.compile("Playwright")) # Title
expect(page).to_have_url(re.compile(".*/dashboard")) # URLList assertions can validate the order of all entries at once:
expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])5.3 Retry Window vs Instant Query Pitfalls
expect retries for 5 seconds by default; override per-assertion:
expect(page.get_by_text("Done")).to_be_visible(timeout=10_000)The counter-example is Locator's instant query methods — they query the current moment only and return immediately:
# ❌ is_visible() returns bool, no waiting, no retry
if page.get_by_text("Confirm").is_visible():
...
# ✅ Use expect when you need wait semantics
expect(page.get_by_text("Confirm")).to_be_visible()The only合理 use of if locator.is_visible(): is for "instant state judgment" (e.g., the official example checking whether a modal has appeared to decide whether to click close). Any judgment with a "waiting" intent must go through expect.
5.4 Soft Assertions
The Python版 Playwright doesn't ship a direct port of Node's expect.soft(), but you can achieve the same pattern with try/except — collect all failures instead of bailing on the first:
failures = []
def soft_check(fn, desc):
try:
fn()
except AssertionError as e:
failures.append(f"{desc}: {e}")
soft_check(lambda: expect(page.get_by_role("heading")).to_be_visible(), "Heading")
soft_check(lambda: expect(page.get_by_role("button", name="Submit")).to_be_enabled(), "Submit button")
assert not failures, "Soft assertion failures:\n" + "\n".join(failures)Ideal for acceptance tests checking a dozen elements on one screen — run once, get the full problem list, instead of fixing one and re-running.
5.5 Chapter Summary
- expect assertions auto-retry within the timeout window — the core mechanism against flakiness;
- Common family: visibility/enabled/checked (to_be_visible/enabled/checked), text/value/count (to_have_text/value/count), title/URL (to_have_title/url);
is_visible()and other instant methods don't wait — only use them for immediate state branching;- Single assertions can override the default 5-second retry window with
timeout=; - Python soft assertions use try/except collection; ideal for batch acceptance checks.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. After clicking save, confirmation text appears after ~800ms. Which写法 is not flaky?
2. What is expect(locator).to_have_count(5) typically used for?
3. What is the correct use case for locator.is_visible()?
4. What does expect(page).to_have_url(re.compile(".*dashboard")) verify?
🛠️ Hands-On Exercises
- Visit
playwright.dev; assert the title contains "Playwright" withto_have_title, then deliberately assert a wrong title and observe the retry-then-fail error format after 5 seconds. - Write a local page: a button that shows "Done" after 1 second. Verify with both instant query and expect to feel the difference.
- Using the 5.4 soft_check pattern, check a real page's title, navbar visibility, and footer text simultaneously; output the full failure list.
Proceed to Chapter 6: pytest Integration and Fixture System.