远程团队把网页改动合并后,最容易漏掉的不是 Chromium 路径,而是 Safari 独有的表单、焦点与布局行为。与其在发布前临时打开远程桌面逐页点击,不如在 GPUMini 云端 Mac 上保留一套固定的 Safari WebDriver 回归任务:每次部署候选版本都执行同一组关键流程,失败时留下可复查的页面现场。
先固定可重复的执行边界
Safari 自动化依赖 macOS 图形会话,不能简单照搬无头浏览器的运行方式。先建立专用测试用户,保持图形会话已登录,并确保任务不会与人工远程操作同时进行。同一用户目录一次只运行一个 Safari 会话,避免历史记录、下载文件和窗口状态互相污染。
首次准备节点时,在交互式终端执行驱动启用与诊断:
sudo safaridriver --enable
safaridriver --diagnose
mkdir -p "$HOME/browser-tests/artifacts"
--enable 属于节点准备动作,不要放进每次自动任务。日常任务只运行诊断;诊断失败就停止测试,不要继续生成一批缺乏解释力的超时结果。Python 环境也应锁定依赖版本,把 selenium 的确定版本写入项目依赖文件,而不是在每次执行时安装最新版本。
浏览器回归的第一条基线不是“能启动”,而是同一提交、同一测试数据和同一等待条件能够得到相同结论。
用本地夹具切断外部波动
登录、搜索和结账等端到端流程可以连接测试环境,但组件交互与浏览器兼容性检查应尽量使用本地夹具。把表单、弹窗、上传和键盘导航页面放进仓库,通过回环地址提供服务,可以排除 DNS、证书和外部接口波动。
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$ARTIFACTS"
python3 -m http.server 8080 \
--bind 127.0.0.1 \
--directory "$ROOT/fixtures" \
>"$ARTIFACTS/http-server.log" 2>&1 &
SERVER_PID=$!
cleanup() {
kill "$SERVER_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
"$ROOT/.venv/bin/python" "$ROOT/tests/safari_smoke.py"
固定端口前先用 lsof -nP -iTCP:8080 -sTCP:LISTEN 检查占用。若多个任务共用节点,不要随机杀进程;让调度层保证互斥,或者为每个任务分配明确端口,并把端口写进测试配置。
把等待条件写成业务状态
最常见的脆弱写法是点击后固定休眠数秒。机器负载稍有变化,休眠过短会误报,过长则拖慢整组测试。应等待可观察的业务状态,例如按钮可点击、结果列表出现,或状态文本变为预期值。
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
artifacts = Path("artifacts")
driver = webdriver.Safari()
driver.set_page_load_timeout(20)
try:
driver.get("http://127.0.0.1:8080/login.html")
wait = WebDriverWait(driver, 12)
wait.until(
EC.visibility_of_element_located((By.ID, "email"))
).send_keys("tester@local.invalid")
driver.find_element(By.ID, "continue").click()
status = wait.until(
EC.visibility_of_element_located((By.ID, "result"))
)
assert status.text == "Ready"
finally:
driver.quit()
定位器优先使用稳定的 id 或专用测试属性,不要依赖会随排版调整变化的层级选择器。断言也要验证结果,而不只是验证元素存在:提交按钮出现不代表提交成功,列表容器存在也不代表数据已经渲染。
让每次失败都能还原现场
浏览器测试最昂贵的状态是“CI 显示超时,但没有现场”。每个用例失败时应立即保存截图、页面源码、当前 URL、用例名和时间信息;只有整个套件结束后再截图,往往只会得到已经跳转或关闭的页面。
| 失败现象 | 首先保存 | 优先检查 |
|---|---|---|
| 元素始终不可见 | 截图、页面源码 | 遮罩层、响应式布局、定位器 |
| 点击没有结果 | 当前 URL、按钮状态 | 禁用状态、焦点、事件绑定 |
| 页面加载超时 | URL、服务日志 | 本地服务、重定向、资源阻塞 |
| 驱动无法建会话 | 驱动诊断 | 图形会话、残留进程、权限 |
截图文件名应包含用例标识和唯一运行号,但不要写入令牌、邮箱或项目密钥。页面源码可能带有测试数据,归档前同样要脱敏,并设置明确的保留期限。
在异常点立即落盘
测试框架的失败钩子应调用统一的证据函数。保存动作本身也可能失败,因此要分别捕获截图和源码写入错误,不能让归档异常覆盖原始断言。
恢复残留会话而不是盲目重试
任务被中断后,Safari 或驱动进程可能仍占用会话。下一次执行若直接重试,常见结果是连续建会话失败。先检查是否还有属于测试用户的运行任务,并确认没有其他正常测试正在执行,再终止残留会话。不要在共享节点上使用宽泛的进程名批量清理。
重试只适合已知的瞬时建会话失败,而且最多一次。断言失败、页面结构变化和业务状态不符都不应自动重试,否则真实回归会被伪装成偶发问题。若一次重试后恢复,应单独记录为不稳定事件,而不是把任务标记成完全正常。
最后把门禁拆成两层:每次提交运行本地夹具冒烟测试,部署候选版本再运行少量关键端到端流程。上线前检查驱动诊断通过、测试用户独占、夹具服务可访问、显式等待无固定休眠、失败证据可读取、清理函数必定执行。这样得到的不是一组“偶尔通过”的浏览器脚本,而是一条可以长期追踪变化的 Safari 回归基线。
常见问题
Safari WebDriver 测试可以在没有图形登录会话时运行吗?
不建议这样设计。Safari 自动化依赖可用的 macOS 图形会话,应使用专门的测试用户保持会话已登录,并在任务开始前执行驱动诊断。
同一台云端 Mac 可以并行运行多个 Safari 会话吗?
稳定基线应从单会话串行执行开始。需要提高吞吐量时,优先按独立节点拆分任务,而不是让多个会话争用同一用户目录。
Safari 回归失败时至少要保存哪些证据?
至少保存失败截图、页面源码、当前 URL、用例名称和驱动诊断结果;涉及网络请求时,再附上经过脱敏的服务端请求日志。
为开发与构建任务选择一台云端 Mac
比较两档 M4 配置、四种租用周期与四个在售节点,再按任务需要完成部署。