云端 Mac 上建立 iOS 深链回归门禁

云端 Mac 上建立 iOS 深链回归门禁

iOS 应用把登录回调、活动页、订单详情和通知落点交给深链后,一次路由重构就可能让链接停在首页,或者在应用已打开时重复压入同一页面。人工点击几个链接只能证明当时可用,无法覆盖冷启动、前台状态、编码参数和非法输入。更稳妥的做法,是在云端 Mac 上固定模拟器环境,把每条深链的最终路由变成持续集成可读取的结果。

不要从测试脚本里零散拼 URL。先维护一份路由清单,明确输入、期望路由和应用状态。

场景 输入 期望结果
商品详情 linklab://product/42 product/42
搜索参数 linklab://search?q=swift%20ui search?query=swift ui
缺少标识 linklab://product/ error/invalid-product
未知路径 linklab://unknown error/not-found

清单中的期望值应是路由器解析后的标准形式,而不是某个按钮标题。这样界面文案变化不会制造误报,测试也能直接指出路径、参数还是状态恢复出了问题。

深链门禁验证的是“输入是否到达正确业务路由”,不是“系统是否接受了一条 URL”。命令返回成功不等于页面正确。

同一路由至少准备冷启动和前台两种案例。涉及鉴权时,再区分已登录与未登录,但测试账户状态必须由脚本显式建立,不能继承上一条用例。

给调试构建加入可观测探针

命令行能够打开链接,却无法可靠判断应用最终展示了什么。可在 DEBUG 构建中,让统一路由入口把标准化结果写入缓存目录。发布构建不包含这段逻辑,也不要记录令牌或完整查询参数。

#if DEBUG
func recordResolvedRoute(_ route: String) {
    let payload: NSDictionary = [
        "route": route,
        "recordedAt": Date().timeIntervalSince1970
    ]
    let directory = FileManager.default.urls(
        for: .cachesDirectory,
        in: .userDomainMask
    )[0]
    let file = directory.appendingPathComponent("link-probe.plist")
    payload.write(to: file, atomically: true)
}
#endif

探针应放在 URL Scheme、Universal Link 和通知跳转共同经过的路由器之后。若分别在生命周期回调中记录,测试可能只证明回调被触发,却没有证明参数经过校验并成功落到业务页面。

保持结果原子化

每次执行前删除旧文件,写入时使用原子替换。否则应用崩溃或读取时机过早,可能让本次测试误读上一次结果。建议只记录路由键、错误类型和时间戳,用户输入先脱敏。

固定模拟器并执行路由矩阵

CI 不要使用含糊的 booted 作为起点。先按设备名称和系统版本解析 UDID,确认只有目标模拟器启动,再等待系统完成引导。下面假设流水线已经得到 SIMULATOR_UDID

set -euo pipefail

xcrun simctl shutdown all
xcrun simctl boot "$SIMULATOR_UDID"
xcrun simctl bootstatus "$SIMULATOR_UDID" -b

xcodebuild \
  -scheme LinkLab \
  -configuration Debug \
  -sdk iphonesimulator \
  -derivedDataPath .ci/DerivedData \
  build

APP_PATH=".ci/DerivedData/Build/Products/Debug-iphonesimulator/LinkLab.app"
xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH"

安装后先取得数据容器,再逐条清理探针、打开链接并轮询结果。不要写固定的长时间 sleep;应用启动速度变化时,它要么浪费时间,要么仍然读不到文件。

BUNDLE_ID="com.example.linklab"
DATA_DIR="$(xcrun simctl get_app_container \
  "$SIMULATOR_UDID" "$BUNDLE_ID" data)"
PROBE="$DATA_DIR/Library/Caches/link-probe.plist"

rm -f "$PROBE"
xcrun simctl openurl "$SIMULATOR_UDID" "linklab://product/42"

for attempt in 1 2 3 4 5 6 7 8 9 10; do
  test -f "$PROBE" && break
  sleep 1
done

test -f "$PROBE"
ACTUAL="$(/usr/libexec/PlistBuddy -c "Print :route" "$PROBE")"
test "$ACTUAL" = "product/42"

前台案例在首次打开后继续发送 URL;冷启动案例则先执行 simctl terminate。每条失败用例保存输入、期望值、实际值和测试日志,但不要归档包含秘密信息的完整应用容器。

URL Scheme 适合稳定地验证路由器,但不能替代 Universal Link 的端到端检查。后者还依赖关联域权限、HTTPS 站点文件、内容类型、应用标识和系统缓存。

第一层在每次提交中直接调用统一路由器,快速覆盖路径标准化、百分号编码、重复参数、空值和未知路由。第二层在受控测试域执行真实 HTTPS 链接,重新安装应用后验证冷启动与前台切换。站点关联文件变更时必须触发第二层,不能只跑单元测试。

常见误区是修改关联文件后立即重复点击,并把旧缓存结果当成新配置。排查时应记录应用构建号、系统版本、安装顺序和测试域响应头;先证明系统把链接交给应用,再检查业务路由。

把失败证据和清理动作做进门禁

可靠门禁不仅要返回非零状态,还要让维护者快速判断故障层级。失败摘要至少包含用例名、应用状态、输入类型、期望路由、实际路由,以及探针文件是否生成。若文件不存在,优先检查安装、生命周期入口和链接声明;若文件存在但路由错误,再检查解析器与导航状态。

合并前清单可以保持简短:

  • 模拟器型号与系统版本由流水线固定;
  • 冷启动和前台状态分别执行;
  • 每条用例先清理应用进程或探针状态;
  • 特殊字符、空参数和未知路径都有反例;
  • Universal Link 有独立的 HTTPS 验收;
  • 失败产物不包含凭据、令牌和用户数据;
  • 任务结束后关闭模拟器并删除临时构建目录。

最后执行 xcrun simctl shutdown "$SIMULATOR_UDID",并让工作区清理策略处理 .ci/DerivedData。当路由契约、可观测探针和状态隔离同时存在时,深链测试才从“链接能打开”升级为可阻止导航回归的工程门禁。

常见问题

只用 simctl openurl 能证明 Universal Link 配置正确吗?

不能。它适合验证路由解析和应用行为,但 Universal Link 还依赖关联域声明、站点文件、TLS 与系统缓存,必须另设真实 HTTPS 验收。

深链测试为什么要分别覆盖冷启动和前台状态?

两种状态通常进入不同的生命周期回调。只测前台可能漏掉首次启动路由,只测冷启动则可能漏掉重复跳转和状态栈污染。

ZoomMini 云端 Mac

为构建、测试与实验选择独享物理节点

两档 M4 配置覆盖新加坡、东京、首尔与香港节点。实际可用状态以下单时返回的结果为准。

选择机型并订购