@be-link/smart-test 1.0.1-beta.8 → 1.0.1-beta.9
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 +344 -168
- 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 -5
- 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 +138 -17
- 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 +287 -128
- package/dist/index.js +288 -146
- package/dist/utils/screenshot.d.ts +0 -9
- package/dist/utils/screenshot.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,285 +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
|
-
- 🚫 **优雅降级**:无 API Key 时自动跳过,不影响其他测试
|
|
14
|
-
- 📦 **TypeScript**:完整的类型定义支持
|
|
7
|
+
- 🎯 **AI 视觉检查**:使用 AI 视觉模型自动检查页面截图是否符合预期
|
|
8
|
+
- 🤖 **AI 代码生成**:使用 AI 自动生成测试用例和 Mock 数据
|
|
9
|
+
- 📝 **TypeScript 支持**:完整的 TypeScript 类型定义
|
|
10
|
+
- 🔧 **灵活配置**:支持自定义 AI 模型和服务地址
|
|
15
11
|
|
|
16
12
|
## 📦 安装
|
|
17
13
|
|
|
18
14
|
```bash
|
|
19
|
-
# 使用 pnpm
|
|
20
15
|
pnpm add -D @be-link/smart-test
|
|
21
|
-
|
|
22
|
-
# 使用 npm
|
|
23
|
-
npm install -D @be-link/smart-test
|
|
24
|
-
|
|
25
|
-
# 使用 yarn
|
|
26
|
-
yarn add -D @be-link/smart-test
|
|
27
16
|
```
|
|
28
17
|
|
|
29
18
|
## 🚀 快速开始
|
|
30
19
|
|
|
31
|
-
### 1. 配置 API
|
|
20
|
+
### 1. 配置 AI API
|
|
32
21
|
|
|
33
|
-
|
|
22
|
+
```typescript
|
|
23
|
+
import { configure } from '@be-link/smart-test';
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
AI_API_KEY
|
|
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
|
+
});
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
### 2.
|
|
33
|
+
### 2. AI 视觉检查
|
|
40
34
|
|
|
41
|
-
在
|
|
35
|
+
在 Playwright 测试中使用 AI 检查页面截图:
|
|
42
36
|
|
|
43
37
|
```typescript
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
38
|
+
import { test } from '@playwright/test';
|
|
39
|
+
import { aiReviewScreenshot } from '@be-link/smart-test';
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
apiKey: process.env.AI_API_KEY,
|
|
50
|
-
screenshotDir: './test-results/ai-screenshots',
|
|
51
|
-
});
|
|
41
|
+
test('页面显示正确', async ({ page }) => {
|
|
42
|
+
await page.goto('/home');
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
const result = await aiReviewScreenshot(page, '顶部显示logo和导航栏,主内容区域显示3个产品卡片');
|
|
45
|
+
|
|
46
|
+
if (!result.skipped && result.issues?.length) {
|
|
47
|
+
console.log('AI 发现的问题:', result.issues);
|
|
48
|
+
}
|
|
55
49
|
});
|
|
56
50
|
```
|
|
57
51
|
|
|
58
|
-
### 3.
|
|
52
|
+
### 3. AI 生成测试用例
|
|
53
|
+
|
|
54
|
+
自动生成完整的 Playwright 测试用例:
|
|
59
55
|
|
|
60
56
|
```typescript
|
|
61
|
-
import {
|
|
62
|
-
|
|
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
|
+
});
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
if (result.ok) {
|
|
77
|
+
console.log('生成的测试代码:\n', result.code);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
const result = await aiReviewScreenshot(page, '页面应该显示优惠券列表,包含标题、优惠券卡片和底部按钮');
|
|
81
|
+
### 4. AI 生成 Mock 数据
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
if (result.skipped) {
|
|
73
|
-
test.skip(); // 无 API Key 时跳过
|
|
74
|
-
}
|
|
83
|
+
通过传入实际的 TypeScript 类型定义,AI 会生成完全符合接口规范的 Mock 数据:
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
```typescript
|
|
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
|
+
],
|
|
78
145
|
});
|
|
79
|
-
```
|
|
80
146
|
|
|
81
|
-
|
|
147
|
+
if (result.ok) {
|
|
148
|
+
console.log('生成的 Mock 代码:\n', result.code);
|
|
149
|
+
// AI 会严格按照类型定义生成符合规范的 mock 数据
|
|
150
|
+
}
|
|
151
|
+
```
|
|
82
152
|
|
|
83
|
-
###
|
|
153
|
+
### 5. 通用代码生成
|
|
84
154
|
|
|
85
|
-
|
|
155
|
+
使用 AI 生成任意 TypeScript 代码:
|
|
86
156
|
|
|
87
157
|
```typescript
|
|
88
|
-
import {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
timeout: 30000, // 超时时间(可选)
|
|
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
|
+
}`,
|
|
97
166
|
});
|
|
167
|
+
|
|
168
|
+
if (result.ok) {
|
|
169
|
+
console.log('生成的代码:\n', result.code);
|
|
170
|
+
}
|
|
98
171
|
```
|
|
99
172
|
|
|
100
|
-
|
|
173
|
+
## 📖 API 文档
|
|
174
|
+
|
|
175
|
+
### aiReviewScreenshot
|
|
101
176
|
|
|
102
|
-
使用 AI
|
|
177
|
+
使用 AI 视觉模型检查页面截图是否符合预期。
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
function aiReviewScreenshot(page: Page, options: string | AiCheckOptions): Promise<AiCheckResult>;
|
|
181
|
+
```
|
|
103
182
|
|
|
104
183
|
**参数:**
|
|
105
184
|
|
|
106
|
-
- `page
|
|
107
|
-
- `options
|
|
185
|
+
- `page`: Playwright Page 对象
|
|
186
|
+
- `options`: 检查选项
|
|
187
|
+
- 字符串:直接传入预期描述
|
|
188
|
+
- 对象:包含 `expected`(预期描述)、`fullPage`(是否全页截图)等配置
|
|
108
189
|
|
|
109
|
-
**返回值:**
|
|
190
|
+
**返回值:**
|
|
110
191
|
|
|
111
192
|
```typescript
|
|
112
193
|
interface AiCheckResult {
|
|
113
194
|
ok: boolean; // 检查是否通过
|
|
114
|
-
issues?: string[]; //
|
|
115
|
-
|
|
195
|
+
issues?: string[]; // 发现的问题列表
|
|
196
|
+
suggestion?: string[]; // 改进建议列表
|
|
197
|
+
skipped?: boolean; // 是否跳过检查
|
|
116
198
|
reason?: string; // 跳过原因
|
|
117
|
-
screenshotPath?: string; // 截图路径
|
|
118
199
|
rawResponse?: string; // AI 原始响应
|
|
119
200
|
}
|
|
120
201
|
```
|
|
121
202
|
|
|
122
|
-
|
|
203
|
+
### aiGenerateTestCase
|
|
123
204
|
|
|
124
|
-
|
|
205
|
+
使用 AI 生成完整的 Playwright 测试用例。
|
|
125
206
|
|
|
126
207
|
```typescript
|
|
127
|
-
|
|
128
|
-
const result = await aiReviewScreenshot(page, '页面应该显示登录表单');
|
|
208
|
+
function aiGenerateTestCase(options: AiTestCaseGenerationOptions): Promise<AiCodeGenerationResult>;
|
|
129
209
|
```
|
|
130
210
|
|
|
131
|
-
|
|
211
|
+
**参数:**
|
|
132
212
|
|
|
133
213
|
```typescript
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
}
|
|
223
|
+
|
|
224
|
+
interface ApiMockConfig {
|
|
225
|
+
path: string; // API 路径(支持通配符)
|
|
226
|
+
description: string; // Mock 数据描述
|
|
227
|
+
}
|
|
139
228
|
```
|
|
140
229
|
|
|
141
|
-
###
|
|
230
|
+
### aiGenerateMock
|
|
231
|
+
|
|
232
|
+
使用 AI 生成 API Mock 函数。
|
|
142
233
|
|
|
143
234
|
```typescript
|
|
144
|
-
|
|
145
|
-
|
|
235
|
+
function aiGenerateMock(options: AiMockGenerationOptions): Promise<AiCodeGenerationResult>;
|
|
236
|
+
```
|
|
146
237
|
|
|
147
|
-
|
|
148
|
-
test.beforeEach(async ({ page }) => {
|
|
149
|
-
await page.goto('/products');
|
|
150
|
-
await page.waitForLoadState('networkidle');
|
|
151
|
-
});
|
|
238
|
+
**参数:**
|
|
152
239
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
}
|
|
158
249
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
250
|
+
interface TypeDefinition {
|
|
251
|
+
typeName: string; // 类型名称
|
|
252
|
+
content: string; // 类型定义内容
|
|
253
|
+
}
|
|
254
|
+
```
|
|
163
255
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
256
|
+
**使用说明:**
|
|
257
|
+
传入 `typeDefinitions` 和 `responseType`,AI 会严格按照 TypeScript 类型定义生成 mock 数据,确保类型安全和数据准确性
|
|
167
258
|
|
|
168
|
-
|
|
169
|
-
});
|
|
259
|
+
### aiGenerateCode
|
|
170
260
|
|
|
171
|
-
|
|
172
|
-
// 切换到移动端视图
|
|
173
|
-
await page.setViewportSize({ width: 375, height: 667 });
|
|
261
|
+
通用的 AI 代码生成函数。
|
|
174
262
|
|
|
175
|
-
|
|
263
|
+
```typescript
|
|
264
|
+
function aiGenerateCode(options: AiCodeGenerationOptions): Promise<AiCodeGenerationResult>;
|
|
265
|
+
```
|
|
176
266
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
267
|
+
**参数:**
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
interface AiCodeGenerationOptions {
|
|
271
|
+
prompt: string; // 代码生成描述
|
|
272
|
+
context?: string; // 上下文代码
|
|
273
|
+
// ... 以及 AiBaseConfig 的所有配置项
|
|
274
|
+
}
|
|
182
275
|
```
|
|
183
276
|
|
|
184
|
-
|
|
277
|
+
**返回值:**
|
|
185
278
|
|
|
186
279
|
```typescript
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
for (const check of checks) {
|
|
195
|
-
const element = await page.locator(check.selector);
|
|
196
|
-
const screenshot = await element.screenshot();
|
|
197
|
-
|
|
198
|
-
// 这里可以扩展为单独检查某个元素的功能
|
|
199
|
-
// 当前版本检查整页,后续可以增强
|
|
200
|
-
}
|
|
201
|
-
});
|
|
280
|
+
interface AiCodeGenerationResult {
|
|
281
|
+
ok: boolean; // 是否成功生成
|
|
282
|
+
code?: string; // 生成的代码
|
|
283
|
+
skipped?: boolean; // 是否跳过
|
|
284
|
+
reason?: string; // 跳过或失败原因
|
|
285
|
+
rawResponse?: string; // AI 原始响应
|
|
286
|
+
}
|
|
202
287
|
```
|
|
203
288
|
|
|
204
|
-
|
|
289
|
+
### configure
|
|
205
290
|
|
|
206
|
-
|
|
291
|
+
配置全局 AI 设置。
|
|
207
292
|
|
|
208
293
|
```typescript
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
|
303
|
+
```
|
|
215
304
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
305
|
+
## 🎯 使用场景
|
|
306
|
+
|
|
307
|
+
### 场景 1:视觉回归测试
|
|
308
|
+
|
|
309
|
+
使用 AI 自动检测页面的视觉变化,无需手动维护大量的断言代码:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
test('首页显示正常', async ({ page }) => {
|
|
313
|
+
await page.goto('/');
|
|
314
|
+
|
|
315
|
+
const result = await aiReviewScreenshot(page, {
|
|
316
|
+
expected: '页面顶部显示网站logo和主导航,中间显示轮播图,下方显示4个产品分类卡片',
|
|
317
|
+
fullPage: true,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(result.ok).toBeTruthy();
|
|
221
321
|
});
|
|
222
322
|
```
|
|
223
323
|
|
|
224
|
-
###
|
|
324
|
+
### 场景 2:快速生成测试代码
|
|
325
|
+
|
|
326
|
+
为新功能快速生成测试用例骨架,节省编写测试代码的时间:
|
|
225
327
|
|
|
226
328
|
```typescript
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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: ['验证显示产品名称和价格', '点击"加入购物车"按钮', '验证显示成功提示'],
|
|
230
338
|
});
|
|
231
339
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
340
|
+
// 将生成的代码保存到文件,然后根据实际需要调整
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### 场景 3:Mock 数据管理
|
|
344
|
+
|
|
345
|
+
通过传入实际的类型定义,确保生成的 Mock 数据完全符合接口规范:
|
|
346
|
+
|
|
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'),
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
typeName: 'IUserAddress',
|
|
365
|
+
content: extractTypeFromFile(addressTypeContent, 'IUserAddress'),
|
|
366
|
+
},
|
|
367
|
+
],
|
|
235
368
|
});
|
|
369
|
+
|
|
370
|
+
// 生成的 mock 数据会严格符合类型定义,包括:
|
|
371
|
+
// - 字段类型匹配(string、number、boolean、null 等)
|
|
372
|
+
// - 必填/可选字段正确
|
|
373
|
+
// - 嵌套对象结构准确
|
|
374
|
+
// - 根据注释生成合理的测试数据
|
|
236
375
|
```
|
|
237
376
|
|
|
238
|
-
|
|
377
|
+
## ⚙️ 配置说明
|
|
378
|
+
|
|
379
|
+
### 默认配置
|
|
239
380
|
|
|
240
381
|
```typescript
|
|
241
|
-
|
|
382
|
+
{
|
|
383
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
384
|
+
visionModel: 'qwen3-vl-plus',
|
|
385
|
+
codeModel: 'qwen3-coder-plus',
|
|
386
|
+
timeout: 30000
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 自定义配置
|
|
391
|
+
|
|
392
|
+
可以在全局配置中设置默认值,也可以在每次调用时覆盖:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// 全局配置
|
|
242
396
|
configure({
|
|
243
397
|
apiKey: process.env.AI_API_KEY,
|
|
244
|
-
|
|
398
|
+
visionModel: 'custom-vision-model',
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// 单次调用覆盖
|
|
402
|
+
await aiReviewScreenshot(page, {
|
|
403
|
+
expected: '...',
|
|
404
|
+
visionModel: 'another-model', // 仅此次调用使用
|
|
245
405
|
});
|
|
246
406
|
```
|
|
247
407
|
|
|
248
|
-
##
|
|
408
|
+
## 🔍 最佳实践
|
|
249
409
|
|
|
250
|
-
###
|
|
410
|
+
### 1. 环境变量管理
|
|
251
411
|
|
|
252
|
-
|
|
412
|
+
建议将 API Key 存储在环境变量中:
|
|
253
413
|
|
|
254
|
-
|
|
414
|
+
```typescript
|
|
415
|
+
// playwright.config.ts
|
|
416
|
+
import { defineConfig } from '@playwright/test';
|
|
417
|
+
import { configure } from '@be-link/smart-test';
|
|
255
418
|
|
|
256
|
-
|
|
419
|
+
configure({
|
|
420
|
+
apiKey: process.env.AI_API_KEY,
|
|
421
|
+
});
|
|
257
422
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
423
|
+
export default defineConfig({
|
|
424
|
+
// ... 其他配置
|
|
425
|
+
});
|
|
426
|
+
```
|
|
261
427
|
|
|
262
|
-
###
|
|
428
|
+
### 2. 合理使用 AI 检查
|
|
263
429
|
|
|
264
|
-
|
|
430
|
+
AI 视觉检查适合用于:
|
|
265
431
|
|
|
266
|
-
|
|
432
|
+
- ✅ 整体布局和结构验证
|
|
433
|
+
- ✅ 内容完整性检查
|
|
434
|
+
- ✅ 视觉回归测试
|
|
267
435
|
|
|
268
|
-
|
|
436
|
+
不适合用于:
|
|
269
437
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
```
|
|
438
|
+
- ❌ 精确的像素级对比
|
|
439
|
+
- ❌ 颜色值精确匹配
|
|
440
|
+
- ❌ 性能敏感的场景
|
|
274
441
|
|
|
275
|
-
|
|
442
|
+
### 3. 组合使用
|
|
276
443
|
|
|
277
|
-
|
|
444
|
+
将 AI 检查与传统断言结合使用,获得最佳效果:
|
|
278
445
|
|
|
279
|
-
|
|
446
|
+
```typescript
|
|
447
|
+
test('优惠券页面', async ({ page }) => {
|
|
448
|
+
await page.goto('/coupon');
|
|
280
449
|
|
|
281
|
-
|
|
450
|
+
// 传统断言:精确的功能验证
|
|
451
|
+
await expect(page.getByText('优惠券')).toBeVisible();
|
|
452
|
+
await expect(page.getByRole('button', { name: '核销券' })).toBeVisible();
|
|
282
453
|
|
|
283
|
-
|
|
454
|
+
// AI 检查:整体视觉验证
|
|
455
|
+
const aiResult = await aiReviewScreenshot(page, '页面整体布局合理,优惠券卡片排列整齐,信息展示清晰');
|
|
284
456
|
|
|
285
|
-
|
|
457
|
+
if (!aiResult.skipped && aiResult.issues?.length) {
|
|
458
|
+
console.warn('AI 发现的潜在问题:', aiResult.issues);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
```
|