共计 2540 个字符,预计需要花费 7 分钟才能阅读完成。
1. 背景与痛点:AI 应用测试的特殊挑战
测试传统 Web 应用时,我们通常面对的是确定性的 DOM 结构和响应。但 AI 应用如 Claude 带来了三个独特挑战:

- 动态响应内容:每次交互生成的文本长度、措辞和结构都可能不同
- 非确定性输出:相同输入可能产生不同但语义相近的回答
- 延迟波动大:思考时间长且不固定,从几百毫秒到数十秒不等
2. 技术选型:为什么选择 Playwright
对比主流测试框架在 AI 测试场景的表现:
| 特性 | Playwright | Selenium | Cypress |
|---|---|---|---|
| 多标签页支持 | ✅ | ❌ | ❌ |
| 自动等待机制 | ✅ | ⚠️需手动 | ✅ |
| 无头模式性能 | 快 3 倍 | 基准 | 慢 2 倍 |
| 网络拦截能力 | ✅ | ❌ | ⚠️有限 |
| 移动端仿真 | ✅ | ⚠️复杂 | ❌ |
Playwright 的 多上下文隔离 特性特别适合测试多轮对话场景,而内置的 智能等待 能有效处理 AI 响应延迟。
3. 核心实现
3.1 环境配置(TypeScript)
// 安装依赖
// npm install playwright @playwright/test typescript ts-node
import {test, expect} from '@playwright/test';
// 配置浏览器参数
const browserOptions = {
headless: false, // 调试时可关闭无头模式
slowMo: 500, // 放慢操作速度便于观察
timeout: 30000 // 针对 AI 响应延长超时
};
// 初始化上下文
const context = await browser.newContext({viewport: { width: 1920, height: 1080},
userAgent: 'Mozilla/5.0...' // 伪装真实浏览器
});
3.2 元素定位策略
Claude 的 DOM 结构特点:
- 使用 Shadow DOM 封装聊天内容
- 消息容器有随机生成的类名
- 输入框通过 contenteditable 实现
可靠定位方案:
// 使用属性选择器定位输入框
const inputBox = page.locator('[contenteditable="true"]').first();
// 结合 XPath 定位最新回复(Playwright 1.32+)const lastResponse = page.locator('//div[contains(@class,"Message")][last()]');
// 处理 Shadow DOM
const shadowHost = page.locator('.chat-container');
const shadowRoot = await shadowHost.evaluateHandle(el => el.shadowRoot);
3.3 异步响应处理
AI 响应等待的四个关键点:
- 检测「正在输入」指示器
- 等待消息容器停止更新
- 设置合理超时
- 失败后重试机制
async function waitForAIResponse(page, maxWait = 30000) {
// 等待「思考中」动画出现
await page.waitForSelector('.typing-indicator',
{state: 'attached', timeout: 5000});
// 等待动画消失 + 稳定期
await Promise.all([
page.waitForSelector('.typing-indicator',
{state: 'detached', timeout: maxWait}),
page.waitForTimeout(2000) // 额外缓冲时间
]);
// 返回最新消息文本
return lastResponse.innerText();}
4. 验证非确定性输出
应对 AI 输出的验证策略:
- 语义验证:检查是否包含关键词
- 结构验证:确认回答格式(如代码块)
- 逻辑验证:评估回答相关性
// 模糊匹配示例
const response = await waitForAIResponse(page);
expect(response).toMatch(/\b(error|exception)\b/i); // 验证包含术语
expect(response).toContain('```'); // 验证代码块
// 使用 LLM 验证 LLM(Meta 验证)const isValid = await checkResponseQuality(response);
5. 性能优化
并发测试配置要点:
// playwright.config.ts
import type {PlaywrightTestConfig} from '@playwright/test';
const config: PlaywrightTestConfig = {
workers: 3, // 控制并行度
retries: 2, // 自动重试次数
timeout: 120000, // 全局超时
use: {
trace: 'on-first-retry', // 故障诊断
video: 'retain-on-failure'
}
};
export default config;
资源管理技巧:
- 复用登录状态:
storageState - 限制并发对话数
- 监控内存泄漏
6. 避坑指南
会话状态管理
常见错误:
– 未清理对话历史
– 跨测试污染上下文
解决方案:
test.beforeEach(async ({ page}) => {await page.evaluate(() => {localStorage.clear();
sessionStorage.clear();});
});
绕过安全机制
Cloudflare 挑战应对:
– 使用真实 UserAgent
– 添加延迟模拟人工
– 考虑第三方绕过方案
测试数据管理
建议采用:
// 使用 Faker 生成测试数据
import {faker} from '@faker-js/faker';
const testCase = {query: faker.lorem.sentence(),
expectedKeywords: [faker.word.sample(),
faker.word.sample()]
};
7. 延伸思考
- 如何将 AI 输出验证集成到 SonarQube?
- 动态调整测试阈值(如响应时间)的算法设计
- 在 CI/CD 中处理非确定性测试失败
- 构建对话场景的 DAG 测试模型
最佳实践:建立「允许失败」测试套件,对非核心功能设置宽松断言,通过历史数据动态校准期望值。
正文完
发表至: 技术分享
近一天内
