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