Skip to content

第 5 章 · Web-First 断言 expect

本章目标:掌握 expect 断言家族与自动重试机制,分清"重试断言"和"瞬时查询"的本质区别,学会页面级断言与软断言思路。

5.1 为什么 expect 是"Web-First"的

前端页面是异步的:点击后数据要几百毫秒才回来,DOM 才更新。传统写法是"查一次就判":

python
# ❌ 瞬时查询:此刻没渲染完就判死
assert page.get_by_text("保存成功").is_visible()

Playwright 的 expect 断言把"期望"描述出来交给框架,在超时窗口内反复重试直到满足

python
from playwright.sync_api import expect

# ✅ 重试断言:等它出现为止(默认 5 秒)
expect(page.get_by_text("保存成功")).to_be_visible()

这就是 Web-First 断言的含义——断言面向"网页最终会达到的状态",天然免疫竞态。

5.2 断言家族速查

最常用的 Locator 断言:

断言验证内容
to_be_visible() / to_be_hidden()可见 / 不可见
to_be_enabled() / to_be_disabled()可用 / 禁用
to_be_checked()复选框已勾选
to_have_text("x")文本精确匹配
to_contain_text("x")包含文本
to_have_value("x")input 当前值
to_have_count(n)匹配元素数量
to_have_attribute("href", "...")属性值
to_have_class(re.compile("active"))class(支持正则)

页面级断言:

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

列表断言可以一次校验全部条目顺序:

python
expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])

5.3 重试窗口与瞬时查询的坑

expect 默认重试 5 秒,可按断言覆盖:

python
expect(page.get_by_text("Done")).to_be_visible(timeout=10_000)

反面教材是 Locator 的瞬时查询方法——它们只查当前瞬间并立即返回:

python
# ❌ is_visible() 返回 bool,不等待、不重试
if page.get_by_text("Confirm").is_visible():
    ...

# ✅ 需要等待语义时用 expect
expect(page.get_by_text("Confirm")).to_be_visible()

if locator.is_visible(): 唯一合理的用法是"此刻的即时状态判断"(如官方示例中判断弹窗是否已出现后决定点关闭),任何带"等待"意图的判断都必须走 expect。

5.4 软断言思路

Python 版 Playwright 没有移植 Node 的 expect.soft(),但可以用 try/except 实现同款思路——收集所有失败而不是遇错即停:

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(), "标题")
soft_check(lambda: expect(page.get_by_role("button", name="提交")).to_be_enabled(), "提交按钮")

assert not failures, "软断言失败:\n" + "\n".join(failures)

适合"一屏内要检查十几个元素"的验收型测试:一次跑完拿到全部问题清单,而不是修一个跑一次。

5.5 本章小结

  • expect 断言在超时窗口内自动重试,是抗 flaky 的核心机制;
  • 常用家族:可见/可用/勾选类 to_be_visible/enabled/checked、文本/值/数量类 to_have_text/value/count、标题/URL 类 to_have_title/url;
  • is_visible() 等瞬时方法不等待,只用于即时状态分支判断;
  • 单条断言可用 timeout= 覆盖默认 5 秒重试窗口;
  • Python 版软断言用 try/except 收集模式实现,适合批量验收检查。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 点击保存后提示文字约 800ms 后才出现,哪种写法不会 flaky?

2. expect(locator).to_have_count(5) 的典型用途是?

3. locator.is_visible() 的正确使用场景是?

4. expect(page).to_have_url(re.compile(".*dashboard")) 验证的是?

🛠️ 动手实践

  1. 访问 playwright.dev,用 to_have_title 断言标题包含 "Playwright",再故意断言错误标题观察重试 5 秒后的报错格式。
  2. 写一个本地页面:按钮点击 1 秒后出现"Done"。分别用瞬时查询和 expect 验证,体会差异。
  3. 用 5.4 的 soft_check 模式,对一个真实页面同时检查标题、导航栏可见性、页脚文本,输出完整失败清单。

进入第 6 章:pytest 集成与 fixture 体系