起因:我们被“回归测试”拖垮的那周

那是上个月17号,周五晚九点。
服务器监控告警刚消,产研群消息已炸成红色瀑布流:“支付成功率跌到92%!”“订单页价格显示为NaN!”“发票模块白屏!”——三个紧急热修复塞进发布窗口,外加两个UI组件重构(Ant Design 5.x 升级引发的表单校验链断裂)。

我打开测试清单:27个核心路径,从登录态保持、搜索联想、购物车增删,到跨端同步、异常网络兜底……全靠手动点。四人轮班,手机支架架在MacBook上录屏,咖啡机24小时待命。凌晨两点,小王在测“优惠券叠加逻辑”,漏掉了“满300减50+店铺红包”组合下,结算页底部“立减¥50”文案未高亮;凌晨三点,我卡在“发票申请表单提交后,邮箱校验提示应为红色且聚焦”,却忘了验证“连续两次错误输入后,防暴击锁30秒”的交互状态。

崩溃发生在三点十七分。我盯着Playwright控制台里第12次重跑的test:regression套件,看着它又花47秒加载整个首页、跳转用户中心、展开侧边栏、点击“开发设置”——就为了验证一个按钮颜色从#1890ff改成了#1677ff

我抓起保温杯砸向软垫(没真砸),吼出那句后来被截图传遍公司IM群的话:

“连改个按钮颜色都要重跑整套E2E?这哪是测试,这是体力活!”

就在我瘫在椅子上刷手机想转移注意力时,手指无意识点开了Claude Code的网页版,把一段刚写的、带三处waitForSelector的脚本粘了进去。它没直接给答案,反而弹出一句反问:

“你是否考虑过用自然语言描述验证逻辑,由AI生成可执行断言?”

我愣住。手指悬在键盘上方三秒。
不是“怎么写代码”,而是“怎么说人话”。
那一刻,我忽然意识到:我们花了80%时间把需求翻译成Playwright语法,却只留20%时间思考——这个需求,到底值不值得测?

团队凌晨三点围在显示器前,屏幕上是密密麻麻的Playwright日志和Confluence测试表格

尝试:从“写代码”到“说人话”的第一次迁移

我翻出Confluence里那份叫《V2.3核心回归用例》的Word表格——最后编辑时间是2021年8月,作者是已离职的前QA leader。里面写着:

  • 搜索功能:输入关键词,展示结果,数据应合理
  • 订单页:价格计算正确,支持修改数量
  • 发票申请:邮箱格式校验,错误时提示明确

我把整页复制进Claude,写下prompt:

“请基于以下业务规则,生成可在Playwright中直接运行的端到端断言代码,不修改页面交互步骤,只补充验证逻辑。所有选择器必须使用data-testid,禁用ID和class。若规则表述模糊,请先追问业务含义。”

它立刻回问:

❓ “‘数据应合理’具体指什么?例如:搜索‘苹果’是否应排除‘苹果手机’?结果数是否需大于0?是否有排序规则(如销量优先)?”

我愣了——这问题直戳痛点。我拉上产品经理,在茶水间白板上画了15分钟:原来“合理”=“匹配度>0.7且按GMV降序”,而这条规则,三年来只存在于他脑内。

接着它输出代码。但第一版栽在选择器上:

// ❌ AI默认生成(错!项目不用id)
await page.waitForSelector('div#loading');
expect(await page.locator('span.price').textContent()).toContain('¥');

而我们项目规范是:

// ✅ 正确写法(data-testid)
await page.locator('[data-testid="loading-spinner"]').waitFor({ state: 'visible' });
expect(await page.locator('[data-testid="product-price"]').textContent()).toContain('¥');

踩坑总结:AI没有上下文记忆。你没给它CSS规范文档,它就按Stack Overflow最高票答案猜。

实战:用Claude Code跑通真实回归流水线

我们决定拿最痛的“搜索结果页”开刀。流程如下:

Step 1:语音转文字,拒绝脑内编译
我打开iPhone语音备忘录,对着麦克风说:

“搜索框输入‘发票’,结果页顶部显示‘共12条匹配’,且第1条标题含‘电子发票’字样,点击第1条后URL包含/invoice/detail?id=,页面标题是‘电子发票详情’。”

转文字后粘贴进Claude,加一句:

“生成Playwright断言块,要求:1)使用locator.waitFor({ state: 'visible' })替代waitForTimeout;2)对文本断言加.trim();3)URL校验用正则,注释说明匹配逻辑。”

Step 2:Claude输出(带注释,可直接抄)

// ✅ 验证搜索结果总数(容错:空格、换行、中文数字)
const totalCount = await page.locator('[data-testid="search-result-count"]').textContent();
expect(totalCount?.trim()).toMatch(/共\s*\d+\s*条匹配/);

// ✅ 验证首条标题含关键词(避免误匹配“纸质发票”)
const firstTitle = await page.locator('[data-testid="search-result-item"]').first().locator('h3').textContent();
expect(firstTitle?.trim()).toContain('电子发票');

// ✅ 验证跳转URL(正则确保?id=后跟数字)
await page.locator('[data-testid="search-result-item"]').first().click();
await page.waitForURL(/\/invoice\/detail\?id=\d+/);
expect(await page.title()).toBe('电子发票详情');

Step 3:注入现有测试文件,仅改2处变量名

// 原有交互步骤(不变)
await page.locator('[data-testid="search-input"]').fill(searchTerm); // ← searchTerm已定义
await page.locator('[data-testid="search-submit"]').click();

// 粘贴AI生成的断言块(仅替换searchTerm→'发票',expectedCount→12)
// ……(代码同上)

CI通过。耗时:18分钟(含3次prompt微调+1次本地调试)。
对比历史:同样5条用例,老方法平均2小时/人,且常因waitForSelector('.price')写错class名导致偶发失败。

⚠️ 血泪提醒:必须人工校验assertion顺序!AI曾把“检查加载态”放在“检查结果数”之后,导致偶发性失败——因为结果数DOM可能已渲染,但加载态还没消失。我们后来加了条团队约定:所有waitFor必须在任何expect之前,且按页面渲染流顺序排列

防坑指南:那些让AI测试翻车的隐蔽雷区

前端特有陷阱

  • 动态ID:AI见<div id="user-123">就用#user-123,但我们用data-user-id="123"
    ✅ 解决方案:prompt开头必加——“所有选择器强制使用data-testid或aria-label,禁用id/class”

  • React Suspense时机:AI习惯写await page.waitForTimeout(2000),但网络波动时2秒不够,稳定时又浪费。
    ✅ 终极解法:在prompt模板里固化——“优先用locator.waitFor({ state: 'visible' }),禁用固定延时;若需等待异步数据,用page.waitForResponse(/\/api\/search/)

团队协作雷区

  • 新人直接复制AI生成的expect(page).toHaveURL(/\/search\?q=.*/),上线后产品把参数名从q改成keyword,全量回归崩盘。
    ✅ 强制规范:每段AI生成代码上方,手写一行中文注释,例如:
    // 🔑 此处验证搜索跳转后URL必须含q参数(非keyword!)
    expect(page).toHaveURL(/\/search\?q=.*/);
    

进阶:把AI变成团队的“测试翻译官”

我们落地了三个轻量但高频的场景:

  1. PR描述 → 测试清单
    开发提PR写:“修复订单页价格显示错误(小数位丢失)”。
    Claude prompt:

    “将以下PR描述转为3条Playwright断言,覆盖:总价=单价×数量、小数位精确到2位、促销价标签存在”
    输出精准命中,连expect(priceEl).toHaveText(/¥\d+\.\d{2}/)都带上了。

  2. 客服工单 → 复现用例
    用户报障截图OCR文字:“输入‘12345678901234567890’搜索,页面卡死”。
    Claude生成:

    步骤:填入20位数字 → 点搜索 → 断言:页面不抛错、无空白、搜索框仍可输入
    边界补丁:人工追加“输入1000字符时,应截断并提示‘最多输入100字符’”

  3. 失败日志 → 修复建议
    日志报错:Error: locator.locator('.price').first() resolved to 0 elements
    Claude输入日志+当前页面HTML片段,输出:

    ✅ 建议1:检查class名是否已改为data-testid="product-price"
    ✅ 建议2:添加await page.locator('[data-testid="search-results"]').waitFor({ state: 'visible' });
    ✅ 建议3:确认DOM结构是否从<div class="price">变为<span data-testid="price">

⚠️ 终极禁忌:别让AI删测试!我曾写prompt:“清理冗余测试用例”,AI真删了3个低频但关键的支付异常路径(如“微信支付回调超时”)。现在我们的红线是:AI只生成/解释/建议,删除权限永远在人手里

心得:不写JS≠不思考,而是把精力花在刀刃上

省下的时间去哪了?我扒了自己上周的工时表:

  • 65% 和产品对齐业务规则(以前藏在代码里,现在显性化在prompt中,连“发票”是否包含“电子普票”都写进Confluence)
  • 20% 设计更刁钻的边界场景(比如“输入1000个字符的搜索词”“弱网下连续点击提交按钮5次”)
  • 15% 喝咖啡发呆(诚实交代,但发呆时突然想通了“为什么用户总在第三步放弃”)

最大的认知升级是:AI不是替代测试工程师,而是把“把需求翻译成代码”的机械劳动剥离,让我们专注“这个需求到底该不该测”“用户真正卡在哪一步”

给想试试的同学一句大实话:

先拿1个最让你头疼的回归用例练手,别一上来就想覆盖全站。
我就是从“登录页记住密码勾选框状态保持”这1个点开始——写prompt、调生成、跑CI、修bug、加注释——用了3天,才敢推开整扇门。

现在,当我看到新需求PR,第一反应不再是打开VS Code写test.spec.ts,而是打开Claude,深吸一口气,敲下第一行:

“请把以下需求,翻译成Playwright可执行的断言……”

那一刻,我终于觉得,自己是个测试工程师,而不是人肉编译器。

作者在工位上笑着敲键盘,屏幕左侧是Claude对话窗口,右侧是Playwright测试文件,终端显示绿色PASS