将视觉QA作为CI流水线阶段

上个月我合并了一个PR。代码审查看起来不错。测试通过了。然后我在手机上打开网站,侧边栏完全坏了。

修复很简单——缺少一个媒体查询。一旦你真正查看移动视图,bug就很明显了。没人看。

所以我添加了一个会看的流水线阶段。

我打开一个GitHub issue,上面写着:

实现手动输入选项以添加客户。点击时,打开一个宽幅滑出抽屉,其中包含创建新客户的表单。

一次提交后,PR带着30多张截图到达,证明每个状态在每个视口下都能工作。零手动测试。唯一的努力是编写功能描述。

概念

每次推送到PR时,GitHub Actions工作流会:

  1. 从PR分支启动Docker Compose堆栈(Django + Postgres)
  2. 运行迁移并植入测试用户
  3. 将本地URL交给带有Playwright MCP(无头Chromium)的Claude Code
  4. 基于PR差异执行交互元素
  5. 在三个视口截取每个状态
  6. 将结果作为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×768iPad纵向
移动640×1136iPhone SE

每个交互状态都在所有三个视口中捕获。一个侧边栏组件生成12+张截图:

乘以明/暗主题,你看到的是一个组件24张截图。这就是重点。详尽的视觉证明作为CI工件。

实际输出内容

这是一个添加了手动输入页面的PR的实际输出。代理发布PR评论,包含结构化摘要、每个视口的截图和测试清单:

视觉QA代理创建的GitHub issue

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

桌面视图: 1440x900下的客户列表和添加客户抽屉

代理测试了:

我跳过了此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评论中的内联截图有几个优势:

  1. 可见 — 审查者无需点击工件即可看到它们
  2. 上下文化 — 就在他们正在审查的差异旁边
  3. 可比较 — 每次推送都会更新评论,所以你看到前后对比
  4. 可审计 — 评论历史显示每次提交时测试了什么

这能捕获什么

第一周捕获的真实bug:

每一个都通过了代码审查。每一个在截图中都很明显。

成本

在CI中使用Playwright MCP运行Claude需要2-4分钟,具体取决于需要测试多少状态。对于触及一个组件的典型PR,大约需要90秒。

对比:部署到生产环境,然后从用户那里得知移动布局坏了。无价。

转变

视觉QA一直是瓶颈。你可以自动化单元测试、集成测试,甚至端到端流程——但仍然有人必须查看UI。几十年来一直如此。

现在不再如此。带浏览器的代理不只是运行脚本。它们解释、导航、交互和判断。测试面不是硬编码的——它是从变更中推断出来的。每个PR都获得详尽的视觉覆盖,这是任何手动过程都无法匹敌的。

这将QE从门户变为保证。不是"有人检查过吗",而是"流水线检查过了,这是证明"。每个PR,每次推送,每个视口。QE角色不会消失。它向上游移动。不是执行测试计划,而是定义代理应该关心什么。不是捕获bug,而是设计捕获它们的系统。