将视觉QA作为CI流水线阶段
上个月我合并了一个PR。代码审查看起来不错。测试通过了。然后我在手机上打开网站,侧边栏完全坏了。
修复很简单——缺少一个媒体查询。一旦你真正查看移动视图,bug就很明显了。没人看。
所以我添加了一个会看的流水线阶段。
我打开一个GitHub issue,上面写着:
实现手动输入选项以添加客户。点击时,打开一个宽幅滑出抽屉,其中包含创建新客户的表单。
一次提交后,PR带着30多张截图到达,证明每个状态在每个视口下都能工作。零手动测试。唯一的努力是编写功能描述。
概念
每次推送到PR时,GitHub Actions工作流会:
- 从PR分支启动Docker Compose堆栈(Django + Postgres)
- 运行迁移并植入测试用户
- 将本地URL交给带有Playwright MCP(无头Chromium)的Claude Code
- 基于PR差异执行交互元素
- 在三个视口截取每个状态
- 将结果作为PR评论发布
Claude Code控制浏览器。Docker Compose提供应用。GitHub Actions将它们联系在一起。审查者无需在本地运行任何东西就能看到功能证明。
%%{init: {"flowchart": {"subGraphTitleMargin": {"top": 3, "bottom": 10}}} }%%
graph TD
A["PR推送"] --> B
subgraph GHA["GitHub Actions Runner"]
B["Checkout + Docker Compose"] --> C["迁移 + 种子数据"]
C --> Agent
subgraph Agent["Claude Code + Playwright"]
direction LR
D["读取PR差异"] --> E["映射文件 → URLs"]
E --> F["登录 + 导航"]
F --> G["截图 ×3视口"]
end
end
Agent --> H["PR评论"]
style GHA fill:transparent,stroke:#888
style Agent fill:transparent,stroke:#888
工作流本身很直接:
name: UI Screenshots
on:
pull_request:
types: [opened, synchronize]
jobs:
screenshots:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
# ... Docker Compose设置、迁移、创建测试用户 ...
- name: Start services
run: |
docker compose -f docker-compose.local.yml up -d --wait
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Run Claude for Screenshots
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: >-
--allowed-tools
"mcp__playwright__*,
Bash(gh pr diff:*),
Bash(gh pr comment:*),
Read,Glob,Grep"
prompt: |
You are a visual QA agent. Your job is to visually
verify a UI deployment.
PR: ${{ github.event.pull_request.number }}
The Django app is running at http://localhost:8001
1. Run `gh pr diff` to find changed templates/views
2. Map changed files to URLs — only test pages
affected by this push
3. Login, then screenshot each page at three viewports:
1440x900, 1024x768, 640x1136
4. Save screenshots with descriptive filenames
5. Post a PR comment with:
- Each test performed with pass/fail
- Any visual issues found
- Total screenshots captured
视口矩阵
| 设备 | 尺寸 | 原因 |
|---|---|---|
| 桌面 | 1440×900 | 标准笔记本电脑 |
| 平板 | 1024×768 | iPad纵向 |
| 移动 | 640×1136 | iPhone SE |
每个交互状态都在所有三个视口中捕获。一个侧边栏组件生成12+张截图:
- 页面加载(关闭) — 3个视口
- 触发器点击 — 3个视口
- 完全打开 — 3个视口
- 关闭动画 — 3个视口
乘以明/暗主题,你看到的是一个组件24张截图。这就是重点。详尽的视觉证明作为CI工件。
实际输出内容
这是一个添加了手动输入页面的PR的实际输出。代理发布PR评论,包含结构化摘要、每个视口的截图和测试清单:

在该摘要下方,代理附加了它执行的每个状态的截图。这是桌面视图——包含数据的客户列表,以及打开的添加客户抽屉:

代理测试了:
- 空表单状态
- 带验证错误的表单(缺少必需的名称字段)
- 表单提交成功
- 带数据的客户列表渲染
- 无图像时的头像后备
- 模态框打开/关闭过渡
- 所有三个视口
我跳过了此PR的单个字段焦点状态——简单表单的收益递减。代理可以做到,但文本输入获得焦点的50张截图不是有用的信号。
PR评论成为视觉变更日志。每次测试运行都以清单结束:

六个月后,我可以查看任何PR并准确看到发布时UI的样子。
测试清单
上述工作流中的提示是通用的——适用于任何PR。特定性来自差异本身。代理读取gh pr diff,找出哪些模板和视图发生了变化,将它们映射到URL,并决定测试什么。
对于表单组件,这意味着它将执行:
component: ClientForm
states:
- empty form
- filled form (valid data)
- validation error (submit without name)
- submitting (loading state)
- success (confirmation)
interactions:
- submit empty form (trigger validation)
- fill all fields, submit
- verify client appears in list after submit
不需要手动测试计划。代理从代码更改中推断测试面。
为什么在PR评论中使用截图
PR评论中的内联截图有几个优势:
- 可见 — 审查者无需点击工件即可看到它们
- 上下文化 — 就在他们正在审查的差异旁边
- 可比较 — 每次推送都会更新评论,所以你看到前后对比
- 可审计 — 评论历史显示每次提交时测试了什么
这能捕获什么
第一周捕获的真实bug:
- 被
overflow: hidden父元素裁剪的下拉菜单(仅在平板上可见) - 深色模式下不可见的按钮文本(错误的CSS变量)
- iPhone上错位的表单标签(flexbox gap问题)
- 触摸后卡住的悬停状态(需要
@media (hover: hover)) - 横向平板上模态背景未覆盖完整视口
每一个都通过了代码审查。每一个在截图中都很明显。
成本
在CI中使用Playwright MCP运行Claude需要2-4分钟,具体取决于需要测试多少状态。对于触及一个组件的典型PR,大约需要90秒。
对比:部署到生产环境,然后从用户那里得知移动布局坏了。无价。
转变
视觉QA一直是瓶颈。你可以自动化单元测试、集成测试,甚至端到端流程——但仍然有人必须查看UI。几十年来一直如此。
现在不再如此。带浏览器的代理不只是运行脚本。它们解释、导航、交互和判断。测试面不是硬编码的——它是从变更中推断出来的。每个PR都获得详尽的视觉覆盖,这是任何手动过程都无法匹敌的。
这将QE从门户变为保证。不是"有人检查过吗",而是"流水线检查过了,这是证明"。每个PR,每次推送,每个视口。QE角色不会消失。它向上游移动。不是执行测试计划,而是定义代理应该关心什么。不是捕获bug,而是设计捕获它们的系统。