Skip to content

第 4 章 · 自动等待与 Actionability

本章目标:理解 Playwright 自动等待的原理与五项 actionability 检查,知道每个操作到底在等什么,以及 timeout / force 的正确用法。

4.1 自动等待解决的是什么问题

Selenium 时代的测试长这样:

python
# ❌ 反模式:猜一个"应该够久"的时间
time.sleep(3)
driver.find_element(By.ID, "submit").click()

睡 3 秒:网络快时浪费 2.8 秒,网络慢时依然失败。Playwright 把等待内置进每个操作——执行动作前自动等所有相关检查通过,超时才报 TimeoutError。你的测试代码里不应该再出现任何 sleep

4.2 五项 Actionability 检查

locator.click() 为例,执行前会依次确认:

检查项含义
Visible有非空边界框且无 visibility:hidden
Stable未处于动画中(连续两帧位置一致)
Receives Events命中测试证明该点没有被遮罩层挡住
Enabled不是 disabled
Editable(输入类)可编辑

不同操作的检查组合不同,官方文档给出了完整对照表(节选):

text
Action                Visible Stable Receives Events Enabled Editable
locator.click()         Yes     Yes       Yes         Yes      -
locator.check()         Yes     Yes       Yes         Yes      -
locator.hover()         Yes     Yes       Yes          -       -
locator.fill()          Yes      -         -          Yes     Yes
locator.select_option() Yes      -         -          Yes      -
locator.set_input_files() -      -         -           -       -

两个值得注意的细节:

  • fill 不要求 Stable 和 Receives Events——往 input 里填文本不需要它静止或可点击;
  • pressfocusdispatch_event 几乎不做 actionability 检查——它们模拟的是键盘/事件层面的行为。

4.3 timeout:局部覆盖与全局默认

每次操作的默认超时是 30 秒,可按需调整:

python
# 单次操作放宽到 10 秒
page.get_by_role("button", name="提交").click(timeout=10_000)

# 全局默认改成 15 秒
page.set_default_timeout(15_000)

# 导航有独立的默认超时(45 秒),也可单独设置
page.set_default_navigation_timeout(60_000)

不要用加长 timeout 来掩盖问题

timeout 是给"合理慢"的缓冲,不是失败重试器。一个稳定 5 秒内完成的页面配 120 秒超时,只会让 CI 失败时多等两分钟。

4.4 force=True:绕过检查的代价与用途

force=True 会跳过 actionability 检查直接派发动作:

python
# 即使元素被动画遮挡也强行点击
page.get_by_role("button").click(force=True)

适用场景很窄:自定义动画导致 Receives Events 永远不通过、或确知遮挡元素不拦截事件时的一把"手术刀"。代价是放弃了 Playwright 最重要的安全保障——能用则不用,用了请在代码注释里写明原因。

4.5 等待的另一半:显式状态等待

自动等待管的是"操作前等元素就绪",但有些场景需要你主动声明等待条件:

python
# 等 URL 变化(如登录跳转)
page.wait_for_url("**/dashboard")

# 等特定网络响应到达
with page.expect_response("**/api/user") as resp_info:
    page.get_by_role("button", name="刷新").click()
print(resp_info.value.status)

# 等某个请求发出
with page.expect_request("**/analytics") as req_info:
    page.goto("https://example.com")

# 等 load/networkidle 状态
page.wait_for_load_state("networkidle")

这些"事件等待器"同样是自动重试语义:在超时窗口内持续探测,而不是只查一次就走。

4.6 本章小结

  • 每个动作前自动执行 actionability 检查,全部通过才执行,超时报 TimeoutError;
  • 五大检查:可见(Visible)、稳定(Stable)、接收事件(Receives Events)、可用(Enabled)、可编辑(Editable),不同动作组合的检查项不同;
  • 测试代码中禁止 sleep;timeout 只做局部微调,别全局调大掩盖问题;
  • force=True 跳过检查是最后手段;
  • 页面级变化用 wait_for_url / expect_response 等显式等待表达意图。

🧪 随堂测验

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

1. 对一个带入场动画的按钮执行 click(),Playwright 会?

2. 关于 locator.fill() 的 actionability 检查,正确的是?

3. 测试里大量出现 time.sleep(2) 的最大危害是?

4. 什么时候适合使用 force=True?

🛠️ 动手实践

  1. 写一个含 CSS 加载动画按钮的本地页面,分别在动画进行中和结束后点击,观察 Playwright 自动等待的行为。
  2. expect_response 捕获一次真实站点的 XHR 请求,打印其状态码。
  3. 故意把 timeout 设为 1ms 点击一个延迟出现的按钮,观察 TimeoutError 报错信息里的检查细节。

进入第 5 章:Web-First 断言 expect