@be-link/smart-test 1.0.1-beta.1 → 1.0.1-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -386
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +379 -0
- package/dist/core/ai-assistant.d.ts +55 -14
- package/dist/core/ai-assistant.d.ts.map +1 -1
- package/dist/core/config.d.ts +12 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/prompts.d.ts +14 -0
- package/dist/core/prompts.d.ts.map +1 -0
- package/dist/core/types.d.ts +140 -9
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +289 -102
- package/dist/index.js +291 -101
- package/package.json +6 -18
- package/bin/smart-test.js +0 -15
- package/dist/cli/commands/generate.d.ts +0 -16
- package/dist/cli/commands/generate.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts +0 -5
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -618
- package/dist/config/config-loader.d.ts +0 -35
- package/dist/config/config-loader.d.ts.map +0 -1
- package/dist/config/config-schema.d.ts +0 -85
- package/dist/config/config-schema.d.ts.map +0 -1
- package/dist/utils/error-handler.d.ts +0 -41
- package/dist/utils/error-handler.d.ts.map +0 -1
- package/dist/utils/logger.d.ts +0 -88
- package/dist/utils/logger.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,524 +1,461 @@
|
|
|
1
1
|
# @be-link/smart-test
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
使用 AI 视觉识别能力,让你的 E2E 测试更智能、更可靠。
|
|
3
|
+
AI-powered visual testing for Playwright | 基于 AI 视觉模型的 Playwright 测试工具
|
|
6
4
|
|
|
7
5
|
## ✨ 特性
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- 自动识别页面问题并给出详细反馈
|
|
14
|
-
|
|
15
|
-
### 🎯 零代码测试生成(开发中)
|
|
16
|
-
|
|
17
|
-
- **CLI 工具**:一条命令自动生成测试代码
|
|
18
|
-
- **录制模式**:访问页面自动录制接口和交互
|
|
19
|
-
- **代码分析**:扫描项目代码自动生成 Mock 数据
|
|
20
|
-
- **AI 生成**:基于自然语言描述生成完整测试
|
|
21
|
-
|
|
22
|
-
### 🔧 灵活易用
|
|
23
|
-
|
|
24
|
-
- 与 Playwright 无缝集成,零侵入设计
|
|
25
|
-
- 支持全局配置和单次调用配置
|
|
26
|
-
- 优雅降级:无 API Key 时自动跳过
|
|
27
|
-
- 完整的 TypeScript 类型支持
|
|
28
|
-
|
|
29
|
-
### 📸 自动化
|
|
30
|
-
|
|
31
|
-
- 自动截图并保存
|
|
32
|
-
- 自动生成 Mock 数据
|
|
33
|
-
- 自动生成测试用例
|
|
7
|
+
- 🎯 **AI 视觉检查**:使用 AI 视觉模型自动检查页面截图是否符合预期
|
|
8
|
+
- 🤖 **AI 代码生成**:使用 AI 自动生成测试用例和 Mock 数据
|
|
9
|
+
- 📝 **TypeScript 支持**:完整的 TypeScript 类型定义
|
|
10
|
+
- 🔧 **灵活配置**:支持自定义 AI 模型和服务地址
|
|
34
11
|
|
|
35
12
|
## 📦 安装
|
|
36
13
|
|
|
37
14
|
```bash
|
|
38
|
-
# 使用 pnpm(推荐)
|
|
39
15
|
pnpm add -D @be-link/smart-test
|
|
40
|
-
|
|
41
|
-
# 使用 npm
|
|
42
|
-
npm install -D @be-link/smart-test
|
|
43
|
-
|
|
44
|
-
# 使用 yarn
|
|
45
|
-
yarn add -D @be-link/smart-test
|
|
46
16
|
```
|
|
47
17
|
|
|
48
18
|
## 🚀 快速开始
|
|
49
19
|
|
|
50
|
-
###
|
|
51
|
-
|
|
52
|
-
#### 1. 初始化配置
|
|
20
|
+
### 1. 配置 AI API
|
|
53
21
|
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
交互式配置向导会引导你完成设置:
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
━━━ 初始化 Smart Test 配置 ━━━
|
|
62
|
-
|
|
63
|
-
? 请输入 AI API Key: sk-xxxxx
|
|
64
|
-
? 选择 AI 模型: 通义千问 (qwen3-vl-plus)
|
|
65
|
-
? 测试文件目录: ./tests/e2e
|
|
66
|
-
? Helpers 目录: ./tests/helpers
|
|
67
|
-
? Playwright Base URL: http://localhost:8080
|
|
68
|
-
? 文件命名风格: kebab-case
|
|
22
|
+
```typescript
|
|
23
|
+
import { configure } from '@be-link/smart-test';
|
|
69
24
|
|
|
70
|
-
|
|
25
|
+
configure({
|
|
26
|
+
apiKey: process.env.AI_API_KEY,
|
|
27
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
28
|
+
visionModel: 'qwen3-vl-plus',
|
|
29
|
+
codeModel: 'qwen3-coder-plus',
|
|
30
|
+
});
|
|
71
31
|
```
|
|
72
32
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
# 录制模式 - 访问页面自动录制
|
|
77
|
-
npx smart-test record --url http://localhost:8080/coupon
|
|
33
|
+
### 2. AI 视觉检查
|
|
78
34
|
|
|
79
|
-
|
|
80
|
-
npx smart-test generate --page src/pages/coupon/index.tsx
|
|
35
|
+
在 Playwright 测试中使用 AI 检查页面截图:
|
|
81
36
|
|
|
82
|
-
|
|
83
|
-
|
|
37
|
+
```typescript
|
|
38
|
+
import { test } from '@playwright/test';
|
|
39
|
+
import { aiReviewScreenshot } from '@be-link/smart-test';
|
|
84
40
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
```
|
|
41
|
+
test('页面显示正确', async ({ page }) => {
|
|
42
|
+
await page.goto('/home');
|
|
88
43
|
|
|
89
|
-
|
|
44
|
+
const result = await aiReviewScreenshot(page, '顶部显示logo和导航栏,主内容区域显示3个产品卡片');
|
|
90
45
|
|
|
91
|
-
|
|
92
|
-
|
|
46
|
+
if (!result.skipped && result.issues?.length) {
|
|
47
|
+
console.log('AI 发现的问题:', result.issues);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
93
50
|
```
|
|
94
51
|
|
|
95
|
-
|
|
52
|
+
### 3. AI 生成测试用例
|
|
96
53
|
|
|
97
|
-
|
|
54
|
+
自动生成完整的 Playwright 测试用例:
|
|
98
55
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
56
|
+
```typescript
|
|
57
|
+
import { aiGenerateTestCase } from '@be-link/smart-test';
|
|
58
|
+
|
|
59
|
+
const result = await aiGenerateTestCase({
|
|
60
|
+
pageUrl: '/coupon?userId=e2e-user&isPopup=true',
|
|
61
|
+
scenario: '优惠券页面展示和 Tab 切换功能',
|
|
62
|
+
apiMocks: [
|
|
63
|
+
{
|
|
64
|
+
path: '**/api/user/info/get-user-detail',
|
|
65
|
+
description: '返回用户信息,包含 id、nickname、memberLevel 等字段',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
path: '**/api/user/coupon/get-available-coupons-grouped',
|
|
69
|
+
description: '返回可用优惠券列表,按券 ID 分组',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
aiCheckExpected: '顶部展示门店名称和用户昵称,列表中应显示3张优惠券卡片',
|
|
73
|
+
testSteps: ['验证页面显示"优惠券"标题', '验证显示"夏日满减券"', '点击"核销券"按钮', '验证显示"到店核销券"文本'],
|
|
74
|
+
});
|
|
102
75
|
|
|
103
|
-
|
|
104
|
-
|
|
76
|
+
if (result.ok) {
|
|
77
|
+
console.log('生成的测试代码:\n', result.code);
|
|
78
|
+
}
|
|
105
79
|
```
|
|
106
80
|
|
|
107
|
-
|
|
81
|
+
### 4. AI 生成 Mock 数据
|
|
108
82
|
|
|
109
|
-
|
|
83
|
+
通过传入实际的 TypeScript 类型定义,AI 会生成完全符合接口规范的 Mock 数据:
|
|
110
84
|
|
|
111
85
|
```typescript
|
|
112
|
-
import {
|
|
113
|
-
import
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
86
|
+
import { aiGenerateMock } from '@be-link/smart-test';
|
|
87
|
+
import fs from 'fs';
|
|
88
|
+
|
|
89
|
+
// 读取类型定义文件
|
|
90
|
+
const addressTypes = fs.readFileSync('./src/api/address/types.ts', 'utf-8');
|
|
91
|
+
|
|
92
|
+
// 提取需要的类型定义
|
|
93
|
+
const result = await aiGenerateMock({
|
|
94
|
+
apiPath: '**/api/user/address/get-default-address',
|
|
95
|
+
description: '返回用户默认地址信息',
|
|
96
|
+
responseType: 'IDefaultAddressResp',
|
|
97
|
+
typeDefinitions: [
|
|
98
|
+
{
|
|
99
|
+
typeName: 'IDefaultAddressResp',
|
|
100
|
+
content: `export interface IDefaultAddressResp {
|
|
101
|
+
/** 默认地址 */
|
|
102
|
+
defaultAddress: IUserAddress | null;
|
|
103
|
+
/** 自提地址 */
|
|
104
|
+
pickupAddress: IPickUpStoreUserInfo | null;
|
|
105
|
+
}`,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
typeName: 'IUserAddress',
|
|
109
|
+
content: `export interface IUserAddress {
|
|
110
|
+
/** 地址ID */
|
|
111
|
+
id: string;
|
|
112
|
+
/** 用户ID */
|
|
113
|
+
userId: string;
|
|
114
|
+
/** 收件人姓名 */
|
|
115
|
+
receiverName: string;
|
|
116
|
+
/** 收件人手机号 */
|
|
117
|
+
receiverMobile: string;
|
|
118
|
+
/** 省份名称 */
|
|
119
|
+
provinceName: string;
|
|
120
|
+
/** 城市名称 */
|
|
121
|
+
cityName: string;
|
|
122
|
+
/** 区县名称 */
|
|
123
|
+
districtName: string;
|
|
124
|
+
/** 详细地址 */
|
|
125
|
+
detailAddress: string;
|
|
126
|
+
/** 是否默认地址:1-是,0-否 */
|
|
127
|
+
isDefault: number;
|
|
128
|
+
/** 创建时间(毫秒时间戳) */
|
|
129
|
+
createdAt: number;
|
|
130
|
+
}`,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
typeName: 'IPickUpStoreUserInfo',
|
|
134
|
+
content: `export interface IPickUpStoreUserInfo {
|
|
135
|
+
id?: string;
|
|
136
|
+
/** 用户ID */
|
|
137
|
+
userId: string;
|
|
138
|
+
/** 提货人手机号 */
|
|
139
|
+
receiverMobile: string;
|
|
140
|
+
/** 提货人名称 */
|
|
141
|
+
receiverName: string;
|
|
142
|
+
}`,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
119
145
|
});
|
|
120
146
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
147
|
+
if (result.ok) {
|
|
148
|
+
console.log('生成的 Mock 代码:\n', result.code);
|
|
149
|
+
// AI 会严格按照类型定义生成符合规范的 mock 数据
|
|
150
|
+
}
|
|
124
151
|
```
|
|
125
152
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
import { test, expect } from '@playwright/test';
|
|
130
|
-
import { aiReviewScreenshot } from '@be-link/smart-test';
|
|
131
|
-
|
|
132
|
-
test.describe('优惠券页面', () => {
|
|
133
|
-
test('AI 检查页面布局', async ({ page }) => {
|
|
134
|
-
await page.goto('/coupon');
|
|
135
|
-
|
|
136
|
-
// 使用 AI 检查页面
|
|
137
|
-
const result = await aiReviewScreenshot(page, '页面应该显示优惠券列表,包含标题、优惠券卡片和底部按钮');
|
|
153
|
+
### 5. 通用代码生成
|
|
138
154
|
|
|
139
|
-
|
|
140
|
-
if (result.skipped) {
|
|
141
|
-
test.skip(); // 无 API Key 时跳过
|
|
142
|
-
}
|
|
155
|
+
使用 AI 生成任意 TypeScript 代码:
|
|
143
156
|
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
```typescript
|
|
158
|
+
import { aiGenerateCode } from '@be-link/smart-test';
|
|
159
|
+
|
|
160
|
+
const result = await aiGenerateCode({
|
|
161
|
+
prompt: '生成一个函数,用于计算两个日期之间的天数差',
|
|
162
|
+
context: `// 项目中的日期工具示例
|
|
163
|
+
function formatDate(date: Date): string {
|
|
164
|
+
return date.toISOString().split('T')[0];
|
|
165
|
+
}`,
|
|
146
166
|
});
|
|
167
|
+
|
|
168
|
+
if (result.ok) {
|
|
169
|
+
console.log('生成的代码:\n', result.code);
|
|
170
|
+
}
|
|
147
171
|
```
|
|
148
172
|
|
|
149
|
-
## 📖
|
|
173
|
+
## 📖 API 文档
|
|
150
174
|
|
|
151
|
-
###
|
|
175
|
+
### aiReviewScreenshot
|
|
152
176
|
|
|
153
|
-
|
|
177
|
+
使用 AI 视觉模型检查页面截图是否符合预期。
|
|
154
178
|
|
|
155
|
-
```
|
|
156
|
-
|
|
179
|
+
```typescript
|
|
180
|
+
function aiReviewScreenshot(page: Page, options: string | AiCheckOptions): Promise<AiCheckResult>;
|
|
157
181
|
```
|
|
158
182
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
生成测试代码。
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
# 基础用法
|
|
165
|
-
smart-test generate
|
|
183
|
+
**参数:**
|
|
166
184
|
|
|
167
|
-
|
|
168
|
-
|
|
185
|
+
- `page`: Playwright Page 对象
|
|
186
|
+
- `options`: 检查选项
|
|
187
|
+
- 字符串:直接传入预期描述
|
|
188
|
+
- 对象:包含 `expected`(预期描述)、`fullPage`(是否全页截图)等配置
|
|
169
189
|
|
|
170
|
-
|
|
171
|
-
smart-test generate --url <url>
|
|
190
|
+
**返回值:**
|
|
172
191
|
|
|
173
|
-
|
|
174
|
-
|
|
192
|
+
```typescript
|
|
193
|
+
interface AiCheckResult {
|
|
194
|
+
ok: boolean; // 检查是否通过
|
|
195
|
+
issues?: string[]; // 发现的问题列表
|
|
196
|
+
suggestion?: string[]; // 改进建议列表
|
|
197
|
+
skipped?: boolean; // 是否跳过检查
|
|
198
|
+
reason?: string; // 跳过原因
|
|
199
|
+
rawResponse?: string; // AI 原始响应
|
|
200
|
+
}
|
|
201
|
+
```
|
|
175
202
|
|
|
176
|
-
|
|
177
|
-
smart-test generate --interactive
|
|
203
|
+
### aiGenerateTestCase
|
|
178
204
|
|
|
179
|
-
|
|
180
|
-
smart-test generate --page <path> --output tests/custom
|
|
205
|
+
使用 AI 生成完整的 Playwright 测试用例。
|
|
181
206
|
|
|
182
|
-
|
|
183
|
-
|
|
207
|
+
```typescript
|
|
208
|
+
function aiGenerateTestCase(options: AiTestCaseGenerationOptions): Promise<AiCodeGenerationResult>;
|
|
184
209
|
```
|
|
185
210
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
| 选项 | 简写 | 说明 |
|
|
189
|
-
| ---------------------- | ---- | ---------------------------- |
|
|
190
|
-
| `--page <path>` | `-p` | 页面文件路径(代码分析模式) |
|
|
191
|
-
| `--url <url>` | `-u` | 页面 URL(录制模式) |
|
|
192
|
-
| `--desc <description>` | `-d` | 页面描述(自然语言模式) |
|
|
193
|
-
| `--output <dir>` | `-o` | 输出目录 |
|
|
194
|
-
| `--interactive` | `-i` | 交互式模式 |
|
|
195
|
-
| `--dry-run` | | 预览模式,不写入文件 |
|
|
211
|
+
**参数:**
|
|
196
212
|
|
|
197
|
-
|
|
213
|
+
```typescript
|
|
214
|
+
interface AiTestCaseGenerationOptions {
|
|
215
|
+
pageUrl: string; // 页面 URL
|
|
216
|
+
scenario: string; // 测试场景描述
|
|
217
|
+
apiMocks?: ApiMockConfig[]; // API Mock 配置列表
|
|
218
|
+
aiCheckExpected?: string; // AI 视觉检查预期
|
|
219
|
+
testSteps?: string[]; // 测试步骤列表
|
|
220
|
+
referenceCode?: string; // 参考代码
|
|
221
|
+
// ... 以及 AiBaseConfig 的所有配置项
|
|
222
|
+
}
|
|
198
223
|
|
|
199
|
-
|
|
224
|
+
interface ApiMockConfig {
|
|
225
|
+
path: string; // API 路径(支持通配符)
|
|
226
|
+
description: string; // Mock 数据描述
|
|
227
|
+
}
|
|
228
|
+
```
|
|
200
229
|
|
|
201
|
-
|
|
202
|
-
# 基础录制
|
|
203
|
-
smart-test record --url http://localhost:8080/coupon
|
|
230
|
+
### aiGenerateMock
|
|
204
231
|
|
|
205
|
-
|
|
206
|
-
smart-test record --url http://localhost:8080/coupon --manual
|
|
232
|
+
使用 AI 生成 API Mock 函数。
|
|
207
233
|
|
|
208
|
-
|
|
209
|
-
|
|
234
|
+
```typescript
|
|
235
|
+
function aiGenerateMock(options: AiMockGenerationOptions): Promise<AiCodeGenerationResult>;
|
|
210
236
|
```
|
|
211
237
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
| 选项 | 简写 | 说明 |
|
|
215
|
-
| ---------------- | ---- | ------------------------ |
|
|
216
|
-
| `--url <url>` | `-u` | 要录制的页面 URL(必需) |
|
|
217
|
-
| `--output <dir>` | `-o` | 输出目录 |
|
|
218
|
-
| `--manual` | | 手动操作模式 |
|
|
219
|
-
| `--interactive` | | 交互式引导 |
|
|
220
|
-
|
|
221
|
-
### `smart-test config`
|
|
238
|
+
**参数:**
|
|
222
239
|
|
|
223
|
-
|
|
240
|
+
```typescript
|
|
241
|
+
interface AiMockGenerationOptions {
|
|
242
|
+
apiPath: string; // API 路径
|
|
243
|
+
description: string; // Mock 数据描述
|
|
244
|
+
responseType?: string; // 响应数据类型名称(可选)
|
|
245
|
+
typeDefinitions?: TypeDefinition[]; // TypeScript 类型定义列表(可选)
|
|
246
|
+
referenceCode?: string; // 参考代码
|
|
247
|
+
// ... 以及 AiBaseConfig 的所有配置项
|
|
248
|
+
}
|
|
224
249
|
|
|
225
|
-
|
|
226
|
-
|
|
250
|
+
interface TypeDefinition {
|
|
251
|
+
typeName: string; // 类型名称
|
|
252
|
+
content: string; // 类型定义内容
|
|
253
|
+
}
|
|
227
254
|
```
|
|
228
255
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
| 选项 | 简写 | 说明 |
|
|
232
|
-
| ----------- | ---- | -------------------- |
|
|
233
|
-
| `--version` | `-V` | 显示版本号 |
|
|
234
|
-
| `--verbose` | `-v` | 详细输出(调试模式) |
|
|
235
|
-
| `--help` | `-h` | 显示帮助信息 |
|
|
236
|
-
|
|
237
|
-
## 📖 编程 API 文档
|
|
256
|
+
**使用说明:**
|
|
257
|
+
传入 `typeDefinitions` 和 `responseType`,AI 会严格按照 TypeScript 类型定义生成 mock 数据,确保类型安全和数据准确性
|
|
238
258
|
|
|
239
|
-
###
|
|
259
|
+
### aiGenerateCode
|
|
240
260
|
|
|
241
|
-
|
|
261
|
+
通用的 AI 代码生成函数。
|
|
242
262
|
|
|
243
263
|
```typescript
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
configure({
|
|
247
|
-
apiKey: 'sk-xxx', // AI API 密钥
|
|
248
|
-
baseURL: 'https://...', // AI 服务地址(可选)
|
|
249
|
-
model: 'qwen3-vl-plus', // AI 模型(可选)
|
|
250
|
-
timeout: 30000, // 超时时间(可选)
|
|
251
|
-
});
|
|
264
|
+
function aiGenerateCode(options: AiCodeGenerationOptions): Promise<AiCodeGenerationResult>;
|
|
252
265
|
```
|
|
253
266
|
|
|
254
|
-
### `aiReviewScreenshot(page, options)`
|
|
255
|
-
|
|
256
|
-
使用 AI 检查页面截图。
|
|
257
|
-
|
|
258
267
|
**参数:**
|
|
259
268
|
|
|
260
|
-
|
|
261
|
-
|
|
269
|
+
```typescript
|
|
270
|
+
interface AiCodeGenerationOptions {
|
|
271
|
+
prompt: string; // 代码生成描述
|
|
272
|
+
context?: string; // 上下文代码
|
|
273
|
+
// ... 以及 AiBaseConfig 的所有配置项
|
|
274
|
+
}
|
|
275
|
+
```
|
|
262
276
|
|
|
263
|
-
**返回值:**
|
|
277
|
+
**返回值:**
|
|
264
278
|
|
|
265
279
|
```typescript
|
|
266
|
-
interface
|
|
267
|
-
ok: boolean; //
|
|
268
|
-
|
|
280
|
+
interface AiCodeGenerationResult {
|
|
281
|
+
ok: boolean; // 是否成功生成
|
|
282
|
+
code?: string; // 生成的代码
|
|
269
283
|
skipped?: boolean; // 是否跳过
|
|
270
|
-
reason?: string; //
|
|
271
|
-
screenshotPath?: string; // 截图路径
|
|
284
|
+
reason?: string; // 跳过或失败原因
|
|
272
285
|
rawResponse?: string; // AI 原始响应
|
|
273
286
|
}
|
|
274
287
|
```
|
|
275
288
|
|
|
276
|
-
|
|
289
|
+
### configure
|
|
277
290
|
|
|
278
|
-
|
|
291
|
+
配置全局 AI 设置。
|
|
279
292
|
|
|
280
293
|
```typescript
|
|
281
|
-
|
|
282
|
-
|
|
294
|
+
function configure(config: Partial<AiBaseConfig>): void;
|
|
295
|
+
|
|
296
|
+
interface AiBaseConfig {
|
|
297
|
+
apiKey?: string; // AI API 密钥
|
|
298
|
+
baseURL?: string; // AI 服务地址
|
|
299
|
+
visionModel?: string; // 视觉模型名称
|
|
300
|
+
codeModel?: string; // 代码模型名称
|
|
301
|
+
timeout?: number; // 请求超时时间(毫秒)
|
|
302
|
+
}
|
|
283
303
|
```
|
|
284
304
|
|
|
285
|
-
|
|
305
|
+
## 🎯 使用场景
|
|
286
306
|
|
|
287
|
-
|
|
288
|
-
// 带配置对象
|
|
289
|
-
const result = await aiReviewScreenshot(page, {
|
|
290
|
-
expected: '页面应该显示商品列表',
|
|
291
|
-
model: 'gpt-4-vision-preview', // 使用其他模型
|
|
292
|
-
});
|
|
293
|
-
```
|
|
307
|
+
### 场景 1:视觉回归测试
|
|
294
308
|
|
|
295
|
-
|
|
309
|
+
使用 AI 自动检测页面的视觉变化,无需手动维护大量的断言代码:
|
|
296
310
|
|
|
297
311
|
```typescript
|
|
298
|
-
|
|
299
|
-
|
|
312
|
+
test('首页显示正常', async ({ page }) => {
|
|
313
|
+
await page.goto('/');
|
|
300
314
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
await page.waitForLoadState('networkidle');
|
|
315
|
+
const result = await aiReviewScreenshot(page, {
|
|
316
|
+
expected: '页面顶部显示网站logo和主导航,中间显示轮播图,下方显示4个产品分类卡片',
|
|
317
|
+
fullPage: true,
|
|
305
318
|
});
|
|
306
319
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
fullPage: true,
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
if (result.skipped) {
|
|
314
|
-
console.log('跳过原因:', result.reason);
|
|
315
|
-
test.skip();
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (!result.ok && result.issues) {
|
|
319
|
-
console.warn('发现的问题:', result.issues);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
expect(result.ok, `AI 检查失败: ${result.issues?.join(', ')}`).toBeTruthy();
|
|
323
|
-
});
|
|
320
|
+
expect(result.ok).toBeTruthy();
|
|
321
|
+
});
|
|
322
|
+
```
|
|
324
323
|
|
|
325
|
-
|
|
326
|
-
// 切换到移动端视图
|
|
327
|
-
await page.setViewportSize({ width: 375, height: 667 });
|
|
324
|
+
### 场景 2:快速生成测试代码
|
|
328
325
|
|
|
329
|
-
|
|
326
|
+
为新功能快速生成测试用例骨架,节省编写测试代码的时间:
|
|
330
327
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
328
|
+
```typescript
|
|
329
|
+
// 生成测试用例
|
|
330
|
+
const result = await aiGenerateTestCase({
|
|
331
|
+
pageUrl: '/product/123',
|
|
332
|
+
scenario: '产品详情页展示和加入购物车功能',
|
|
333
|
+
apiMocks: [
|
|
334
|
+
{ path: '**/api/product/detail', description: '返回产品详情' },
|
|
335
|
+
{ path: '**/api/cart/add', description: '加入购物车成功' },
|
|
336
|
+
],
|
|
337
|
+
testSteps: ['验证显示产品名称和价格', '点击"加入购物车"按钮', '验证显示成功提示'],
|
|
335
338
|
});
|
|
336
|
-
```
|
|
337
339
|
|
|
338
|
-
|
|
340
|
+
// 将生成的代码保存到文件,然后根据实际需要调整
|
|
341
|
+
```
|
|
339
342
|
|
|
340
|
-
###
|
|
343
|
+
### 场景 3:Mock 数据管理
|
|
341
344
|
|
|
342
|
-
|
|
345
|
+
通过传入实际的类型定义,确保生成的 Mock 数据完全符合接口规范:
|
|
343
346
|
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
"mock": "default",
|
|
360
|
-
"test": "default"
|
|
361
|
-
},
|
|
362
|
-
"playwright": {
|
|
363
|
-
"baseURL": "http://localhost:8080",
|
|
364
|
-
"viewport": {
|
|
365
|
-
"width": 375,
|
|
366
|
-
"height": 667
|
|
347
|
+
```typescript
|
|
348
|
+
// 读取或定义类型
|
|
349
|
+
import { readFileSync } from 'fs';
|
|
350
|
+
|
|
351
|
+
const addressTypeContent = readFileSync('./src/api/address/types.ts', 'utf-8');
|
|
352
|
+
// 或者直接定义类型字符串
|
|
353
|
+
|
|
354
|
+
const result = await aiGenerateMock({
|
|
355
|
+
apiPath: '**/api/user/address/get-default-address',
|
|
356
|
+
description: '返回用户默认地址',
|
|
357
|
+
responseType: 'IDefaultAddressResp',
|
|
358
|
+
typeDefinitions: [
|
|
359
|
+
{
|
|
360
|
+
typeName: 'IDefaultAddressResp',
|
|
361
|
+
content: extractTypeFromFile(addressTypeContent, 'IDefaultAddressResp'),
|
|
367
362
|
},
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
363
|
+
{
|
|
364
|
+
typeName: 'IUserAddress',
|
|
365
|
+
content: extractTypeFromFile(addressTypeContent, 'IUserAddress'),
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
});
|
|
374
369
|
|
|
375
|
-
|
|
376
|
-
-
|
|
377
|
-
-
|
|
378
|
-
-
|
|
370
|
+
// 生成的 mock 数据会严格符合类型定义,包括:
|
|
371
|
+
// - 字段类型匹配(string、number、boolean、null 等)
|
|
372
|
+
// - 必填/可选字段正确
|
|
373
|
+
// - 嵌套对象结构准确
|
|
374
|
+
// - 根据注释生成合理的测试数据
|
|
375
|
+
```
|
|
379
376
|
|
|
380
|
-
##
|
|
377
|
+
## ⚙️ 配置说明
|
|
381
378
|
|
|
382
|
-
###
|
|
379
|
+
### 默认配置
|
|
383
380
|
|
|
384
381
|
```typescript
|
|
385
|
-
|
|
386
|
-
configure({
|
|
387
|
-
apiKey: process.env.QWEN_API_KEY,
|
|
382
|
+
{
|
|
388
383
|
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
configure({
|
|
394
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
395
|
-
baseURL: 'https://api.openai.com/v1',
|
|
396
|
-
model: 'gpt-4-vision-preview',
|
|
397
|
-
});
|
|
384
|
+
visionModel: 'qwen3-vl-plus',
|
|
385
|
+
codeModel: 'qwen3-coder-plus',
|
|
386
|
+
timeout: 30000
|
|
387
|
+
}
|
|
398
388
|
```
|
|
399
389
|
|
|
400
|
-
###
|
|
390
|
+
### 自定义配置
|
|
391
|
+
|
|
392
|
+
可以在全局配置中设置默认值,也可以在每次调用时覆盖:
|
|
401
393
|
|
|
402
394
|
```typescript
|
|
395
|
+
// 全局配置
|
|
403
396
|
configure({
|
|
404
|
-
|
|
405
|
-
|
|
397
|
+
apiKey: process.env.AI_API_KEY,
|
|
398
|
+
visionModel: 'custom-vision-model',
|
|
406
399
|
});
|
|
407
400
|
|
|
408
|
-
//
|
|
409
|
-
|
|
401
|
+
// 单次调用覆盖
|
|
402
|
+
await aiReviewScreenshot(page, {
|
|
410
403
|
expected: '...',
|
|
411
|
-
|
|
412
|
-
screenshotDir: './feature-screenshots',
|
|
404
|
+
visionModel: 'another-model', // 仅此次调用使用
|
|
413
405
|
});
|
|
414
406
|
```
|
|
415
407
|
|
|
416
|
-
|
|
408
|
+
## 🔍 最佳实践
|
|
409
|
+
|
|
410
|
+
### 1. 环境变量管理
|
|
411
|
+
|
|
412
|
+
建议将 API Key 存储在环境变量中:
|
|
417
413
|
|
|
418
414
|
```typescript
|
|
419
|
-
//
|
|
415
|
+
// playwright.config.ts
|
|
416
|
+
import { defineConfig } from '@playwright/test';
|
|
417
|
+
import { configure } from '@be-link/smart-test';
|
|
418
|
+
|
|
420
419
|
configure({
|
|
421
420
|
apiKey: process.env.AI_API_KEY,
|
|
422
|
-
saveScreenshots: process.env.CI !== 'true',
|
|
423
421
|
});
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
## 🤔 常见问题
|
|
427
|
-
|
|
428
|
-
### Q: 没有配置 API Key 会怎样?
|
|
429
|
-
|
|
430
|
-
A: 测试会自动跳过 AI 检查,返回 `skipped: true`,不会影响其他测试的执行。
|
|
431
|
-
|
|
432
|
-
### Q: 支持哪些 AI 模型?
|
|
433
|
-
|
|
434
|
-
A: 理论上支持所有兼容 OpenAI Chat Completions API 的视觉模型,包括:
|
|
435
|
-
|
|
436
|
-
- 通义千问 (qwen3-vl-plus) - 默认
|
|
437
|
-
- OpenAI GPT-4 Vision
|
|
438
|
-
- 其他兼容的模型服务
|
|
439
|
-
|
|
440
|
-
### Q: CLI 生成的测试代码需要手动修改吗?
|
|
441
|
-
|
|
442
|
-
A: 生成的代码是可以直接运行的,但建议根据实际需求进行调整。AI 会尽量生成合理的测试用例,但可能无法覆盖所有业务场景。
|
|
443
|
-
|
|
444
|
-
### Q: 录制模式如何处理需要点击才能触发的接口?
|
|
445
|
-
|
|
446
|
-
A: 录制模式支持三种方式:
|
|
447
|
-
|
|
448
|
-
1. **手动操作**:`--manual` 参数,用户手动操作浏览器
|
|
449
|
-
2. **交互式引导**:`--interactive` 参数,AI 引导用户操作
|
|
450
|
-
3. **脚本引导**:`--actions` 参数,提供操作脚本自动执行
|
|
451
422
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
const result = await aiReviewScreenshot(page, '...');
|
|
458
|
-
console.log('AI 原始响应:', result.rawResponse);
|
|
423
|
+
export default defineConfig({
|
|
424
|
+
// ... 其他配置
|
|
425
|
+
});
|
|
459
426
|
```
|
|
460
427
|
|
|
461
|
-
###
|
|
462
|
-
|
|
463
|
-
A: 默认按页面分组:
|
|
464
|
-
|
|
465
|
-
- `tests/helpers/mock-{页面名}.ts` - Mock 数据
|
|
466
|
-
- `tests/e2e/{页面名}.spec.ts` - 测试用例
|
|
467
|
-
|
|
468
|
-
可以通过 `--output` 参数自定义输出目录。
|
|
469
|
-
|
|
470
|
-
## 🗺️ 开发路线图
|
|
471
|
-
|
|
472
|
-
### ✅ 已完成
|
|
473
|
-
|
|
474
|
-
- [x] AI 视觉检查核心功能
|
|
475
|
-
- [x] CLI 基础架构
|
|
476
|
-
- [x] 配置系统
|
|
477
|
-
- [x] 交互式初始化
|
|
428
|
+
### 2. 合理使用 AI 检查
|
|
478
429
|
|
|
479
|
-
|
|
430
|
+
AI 视觉检查适合用于:
|
|
480
431
|
|
|
481
|
-
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
- [ ] AI 生成 Mock 代码
|
|
485
|
-
- [ ] 交互操作录制
|
|
432
|
+
- ✅ 整体布局和结构验证
|
|
433
|
+
- ✅ 内容完整性检查
|
|
434
|
+
- ✅ 视觉回归测试
|
|
486
435
|
|
|
487
|
-
|
|
436
|
+
不适合用于:
|
|
488
437
|
|
|
489
|
-
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
- [ ] 类型定义分析
|
|
438
|
+
- ❌ 精确的像素级对比
|
|
439
|
+
- ❌ 颜色值精确匹配
|
|
440
|
+
- ❌ 性能敏感的场景
|
|
493
441
|
|
|
494
|
-
|
|
495
|
-
- [ ] 自然语言理解
|
|
496
|
-
- [ ] 交互式问答
|
|
497
|
-
- [ ] 代码生成
|
|
442
|
+
### 3. 组合使用
|
|
498
443
|
|
|
499
|
-
|
|
500
|
-
- [ ] 增量更新
|
|
501
|
-
- [ ] 自定义模板
|
|
502
|
-
- [ ] 测试覆盖率分析
|
|
503
|
-
- [ ] VS Code 插件
|
|
444
|
+
将 AI 检查与传统断言结合使用,获得最佳效果:
|
|
504
445
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
- [使用示例](./examples/) - 更多使用示例
|
|
509
|
-
|
|
510
|
-
## 📝 License
|
|
511
|
-
|
|
512
|
-
MIT
|
|
513
|
-
|
|
514
|
-
## 🤝 贡献
|
|
515
|
-
|
|
516
|
-
欢迎提交 Issue 和 Pull Request!
|
|
517
|
-
|
|
518
|
-
## 📮 联系方式
|
|
446
|
+
```typescript
|
|
447
|
+
test('优惠券页面', async ({ page }) => {
|
|
448
|
+
await page.goto('/coupon');
|
|
519
449
|
|
|
520
|
-
|
|
450
|
+
// 传统断言:精确的功能验证
|
|
451
|
+
await expect(page.getByText('优惠券')).toBeVisible();
|
|
452
|
+
await expect(page.getByRole('button', { name: '核销券' })).toBeVisible();
|
|
521
453
|
|
|
522
|
-
|
|
454
|
+
// AI 检查:整体视觉验证
|
|
455
|
+
const aiResult = await aiReviewScreenshot(page, '页面整体布局合理,优惠券卡片排列整齐,信息展示清晰');
|
|
523
456
|
|
|
524
|
-
|
|
457
|
+
if (!aiResult.skipped && aiResult.issues?.length) {
|
|
458
|
+
console.warn('AI 发现的潜在问题:', aiResult.issues);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
```
|