本节摘要:脚本与页面是两个独立的时钟:脚本微秒级执行,页面秒级渲染,两者的时间差就是竞态条件——自动化一大半的"随机红屏"都源于此。Selenium 提供隐式等待与显式等待两种同步机制,加上应当禁用的固定延时共三种打法。本节讲清三者的机制差异与职责边界,给出"全局隐式 + 关键处显式"的组合策略,把工单簿里出场率最高的"时红时绿"从机制上根治。
为什么定位器明明写对了,用例还是隔三差五红一次?把两个时钟摆在同一根时间轴上就懂了。脚本侧:发完"找元素"的指令,几毫秒内就期待结果;页面侧:导航发出后,要经历响应、解析、拉取资源、执行脚本、渲染成品,短则数百毫秒,长则数秒。脚本永远比页面快——所以"找不到"往往不是"不存在",而是"还没到"。
这类故障的恼人之处在于随机:本地网快天天绿,流水线网慢经常红;早上跑绿了,午高峰跑红了。新手应对方式是加一句"等三秒再找",于是套件整体慢三秒,而网络偶尔抖到四秒时照样红——固定延时是用确定的慢换不确定的脆,双输。要根治,得先理解竞态条件的三个典型形态:元素晚到(要找的还没渲染出来)、状态晚到(元素在但不可点)、结果晚到(点击成功但页面还在跳转)。三种等待机制,本质上就是针对这三种形态的三个层次的解法。
先把最差的打法说透。固定延时让脚本睡够固定秒数再动手,它的问题不是"土",而是数学上的:延时必须大于最坏情况的页面耗时才安全,而最坏情况不可知——设三秒,四秒的抖动照样红;设十秒,每条用例白白慢十秒。更糟的是它掩盖问题:本该暴露的页面性能退化被硬延时吞掉了,等到哪天十秒都兜不住,红屏会以更莫名其妙的方式爆发。全册唯一推荐用固定延时的场合是调试时临时垫一下观察现象,且不允许进主干代码。
隐式等待是会话级的设置:告诉驱动"此后所有定位指令,找不到就轮询重试,直到超时"。它一处声明、全局生效,不占用正常路径的时间——元素早就绪就立即返回,没就绪才按间隔轮询。
driver.implicitly_wait(5) # 全局:定位最长等5秒 driver.get("https://shop.example.com") driver.find_element("id", "search-box") # 1秒后出现就只等1秒
它的边界同样要清楚。第一,它只管"找得到",不管"可交互"——元素渲染出来了但还在动画中,隐式等待不会替你等动画结束。第二,它是全局开关,粒度粗:个别需要等二十秒的慢页面没法单独放宽,个别必须立刻判存在性的场景也没法单独收紧。第三,也是最容易被忽视的坑:隐式等待与显式等待混用时,超时行为会相互纠缠,可能出现"显式等十秒实际等了几十秒"的诡异现象——两种等待不要叠加使用,是本节最重要的纪律。
显式等待把"等什么条件"写成代码:给一个最长时限和一个预期条件,条件满足立即放行,超时报错。它精准、局部、可表达复杂条件,是三种打法里唯一值得主力使用的:
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) box = wait.until(EC.element_to_be_clickable((By.ID, "search-box"))) box.send_keys("机械键盘") # 等待列表渲染出至少5行再断言 wait.until(lambda d: len(d.find_elements(By.CSS_SELECTOR, "[data-testid='goods-row']")) >= 5)
条件库覆盖了绝大多数场景:可见、可点击、存在于、文本包含、属性含值、窗口数量变化、元素离开可见区等;自定义条件就是返回真值的 lambda,比如上面"列表至少五行"的写法。注意条件与操作要成对设计——要点击就等"可点击",要取文本就等"可见且文本非空",条件选错等于没等。

工程上的答案不是三选一,而是分层组合。全局给一个短隐式等待(三到五秒)当兜底,保证普通定位不至于瞬间失败;关键路径——登录提交后的跳转、支付结果的返回、慢接口驱动的列表——用显式等待定点设防,条件按操作语义挑选。固定延时逐出代码库。这套组合的调优口诀:隐式管下限,显式管上限,哪个等待总在超时,先怀疑页面性能而不是调大时限——等待参数是监测仪,不是遮羞布。
还有一个反直觉的建议:等待应该"等最少必要条件"。等页面完全加载(所有图片下完)才动手,是常见的过度等待;多数交互只需要 DOM 就绪与目标可交互。第 2 章的 eager 加载策略配合定点显式等待,往往能同时拿到速度与稳定。
要留一个清醒的认识:等待解决"时序不对",解决不了"逻辑不对"。页面真的没弹出按钮(功能坏了)、接口真的返回了空列表(数据问题),等待只会在超时后如实报错——这是它该做的。所以等待设计的完成态是三件事:正常路径快速放行、异常路径及时超时、超时后的报错信息带截图与当前 URL(取证细节在第 7 章)。做到了这三件,"随机红屏"就会变成"确定性失败",工单簿从此清爽。
问:超时时限设多少合适?有没有标准值? 没有普适值,只有推导法:取该操作在当前环境的典型耗时乘二到三倍作为上限,再按流水线与本地中较慢者校准。比数值更重要的一条纪律是——时限要有依据地设置并注明理由,"抄来的十秒"既可能太紧也可能太松,出了问题谁也说不清当初为什么是十。
问:页面用了前端路由,没有传统意义的"加载完成",怎么等? 前端路由的导航不触发整页加载,等待条件要改盯"路由结果":地址变化(等当前地址包含目标路径)、目标区域元素出现(等新视图的标志性元素可交互)、数据驱动的内容就绪(等列表行数符合预期)。原则不变——盯业务结果,不盯机制事件。
最后补一个反直觉的观察:等待代码写得多的团队,往往红屏更少但更慢;写得少的团队,红屏多且慢。前者的问题能用最少必要条件修正,后者的随机红屏却要用整个第 7 章来还债——两害相权,先治理正确性,再谈速度。
读完本节,建议对自己的工程(或练习项目)做一次等待审计:搜索全部固定延时调用,逐条追问"它在等什么条件",能翻译成条件的直接改写为显式等待,翻译不了的多半意味着对页面机制理解有缺口——顺着缺口去读一次页面代码,比调参数收获大。审计的另一产出是等待条件清单:把工程里反复出现的等待场景(跳转后等元素、列表渲染、浮层关闭)整理成团队共享的条件工具函数,新用例直接取用。等待代码从"各自为政的数字"变成"共享的条件库",是同步工程成熟的标志。
同步问题清了,交互本身还有讲究:怎么点、怎么填、怎么选、怎么拖。下一节把交互 API 全集过一遍,收拢本章的全部功力。