报表在 iOS 应用里点了「导出」,分享面板也出现了文件,但接收方打开后发现少了一页。对这类故障,只断言导出函数返回成功是不够的:测试任务需要拿到最终 PDF 文件,检查可机器判定的内容,并把其余部分留给视觉复核。下面的流程适合在云端 Mac 上运行模拟器测试,也能在本地照做。
先定义可验收的文件
选一份固定输入,例如两条明细和一个明确的报表标题。测试用例应知道预期文件名、最少页数及一段稳定的正文文字。不要把生成时间、随机编号或金额总计直接写成字符串断言;这些字段可能随测试数据变化。更稳妥的做法是固定输入数据,另对动态字段设定明确的格式规则。
让测试版本的应用把 PDF 写入自身 Documents/export.pdf。这是为了方便验收,不要求正式产品采用相同的交付路径。每次运行前删除旧文件,导出完成后再检查文件存在且非空,否则上一次的产物可能让本次测试误报通过。
文件存在、能被解析、内容正确是三道不同的检查。前一道通过,不能代替后一道。
从模拟器取出本次产物
先用测试任务指定的模拟器标识运行应用及导出操作。任务结束后,在同一个标识对应的设备上查询应用容器;不要用 booted 猜测目标,多台模拟器同时启动时尤其容易取错。下例中的标识和应用包名应由测试配置提供:
UDID="替换为测试使用的模拟器标识"
BUNDLE_ID="替换为测试应用的包名"
CONTAINER="$(xcrun simctl get_app_container "$UDID" "$BUNDLE_ID" data)" || exit 1
mkdir -p artifacts
test -s "$CONTAINER/Documents/export.pdf" || exit 1
cp "$CONTAINER/Documents/export.pdf" artifacts/export.pdf
file artifacts/export.pdf
get_app_container 要求应用已安装在指定模拟器上。查询失败时,先核对测试实际使用的设备标识、包名和应用安装结果,而不是改为搜索整块磁盘。复制后检查的是任务目录中的文件;后续解析与归档都使用这份副本,避免应用再次写入时改变检查对象。file 的输出可辅助诊断,但不能代替 PDF 解析。
用 PDFKit 检查结构和文字
将下面的 Swift 代码保存为 scripts/check-pdf.swift。它接收文件路径和预期短语,逐页检查页面边界,并确认至少有一页包含该短语。退出码非零时,构建任务即可判定本次自动检查未通过。
import Foundation
import PDFKit
guard CommandLine.arguments.count == 3,
let pdf = PDFDocument(url: URL(fileURLWithPath: CommandLine.arguments[1])),
pdf.pageCount > 0 else {
fputs("PDF is missing or cannot be parsed\n", stderr)
exit(2)
}
let expected = CommandLine.arguments[2]
var extracted = ""
for index in 0..<pdf.pageCount {
guard let page = pdf.page(at: index) else {
fputs("PDF page is missing\n", stderr)
exit(2)
}
let media = page.bounds(for: .mediaBox)
let crop = page.bounds(for: .cropBox)
guard media.width > 0, media.height > 0,
crop.width > 0, crop.height > 0,
media.contains(crop) else {
fputs("PDF page bounds are invalid\n", stderr)
exit(2)
}
extracted += page.string ?? ""
}
guard extracted.contains(expected) else {
fputs("Expected text was not extracted\n", stderr)
exit(2)
}
print("Checked \(pdf.pageCount) PDF page(s)")
执行 swift scripts/check-pdf.swift artifacts/export.pdf "Quarterly Report"。示例短语要换成固定测试数据里确实存在的文字。如果需求规定恰好两页,还应把 pdf.pageCount == 2 加入断言;仅检查大于零无法发现分页变化。页面尺寸也可以按产品要求分别核对,但不要未经约定就把所有合法纸张规格判成错误。
文字提取不是视觉验收
page.string 检查的是可提取文字,不保证字形没有被裁切,也不能证明图表、图片或颜色正确。相反,扫描件和绘制成图片的文字即使看起来正常,也可能提取不到内容。遇到文字断言失败,先保留原 PDF,判断是内容没有写入,还是提取方式不适用于该页面;不要为让任务通过而直接删除断言。
把失败定位到具体环节
建议为每次任务分别记录导出操作结果、文件复制结果和 PDFKit 退出码。这样出现失败时,可以按顺序判断:应用是否执行导出;容器中是否产生了本次文件;副本能否解析;页数、边界和文字是否符合预期。不要只输出一句「PDF 测试失败」,否则排查仍要重新运行完整流程。
| 现象 | 优先核对 |
|---|---|
| 找不到容器 | 模拟器标识、包名、安装结果 |
| 文件为空 | 旧文件是否清除、写入是否完成 |
| 无法解析 | 复制过程、最终文件格式 |
| 文字不匹配 | 固定输入、页面内容、文字提取限制 |
失败时归档 artifacts/export.pdf 和测试日志,但先确认报表不含真实用户数据。若文件可能包含敏感信息,改用合成样本,限制产物访问,并按团队约定清理。不要把生产报表上传到公开流水线产物中。
以一次可复查的运行收尾
提交验收流程前,用同一份固定输入连续运行两次:两次都应重新导出、覆盖目标文件,并得到一致的页数与文字检查结果。然后故意移除预期短语,确认脚本会失败;再让测试应用不生成文件,确认复制步骤会失败。这两项反向检查能排除「断言根本没执行」的情况。
自动检查通过后,仍需用代表性样本打开 PDF,查看分页交界、长文字换行和图表边缘。机器负责稳定的结构与文本契约,人负责确认实际呈现;把两者的结论分开记录,下一次导出异常才有清晰的比较基线。
常见问题
PDFKit 提取不到文字,是否一定是导出失败?
不一定。扫描图像、绘制成图片的文字或特殊字体都可能无法被提取;应保留文件并进行视觉复核,不能仅凭空字符串判定失败。
如何让验收任务拿到模拟器中的 PDF?
测试应用将文件写入自身 Documents 目录后,可用 simctl get_app_container 查询该模拟器上的应用数据目录,再复制文件到任务产物目录。
把下一次构建交给独享的云端 Mac。
核对 ViewMini 的机型、区域和租期,再配置适合自己工作流的物理节点。