AI 能快速生成界面,但生成结果只能算交付候选,不能直接等同于可维护的产品。本文用一个可运行的任务面板演示如何把交互、响应式、可访问性和视觉回归纳入 Playwright 验收线,并让失败结果真正阻断合并。
先把“看起来能用”改写成可验证条件
接管 AI 生成的原型时,我不会先讨论组件是否优雅,而是先确认用户能否完成关键任务。原因很简单:样式重构可以逐步进行,错误的交互路径、不可操作的移动端页面和缺失的表单语义却会直接影响交付。
验收清单需要描述外部行为,而不是绑定实现细节。例如,测试“用户填写任务名称并提交后,列表出现新任务”,比测试某个 .task-item 节点数量增加更稳定。前者允许后续替换框架或调整 DOM,后者很容易因重构失效。
| 风险 | 可验证条件 | Playwright 手段 |
|---|---|---|
| 关键交互失效 | 提交任务后列表和状态提示同步更新 | 角色定位、表单操作、断言 |
| 移动端溢出 | 390 像素视口下没有横向滚动 | 设置视口、读取布局尺寸 |
| 键盘不可操作 | 输入框之后可通过 Tab 到达提交按钮 | 键盘操作、焦点断言 |
| 语义或对比度问题 | 页面没有已知的 axe 违规项 | @axe-core/playwright |
| 样式意外变化 | 桌面端和移动端截图与基线一致 | toHaveScreenshot |
这份清单不等于完整的产品测试。支付、权限、数据一致性等风险仍需单独设计,但它足以构成前端原型进入评审和持续集成的最低验收线。
建立一个可运行的原型项目
示例使用 Vite 提供静态页面,Playwright 负责验收。新建目录后写入以下 package.json:
{
"name": "prototype-acceptance",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test:e2e": "playwright test",
"test:update": "playwright test --update-snapshots"
},
"devDependencies": {
"@axe-core/playwright": "^4.10.0",
"@playwright/test": "^1.49.0",
"vite": "^5.4.0"
}
}
创建 index.html。页面保留一个最小但完整的业务闭环:填写任务、提交并得到反馈;布局在窄屏下自动改为单列。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>交付任务面板</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; color: #0f172a; background: #f8fafc; font-family: system-ui, sans-serif; }
main { width: min(960px, calc(100% - 32px)); margin: 40px auto; }
h1 { margin-bottom: 8px; }
.intro { color: #475569; }
.grid { display: grid; grid-template-columns: 2fr 3fr; gap: 24px; margin-top: 24px; }
.card { padding: 24px; border: 1px solid #cbd5e1; border-radius: 12px; background: white; }
label { display: block; margin-bottom: 8px; font-weight: 700; }
input { width: 100%; min-height: 44px; padding: 10px; border: 1px solid #64748b; border-radius: 6px; }
button { min-height: 44px; margin-top: 12px; padding: 0 18px; border: 0; border-radius: 6px; color: white; background: #1d4ed8; cursor: pointer; }
input:focus, button:focus { outline: 3px solid #f59e0b; outline-offset: 2px; }
li + li { margin-top: 10px; }
#status { min-height: 24px; color: #166534; }
@media (max-width: 640px) {
main { margin: 24px auto; }
.grid { grid-template-columns: 1fr; }
.card { padding: 18px; }
}
</style>
</head>
<body>
<main>
<h1>交付任务面板</h1>
<p class="intro">记录进入发布前必须完成的检查项。</p>
<div class="grid">
<section class="card" aria-labelledby="create-title">
<h2 id="create-title">添加任务</h2>
<form id="task-form">
<label for="task-name">任务名称</label>
<input id="task-name" name="task" required>
<button type="submit">加入清单</button>
</form>
<p id="status" role="status" aria-live="polite"></p>
</section>
<section class="card" aria-labelledby="list-title">
<h2 id="list-title">待验收任务</h2>
<ul id="task-list"><li>检查支付流程</li></ul>
</section>
</div>
</main>
<script>
const form = document.querySelector('#task-form');
const input = document.querySelector('#task-name');
const list = document.querySelector('#task-list');
const status = document.querySelector('#status');
form.addEventListener('submit', (event) => {
event.preventDefault();
const name = input.value.trim();
if (!name) return;
const item = document.createElement('li');
item.textContent = name;
list.append(item);
status.textContent = `已添加:${name}`;
form.reset();
input.focus();
});
</script>
</body>
</html>
执行 npm install 和 npx playwright install chromium 即可安装依赖。生成的锁文件也应提交到仓库,保证本地与 CI 使用相同的依赖解析结果。
用端到端测试覆盖交互、布局与可访问性
创建 playwright.config.ts,让测试启动生产构建后的预览服务,而不是依赖开发者提前运行服务器:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list',
use: {
baseURL: 'http://127.0.0.1:4173',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
expect: {
toHaveScreenshot: { maxDiffPixelRatio: 0.01 }
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }
],
webServer: {
command: 'npm run build && npm run preview -- --host 127.0.0.1',
url: 'http://127.0.0.1:4173',
reuseExistingServer: !process.env.CI
}
});
再创建 tests/acceptance.spec.ts:
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('用户可以添加验收任务', async ({ page }) => {
await page.getByLabel('任务名称').fill('补充退款说明');
await page.getByRole('button', { name: '加入清单' }).click();
await expect(page.getByRole('listitem', { name: '补充退款说明' })).toBeVisible();
await expect(page.getByRole('status')).toHaveText('已添加:补充退款说明');
await expect(page.getByLabel('任务名称')).toBeFocused();
});
test('表单支持键盘导航且没有已知可访问性违规', async ({ page }) => {
const input = page.getByLabel('任务名称');
await input.focus();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', { name: '加入清单' })).toBeFocused();
const result = await new AxeBuilder({ page }).analyze();
expect(result.violations).toEqual([]);
});
test('移动端布局没有横向溢出', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
const size = await page.evaluate(() => ({
scrollWidth: document.documentElement.scrollWidth,
clientWidth: document.documentElement.clientWidth
}));
expect(size.scrollWidth).toBeLessThanOrEqual(size.clientWidth);
});
test('桌面端和移动端视觉基线保持稳定', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await expect(page).toHaveScreenshot('home-desktop.png', {
fullPage: true,
animations: 'disabled'
});
await page.setViewportSize({ width: 390, height: 844 });
await expect(page).toHaveScreenshot('home-mobile.png', {
fullPage: true,
animations: 'disabled'
});
});
这些测试有意优先使用标签、角色和可见文本定位。它既接近用户操作方式,也会迫使页面保留基本语义。axe 检查可以发现一部分自动化可识别的问题,但不能替代读屏器试用、内容理解和真实用户验证。
管理视觉基线并让 CI 阻断回归
首次建立快照时运行 npm run test:update,审核生成的图片后再提交。视觉快照应在与 CI 相同的操作系统、浏览器版本和字体环境中生成,否则渲染差异可能产生无意义的失败。基线变化最好独立说明原因,不要在 CI 中自动更新,也不要为了让测试通过而持续放宽差异阈值。
GitHub Actions 可使用 .github/workflows/e2e.yml:
name: E2E acceptance
on:
pull_request:
push:
branches: [main]
jobs:
playwright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run test:e2e
- if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-failure
path: |
playwright-report
test-results
if-no-files-found: ignore
工作流失败只代表检查结果可见,要实现真正阻断,还需要在仓库分支保护规则中把该工作流设为必需状态检查。失败时保留 trace、截图和视频,评审者可以区分产品回归、测试缺陷与环境差异;若确认是预期改版,再由开发者更新并审核基线。
总结
接管 AI 原型的关键不是增加多少测试,而是把模糊的“能用”转换为团队可以重复执行的交付条件:
- 先按用户任务、移动端布局、键盘操作和回归风险整理验收清单;
- 使用角色、标签和可见文本编写端到端测试,减少对 DOM 结构的依赖;
- 用 axe 补充自动化可访问性检查,但保留必要的人工验证;
- 在稳定环境中维护视觉基线,任何更新都需要评审;
- 通过 CI、失败产物和分支保护,让验收失败真正阻断合并。
AI 可以继续承担页面生成和局部修改,但交付标准应掌握在开发团队手中。这样得到的不是一次性演示,而是一条能随产品迭代持续运行的验收线。
评论