@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 CHANGED
@@ -1,285 +1,461 @@
1
1
  # @be-link/smart-test
2
2
 
3
- > AI-powered visual testing for Playwright - 基于 AI 视觉模型的 Playwright 测试工具
4
-
5
- 使用 AI 视觉识别能力,让你的 E2E 测试更智能、更可靠。
3
+ AI-powered visual testing for Playwright | 基于 AI 视觉模型的 Playwright 测试工具
6
4
 
7
5
  ## ✨ 特性
8
6
 
9
- - 🤖 **AI 视觉检查**:基于通义千问等视觉模型,智能检查页面是否符合预期
10
- - 🎯 **零侵入设计**:与 Playwright 无缝集成,无需修改现有测试代码
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 Key
20
+ ### 1. 配置 AI API
32
21
 
33
- 在项目根目录创建 `.env` 文件:
22
+ ```typescript
23
+ import { configure } from '@be-link/smart-test';
34
24
 
35
- ```bash
36
- AI_API_KEY=sk-your-api-key-here
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
- `playwright.config.ts` 中配置:
35
+ Playwright 测试中使用 AI 检查页面截图:
42
36
 
43
37
  ```typescript
44
- import { defineConfig } from '@playwright/test';
45
- import { configure } from '@be-link/smart-test';
38
+ import { test } from '@playwright/test';
39
+ import { aiReviewScreenshot } from '@be-link/smart-test';
46
40
 
47
- // 初始化 AI 配置
48
- configure({
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
- export default defineConfig({
54
- // ... 其他 Playwright 配置
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 { test, expect } from '@playwright/test';
62
- import { aiReviewScreenshot } from '@be-link/smart-test';
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
- test.describe('优惠券页面', () => {
65
- test('AI 检查页面布局', async ({ page }) => {
66
- await page.goto('/coupon');
76
+ if (result.ok) {
77
+ console.log('生成的测试代码:\n', result.code);
78
+ }
79
+ ```
67
80
 
68
- // 使用 AI 检查页面
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
- expect(result.ok, result.issues?.join(', ')).toBeTruthy();
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
- ## 📖 API 文档
147
+ if (result.ok) {
148
+ console.log('生成的 Mock 代码:\n', result.code);
149
+ // AI 会严格按照类型定义生成符合规范的 mock 数据
150
+ }
151
+ ```
82
152
 
83
- ### `configure(config)`
153
+ ### 5. 通用代码生成
84
154
 
85
- 配置全局设置。
155
+ 使用 AI 生成任意 TypeScript 代码:
86
156
 
87
157
  ```typescript
88
- import { configure } from '@be-link/smart-test';
89
-
90
- configure({
91
- apiKey: 'sk-xxx', // AI API 密钥
92
- baseURL: 'https://...', // AI 服务地址(可选)
93
- model: 'qwen3-vl-plus', // AI 模型(可选)
94
- screenshotDir: './screenshots', // 截图保存目录(可选)
95
- saveScreenshots: true, // 是否保存截图(可选)
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
- ### `aiReviewScreenshot(page, options)`
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: Page` - Playwright Page 对象
107
- - `options: string | AiCheckOptions` - 检查选项
185
+ - `page`: Playwright Page 对象
186
+ - `options`: 检查选项
187
+ - 字符串:直接传入预期描述
188
+ - 对象:包含 `expected`(预期描述)、`fullPage`(是否全页截图)等配置
108
189
 
109
- **返回值:** `Promise<AiCheckResult>`
190
+ **返回值:**
110
191
 
111
192
  ```typescript
112
193
  interface AiCheckResult {
113
194
  ok: boolean; // 检查是否通过
114
- issues?: string[]; // 问题列表
115
- skipped?: boolean; // 是否跳过
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
- const result = await aiReviewScreenshot(page, {
136
- expected: '页面应该显示商品列表',
137
- model: 'gpt-4-vision-preview', // 使用其他模型
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
- import { test, expect } from '@playwright/test';
145
- import { aiReviewScreenshot } from '@be-link/smart-test';
235
+ function aiGenerateMock(options: AiMockGenerationOptions): Promise<AiCodeGenerationResult>;
236
+ ```
146
237
 
147
- test.describe('商品页面测试', () => {
148
- test.beforeEach(async ({ page }) => {
149
- await page.goto('/products');
150
- await page.waitForLoadState('networkidle');
151
- });
238
+ **参数:**
152
239
 
153
- test('检查页面布局', async ({ page }) => {
154
- const result = await aiReviewScreenshot(page, {
155
- expected: '页面顶部应有搜索框,中间显示商品网格布局,底部有分页器',
156
- fullPage: true,
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
- if (result.skipped) {
160
- console.log('跳过原因:', result.reason);
161
- test.skip();
162
- }
250
+ interface TypeDefinition {
251
+ typeName: string; // 类型名称
252
+ content: string; // 类型定义内容
253
+ }
254
+ ```
163
255
 
164
- if (!result.ok && result.issues) {
165
- console.warn('发现的问题:', result.issues);
166
- }
256
+ **使用说明:**
257
+ 传入 `typeDefinitions` 和 `responseType`,AI 会严格按照 TypeScript 类型定义生成 mock 数据,确保类型安全和数据准确性
167
258
 
168
- expect(result.ok, `AI 检查失败: ${result.issues?.join(', ')}`).toBeTruthy();
169
- });
259
+ ### aiGenerateCode
170
260
 
171
- test('检查响应式布局', async ({ page }) => {
172
- // 切换到移动端视图
173
- await page.setViewportSize({ width: 375, height: 667 });
261
+ 通用的 AI 代码生成函数。
174
262
 
175
- const result = await aiReviewScreenshot(page, '移动端布局:商品应该以单列显示,每个卡片占满宽度');
263
+ ```typescript
264
+ function aiGenerateCode(options: AiCodeGenerationOptions): Promise<AiCodeGenerationResult>;
265
+ ```
176
266
 
177
- if (!result.skipped) {
178
- expect(result.ok).toBeTruthy();
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
- test('检查多个区域', async ({ page }) => {
188
- const checks = [
189
- { selector: '.header', expected: '顶部导航栏应显示 logo 和菜单' },
190
- { selector: '.main-content', expected: '主内容区应显示文章列表' },
191
- { selector: '.sidebar', expected: '侧边栏应显示热门标签' },
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
- ### 支持多种 AI 模型
291
+ 配置全局 AI 设置。
207
292
 
208
293
  ```typescript
209
- // 使用通义千问(默认)
210
- configure({
211
- apiKey: process.env.QWEN_API_KEY,
212
- baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
213
- model: 'qwen3-vl-plus',
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
- // 使用 OpenAI GPT-4 Vision
217
- configure({
218
- apiKey: process.env.OPENAI_API_KEY,
219
- baseURL: 'https://api.openai.com/v1',
220
- model: 'gpt-4-vision-preview',
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
- configure({
228
- screenshotDir: './custom-screenshots',
229
- saveScreenshots: true,
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
- const result = await aiReviewScreenshot(page, {
234
- expected: '...',
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
- ### CI 环境中使用
377
+ ## ⚙️ 配置说明
378
+
379
+ ### 默认配置
239
380
 
240
381
  ```typescript
241
- // 在 CI 中可能不需要保存截图,减少存储开销
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
- saveScreenshots: process.env.CI !== 'true',
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
- ### Q: 没有配置 API Key 会怎样?
410
+ ### 1. 环境变量管理
251
411
 
252
- A: 测试会自动跳过 AI 检查,返回 `skipped: true`,不会影响其他测试的执行。
412
+ 建议将 API Key 存储在环境变量中:
253
413
 
254
- ### Q: 支持哪些 AI 模型?
414
+ ```typescript
415
+ // playwright.config.ts
416
+ import { defineConfig } from '@playwright/test';
417
+ import { configure } from '@be-link/smart-test';
255
418
 
256
- A: 理论上支持所有兼容 OpenAI Chat Completions API 的视觉模型,包括:
419
+ configure({
420
+ apiKey: process.env.AI_API_KEY,
421
+ });
257
422
 
258
- - 通义千问 (qwen3-vl-plus) - 默认
259
- - OpenAI GPT-4 Vision
260
- - 其他兼容的模型服务
423
+ export default defineConfig({
424
+ // ... 其他配置
425
+ });
426
+ ```
261
427
 
262
- ### Q: 截图保存在哪里?
428
+ ### 2. 合理使用 AI 检查
263
429
 
264
- A: 默认保存在 `./test-results/ai-screenshots/` 目录,可以通过 `screenshotDir` 配置修改。
430
+ AI 视觉检查适合用于:
265
431
 
266
- ### Q: 如何查看 AI 的原始响应?
432
+ - 整体布局和结构验证
433
+ - ✅ 内容完整性检查
434
+ - ✅ 视觉回归测试
267
435
 
268
- A: 检查结果中的 `rawResponse` 字段包含了 AI 的原始响应内容。
436
+ 不适合用于:
269
437
 
270
- ```typescript
271
- const result = await aiReviewScreenshot(page, '...');
272
- console.log('AI 原始响应:', result.rawResponse);
273
- ```
438
+ - ❌ 精确的像素级对比
439
+ - 颜色值精确匹配
440
+ - 性能敏感的场景
274
441
 
275
- ## 📝 License
442
+ ### 3. 组合使用
276
443
 
277
- MIT
444
+ 将 AI 检查与传统断言结合使用,获得最佳效果:
278
445
 
279
- ## 🤝 贡献
446
+ ```typescript
447
+ test('优惠券页面', async ({ page }) => {
448
+ await page.goto('/coupon');
280
449
 
281
- 欢迎提交 Issue 和 Pull Request!
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
- 如有问题,请在 [GitHub Issues](https://github.com/snowmountain-top/be-link/issues) 中提出。
457
+ if (!aiResult.skipped && aiResult.issues?.length) {
458
+ console.warn('AI 发现的潜在问题:', aiResult.issues);
459
+ }
460
+ });
461
+ ```