Skip to content

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":

python
# ❌ 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:

python
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:

AssertionWhat 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:

python
import re
expect(page).to_have_title(re.compile("Playwright"))     # Title
expect(page).to_have_url(re.compile(".*/dashboard"))     # URL

List assertions can validate the order of all entries at once:

python
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:

python
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:

python
# ❌ 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:

python
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 ​

  1. Visit playwright.dev; assert the title contains "Playwright" with to_have_title, then deliberately assert a wrong title and observe the retry-then-fail error format after 5 seconds.
  2. Write a local page: a button that shows "Done" after 1 second. Verify with both instant query and expect to feel the difference.
  3. 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.