mcpField
Q
qa

qa

github.com/inkeep测试与质量61 次安装
代码仓库

关于 qa

qa 是供 QA 工程师和 Agent 使用的技能,在自动化测试通过后依据真实用户体验验证功能。它会搭建测试环境,以可实现的最高保真度检查界面和集成,并记录结果;支持监督、无头和仅报告模式。

主要功能

  1. 01搭建测试环境与依赖
  2. 02使用 Playwright 检查界面和体验
  3. 03验证真实 API 与数据行为
  4. 04跟踪场景及验证结果
  5. 05支持监督、无头和仅报告模式

适用场景

  1. 01检查功能的真实界面流程
  2. 02验证 API 响应和存储数据
  3. 03在容器中自主运行 QA
  4. 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。

“无头模式”≠“没有浏览器”

在此工作流中,“无头”有两种互不相关的含义。请勿混淆:

  1. 编排无头模式——上文的 --headless 标志/容器检测入口。意思是“没有人工参与、没有用户关卡、自主运行”。它与工具无关。
  2. 无头浏览器——在没有可见窗口的情况下运行的 Chromium(或其他)浏览器进程。这是 /browser 的 Playwright 引擎默认的运行方式,在任何环境中都如此,包括 CI、Docker 和 --headless ship 运行。

/browser 是自主 QA 的主要工具,正是因为无头 Playwright 是其默认模式。依赖浏览器的场景——视觉正确性、UX 流程、表单提交、响应式布局——是 /browser 的主要用途,也是它存在的原因。

绝不要以“需要浏览器”“需要 Playwright”“需要开发服务器”“需要 UI”或“无头模式下无法运行”等理由将场景标记为 blocked。 这些都不是有效的阻塞原因——它们描述的正是 /qa、/browser 和开发服务器启动步骤旨在处理的场景。加载 /browser,启动开发服务器,然后运行该场景。


创建工作流任务(首要操作)

开始任何工作前,使用 TaskCreate 为每个步骤创建一个任务,并通过 addBlockedBy 强制执行顺序。根据每个步骤自身的工作流文本推导描述和完成标准。

  1. QA:检测工具
  2. QA:初始化环境 — 启动开发服务器,启动 Docker 依赖,加载 /browser skill(强制,无条件——无头 Playwright 是默认引擎),填充数据库。尽可能达到最高保真度。
  3. QA:制定测试计划并检查覆盖率实际情况 (如果 tmp/ship/qa-progress.json 存在,则跳过——计划由 /qa-plan 提供)
  4. QA:解决可修复的缺口 (当 qa-progress.json 包含 enrichment.gapType === "fixable_gap" 的场景时) (如果使用 --report-only,则跳过——创建时将任务标记为 deleted)
  5. QA:执行测试场景
  6. QA:记录结果
  7. QA:报告并拆除环境

每个任务开始时将其标记为 in_progress,并在其步骤的退出标准满足时标记为 completed。重新进入时,先检查 TaskList,然后从第一个未完成的任务继续。


工作流

步骤 1:检测可用工具

必需技能(始终可加载 — 无需探测):

技能在 QA 中的作用加载时机
/browserUI 测试、表单流程、视觉验证、端到端 UX、错误状态渲染、布局审计、控制台/网络检查、a11y 审计、视频录制的主要引擎。默认运行无头 Playwright。步骤 2,无条件加载

/browser 是一项技能,不是受环境限制的功能。Skill 工具能够可靠地加载它——无需探测、无需“不可用时”的回退,也不受环境条件限制。其无头 Playwright 引擎适用于所有可能运行 /qa 的环境(本地开发、容器、CI、--headless ship)。步骤 2“浏览器层级”会直接加载它;工作流的其余部分都假定它已加载。

环境探测(检测 → 使用,或记录并回退):

功能检测方法用途不可用时
Shell / CLI始终可用API 调用(curl)、CLI 验证、数据校验、数据库状态检查、进程行为检查、文件/日志检查—
Dockerdocker 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",并记录相关差距——不能标记为“通过推断验证”。

启动流程:

  1. 阅读设置说明。 检查 CLAUDE.md、AGENTS.md、package.json(scripts)、Makefile、docker-compose.yml、README,查找构建/运行/设置命令。这是启动环境的操作指南。

  2. 确定目标保真度。 如果存在 qa-progress.json,则读取该文件——根据场景类别确定引导目标:如果任一场景的 category 为 visual 或 ux-flow,目标为 browser;如果最高类别为 error-state、integration 或 cross-system,目标为 api;否则目标为 shell。如果不存在 qa-progress.json,则根据可用工具以及功能涉及的内容进行推断。

  3. 自下而上进行引导,然后在此基础上加载浏览器。 从 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,就使用它。如果项目有种子脚本,就运行这些脚本。目标是尽可能达到最高保真度,而非尽量减少设置工作量。

  4. **如果某一级别的引导失败,**记录原因(缺少环境变量、Docker 未运行、依赖安装失败、端口被占用),并继续执行成功的级别。绝不要因为某项服务无法启动而完全阻止 QA——应在实际可达到的保真度下进行测试。

  5. 立即记录已达到的上限——在引导完成后、开始执行前,将 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 技能并遵循以下流程:
    1. 检查 BROWSER_AUTH_USER / BROWSER_AUTH_PASS 环境变量。如果存在,调用 helpers.authenticate(page, { username: process.env.BROWSER_AUTH_USER, password: process.env.BROWSER_AUTH_PASS })。
    2. 如果之后需要 TOTP/2FA,调用 helpers.generateTOTP(process.env.BROWSER_AUTH_TOTP_SECRET) 并填写验证码。
    3. 对于多站点身份验证,优先尝试特定域名变量(BROWSER_AUTH_GITHUB_USER 等),再回退到默认变量组。凭据组应与要进行身份验证的域名匹配。
    4. 对于 OAuth/SSO 重定向,点击 SSO 按钮,跟随重定向,并在 IdP 页面填写凭据。
    5. 根据 /browser SKILL.md 中的身份验证墙分类表对身份验证墙进行分类。穷尽自动化方法后再将其标记为阻塞。
    6. 在受监督模式下:如果遇到难以自动处理的障碍(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 是否能正确更新界面?”

将每个场景写成独立的测试用例:

  1. 用户体验到的内容(从用户角度描述结果)
  2. 你要执行的操作(用于验证结果的操作)
  3. 通过标准(基于可观察行为的预期结果)

将这些内容创建为任务清单项,以跟踪执行进度。

步骤 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 评论发布。

  1. 读取当前 PR 正文:gh pr view <number> --json body -q '.body'
  2. 如果已存在 ## Verification(或旧版 ## Manual QA)部分,请用更新后的检查清单替换其内容。
  3. 如果不存在此类部分,请将其追加到正文末尾。
  4. 将更新后的正文写回: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")时,请在执行前解决这些问题:

  1. 排序:按数组顺序排列可修复的缺口(即 /qa-plan 中的场景顺序)。
  2. 对于每个缺口,尝试使用现有的修复循环进行修复:定位源代码缺口 → 实施修复 → 提交 → 验证修复是否解决了缺口。
  3. 如果已修复 → 将场景的状态设为 "planned",以便在执行期间进行测试。
  4. 如果未修复(达到自我调节阈值或修复不可行)→ 将状态设为 "blocked",并添加说明已尝试的内容的备注。
  5. 继续在步骤 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 层后端行为、响应结构、身份验证流程
shellCLI、数据库查询、文件/日志检查状态验证、数据完整性、进程行为

每个场景默认使用可行的最高等级。通过 api 验证的视觉布局场景,与通过 browser 验证的场景有实质区别——报告使用方需要了解这一点。 使用临时脚本为自己解围。 不要等待正式的测试基础设施、已发布的包或 CI 流水线。如果需要验证某些内容,就快速编写并运行脚本。将所有一次性产物——脚本、测试夹具、测试数据、临时配置——放在仓库根目录的 tmp/ 目录中(通常已被 Git 忽略)。这些都是临时文件,无需达到生产级质量。具体做法:

  • 快速验证脚本: 编写一个导入模块、调用函数并断言输出的脚本,然后运行它。完成后删除(或将其留在 tmp/ 中)。
  • 本地包引用: 使用 file:../path、工作区链接或 link:,而不是等待包发布。直接测试磁盘上的代码。
  • 从使用者角度编写脚本: 编写一个脚本,按照下游使用者的方式导入/引用该包。验证导出内容、类型、公共 API 接口和行为是否符合预期。
  • REPL 探索: 使用 REPL(node、python 等)交互式探查行为、测试边界情况,或在提交完整场景前验证假设。
  • 临时测试服务或夹具: 启动一个最小化服务、填充测试数据库,或在 tmp/ 中创建夹具文件以供测试。完成后关闭并清理。
  • 环境变化: 使用不同的环境变量、功能标志或配置值进行测试,验证功能能否正确处理配置,尤其是缺失或无效的配置。

使用浏览器自动化时:

  • 打开功能页面并浏览操作。点击、填写表单并提交。
  • 端到端地走完整个用户流程——不要只验证单个页面。
  • 检查视觉布局——显示是否正常?是否有错位、裁切或缺失?
  • 测试错误状态——提交无效数据、断开连接、触发边界情况。
  • 如果功能支持响应式布局,请在不同视口尺寸下测试。
  • 测试键盘导航和焦点管理。

视频录制(所有浏览器场景均默认启用): 对于每个使用浏览器自动化的场景,请在开始场景前使用 /browser 的 helpers.createVideoContext(browser, { outputDir: '/tmp/playwright-videos' }) 创建视频上下文。它会自动录制所有操作——无需提前规划。场景完成后(无论通过还是失败):

  1. 关闭页面以完成录制:const videoPath = await page.video().path(); await page.close();
  2. 上传到 Bunny Stream:加载 /media-upload 技能,然后调用 uploadToBunnyStream(videoPath, { name: '<scenario-id>-<scenario-name>' })。设置命令:./secrets/setup.sh --skill media-upload。
  3. 将 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),请验证任务已正确入队并完成。

通用测试方法:

  1. 从干净状态开始(没有缓存数据,使用新会话)。
  2. 先走通正常流程——按照规范进行端到端测试。
  3. 然后尝试破坏流程——测试你识别出的每一种失败模式。
  4. 然后施加压力——测试边界条件、意外输入和并发访问。
  5. 最后检查呈现效果——视觉正确性、易用性,以及“感觉是否合适”。

断言深度——验证状态变更,而非只观察:

不要只确认页面已加载或操作已完成。对于每项涉及状态变更的验证(变更、导航、表单提交、模态框打开/关闭),请遵循以下准则:

  • 每项断言使用两个独立信号。 至少检查两个独立信号,以确认状态变更。例如:URL 已更改,且新内容已出现;项目已添加到列表,且计数已更新;表单已提交,且测试收件箱中已收到确认邮件。单一信号容易受到偶然因素影响,两个信号能显著降低误报的可能性。
  • 优先使用结构化证据,而非目视检查。 使用浏览器自动化时,优先返回 Playwright 调用的结构化证据,而不是目视检查屏幕截图:
    // 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' });
    
    结构化证据速度更快(无需视觉处理)、成本更低(无需图像 token),并且会在 qa-progress.json 笔记中生成可审计的结果。

这些准则适用于状态变更验证,而非“页面已加载”或“元素存在”这类简单检查。

浏览器场景的自愈(healer 循环)

浏览器脚本失败时,先对失败进行分类,再采取行动:

失败类型信号操作
选择器漂移等待元素时出现 TimeoutError、找不到元素、点击了错误的元素使用 getPageStructure() 重新探索,修正选择器,然后重试(最多 2 次)
时序问题竞态条件、元素尚未显示、网络尚未稳定添加 waitForSelector / waitForLoadState('domcontentloaded'),然后重试
应用程序错误元素存在但显示错误内容、状态码错误、控制台报错、意外重定向不要重试——提供证据并报告失败
环境问题连接被拒绝、DNS 失败、身份验证已过期报告为 blocked,而不是 failed

Healer 循环(最多 2 次迭代):

  1. 脚本失败 → 阅读错误消息
  2. 分类:选择器/时序问题、应用程序错误或环境问题
  3. 如果是选择器/时序问题: a. 在当前页面状态下运行 getPageStructure(page, { interactiveOnly: true }) b. 将观察到的元素与脚本预期的元素进行比较 c. 使用修正后的选择器重写脚本中失败的部分 d. 重新运行脚本 e. 如果再次因同类错误失败 → 报告为 failed,并注明:“Healer:已重试 2 次,选择器/时序问题仍然存在——可能是应用程序错误”
  4. 如果是应用程序错误:立即提供证据(控制台错误、网络故障、截图)并报告——不要重试
  5. 如果是环境问题:提供具体错误并报告为 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 之前,请执行以下操作之一:

  1. 如果尚未加载 /browser: 现在加载它并重新执行该场景。
  2. 如果开发服务器尚未运行: 启动它(使用 npm run dev,或从 package.json 的 scripts / CLAUDE.md / Makefile 中找到等效命令),然后重新执行该场景。
  3. 只有在确认以上两项都已完成后——/browser 已加载,且开发服务器正在响应——如果仍无法验证该场景,再继续下面的 /debug 挑战。

真正应标记为 blocked 的场景:严格的身份验证墙(hCaptcha、SMS MFA、WebAuthn)、没有本地替代方案且无沙盒模式的外部服务,或即使 /browser 已顺利加载,Chromium 在此环境中仍完全无法启动的运行时故障。这些情况很少见——标记阻塞前,请先确认确实属于其中一种。

何时发起挑战: 短路检查之后,任何将被标记为 blocked 的场景都必须发起挑战——没有例外。所需成本(每个场景约 2–5 分钟)与阻塞场景的数量成正比,而阻塞场景应该很少。错误地标记为阻塞、导致人工之后必须手动验证的成本要高得多。

子进程说明:

  1. 启动一个嵌套的 Claude 子进程。
  2. 子进程使用 --headless 加载 /debug。
  3. 向其提供以下信息:
    • 场景(id、name、userOutcome、given/when/then)
    • 你认为该场景被阻塞的原因
    • 项目根目录路径
  4. 子进程独立调查——从基本原理出发,不受你先前假设的影响:
    • 质疑所有关于场景为何被阻塞的假设
    • 探查项目测试框架的能力(spy/mock、集成配置)、可用的 API 密钥(当前环境、.env 文件),以及与此特定项目和生态系统相关的其他能力
    • 尝试编写并运行测试,或寻找替代验证途径
    • 如果发现有阻止测试的 Bug,则修复它
  5. 解析子进程的结果:
    • 如果找到验证该场景的方法:使用 debug 调查所得证据将场景更新为 validated。在 notes 中注明调查贡献:"resolvedBy": "debug-challenge"。
    • 如果确认场景确实被阻塞:保留 blocked 状态,并将 debug 分析作为证据记入 notes。调查过程记录证明已穷尽所有途径——下游使用者可以看到为何被阻塞,而不仅仅是已被阻塞。

为何使用子进程,而非内联调查: /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 场景本身就是回归检查)、受影响区域没有现成测试先例,或者根本原因位于第三方依赖、配置文件或构建流水线中。
  • 根本原因不明确(行为出乎意料,无法从症状看出原因)——在尝试修复前,加载 /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/ 存在: 报告前的最后一步:

  1. 将当前 HEAD 提交哈希(git rev-parse HEAD)写入 qa-progress.json 的 qaCompletedAtCommit。这会标记 QA 与 QA 后更改之间的边界,以便检测过时状态。

  2. 计算并将 executionSummary 写入 planMetadata:

    "executionSummary": {
      "validated": 12,
      "failed": 1,
      "blocked": 2,
      "planned": 0
    }
    

    按状态统计场景数量。这样可以避免所有下游使用方都扫描完整的场景数组。

  3. 计算并将 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 请求)

当场景需要外部服务时:

  1. 首先检查是否有本地替代方案:Docker 镜像、本地模拟器(例如用于 AWS 的 LocalStack、用于 webhook 的 Stripe CLI)、模拟服务或模拟负载
  2. 如果存在本地替代方案,就使用它。这也算作真实验证。
  3. 如果不存在本地替代方案,就尽可能在本地验证(例如,验证 webhook 处理程序能否正确响应模拟负载),然后将场景标记为 status: "blocked",并在备注中说明你在本地验证了什么,以及需要人工在暂存/生产环境中验证什么
  4. 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 之外、由用户临时指定的浏览器任务。

评论

支持 Markdown