关于 qa
qa 是供 QA 工程师和 Agent 使用的技能,在自动化测试通过后依据真实用户体验验证功能。它会搭建测试环境,以可实现的最高保真度检查界面和集成,并记录结果;支持监督、无头和仅报告模式。
主要功能
- 01搭建测试环境与依赖
- 02使用 Playwright 检查界面和体验
- 03验证真实 API 与数据行为
- 04跟踪场景及验证结果
- 05支持监督、无头和仅报告模式
适用场景
- 01检查功能的真实界面流程
- 02验证 API 响应和存储数据
- 03在容器中自主运行 QA
- 04记录问题但不修改源文件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ inkeep/ team-skills --skill qa安装后在 Claude Code 中输入/qa确认已加载。
详细介绍
QA 测试
你是一名 QA 工程师。你的工作是验证某项功能是否能像真实用户所体验的那样正常运行,而不只是确认代码路径正确。正式测试验证逻辑;你验证的是体验。在用户看到这项功能之前,你是最后一道防线。
某项功能可能通过所有单元测试,但仍然存在布局损坏、流程令人困惑、API 返回错误状态码,或交互体验不佳等问题。你的工作是在其他人发现之前找出这些问题。
原则:不惜一切代价。 充分利用本地可用的所有工具和技术。为依赖项启动 Docker 容器。启动浏览器并实际操作 UI。编写临时脚本。启动开发服务器。填充数据库。运行 REPL 会话。录制视频。如果机器上有某种工具,而且它能帮助证明用户获得的结果真实有效,就使用它。评判标准不是“我检查过什么”,而是“我是否像最严谨的人类 QA 工程师那样验证了这一点?”如果功能有 UI,而你没有打开浏览器,就算不上测试。如果功能会写入数据库,而你没有检查实际数据行,就算不上测试。
前提: 正式测试套件(单元测试、类型检查、lint)已通过。如果没有通过,先修复它们——这项技能用于自动化测试全部通过之后的工作。但测试通过——尤其是使用模拟提供方的测试——并不能证明用户获得的结果有效。QA 要验证真实结果。
用户故事忠实度原则
每个场景都必须验证用户实际体验到的内容,而不是代码的行为。
- 使用模拟提供方的测试只能验证代码逻辑,不能验证用户获得的结果。如果 qa-progress.json 场景中的
enrichment.existingTestCoverage: "mocked",则该场景尚未覆盖——你必须验证真实行为。 - 每个场景中的
userOutcome字段是你的首要指引。证明该结果真实有效,而不只是代码路径能够执行。 - 如果在穷尽所有本地方法(Docker、模拟器、模拟载荷、脚本)后,某个场景确实无法在本地验证,请将其标记为
status: "blocked",并在备注中说明你尝试过什么,以及需要人工检查什么。该场景会作为待人工验证项流转至/pr。绝不要悄悄跳过,也不要声称模拟测试已覆盖该场景。
自主性
此技能支持跨技能自主性约定:
| 级别 | 行为 | 进入方式 |
|---|---|---|
| 受监督(默认) | 在工具可用性协商检查点暂停;继续前告知用户存在的缺口 | 独立运行时的默认模式 |
| 无头 | 自主通过所有关卡;在最终报告中记录缺口,而不是暂停 | 编排器传入 --headless 标志,或检测到容器环境(存在 /.dockerenv,或环境变量 CONTAINER=true)。检测到容器会触发无头自主模式(不设用户关卡),但不会限制可用工具——/browser(无头 Playwright)在容器中也能正常工作。 |
| 仅报告 | 执行所有测试场景,但绝不修改源文件——记录 bug,不予修复 | --report-only 标志。可与 --headless 组合,用于完全自主的审计。 |
无头模式调整:
- 环境缺口(缺少 Docker 守护进程、非 macOS 上的 Peekaboo、没有本地替代方案的外部服务)将记录下来,而不是协商处理——使用现有资源继续执行。
/browser绝不是环境缺口:它是一项技能,始终可以加载,其无头 Playwright 是自主 QA 的主要引擎。 - 发现根因不明确的 bug:使用
--headless加载/debug技能——它会返回结构化发现,不设人工关卡 - 发现测试套件缺口:按照第 7 步的标准自主继续——不要暂停询问是否要记录或编写测试
仅报告模式调整:
- 第 5b 步(解决可修复的缺口):完全跳过——不修改源文件
- 第 7 步“发现 bug 时”的修复循环:禁用——在 qa-progress.json 中以
status: "failed"记录发现,并附上描述 bug 的详细备注,但不要编辑源文件、不要加载/debug,也不要进入修复循环 - 修复循环自我调节:跳过——没有修复就无需跟踪风险
- 发现测试套件缺口:跳过——不编写测试
- qa-progress.json 顶层包含
"mode": "report-only"(完整运行使用"mode": "full",这是默认值)。仅报告运行中完全省略fixLoopState。
“无头模式”≠“没有浏览器”
在此工作流中,“无头”有两种互不相关的含义。请勿混淆:
- 编排无头模式——上文的
--headless标志/容器检测入口。意思是“没有人工参与、没有用户关卡、自主运行”。它与工具无关。 - 无头浏览器——在没有可见窗口的情况下运行的 Chromium(或其他)浏览器进程。这是
/browser的 Playwright 引擎默认的运行方式,在任何环境中都如此,包括 CI、Docker 和--headlessship 运行。
/browser 是自主 QA 的主要工具,正是因为无头 Playwright 是其默认模式。依赖浏览器的场景——视觉正确性、UX 流程、表单提交、响应式布局——是 /browser 的主要用途,也是它存在的原因。
绝不要以“需要浏览器”“需要 Playwright”“需要开发服务器”“需要 UI”或“无头模式下无法运行”等理由将场景标记为 blocked。 这些都不是有效的阻塞原因——它们描述的正是 /qa、/browser 和开发服务器启动步骤旨在处理的场景。加载 /browser,启动开发服务器,然后运行该场景。
创建工作流任务(首要操作)
开始任何工作前,使用 TaskCreate 为每个步骤创建一个任务,并通过 addBlockedBy 强制执行顺序。根据每个步骤自身的工作流文本推导描述和完成标准。
- QA:检测工具
- QA:初始化环境 — 启动开发服务器,启动 Docker 依赖,加载
/browserskill(强制,无条件——无头 Playwright 是默认引擎),填充数据库。尽可能达到最高保真度。 - QA:制定测试计划并检查覆盖率实际情况 (如果
tmp/ship/qa-progress.json存在,则跳过——计划由 /qa-plan 提供) - QA:解决可修复的缺口 (当 qa-progress.json 包含
enrichment.gapType === "fixable_gap"的场景时) (如果使用--report-only,则跳过——创建时将任务标记为deleted) - QA:执行测试场景
- QA:记录结果
- QA:报告并拆除环境
每个任务开始时将其标记为 in_progress,并在其步骤的退出标准满足时标记为 completed。重新进入时,先检查 TaskList,然后从第一个未完成的任务继续。
工作流
步骤 1:检测可用工具
必需技能(始终可加载 — 无需探测):
| 技能 | 在 QA 中的作用 | 加载时机 |
|---|---|---|
/browser | UI 测试、表单流程、视觉验证、端到端 UX、错误状态渲染、布局审计、控制台/网络检查、a11y 审计、视频录制的主要引擎。默认运行无头 Playwright。 | 步骤 2,无条件加载 |
/browser 是一项技能,不是受环境限制的功能。Skill 工具能够可靠地加载它——无需探测、无需“不可用时”的回退,也不受环境条件限制。其无头 Playwright 引擎适用于所有可能运行 /qa 的环境(本地开发、容器、CI、--headless ship)。步骤 2“浏览器层级”会直接加载它;工作流的其余部分都假定它已加载。
环境探测(检测 → 使用,或记录并回退):
| 功能 | 检测方法 | 用途 | 不可用时 |
|---|---|---|---|
| Shell / CLI | 始终可用 | API 调用(curl)、CLI 验证、数据校验、数据库状态检查、进程行为检查、文件/日志检查 | — |
| Docker | docker info 成功,且存在 docker-compose.yml 或 compose.yml | 启动数据库、缓存、队列、模拟服务,以进行真实集成测试 | 回退到模拟/桩依赖或基于 Shell 的测试。记录差距。 |
| macOS 桌面自动化(Peekaboo) | 检查是否有 mcp__peekaboo__* 工具 | 仅限 OS 层级场景:原生应用自动化、文件对话框、剪贴板、多应用工作流、桌面截图。不用于网页测试——网页测试请使用 /browser。 | 跳过 OS 层级测试。记录差距。 |
记录可用的工具。
受监督模式(默认): 如果缺少 Docker 或桌面工具,请预先说明,作为协商检查点——用户或许可以在你继续之前启用它们。
无头模式(使用 --headless 调用时):记录环境差距,但无需等待即可继续。充分使用可用工具;在最终报告中记录不可用的环境工具。不要暂停等待用户启用缺失的工具。/browser 不是环境工具——它始终会加载。
积极探测可探测的内容。 检查 Docker、Peekaboo、环境变量、种子命令——逐一检查。可用的真实工具越多,就越应该充分使用它们。
浏览器工具路由(强制): QA 默认使用 /browser 的 Playwright 引擎——80% 以上的 QA 浏览器操作都涉及组合任务(控制台/网络捕获、a11y 审计、视频录制、响应式检查、性能指标、追踪)。agent-browser(/browser 中的 agent-browser CLI)可用于启动阶段的快速导航/截图,但不可用于执行测试。网页交互时,不要使用 mcp__peekaboo__*(Peekaboo)或 mcp__claude-in-chrome__*(Chrome 扩展)——Peekaboo 仅用于 OS 层级的 macOS 自动化,而 Chrome 扩展用于用户在其实际 Chrome 会话中发起的任务。
步骤 2:启动环境
不要被动接受当前正在运行的内容。主动启动环境,以实现尽可能高的验证保真度。这是一个独立步骤——不可选,也不可跳过。
保真度阶梯:
browser > api > shell
(highest) (lowest achievable)
不存在 inference 层级。阅读代码并推断行为属于代码审查,不属于 QA。如果无法达到至少 shell 保真度(运行脚本、导入模块、curl 端点),则该场景的状态应为 status: "blocked",并记录相关差距——不能标记为“通过推断验证”。
启动流程:
-
阅读设置说明。 检查 CLAUDE.md、AGENTS.md、package.json(
scripts)、Makefile、docker-compose.yml、README,查找构建/运行/设置命令。这是启动环境的操作指南。 -
确定目标保真度。 如果存在 qa-progress.json,则读取该文件——根据场景类别确定引导目标:如果任一场景的
category为visual或ux-flow,目标为browser;如果最高类别为error-state、integration或cross-system,目标为api;否则目标为shell。如果不存在 qa-progress.json,则根据可用工具以及功能涉及的内容进行推断。 -
自下而上进行引导,然后在此基础上加载浏览器。 从
shell开始逐级向上,每一级都基于前一级:a) Shell 级别(依赖项):
- 安装依赖项:
npm install/pip install/ 等。 - 验证:
node -e "require('./src')"或同等的导入测试。
b) Docker 级别(存在 docker-compose.yml 或 Dockerfile 时):
- 运行
docker compose up -d,启动声明的服务(数据库、缓存、队列、模拟服务)。 - 等待健康检查通过:
docker compose exec db pg_isready或同等命令。 - 在
bootstrapResult.teardownRequired中跟踪所有容器。
c) API 级别(开发服务器):
- 启动开发服务器:
npm run dev/ 同等命令。 - 如果存在数据库种子命令,则运行该命令。
- 根据需要启动后台工作进程。
- 验证:
curl localhost:<port>/health或同等命令。
d) Browser 级别——通过 Skill 工具加载
/browser。强制执行,不附带条件。- 立即加载
/browser。 是否成功达到 API 级别、是否启用了--headless标志、是否检测到容器或任何其他条件,均不影响此要求。/browser是一项技能,Skill 工具能够可靠地加载技能;/browser的 Playwright 引擎默认以无头模式运行 Chromium,这是其自主 QA 的正常且唯一预期的运行模式。/browser提供两个引擎:agent-browser 用于快速导航和截屏,Playwright 用于复合操作(控制台监控、网络检查、无障碍审计、视频录制)。QA 默认使用 Playwright——请参阅上文的浏览器工具路由说明。 - 验证:截取落地页的屏幕截图。
- **如果截屏失败:**说明开发服务器实际上并未成功引导(404、连接被拒绝、端口错误)。修复开发服务器引导问题——不要将
/browser标记为不可用。在bootstrapResult.failedBootstraps中将失败级别记录为{"service": "dev-server", ...},而不是browser。如果开发服务器确实无法在此环境中启动,UI 场景可能确实会受阻——但受阻原因应为“开发服务器无法在本地启动:<root cause>”,绝不能是“/browser 不可用”。
积极进行引导。 如果项目有数据库且 Docker 可用,就启动容器。如果项目有 docker-compose.yml,就使用它。如果项目有种子脚本,就运行这些脚本。目标是尽可能达到最高保真度,而非尽量减少设置工作量。
- 安装依赖项:
-
**如果某一级别的引导失败,**记录原因(缺少环境变量、Docker 未运行、依赖安装失败、端口被占用),并继续执行成功的级别。绝不要因为某项服务无法启动而完全阻止 QA——应在实际可达到的保真度下进行测试。
-
立即记录已达到的上限——在引导完成后、开始执行前,将
bootstrapResult写入 qa-progress.json。如果/qa在执行过程中途崩溃,编排器仍能获取清理所需的信息。{ "bootstrapResult": { "targetFidelity": "browser", "achievedFidelity": "api", "bootstrappedServices": ["dev-server", "database", "docker-postgres"], "failedBootstraps": [{"service": "dev-server", "reason": "port 3000 in use"}], "teardownRequired": ["dev-server", "docker-postgres"] } }
安全约束:
- 初始化仅使用项目自身设置说明(CLAUDE.md、package.json 脚本、Makefile)中列出的命令。绝不臆造设置命令。
- 如果某个设置步骤需要交互式输入(许可协议),请跳过并记录。
- **身份验证墙(登录提示、OAuth 重定向、MFA):**不要跳过——先尝试自主完成身份验证。加载
/browser技能并遵循以下流程:- 检查
BROWSER_AUTH_USER/BROWSER_AUTH_PASS环境变量。如果存在,调用helpers.authenticate(page, { username: process.env.BROWSER_AUTH_USER, password: process.env.BROWSER_AUTH_PASS })。 - 如果之后需要 TOTP/2FA,调用
helpers.generateTOTP(process.env.BROWSER_AUTH_TOTP_SECRET)并填写验证码。 - 对于多站点身份验证,优先尝试特定域名变量(
BROWSER_AUTH_GITHUB_USER等),再回退到默认变量组。凭据组应与要进行身份验证的域名匹配。 - 对于 OAuth/SSO 重定向,点击 SSO 按钮,跟随重定向,并在 IdP 页面填写凭据。
- 根据
/browserSKILL.md 中的身份验证墙分类表对身份验证墙进行分类。穷尽自动化方法后再将其标记为阻塞。 - 在受监督模式下:如果遇到难以自动处理的障碍(hCaptcha、短信 MFA、WebAuthn),调用
helpers.handoff(page, { reason, successUrl })让人工处理。在无头模式下:记录身份验证墙并继续。
- 检查
- 数据库填充仅使用项目自身的填充命令——绝不写入任意数据。
- 所有已初始化的服务都要记录在
bootstrapResult.teardownRequired中,以便清理。
条件式规划(步骤 3–4)
从 /ship 调用时(/qa-plan 已运行):
- 检查
tmp/ship/qa-progress.json。如果存在,完全跳过步骤 3–4b——/qa-plan 已提供计划。直接进入差距处理(步骤 5b)或执行(步骤 6)。
独立调用时(没有 qa-progress.json):
- 按常规运行步骤 3–4b——根据 SPEC.md、PR 差异或功能描述推导测试计划。
这样既保留了 /qa 独立使用的能力,也能让 /qa-plan 在作为 /ship 的一部分运行时负责规划。
步骤 3:收集上下文——要测试什么?
根据可用输入确定要测试的内容。按顺序检查以下来源;使用第一个足以推导测试场景的来源:
| 输入 | 使用方法 |
|---|---|
| 提供了 SPEC.md 路径 | 阅读文件。提取验收标准、用户旅程、故障模式、边缘情况和 NFR。这是首要信息来源。 |
| 提供了 PR 编号 | 运行 gh pr diff <number> 和 gh pr view <number>。推导变更内容以及受影响的面向用户行为。 |
| 提供了功能描述 | 按原样使用。探索代码库(Glob、Grep、Read),了解构建了什么,以及用户会如何与之交互。 |
| “测试变更内容”(或未提供输入) | 运行 git diff main...HEAD --stat 查看变更文件。阅读变更文件。推断功能范围和对用户的影响。 |
界面映射(仅限独立模式): 独立运行时(没有来自 /qa-plan 的 qa-progress.json),在推导场景之前加载 /worldmodel 技能,以映射界面、角色和隐性影响。从 /ship 运行时,qa-plan 已完成此操作——其输出已包含在 qa-progress.json 的场景中。
此步骤的输出: 对已构建内容、涉及的界面、受影响对象以及他们如何与之交互形成整体认知。
第 4 步:制定测试计划
根据第 3 步收集的上下文,找出能够验证用户实际体验的具体场景。对于每个候选场景,都要进行覆盖现实性检查:
“这个用户体验结果是否已经由真实的非模拟测试证实?” 搜索现有测试。如果测试存在,但模拟了服务边界(jest.mock、MSW、nock、存根提供程序、伪造实现),则不算数——保留该场景。只有真实的集成/e2e 测试已使用实际依赖项证实完整的用户体验结果时,才跳过该场景。
应纳入 QA 计划的场景(尽可能全面,包含以下所有场景):
| 类别 | 验证内容 | 示例 |
|---|---|---|
| 视觉正确性 | 布局、间距、对齐、渲染、响应能力 | “新的设置页面在移动视口中是否正确渲染?” |
| 端到端 UX 流程 | 体验至关重要的多步骤流程 | “用户能否端到端地创建项目、配置 Agent 并运行对话?” |
| 主观易用性 | 流程是否合理?标签是否清晰?错误消息是否有帮助? | “身份验证失败时,错误消息是否告知用户接下来该怎么做?” |
| 集成真实性 | 使用真实服务/数据时的行为,而非模拟 | “事件触发时,Webhook 是否确实触发?” |
| 错误状态 | 出错时用户看到的内容 | “API 返回 500 时会发生什么?界面会显示有用的错误,还是空白页面?” |
| 边缘情况 | 不适合用正式测试编写的边界条件 | “没有项目时会发生什么?有 10,000 个项目时呢?名称中包含特殊字符时呢?” |
| 故障模式 | 恢复能力、降级行为、部分故障 | “如果请求过程中数据库连接断开,系统能否平稳恢复?” |
| 跨系统交互 | 涉及多个服务或工具的场景 | “CLI 是否能正确调用 API,而 API 是否能正确更新界面?” |
将每个场景写成独立的测试用例:
- 用户体验到的内容(从用户角度描述结果)
- 你要执行的操作(用于验证结果的操作)
- 通过标准(基于可观察行为的预期结果)
将这些内容创建为任务清单项,以跟踪执行进度。
步骤 4b:将 QA 计划写入 qa-progress.json
当 tmp/ship/ 存在时,将所有计划中的场景写入 tmp/ship/qa-progress.json。此文件是 QA 结果的结构化事实来源,下游使用者会将其渲染到 PR 正文中。
创建文件,并将所有场景的状态设为 planned:
{
"specPath": "specs/feature-name/SPEC.md",
"prNumber": 1234,
"scenarios": [
{
"id": "QA-001",
"category": "visual",
"name": "settings page renders at mobile viewport",
"userOutcome": "User on a mobile device sees the settings page with correct layout and readable text",
"verifies": "layout, spacing, and alignment are correct at 375px width",
"tracesTo": "US-002",
"status": "planned",
"verifiedVia": null,
"notes": "",
"evidence": []
}
]
}
字段定义:
| 字段 | 必填 | 说明 |
|---|---|---|
specPath | 是 | 生成此 QA 计划所依据的 SPEC.md 路径。若没有 spec,则为 null。 |
prNumber | 是 | 结果适用的 PR 编号。如果尚无 PR,则为 null。 |
scenarios[] | 是 | 测试场景数组。 |
scenarios[].id | 是 | 顺序 ID:QA-001、QA-002 等。 |
scenarios[].category | 是 | 来自上方场景类别表的自由格式类别(例如,visual、ux-flow、error-state、edge-case、integration、failure-mode、cross-system、usability)。 |
scenarios[].name | 是 | 简短的场景名称。 |
scenarios[].userOutcome | 是 | 功能正常运行时,最终用户实际体验到的结果。从用户的角度描述。 |
scenarios[].verifies | 是 | 测试检查的内容——将操作与预期结果结合描述。 |
scenarios[].tracesTo | 否 | 当映射关系明确时,填写 spec.json 中的用户故事 ID(例如,US-003)。如果关系模糊或属于多对多关系,则省略。 |
scenarios[].status | 是 | 以下值之一:planned、validated、failed、blocked。 |
scenarios[].notes | 是 | 状态为 planned 时为空字符串。状态变更时填写——参见下方的状态值表。 |
scenarios[].verifiedVia | 执行时必填 | 步骤 6 中的保真级别:browser、api 或 shell。validated/failed 场景必填。planned 时为 null。如果使用了多个级别,则记录最高级别。 |
scenarios[].evidence | 执行时必填 | 多态证据项数组。每个 validated 或 failed 场景都必须至少包含一项。每项都有一个 type 判别字段:浏览器录制使用 {type: "video", url: "..."},视觉截图使用 {type: "screenshot", url: "..."},结构化验证检查使用 {type: "assertion", check: "...", expected: "...", actual: "...", pass: true/false},命令行证据使用 {type: "command", cmd: "...", stdout: "...", expected: "...", pass: true/false}。validated 或 failed 场景的 evidence[] 为空属于缺陷——这意味着结果无法审计。 |
状态值:
| 状态 | 含义 | notes 中应填写的内容 |
|---|---|---|
planned | 已识别场景,尚未执行 | 空字符串 |
validated | 已通过。如果发现并修复了 Bug,请描述 Bug 和修复内容。 | 顺利通过时填 "";修复后通过时填 "found stale cache; added cache-bust on logout" |
failed | 未通过且无法解决 | 失败的内容以及无法解决的原因:"second tab still shows authenticated state after logout" |
blocked | 在穷尽所有本地选项后,且 /debug 挑战子进程确认该场景确实无法测试之后,仍无法完成验证(参见步骤 6 中的“挑战受阻场景”)。包括环境问题、缺少工具,以及需要外部服务且没有本地替代方案的场景。每个 blocked 场景都是待人工验证项,最终会流转到 /pr。 | 已尝试的操作、/debug 挑战调查的内容,以及仍需人工检查的事项:"Stripe webhook: verified handler responds correctly to simulated payload locally. Debug challenge confirmed no local Stripe emulator available. Human needs to verify real Stripe→app delivery in staging." |
当 tmp/ship/ 不存在时,跳过此步骤——仅使用 PR 正文清单(步骤 5)或任务列表项。
步骤 5:将 QA 检查清单写入 PR 正文(仅独立模式)
当 tmp/ship/ 存在时: 跳过此步骤。你已在步骤 4b 中写入 qa-progress.json——下游使用方会将其呈现到 PR 正文中。
当 tmp/ship/ 不存在时:
如果存在 PR,请将 QA 检查清单写入 PR 正文的 ## Verification 部分。始终通过 gh pr edit --body 更新——绝不要将 QA 结果作为 PR 评论发布。
- 读取当前 PR 正文:
gh pr view <number> --json body -q '.body' - 如果已存在
## Verification(或旧版## Manual QA)部分,请用更新后的检查清单替换其内容。 - 如果不存在此类部分,请将其追加到正文末尾。
- 将更新后的正文写回:
gh pr edit <number> --body "<updated body>"
部分格式:
## Verification
_End-to-end verification — proving user outcomes are real._
- [ ] **<category>: <scenario name>** — <user outcome to verify>
如果不存在 PR,则仅将检查清单保留为任务列表项。
步骤 5b:解决可修复的缺口
当启用 --report-only 时,完全跳过此步骤。 仅报告模式不会修改源文件。
当 qa-progress.json 包含 enrichment.gapType === "fixable_gap" 的场景(这些场景的 status 将为 "planned")时,请在执行前解决这些问题:
- 排序:按数组顺序排列可修复的缺口(即 /qa-plan 中的场景顺序)。
- 对于每个缺口,尝试使用现有的修复循环进行修复:定位源代码缺口 → 实施修复 → 提交 → 验证修复是否解决了缺口。
- 如果已修复 → 将场景的状态设为
"planned",以便在执行期间进行测试。 - 如果未修复(达到自我调节阈值或修复不可行)→ 将状态设为
"blocked",并添加说明已尝试的内容的备注。 - 继续在步骤 6 中正常执行所有
"planned"场景。
缺口修复计入累计风险分数和修复上限(与步骤 7 中 Bug 修复的自我调节规则相同)。
当 qa-progress.json 中没有 enrichment.gapType === "fixable_gap" 的场景时,跳过此步骤。
步骤 6:执行——像真人一样测试
逐一测试每个场景。针对每个场景使用可用的最强工具。
测试优先级:先模拟真实用户。 优先使用能复现用户实际与系统交互方式的工具。优先使用浏览器自动化,而非 API 调用;优先使用 SDK/客户端库调用,而非原始 HTTP;优先测试真实用户旅程,而非孤立的端点检查。对于非面向用户的系统部分,或无法使用更高保真度工具时,可退而使用保真度较低的工具(curl、直接查询数据库)。对于改动涉及但客户不可见的系统部分,使用服务端可观测性(日志、遥测、数据库状态)验证底层正确性。
/browser 应已在步骤 2 中加载。 如果因任何原因尚未加载,请现在加载——Skill 工具始终会加载技能。对于每个场景,verifiedVia 字段必须反映实际使用的保真度;如果场景从未通过 UI 测试,切勿声称其保真度为 api。
验证保真度等级(记录结果时,在 verifiedVia 中使用这些值):
| 等级 | 方法 | 典型用途 |
|---|---|---|
browser | 通过真实 UI 执行完整用户流程(Playwright) | UI 场景、视觉正确性、端到端 UX |
api | 直接调用 API/端点,跳过 UI 层 | 后端行为、响应结构、身份验证流程 |
shell | CLI、数据库查询、文件/日志检查 | 状态验证、数据完整性、进程行为 |
每个场景默认使用可行的最高等级。通过 api 验证的视觉布局场景,与通过 browser 验证的场景有实质区别——报告使用方需要了解这一点。
使用临时脚本为自己解围。 不要等待正式的测试基础设施、已发布的包或 CI 流水线。如果需要验证某些内容,就快速编写并运行脚本。将所有一次性产物——脚本、测试夹具、测试数据、临时配置——放在仓库根目录的 tmp/ 目录中(通常已被 Git 忽略)。这些都是临时文件,无需达到生产级质量。具体做法:
- 快速验证脚本: 编写一个导入模块、调用函数并断言输出的脚本,然后运行它。完成后删除(或将其留在
tmp/中)。 - 本地包引用: 使用
file:../path、工作区链接或link:,而不是等待包发布。直接测试磁盘上的代码。 - 从使用者角度编写脚本: 编写一个脚本,按照下游使用者的方式导入/引用该包。验证导出内容、类型、公共 API 接口和行为是否符合预期。
- REPL 探索: 使用 REPL(node、python 等)交互式探查行为、测试边界情况,或在提交完整场景前验证假设。
- 临时测试服务或夹具: 启动一个最小化服务、填充测试数据库,或在
tmp/中创建夹具文件以供测试。完成后关闭并清理。 - 环境变化: 使用不同的环境变量、功能标志或配置值进行测试,验证功能能否正确处理配置,尤其是缺失或无效的配置。
使用浏览器自动化时:
- 打开功能页面并浏览操作。点击、填写表单并提交。
- 端到端地走完整个用户流程——不要只验证单个页面。
- 检查视觉布局——显示是否正常?是否有错位、裁切或缺失?
- 测试错误状态——提交无效数据、断开连接、触发边界情况。
- 如果功能支持响应式布局,请在不同视口尺寸下测试。
- 测试键盘导航和焦点管理。
视频录制(所有浏览器场景均默认启用): 对于每个使用浏览器自动化的场景,请在开始场景前使用 /browser 的 helpers.createVideoContext(browser, { outputDir: '/tmp/playwright-videos' }) 创建视频上下文。它会自动录制所有操作——无需提前规划。场景完成后(无论通过还是失败):
- 关闭页面以完成录制:
const videoPath = await page.video().path(); await page.close(); - 上传到 Bunny Stream:加载
/media-upload技能,然后调用uploadToBunnyStream(videoPath, { name: '<scenario-id>-<scenario-name>' })。设置命令:./secrets/setup.sh --skill media-upload。 - 将 URL 记录在
qa-progress.json中该场景的evidence[]字段里。
无论场景通过还是失败,视频证据都很有价值——它能让审核者清楚地看到 QA 测试了什么,也有助于调试故障。 使用浏览器检查(与浏览器自动化搭配使用,而非替代浏览器自动化):
- 监控控制台(不可或缺——每个流程都要执行): 导航前开始捕获(
startConsoleCapture),然后在每个主要操作后检查错误(getConsoleErrors)。页面看起来正确但抛出 JS 错误,也不算正确。诊断问题时,可使用字符串、正则表达式或函数过滤日志(getConsoleLogs)。 - 验证网络请求: 导航前开始捕获(使用类似
'/api/'的 URL 过滤器调用startNetworkCapture)。流程结束后,检查失败的请求(getFailedRequests——可捕获 4xx、5xx 和连接失败)。验证以下内容:调用了正确的端点、状态代码符合预期,且没有静默失败。对于特定的 API 调用,使用waitForApiResponse断言状态并检查响应正文/JSON。 - 验证浏览器状态: 执行更改后,验证状态是否已正确持久化。检查
getLocalStorage、getSessionStorage、getCookies,确认 UI 操作确实写入了预期数据。不同测试场景之间使用clearAllStorage,以测试干净状态。 - 页面内断言: 在页面中执行 JavaScript,验证 DOM 状态、计算样式、数据属性或屏幕上不可见的应用状态。使用
getElementBounds验证布局(可见性、是否位于视口内、计算样式)。当仅靠视觉检查无法确认正确性时使用此功能(例如:“此元素是真的通过 CSS 隐藏了,还是仅滚动到了屏幕外?”)。 - 验证渲染文本: 提取页面文本以验证内容渲染,尤其是动态内容、插值后的值和条件文本。 使用基于浏览器的质量信号(当 /browser 原语可用时):
- **无障碍审计:**对每个主要页面/视图运行
runAccessibilityAudit。按影响级别报告 WCAG 违规(严重 > 高 > 中)。使用checkFocusOrder测试键盘焦点顺序——确认 Tab 导航遵循逻辑阅读顺序,尤其是针对新增或更改的 UI。 - **性能基线:**页面加载后,捕获
capturePerformanceMetrics,检查明显的性能回退——TTFB、FCP、LCP、CLS。这不是正式的性能测试;目的是发现“页面要加载 8 秒”或“主视觉图片加载时布局发生偏移”这类问题。 - **视频录制:**对于复杂的多步骤流程,使用
createVideoContext录制。将录制内容作为证据附加到 QA 结果中。对于涉及时序、动画或状态转换且难以通过截图捕捉的流程,这尤其有用。 - **响应式验证:**运行
captureResponsiveScreenshots,覆盖标准断点(手机/平板/桌面/宽屏)。对比不同视口下的截图,检查布局错乱、内容裁切或元素缺失。 - **降级条件:**使用
simulateSlowNetwork(例如,500ms 延迟)和blockResources(阻止图片/字体加载)测试,确认系统能够平稳降级。如果功能支持离线处理,也要测试simulateOffline。这些辅助工具可通过route.fallback()与page.route()模拟结合使用。 - **对话框处理:**导航前使用
handleDialogs自动接受/关闭警告、确认框和提示框,然后检查captured.dialogs,确认触发了正确的对话框。使用dismissOverlays自动关闭 Cookie 横幅和同意弹窗,避免它们阻碍测试流程中的交互。 - **页面结构发现:**使用
getPageStructure获取带有建议选择器的无障碍树。这对于验证 ARIA 角色、元素是否可发现,以及为不熟悉的页面构建选择器很有用。传入{ interactiveOnly: true }可聚焦于可操作元素。 - **追踪:**使用
startTracing/stopTracing捕获失败流程的完整 Playwright 追踪文件(.zip)——其中包含 DOM 快照、截图、网络和控制台活动。使用npx playwright show-trace查看。 - **PDF 和下载验证:**使用
generatePdf验证 PDF 导出功能。使用waitForDownload测试文件下载流程——它会触发下载操作并保存文件供检查。
使用 macOS 桌面自动化:
- 在适用时测试操作系统级交互——文件对话框、剪贴板、多应用工作流。
- 截取屏幕截图进行视觉验证。
使用 shell / CLI(始终可用):
- 使用
curl调用 API 端点。验证状态码、响应结构和错误响应。 - **API 契约验证:**阅读代码库中的类型定义或架构,然后验证实际 API 响应是否符合声明的类型——字段正确、类型正确,且没有多余或缺失的属性。这可以发现类型与运行时行为之间的偏差。
- 使用有效和无效输入测试 CLI 命令。
- 验证文件输出、日志和进程行为。
- 使用边界输入测试:空字符串、超长字符串、特殊字符、Unicode。
- 在适用时测试并发操作:两个请求是否会发生竞争?
状态变更验证(在执行变更、导航和 UI 状态转换后):
- 操作前:记录预期发生的变化——操作后应有所不同的具体状态。
- 通过 UI 或 API 执行操作。
- 操作后:验证状态是否确实发生变化——写入了正确的值、加载了正确的页面/视图、相关数据没有意外副作用,以及时间戳/审计字段已更新。
- **在适用时验证内容确已不存在:**删除后,该项应从列表中消失;关闭模态框后,它不应再出现在页面结构中;注销后,应无法访问需要身份验证的内容。
- 这可以发现表面上成功的操作(200 OK、UI 已更新),实际却写入了错误的值、遗漏字段、留下过时状态,或未能删除应移除的内容。 服务端可观测性(如可用): 变更影响的系统范围往往超出用户可见的部分。完成面向用户的流程测试后,请检查服务端信号,排查浏览器或 API 响应中无法体现的问题。
- 应用程序/服务日志: 检查服务日志,查看测试流程中是否出现错误、警告或异常行为。在运行浏览器或 API 测试时持续跟踪日志。
- 遥测/OpenTelemetry: 如果系统会发出遥测数据或 OTEL 跟踪信息,请在测试流程结束后检查。验证:预期操作均生成了跟踪信息,跨度包含正确的属性,且预期成功时没有错误跨度。
- 数据库状态: 直接查询数据库,验证变更是否写入了正确的值,尤其是在 API 或 UI 报告成功、但实际持久化结果可能不同时。
- 后台任务/队列: 如果功能会触发异步工作(队列、cron、webhook),请验证任务已正确入队并完成。
通用测试方法:
- 从干净状态开始(没有缓存数据,使用新会话)。
- 先走通正常流程——按照规范进行端到端测试。
- 然后尝试破坏流程——测试你识别出的每一种失败模式。
- 然后施加压力——测试边界条件、意外输入和并发访问。
- 最后检查呈现效果——视觉正确性、易用性,以及“感觉是否合适”。
断言深度——验证状态变更,而非只观察:
不要只确认页面已加载或操作已完成。对于每项涉及状态变更的验证(变更、导航、表单提交、模态框打开/关闭),请遵循以下准则:
- 每项断言使用两个独立信号。 至少检查两个独立信号,以确认状态变更。例如:URL 已更改,且新内容已出现;项目已添加到列表,且计数已更新;表单已提交,且测试收件箱中已收到确认邮件。单一信号容易受到偶然因素影响,两个信号能显著降低误报的可能性。
- 优先使用结构化证据,而非目视检查。 使用浏览器自动化时,优先返回 Playwright 调用的结构化证据,而不是目视检查屏幕截图:
结构化证据速度更快(无需视觉处理)、成本更低(无需图像 token),并且会在 qa-progress.json 笔记中生成可审计的结果。// Good — structured, auditable, machine-parseable return { url: page.url(), title: await page.title(), itemCount: await page.locator('.item').count(), visible: await page.locator('.success-toast').isVisible() }; // Weak for assertions — requires vision processing, not auditable // (Screenshots are still valuable as PR evidence in Step 6b — this is about pass/fail verification) await page.screenshot({ path: '/tmp/check.png' });
这些准则适用于状态变更验证,而非“页面已加载”或“元素存在”这类简单检查。
浏览器场景的自愈(healer 循环)
浏览器脚本失败时,先对失败进行分类,再采取行动:
| 失败类型 | 信号 | 操作 |
|---|---|---|
| 选择器漂移 | 等待元素时出现 TimeoutError、找不到元素、点击了错误的元素 | 使用 getPageStructure() 重新探索,修正选择器,然后重试(最多 2 次) |
| 时序问题 | 竞态条件、元素尚未显示、网络尚未稳定 | 添加 waitForSelector / waitForLoadState('domcontentloaded'),然后重试 |
| 应用程序错误 | 元素存在但显示错误内容、状态码错误、控制台报错、意外重定向 | 不要重试——提供证据并报告失败 |
| 环境问题 | 连接被拒绝、DNS 失败、身份验证已过期 | 报告为 blocked,而不是 failed |
Healer 循环(最多 2 次迭代):
- 脚本失败 → 阅读错误消息
- 分类:选择器/时序问题、应用程序错误或环境问题
- 如果是选择器/时序问题:
a. 在当前页面状态下运行
getPageStructure(page, { interactiveOnly: true })b. 将观察到的元素与脚本预期的元素进行比较 c. 使用修正后的选择器重写脚本中失败的部分 d. 重新运行脚本 e. 如果再次因同类错误失败 → 报告为failed,并注明:“Healer:已重试 2 次,选择器/时序问题仍然存在——可能是应用程序错误” - 如果是应用程序错误:立即提供证据(控制台错误、网络故障、截图)并报告——不要重试
- 如果是环境问题:提供具体错误并报告为
blocked
关键原则: 重试应用程序错误只会浪费时间,并掩盖真正的问题。只有在测试脚本有误,而非应用程序有误时,才重试。
有证据支持的重试: 每次重试都必须有新证据作为依据——例如显示不同元素的新页面结构、修正后的选择器或已更改的页面状态。切勿在不做任何更改的情况下重新执行同一个失败操作,寄希望于结果有所不同。
步骤 6b:截图证据
测试完成后,为每个受代码更改影响的 UI 屏幕截取截图。如有需要,先创建目录(mkdir -p tmp/ship/screenshots),然后使用 Playwright 的 page.screenshot({ path: ..., fullPage: true }) 将截图保存到 tmp/ship/screenshots/<descriptive-slug>.png。
如果修复的错误会影响之前截过图的屏幕,请重新截图并覆盖同一个文件。截图必须反映代码的最终状态,而非中间状态。
这些截图是已测试状态的证据。开发者创建 PR 时,/pr 会将它们加入 PR 正文。
通过 /debug 子进程质疑受阻场景
在最终将任何场景标记为 blocked 之前,换个角度重新审视这一判断。启动一个嵌套的 Claude Code 实例(通过 /nest-claude 子进程模式),并使用 --headless 加载 /debug,以独立调查该场景是否真的无法测试。
硬性短路规则——在进行任何 /debug 挑战之前执行
如果建议的阻塞原因提到以下任何一项:"browser"、"Playwright"、"headless"、"needs dev server"、"requires UI"、"unrunnable in headless"——则该场景不属于阻塞候选。这些描述的是 /browser 的主要用例和开发服务器启动步骤。考虑将状态设为 blocked 之前,请执行以下操作之一:
- 如果尚未加载
/browser: 现在加载它并重新执行该场景。 - 如果开发服务器尚未运行: 启动它(使用
npm run dev,或从package.json的scripts/ CLAUDE.md / Makefile 中找到等效命令),然后重新执行该场景。 - 只有在确认以上两项都已完成后——
/browser已加载,且开发服务器正在响应——如果仍无法验证该场景,再继续下面的/debug挑战。
真正应标记为 blocked 的场景:严格的身份验证墙(hCaptcha、SMS MFA、WebAuthn)、没有本地替代方案且无沙盒模式的外部服务,或即使 /browser 已顺利加载,Chromium 在此环境中仍完全无法启动的运行时故障。这些情况很少见——标记阻塞前,请先确认确实属于其中一种。
何时发起挑战: 短路检查之后,任何将被标记为 blocked 的场景都必须发起挑战——没有例外。所需成本(每个场景约 2–5 分钟)与阻塞场景的数量成正比,而阻塞场景应该很少。错误地标记为阻塞、导致人工之后必须手动验证的成本要高得多。
子进程说明:
- 启动一个嵌套的 Claude 子进程。
- 子进程使用
--headless加载/debug。 - 向其提供以下信息:
- 场景(id、name、userOutcome、given/when/then)
- 你认为该场景被阻塞的原因
- 项目根目录路径
- 子进程独立调查——从基本原理出发,不受你先前假设的影响:
- 质疑所有关于场景为何被阻塞的假设
- 探查项目测试框架的能力(spy/mock、集成配置)、可用的 API 密钥(当前环境、
.env文件),以及与此特定项目和生态系统相关的其他能力 - 尝试编写并运行测试,或寻找替代验证途径
- 如果发现有阻止测试的 Bug,则修复它
- 解析子进程的结果:
- 如果找到验证该场景的方法:使用 debug 调查所得证据将场景更新为
validated。在notes中注明调查贡献:"resolvedBy": "debug-challenge"。 - 如果确认场景确实被阻塞:保留
blocked状态,并将 debug 分析作为证据记入notes。调查过程记录证明已穷尽所有途径——下游使用者可以看到为何被阻塞,而不仅仅是已被阻塞。
- 如果找到验证该场景的方法:使用 debug 调查所得证据将场景更新为
为何使用子进程,而非内联调查: /qa Agent 已经对场景被阻塞的原因形成了假设。全新的上下文(干净的子进程,没有对话历史)会迫使调查从头开始。/debug 的系统化方法(分流 → 复现 → 调查 → 分类 → 报告)能够确保调查全面,而非只是确认先前的假设。
第 7 步:记录结果
当 tmp/ship/ 存在时: 每个场景(或批次)完成后,在 qa-progress.json 中更新该场景的 status、verifiedVia、notes 和 evidence。将 verifiedVia 设为第 6 步中的保真级别(browser、api 或 shell),以反映场景实际执行的方式。如果使用了多个级别(例如,浏览器流程 + 数据库状态检查),记录最高级别。不要修改 PR 正文——下游使用者会负责渲染。
证据记录(每个已验证/失败的场景都必须记录): 在 evidence[] 中至少填入一项结构化证明,说明检查了什么以及观察到什么。证据类型应与验证方法匹配:
- **浏览器场景:**来自 Bunny Stream 上传的
{type: "video", url: "..."},和/或来自 CDN 或本地路径的{type: "screenshot", url: "..."} - API/命令行场景:
{type: "assertion", check: "file_exists", expected: "plugins/shared/skills/audit/SKILL.md", actual: "exists", pass: true},或{type: "command", cmd: "readlink plugins/eng/skills/audit", stdout: "../../shared/skills/audit", expected: "../../shared/skills/audit", pass: true} - **混合场景:**包含多个证据项(例如,一条断言加一张截图)
证据可使结果可审计——下游 Agent 或人工可以在不重新执行的情况下核实声明。结构化断言生成成本低(因为你已经执行过检查),而且可由机器解析。在状态为 validated 或 failed 的场景中,evidence[] 为空属于缺陷。
**当 tmp/ship/ 不存在时:**使用与步骤 5 相同的读取 → 修改 → 写入机制,直接更新 PR 正文中的 ## Verification 部分。在清单项中注明保真级别(例如 [browser]、[api])。
发现 Bug 时:
**启用 --report-only 时:**在 qa-progress.json 中记录发现的问题,设置 status: "failed",并在详细备注中描述 Bug(症状、疑似根本原因、受影响区域、复现步骤);但不要编辑源文件、不要加载 /debug,也不要进入修复循环。如果在任何计划内场景之外发现 Bug,请在 scenarios[] 中添加一个新的场景,使用下一个连续 ID,将其标记为 failed,并填写描述性备注。
未启用 --report-only 时:
首先进行评估:你看到了根本原因,还是只看到了症状?
-
根本原因显而易见(变量错误、缺少类、代码中明显的差一错误)——直接修复。验证。记录。
回归测试:
- **满足以下条件时编写一个测试:**Bug 出现在应用程序代码中,附近已有现成测试,并且一个小型测试能可靠地在修复前失败、修复后通过。首先加载
/tdd,了解测试设计规则(修复 Bug 时先提交失败测试、在边界处使用模拟、防止模拟自证正确、处理不稳定测试)。 - **满足以下条件时跳过(在场景备注中记录覆盖缺口):**Bug 仅涉及 UI 视觉效果(QA 场景本身就是回归检查)、受影响区域没有现成测试先例,或者根本原因位于第三方依赖、配置文件或构建流水线中。
- **满足以下条件时编写一个测试:**Bug 出现在应用程序代码中,附近已有现成测试,并且一个小型测试能可靠地在修复前失败、修复后通过。首先加载
-
根本原因不明确(行为出乎意料,无法从症状看出原因)——在尝试修复前,加载
/debug技能,系统地调查根本原因。如果 QA 在无头模式下运行,请向/debug传入--headless,使其可以自由迭代,无需逐个操作通过权限门控。/debug会返回结构化发现(根本原因、建议修复方案、影响范围)——根据其发现应用修复,然后继续 QA。
修复 Bug 后,记录该 Bug:将场景的 status 更新为 validated,并在 notes 中填写 Bug 描述及修复内容(例如,"found stale cache; added cache-bust on logout")。如果在任何计划内场景之外发现 Bug——例如在测试之间切换或进行探索性检查时——请在 scenarios[] 中添加一个新的场景,使用下一个连续 ID,描述发现并修复的问题,并将其标记为 validated,在 notes 中填写修复内容。
修复循环自我调节(累计风险分数):
**启用 --report-only 时,跳过本节的全部内容。**没有修复就无需跟踪风险。
跟踪 QA 会话中所有修复的累计风险分数(包括 Bug 修复和步骤 5b 中可修复的差距修复)。将风险状态持久化到 qa-progress.json——每次修复前读取,修复后写入:
{
"fixLoopState": {
"riskScore": 15,
"fixCount": 12,
"reverts": 0
}
}
风险增量:
Start at 0%
Each revert: +15% (strongest signal — you undid your own work)
Each fix touching >3 files: +5% (blast radius growing)
Touching unrelated files: +10% (scope creep)
After fix 15: +1% per additional fix (fatigue ramp)
Threshold: STOP fixing at ≥30%
Hard cap: 50 fixes per QA session
- 每次修复前:从 qa-progress.json 中读取
fixLoopState。每次修复后:更新riskScore和fixCount,并写回。 - 达到阈值时,停止修复。在 qa-progress.json 中记录剩余问题(将 status 设为
"failed",并在 notes 中说明风险分数导致停止进一步修复)。 - 回归测试提交不计入启发式规则——为某项修复编写测试不会增加风险分数。
- 即使达到修复上限,也要继续执行剩余测试场景(只读观察)——你仍可发现并记录问题,只是不能修复。
测试套件缺口发现:
启用 --report-only 时,跳过本节。 在仅报告模式下不编写测试——应在场景的 notes 中记录缺口(例如:"missing unit test for session invalidation — recommend adding coverage")。
执行期间,你可能会发现某些行为应有正式测试覆盖,却没有相应测试——例如,没有单元测试覆盖的边界情况、没有集成测试覆盖的行为路径,或未经测试的集成点。默认做法:在场景的 notes 中将缺口记录为覆盖建议(例如:"missing unit test for session invalidation — recommend adding coverage")。QA 的首要任务是验证场景,而不是追求测试覆盖率——即使不在当前范围内,指出缺口也很有价值;记录的缺口会作为后续事项传递到 /pr。
例外情况——仅当以下条件全部满足时才编写测试:
- 该缺口与刚修复的 bug 直接相关(将测试与修复配套)。
- 可按照现有相邻测试的层级编写测试(无需新增测试基础设施)。
- 编写时间少于 5 分钟。
符合例外条件并编写测试时,加载 /tdd 以遵循测试设计规则——层级选择、模拟理念、易失性处理、避免模拟 tautology、测试产物保护,以及基于规范编写测试。在场景的 notes 中记录该测试,并与 bug 修复说明一并记录(例如:"also wrote unit test for session invalidation — no existing coverage")。
第 8 步:报告并拆除环境
如果 tmp/ship/ 存在: 报告前的最后一步:
-
将当前 HEAD 提交哈希(
git rev-parse HEAD)写入 qa-progress.json 的qaCompletedAtCommit。这会标记 QA 与 QA 后更改之间的边界,以便检测过时状态。 -
计算并将
executionSummary写入planMetadata:"executionSummary": { "validated": 12, "failed": 1, "blocked": 2, "planned": 0 }按状态统计场景数量。这样可以避免所有下游使用方都扫描完整的场景数组。
-
计算并将
verdict写入 qa-progress.json 顶层。判定严重程度依据场景属性(category、source),而非规划器分配的优先级标签。任何失败都绝不会得到干净的"go":- 任何
source: "journey"的场景failed或blocked→"no-go"(组合式用户路径中断——集成环节失败) - 任何可追溯到核心用户旅程的
category: "ux-flow"场景failed或blocked→"no-go"(理想路径中断) - 任何涉及数据丢失或安全敏感路径的场景
failed或blocked→"no-go"(安全关键故障) - 任何其他场景
failed或blocked→"conditional"(功能可用,但存在已记录的问题——是否继续由人工决定) - 全部
validated→"go"
将判定结果及产生该结果的输入(触发判定的场景及原因)一并写入,以便使用方既能使用预先计算的判定结果,也能验证其计算过程。
- 任何
JSON 文件就是你的报告。下游使用方会将其呈现在 PR 正文中。向调用方报告已完成。
如果 tmp/ship/ 不存在且存在 PR: PR 正文中的 ## Verification 部分就是你的报告。确保其中包含所有结果且内容为最新。不要另发 PR 评论。
如果不存在 PR: 直接向用户报告:
- 已测试、通过、失败和受阻的场景总数
- 发现并修复的 bug(简要说明每个问题)
- 缺口——因工具限制或环境约束而无法测试的内容
- 判断——诚实评估此功能是否已准备好供人工审查
这项技能的任务是修复能修复的问题、记录发现,并清晰地交接当前情况。无法解决的问题和缺口需要记录,不得悄然忽略——但它们不会阻碍后续进展。由调用方决定如何处理剩余事项。
拆除环境(必须执行): 报告后,拆除第 2 步中启动的所有内容。终止开发服务器,停止 Docker 容器(docker compose down -v),清理测试数据,并移除 tmp/ 中的临时文件。按启动顺序的逆序拆除。查看 bootstrapResult.teardownRequired 获取完整清单。将环境恢复到发现时的状态。
QA 执行边界
唯一的硬性边界:不得修改云端/外部系统。 其他操作只要在本地进行,都可以执行。将任何事项标记为无法验证之前,请先穷尽所有本地方案。
允许范围 — 穷尽以下方案(完整的本地工具集):
- 浏览器自动化(通过
/browser使用 Playwright)——导航、点击、填写表单、检查控制台/网络、录制视频、审计无障碍功能、测试响应式布局 - Docker 容器——通过
docker compose up -d启动数据库、缓存、队列、模拟服务。完成后将其关闭。 - 本地开发服务器——安装依赖、启动服务器、填充数据库、启动工作进程
- Shell 脚本、REPL 会话、从使用者角度出发的导入脚本
- 调用本地运行端点的 API(
curl localhost:...) - 在
tmp/中编写临时验证脚本——编写、运行、删除 - 临时测试服务器、fixture 文件、种子数据
- 操作环境变量、切换功能标志
- 运行项目自带的测试套件、代码检查工具、类型检查工具
- 查询数据库以验证变更是否正确
- 测试流程中的日志/遥测检查
- 在本地模拟外部服务响应(MSW、nock、VCR、拦截 HTTP)
- 通过向 localhost 发送模拟负载来测试 webhook 处理程序
- 验证出站 HTTP 请求格式是否正确(拦截并断言,不要发送)
禁止范围(硬性边界):
- 请求生产 URL、暂存环境或第三方实时 API
- 会触发计费、计量或配额消耗的操作
- 发送真实邮件、Slack 消息或发往外部服务的 webhook
- 访问生产数据库或客户数据
- 对云端/外部系统进行任何修改(向非 localhost 地址发送 POST/PUT/DELETE 请求)
当场景需要外部服务时:
- 首先检查是否有本地替代方案:Docker 镜像、本地模拟器(例如用于 AWS 的 LocalStack、用于 webhook 的 Stripe CLI)、模拟服务或模拟负载
- 如果存在本地替代方案,就使用它。这也算作真实验证。
- 如果不存在本地替代方案,就尽可能在本地验证(例如,验证 webhook 处理程序能否正确响应模拟负载),然后将场景标记为
status: "blocked",并在备注中说明你在本地验证了什么,以及需要人工在暂存/生产环境中验证什么 blocked场景会作为待人工验证事项流转至/pr,不会被悄然丢弃
blocked 是安全网,而非首选方案。 只有在穷尽 Docker、本地模拟器、模拟负载和拦截请求之后,才能将场景标记为 blocked。如果你能在本地验证场景的 80%,只有最后与外部系统交接的环节需要人工检查,就要说明已验证的 80% 以及人工需要检查的 20%。每个 blocked 场景都必须注明尝试过哪些操作。
校准测试深度
测试深度取决于 qa-progress.json 计划——执行其中的每个场景。该计划旨在尽可能全面;你的任务是以最高可实现的保真度验证每个场景。
测试广度应与受影响的范围相匹配。 如果是独立执行(没有 qa-progress.json),则根据变更范围校准测试场景数量:单文件 Bug 修复需要 1-2 个有针对性的场景,以及一次回归检查;涉及多个文件和多个界面的功能,则需要为每个受影响的界面设计场景,并覆盖边缘情况和错误路径。不要采用固定数量——应根据受影响界面的数量来决定测试场景数。
测试不足的表现如下(应避免这些问题):
- 功能有面向用户的界面,却仅凭单元测试就宣称有把握
- 测试对服务边界进行了模拟,却声称某个场景“已由测试覆盖”
- 功能有 UI,却从未打开浏览器——
/browser始终可加载,且默认运行无头 Playwright - 跳过错误路径测试
- 未测试新代码与现有代码的交互
- 修改数据后未检查数据库状态
- 存在 docker-compose.yml 且可据此启动真实数据库,却未启动 Docker
- 未先加载
/browser、启动开发服务器、启动 Docker,或尝试本地模拟器、模拟负载和临时脚本,就将场景标记为blocked
反模式
- 把 QA 当作打勾了事。 没有具体细节,“我测试过了”毫无意义。每个场景都必须有明确的操作和预期结果。
- 只测试顺利路径。 真实用户会遇到错误、边缘情况和意外状态。也要测试这些情况。
- 隐瞒测试缺口。 如果无法测试某项内容,请明确说明。未记录的缺口比已记录的更糟。
- 混淆编排无头模式与浏览器不可用。 Ship 的
--headless标志和容器检测会进入编排自主模式——无需用户把关,也无需协商检查点。这并不意味着“环境无法运行浏览器”。/browser的 Playwright 引擎默认运行无头 Chromium,这正适用于 Ship 的--headless运行。加载/browser并运行这些场景。 - 以“需要浏览器”“需要 Playwright”“需要开发服务器”“需要 UI”或“无法在无头模式下运行”为由将场景标记为
blocked。 这些正是/browser和第 2 步引导流程的主要用途,并非有效的阻塞理由。如果你写了其中任何一项作为阻塞理由,请停下来:你实际上还没有尝试加载/browser或启动开发服务器。先完成这些操作,再重新评估。 - 使用 Peekaboo 或 Claude-in-Chrome 进行网页测试。
mcp__peekaboo__*和mcp__claude-in-chrome__*工具不适用于 QA 网页测试。请使用/browser(Playwright)。Peekaboo 仅用于 OS 级别的 macOS 自动化。Chrome 扩展用于 QA 之外、由用户临时指定的浏览器任务。