第 2 课
第一条云端浏览器测试:计划文件、退出码与失败包
建一个前端项目,写两份计划文件——一条应当通过、一条故意写错预期——在 TestSprite 云端真实浏览器里各跑一次,读懂退出码和失败包,再重跑。
课程目录第 2 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课按 TestSprite 官方 Quickstart、Creating Tests、Running Tests 与 Reading Results 整理。 目标:建一个前端项目,写两份计划文件——一条应当通过、一条故意写错预期——在 TestSprite 云端真实浏览器里各跑一次,读懂退出码和失败包,再重跑。
开始前
- 已完成第 1 课:
testsprite auth status能显示你的邮箱。 - 本课会产生真实运行,消耗少量 credits(免费档每月 150)。动手前可以先看余额:
testsprite usage
- 练习目标用 Sauce Labs 官方的自动化练习站 Swag Labs:https://www.saucedemo.com/ 。登录页上公开写着测试账号:用户名
standard_user(正常)和locked_out_user(被锁定),密码都是secret_sauce。熟悉流程后,把网址换成你自己已部署、公网可访问的应用即可。
为什么不用 localhost:官方文档说明云端运行器必须能从公网访问目标,
localhost、127.x、内网地址在发起运行前就会被拒绝(退出码 5)。CLI 0.15 的官方 README 已加入本机隧道(project create --local 3000),但文档站仍写「本地应用请用 MCP Server」;本课只用公网地址,两种说法下都成立。
一、创建前端项目
mkdir -p ~/testsprite-lab && cd ~/testsprite-lab
testsprite --output json project create \
--type frontend \
--name "Swag Labs 练习" \
--url https://www.saucedemo.com/ > project.json
export PROJECT_ID=$(jq -r '.projectId' project.json)
echo "$PROJECT_ID"
对照结果:project.json 里有 projectId、type: "frontend"、targetUrl: "https://www.saucedemo.com/",最后一行打印出你的项目 ID。没有 jq 的话,直接打开 project.json 复制 projectId 的值,再手动 export PROJECT_ID=...。
一个项目只放一种测试:前端或后端在
project create --type时就定了,官方建议不要混放。
二、写第一份计划文件(应当通过)
前端测试不用写代码,只写「用户会做什么、应该看到什么」。计划文件是 JSON,必须包含 projectId、type、name 和 planSteps;每一步的 type 只能是 action 或 assertion。下面用不带引号的 heredoc,让 shell 把 $PROJECT_ID 直接填进去:
cat > login-standard.plan.json <<EOF
{
"projectId": "$PROJECT_ID",
"type": "frontend",
"name": "Swag Labs 标准用户登录后看到商品列表",
"planSteps": [
{ "type": "action", "description": "Navigate to https://www.saucedemo.com/" },
{ "type": "action", "description": "Fill the Username field with standard_user" },
{ "type": "action", "description": "Fill the Password field with secret_sauce" },
{ "type": "action", "description": "Click the Login button" },
{ "type": "assertion", "description": "The title 'Products' and at least one product are visible" }
]
}
EOF
先用 dry-run 在本地校验格式(不花 credits):
testsprite --dry-run test create --plan-from ./login-standard.plan.json
对照结果:打印样例 testId test_dryrun_create_2026、退出码 0。若报 Field ... is invalid,按提示改 JSON。
写断言的要点:描述可观察的结果(页面上出现了什么文字、元素),不要写「登录成功」这种抽象说法。
三、创建并运行,等结论
testsprite test create \
--plan-from ./login-standard.plan.json \
--run --wait \
--output json > run-standard.json
echo "exit=$?"
--plan-from 模式下项目、类型、名称都取自 JSON 文件本身;--run 创建后立即触发云端运行,--wait 一直等到出结论(默认最长 600 秒,可用 --timeout 调整,范围 1–3600)。
对照结果:
- 终端打印
exit=0,表示测试通过。 run-standard.json里能找到新测试的testId;有dashboardUrl时可以直接在浏览器打开,看每一步截图和录屏。
把测试 ID 存起来:
jq -r '.testId' run-standard.json
export TEST_OK=上一行打印的ID
四、退出码就是结论
脚本和 Agent 都只看退出码分支,不用去解析文字。官方定义(test run --wait / test wait):
| 退出码 | 含义 |
|---|---|
| 0 | passed(不带 --wait 时表示已排队) |
| 1 | failed、blocked 或 cancelled |
| 3 | 认证错误:key 或 scope 不对 |
| 4 | 测试或运行不存在 |
| 5 | 参数错误,或目标 URL 被拒(如 localhost) |
| 6 | 该测试已有运行在进行中 |
| 7 | 等待超时,用 testsprite test wait <run-id> 接着等 |
| 10 | 网络传输失败,可重试 |
| 11 | 被限流,按 Retry-After 等待 |
等待时按 Ctrl-C 只是让 CLI 断开,云端运行还在继续并计费;要真正停止用
testsprite test cancel <run-id>,已扣的 credits 不退。
五、故意写一条会失败的测试
被锁定的用户登录后不会进入商品页,而是看到报错 “Epic sadface: Sorry, this user has been locked out.”。我们故意把预期写成「能看到 Products」:
cat > login-locked.plan.json <<EOF
{
"projectId": "$PROJECT_ID",
"type": "frontend",
"name": "Swag Labs 被锁用户进入商品列表(故意写错的预期)",
"planSteps": [
{ "type": "action", "description": "Navigate to https://www.saucedemo.com/" },
{ "type": "action", "description": "Fill the Username field with locked_out_user" },
{ "type": "action", "description": "Fill the Password field with secret_sauce" },
{ "type": "action", "description": "Click the Login button" },
{ "type": "assertion", "description": "The page title 'Products' is visible" }
]
}
EOF
testsprite test create \
--plan-from ./login-locked.plan.json \
--run --wait \
--output json > run-locked.json
echo "exit=$?"
export TEST_BAD=$(jq -r '.testId' run-locked.json)
对照结果:exit=1(失败)。
六、读失败:先看分诊卡,再拿完整失败包
一屏分诊卡:
testsprite test failure summary "$TEST_BAD"
对照结果的格式(官方示例,内容会是你这条测试的):
testId test_3a9f21c7
status failed
failureKind element_not_found
snapshotId snap_9b2c...
rootCauseHypothesis The "Place Order" button selector changed ...
recommendedFixTarget checkout.tsx line 42 — button aria-label
本例里你应当看到断言类失败,根因假设会提到页面显示的是锁定报错而不是 Products。
下载完整失败包。注意:--out 的上级目录必须已经存在,否则会直接报退出码 5(实测):
mkdir -p .testsprite
testsprite test failure get "$TEST_BAD" --out ./.testsprite/failure
ls -R .testsprite/failure
对照结果(文件结构,CLI 0.15 dry-run 实测给出的清单;步骤编号随你的测试而变):
result.json failure.json code.py video.mp4 meta.json
steps/04-screenshot.png steps/04-snapshot.html
steps/05-screenshot.png steps/05-snapshot.html
失败包是自洽的一整套:失败步骤及前后步骤、渲染成文本的 DOM 快照、测试源码(code.py,TestSprite 把前端测试存成 Python + Playwright 脚本)、根因假设和建议修改位置,全部共用同一个 snapshotId——这正是 Agent 修 bug 时需要读的东西。
test failure get总是取该测试最新一次失败。同时有多个运行时,用testsprite test artifact get <run-id>锁定到指定运行。
七、修正并重跑
这里「bug」在测试预期本身。把最后一步的断言改成符合真实行为:
sed -i.bak \
"s/The page title 'Products' is visible/An error message containing 'locked out' is visible/" \
login-locked.plan.json
已创建的测试要更新计划,见官方 Editing & Deleting Tests;最省事的做法是用改好的文件新建一条再跑:
testsprite test create --plan-from ./login-locked.plan.json --run --wait
echo "exit=$?"
对照结果:exit=0。
对已经通过、代码没变的测试,日常回归用 rerun,它会回放已保存的脚本,并默认开启 Auto-heal(页面小改动时自动修复定位):
testsprite test rerun "$TEST_OK" --wait
对照结果的格式(官方示例):
runId run_5c1d...
status passed
startedAt 2026-06-15T10:22:14Z
finishedAt 2026-06-15T10:23:01Z
Dashboard: https://www.testsprite.com/dashboard/tests/proj_8f0f6/test/test_3a9f21c7
计费:重跑和新跑同价(官方 README:前端 0.5 credits、后端 0.2 credits)。跑整个项目用
testsprite test run --all --project "$PROJECT_ID" --wait,每条都按一次运行计费,批量前先看testsprite usage。
本课检查清单
project.json、login-standard.plan.json、login-locked.plan.json三个文件都在- 第一条测试
exit=0,第二条第一次exit=1 .testsprite/failure/里有failure.json、code.py和steps/截图- 改断言后重跑得到
exit=0
下一课:把这套「建 → 跑 → 读失败包 → 修 → 重跑」交给编码 Agent,并接到 GitHub Actions。
