AI 改完页面后,浏览器验收不能省
Typecheck 通过 ≠ 页面能用
我在过去半年的 AI 编程实践中,反复遇到同一类问题:Agent 写完代码,跑通 typecheck,跑过单测,报告「已完成」。但打开浏览器一看——响应式溢出、hydration 错误、资源 404、交互状态断裂——这些没有一个能被 typecheck 或单测捕获。
代码产出越快,这个缺口越大。Autonoma 的研究指出 AI Agent 的代码产出量是人工的三个数量级,而浏览器验收是补上这个缺口最直接的手段。
GitLab 的前端测试规范里有一句话值得贴出来:
"There's always the chance of accidentally breaking the user flow, while the tests pass and provide a false sense of security."
haikumethod.ai 的一篇复盘记录了一个具体的案例:CI 绿灯,单测全过,代码审查签字——页面还是坏的。多步工作流的所有检查都通过了,但最终界面上新增的 UI 元素根本没渲染出来。问题不在于代码写错了,在于整个流程里没有一层检查在打开浏览器看结果。
AI 编程时代,浏览器层的缺口
传统开发中,工程师写完代码会习惯性地在浏览器里点两下——这是肌肉记忆。AI 编程打破了这个循环:Agent 不打开浏览器,它的「完成」定义是 typecheck + test pass。当你依赖 Agent 产出代码,同时又省略了人工浏览器检查这一步,质量缺口就出现了。
下表列出了 AI 生成代码在浏览器层常见的盲区:
| 问题类型 | 具体表现 | Typecheck 能捕获 | 单测能捕获 |
|---|---|---|---|
| 响应式溢出 | 移动端按钮被挤出视口、文字换行断裂 | ✗ | ✗ |
| Hydration 不一致 | SSR 输出与客户端初始渲染不同,控制台报红 | ✗ | 部分 |
| 资源加载失败 | 图片 404、字体回退、SVG 不显示 | ✗ | ✗ |
| 布局遮挡 | 固定定位元素覆盖可交互区域 | ✗ | ✗ |
| 交互状态断裂 | 点击后状态未更新、URL 未变化 | 部分 | 部分 |
| 控制台异常 | 未捕获 Promise rejection | ✗ | ✗ |
| 视觉回归 | 按钮颜色变了、间距不一致 | ✗ | ✗ |
Typecheck 能覆盖的场景,全部是编译期可推导的;用户在浏览器里遇到的问题,绝大多数发生在运行期。A Modern Quality Pipeline 一文中将这种思路总结为「分层置信」——每一层检查只负责它能抓到的问题,越靠近用户感知的层越贵,但也能抓到越底层抓不到的东西。
浏览器验收是质量流水线的最后一道闸门。前面的层越严格,浏览器验收时需要修的越少;但无论前面多严格,浏览器验收都不能跳过。
五个验收层面
一、首屏渲染
首屏渲染是用户形成第一印象的窗口。页面白屏超过 3 秒、骨架屏卡住不消失、关键图片裂开——用户不会等你 debug。
AI 生成代码在首屏渲染上常见的坑:
- 数据请求路径断裂:Agent 写了
useEffect请求数据,但 API 路径拼错或环境变量没传进去,请求 404,页面拿到空数据后渲染空白。 - 骨架屏永远不消失:骨架屏的消失条件依赖某个 loading 状态变量,但 Agent 改动了数据获取逻辑后忘了更新这个状态。
- 动态导入失败:
next/dynamic引用了不存在的组件路径,构建能过(因为 dynamic 是运行时加载),但页面直接白屏。
验收脚本的核心逻辑并不复杂——打开浏览器,导航到目标 URL,收集网络错误和控制台错误,截图存档:
// 首屏验收的核心判定
const pass = firstPaint < 3000 // 3 秒内完成
&& networkErrors.length === 0 // 无 4xx/5xx
&& !consoleErrors.some(e => // 无 hydration 错误
e.includes('hydration')
)任何一条不满足,pass 为 false,需要回传 Agent 修复。完整实现可以用 Playwright 的 page.on('response') 和 page.on('console') 事件收集错误,page.screenshot() 截图存档。
二、关键交互
静态截图能抓布局,但抓不到交互逻辑。AI 生成代码的交互问题往往比布局问题更隐蔽。
我在一个分页组件上遇到过这类问题:Agent 实现了一个分页列表,桌面端截图完美,单测通过(因为单测 mock 了数据)。但在浏览器里点击「下一页」,列表内容更新了,页面滚动位置没变——用户看到的是第二页数据的底部,以为没有反应。
这个问题的根因是 Agent 没有加 scrollTo 逻辑。类型系统不会告诉你「翻页后应该滚回顶部」,这是用户交互经验。
验收脚本需要模拟真实用户操作序列:
// 分页交互验收的核心逻辑
const firstItemBefore = await page
.locator('[data-testid="list-item"]').first().textContent()
await page.locator('[data-testid="next-page"]').click()
await page.locator('[data-testid="list-item"]').first().waitFor({ state: 'visible' })
const firstItemAfter = await page
.locator('[data-testid="list-item"]').first().textContent()
// 验证内容已更新
if (firstItemBefore === firstItemAfter) {
throw new Error('分页点击后内容未更新')
}
// 验证页面滚回顶部
const scrollAfter = await page.evaluate(() => window.scrollY)
if (scrollAfter > 100) {
throw new Error(`分页后页面未滚回顶部,当前滚动位置: ${scrollAfter}px`)
}Agent 写单测时通常只验证内容是否更新,滚动位置这类体验细节需要浏览器验收来补。
三、响应式布局
根据 StatCounter 的数据,2026 年全球移动流量占比稳定在 60% 以上。但 AI 生成 UI 代码时,默认优化的往往是桌面端——训练数据中桌面端示例更多,Agent 的截图工具也默认用桌面视口。
| 检查维度 | 桌面端(1440px) | 移动端(375px) | 常见 AI 生成问题 |
|---|---|---|---|
| 导航栏 | 水平菜单 | 汉堡菜单 | 缺少移动端折叠逻辑 |
| 列表布局 | 多列 Grid | 单列 Stack | Grid 不降列,横向溢出 |
| 弹窗/抽屉 | 居中 Modal | 底部 Sheet | Modal 在小屏被键盘遮挡 |
| 表格 | 完整展示 | 横向滚动或卡片化 | 表格撑破容器 |
响应式验收的核心是多视口检测水平溢出:
// 水平溢出检测——AI 代码最常见的响应式问题
const hasHorizontalOverflow = await page.evaluate(() => {
return document.documentElement.scrollWidth > window.innerWidth
})scrollWidth > innerWidth 这一个判断就能抓住大部分 AI 生成的响应式问题。桌面端写的 min-width: 1200px 容器在 375px 屏幕上不会自动收缩,直接把页面撑出横向滚动条。建议至少在 1440px、768px、375px 三个视口各跑一次。
四、控制台错误
控制台错误是开发者的早期预警。忽略控制台错误,等于对着一堆告警灯选择性地不看。
| 错误类型 | 典型信息 | 影响程度 |
|---|---|---|
| Hydration 错误 | Text content did not match | 高——可能导致交互失效 |
| 资源加载失败 | GET .../image.png 404 | 中——用户看到破碎图标 |
| CORS 错误 | blocked by CORS policy | 高——数据完全无法加载 |
| 未捕获异常 | Unhandled Promise Rejection | 高——可能导致页面白屏 |
| React Key 错误 | Each child should have a unique "key" | 中——列表更新时渲染错乱 |
| Deprecated API | ... is deprecated | 低——当前可用但未来会坏 |
验收时的关键做法是错误分级:Hydration 错误、网络失败、未捕获异常是 critical——必须修复才能通过;React key 警告是 warning——应该修但不阻塞;deprecated API 是 info——记录下来即可。给 Agent 回传修复指令时,只传 critical 错误,避免噪音淹没有效信息。
五、视觉回归
前四个层面关注功能正确性,视觉回归关注「看起来对不对」。AI 改代码时经常产生意外的视觉变更——按钮颜色变了、间距宽了、字体从 Inter 降级到了 system-ui——这些不影响功能,但用户会觉得「页面变了」。
2026 年的视觉回归测试工具链已经相当成熟:Chromatic、Percy、Applitools、Lost Pixel 等工具可以在 CI 中自动对比截图,标记像素差异。Delta-QA 的 2026 前端测试指南建议每个关键页面维护 2-3 组视觉基准截图,覆盖不同设备和交互状态。
对于 AI 编程场景,视觉回归的价值在于给 Agent 提供一个可回传的对比证据。Agent 说「已完成」,你跑一次视觉对比,发现按钮从蓝色变成了绿色——把差异截图丢回给 Agent,它就能定位是哪段 CSS 改动导致的。
四个真实案例
案例一:消失的筛选器
让 Agent 给商品列表页增加价格筛选功能。Typecheck 和单测全过——筛选组件的 props 类型正确,onChange 回调的 mock 验证通过。
浏览器验收发现:切到 375px 移动端视口,筛选器被 overflow: hidden 裁掉了。更严重的是,Firefox 下筛选器的 <input type="range"> 滑块完全无法拖动,因为 Agent 用了 WebKit 私有的 CSS 属性。
根因:Agent 的训练数据以 Chrome 为主,跨浏览器兼容性不在它的默认关注范围内。固定高度容器是复制了另一个组件的样式。
修复:移除 overflow: hidden,改用 min-height + flex-grow;range input 用标准属性替换 -webkit-slider-thumb。
案例二:无限循环的请求
让 Agent 实现搜索建议组件,输入时显示联想词。Typecheck 和单测全过——防抖逻辑的单测验证了 300ms 延迟后触发请求。
浏览器验收发现:控制台每 300ms 发出一次相同的 API 请求,即使输入内容没变。Agent 把输入值放在了 useEffect 的依赖数组里,但 useEffect 内部每次都创建新的请求对象(引用不同),触发父组件 re-render,导致输入值引用变化,又触发 useEffect。控制台里还有一条 Maximum update depth exceeded 警告。
根因:循环依赖——useEffect → 状态更新 → re-render → useEffect。JSDOM 单测很难复现,因为 JSDOM 的 render 生命周期和真实浏览器不同。
修复:用 useRef 缓存上一次的查询值,只在值变化时发请求。
案例三:桌面完美、移动端崩溃的表格
让 Agent 给后台管理系统加一个用户数据表格。Typecheck 和单测全过。
浏览器验收发现:1440px 下表格完美展示 8 列数据。切到 375px,表格直接撑出 1200px 宽,操作列(编辑/删除按钮)被挤到视口外面。
根因:Agent 用了 display: grid; grid-template-columns: repeat(8, 1fr),没有加任何响应式断点。
修复:移动端用卡片布局替代表格,或 overflow-x: auto 加首列固定。
案例四:暗色模式下的隐形文字
让 Agent 实现暗色模式切换。Typecheck 和单测全过。
浏览器验收发现:切换到暗色模式后,一个 Alert 组件的文字变成了深灰色(#333),背景也是深灰色(#2d2d2d)——文字完全看不见。这个 Alert 组件用了硬编码的颜色值,没有走 CSS 变量。
根因:Agent 生成新组件时用了硬编码颜色,没有复用项目已有的 design token 系统。Typecheck 不检查颜色值是否适配主题,单测也不渲染暗色模式的上下文。
修复:将所有硬编码颜色替换为 CSS 变量(var(--color-text-primary))。
完整验收清单
五个层面检查完,整理出一份可直接使用的验收清单。可以交给 Agent 当验收标准,也可以人工当 checklist 用。
桌面端(1440px / 1280px)
| # | 检查项 | 通过标准 | 优先级 |
|---|---|---|---|
| 1 | 首屏渲染 | 3 秒内完成,无白屏/骨架屏卡住 | P0 |
| 2 | 网络请求 | 无 4xx/5xx 响应 | P0 |
| 3 | 关键交互 | 本次改动相关的按钮/链接/表单正常工作 | P0 |
| 4 | 状态更新 | 交互后列表刷新、URL 变化、反馈提示正确 | P0 |
| 5 | Hydration | 控制台无 hydration 错误 | P0 |
| 6 | 资源加载 | 图片、字体、图标全部正常显示 | P1 |
| 7 | 布局完整 | 无元素溢出、重叠、截断 | P1 |
| 8 | 视觉一致 | 与基线截图差异 < 0.5% | P1 |
移动端(375px / 414px)
| # | 检查项 | 通过标准 | 优先级 |
|---|---|---|---|
| 1 | 水平溢出 | scrollWidth 不超过 innerWidth | P0 |
| 2 | 触控目标 | 按钮/链接最小 44x44pt,间距 ≥ 8px | P0 |
| 3 | 文字可读 | 无溢出、截断、字号过小(≥ 12px) | P0 |
| 4 | 导航可用 | 汉堡菜单可展开,导航链接可点击 | P0 |
| 5 | 弹窗适配 | Modal/Sheet 不被键盘遮挡,可关闭 | P1 |
| 6 | 图片缩放 | max-width: 100% 生效,不撑破容器 | P1 |
| 7 | 表格/横向内容 | 横向滚动或卡片化降级正常 | P1 |
通过标准:P0 项全部通过 → 验收通过,可以合并。P0 有失败 → 回传 Agent 修复。仅 P1 失败 → 记录为技术债,可合并但需跟进。
把验收指令写给 Agent
手动做浏览器验收太繁琐,也容易漏项。更靠谱的做法是把验收标准写成指令,让 Agent 自己跑浏览器验收——给它明确的 URL、视口、检查点和期望行为。
启动本地服务(pnpm dev),等待 http://localhost:3000 可访问后:
1. 桌面端验收(viewport 1440x900)
- 打开 /blog,等待 networkidle
- 截图保存到 verify-screenshots/desktop-blog.png
- 检查控制台是否有 hydration 错误或网络 4xx/5xx
- 点击分页组件的「下一页」按钮,确认列表内容更新且页面滚回顶部
2. 移动端验收(viewport 375x812)
- 打开 /blog,等待 networkidle
- 检查是否有水平溢出(scrollWidth > innerWidth)
- 截图保存到 verify-screenshots/mobile-blog.png
- 确认分页按钮可见且可点击(最小触控区域 44x44)
3. 如果任何检查失败,将错误信息和截图写入 FIX_REPORT.md,然后修复后重新验收。
这份指令覆盖了五个层面中的四个(首屏、交互、响应式、控制台),加上截图作为证据。视觉回归需要基线截图对比,可以在基础验收稳定后再加入。
从人工到自动化
每次手动开浏览器点不现实。把检查集成到 CI 流水线,AI 生成代码后自动跑,才是可持续的做法。
| 阶段 | 触发时机 | 浏览器验收内容 | 工具 |
|---|---|---|---|
| 本地开发 | Agent 完成代码后 | 快速首屏检查 + 控制台错误扫描 | Playwright 脚本 |
| PR 提交 | CI 构建成功后 | 多视口截图 + 关键交互验证 | Playwright + Preview Deploy |
| Preview URL | 预览部署完成后 | 视觉回归 + 完整交互流程 | Chromatic / Percy |
| 合并前 | 人工 review 时 | 人工抽查 + 截图对比 | 浏览器 DevTools |
核心原则是越早发现问题,修复成本越低。Agent 在本地写完代码就应立即跑一次浏览器验收,不要等到 CI 或 PR 阶段才第一次打开浏览器。
# .github/workflows/browser-verify.yml
name: Browser Verification
on:
pull_request:
paths: ['apps/**', 'packages/ui/**']
jobs:
browser-verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm preview &
- run: npx wait-on http://localhost:3000 --timeout 30000
- run: pnpm tsx scripts/browser-verify.ts
- uses: actions/upload-artifact@v4
if: always()
with:
name: browser-verify-screenshots
path: verify-screenshots/构建 → 启动预览服务 → 跑浏览器验收脚本 → 上传截图。验收失败阻塞 PR 合并,截图作为 Artifact 上传供人工查看差异。
收束
浏览器验收是前端质量流水线的最后一道闸门。Typecheck 验证代码声明,单测验证函数逻辑,E2E 验证用户流程——但只有浏览器验收在验证用户实际看到和体验到的东西。
AI 编程时代,代码产出速度在加快,浏览器层的验证没有自动跟上。Agent 不会主动打开浏览器,它的「完成」标准是 typecheck + test pass。如果不显式要求浏览器验收,质量缺口会随着 AI 产出量的增加而扩大。
核心思路:
- 把验收标准写成可执行的脚本——Playwright 截图 + 错误收集 + 视口检测
- 把验收指令写给 Agent——明确 URL、视口、检查点、期望行为
- 把验收集成到 CI——PR 提交后自动跑,失败阻塞合并
- 用截图做证据——以截图为准,以主观感受为辅
下次让 AI 改完页面后,不要只看 typecheck 输出。花 5 分钟做一次浏览器验收——或者让 Agent 自己做,然后看截图确认。
参考资料:
- The Page the Tests Can't See — H·AI·K·U:CI 绿灯但页面损坏的真实案例复盘。
- A Modern Quality Pipeline — alexop.dev:前端分层质量策略,「分层置信」思路来源。
- Regression Testing for AI-Generated Code — Autonoma:AI Agent 代码产出量与回归测试的关系。
- Frontend Testing 2026 — Delta-QA:视觉基准截图和多设备测试策略。
- Visual Regression Testing — Testsigma:2026 年视觉回归工具链对比。
- GitLab Frontend Testing Standards:「测试通过但用户流程断裂」的风险提示来源。