@be-link/smart-test 1.0.1-beta.0 → 1.0.1-beta.1
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 +269 -34
- package/bin/smart-test.js +15 -0
- package/dist/cli/commands/generate.d.ts +16 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +618 -0
- package/dist/config/config-loader.d.ts +35 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-schema.d.ts +85 -0
- package/dist/config/config-schema.d.ts.map +1 -0
- package/dist/core/ai-assistant.d.ts +0 -1
- package/dist/core/ai-assistant.d.ts.map +1 -1
- package/dist/core/config.d.ts +0 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/types.d.ts +0 -15
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.esm.js +0 -29
- package/dist/index.js +0 -49
- package/dist/utils/error-handler.d.ts +41 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +88 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/screenshot.d.ts +0 -9
- package/dist/utils/screenshot.d.ts.map +1 -1
- package/package.json +18 -3
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { cosmiconfig } from 'cosmiconfig';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 日志级别
|
|
11
|
+
*/
|
|
12
|
+
var LogLevel;
|
|
13
|
+
(function (LogLevel) {
|
|
14
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
15
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
16
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
17
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
18
|
+
})(LogLevel || (LogLevel = {}));
|
|
19
|
+
/**
|
|
20
|
+
* 日志工具类
|
|
21
|
+
*/
|
|
22
|
+
class Logger {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.level = LogLevel.INFO;
|
|
25
|
+
this.spinner = null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 设置日志级别
|
|
29
|
+
*/
|
|
30
|
+
setLevel(level) {
|
|
31
|
+
this.level = level;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 调试日志
|
|
35
|
+
*/
|
|
36
|
+
debug(message, ...args) {
|
|
37
|
+
if (this.level <= LogLevel.DEBUG) {
|
|
38
|
+
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 信息日志
|
|
43
|
+
*/
|
|
44
|
+
info(message, ...args) {
|
|
45
|
+
if (this.level <= LogLevel.INFO) {
|
|
46
|
+
console.log(chalk.blue(`ℹ ${message}`), ...args);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 成功日志
|
|
51
|
+
*/
|
|
52
|
+
success(message, ...args) {
|
|
53
|
+
if (this.level <= LogLevel.INFO) {
|
|
54
|
+
console.log(chalk.green(`✓ ${message}`), ...args);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 警告日志
|
|
59
|
+
*/
|
|
60
|
+
warn(message, ...args) {
|
|
61
|
+
if (this.level <= LogLevel.WARN) {
|
|
62
|
+
console.warn(chalk.yellow(`⚠ ${message}`), ...args);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 错误日志
|
|
67
|
+
*/
|
|
68
|
+
error(message, ...args) {
|
|
69
|
+
if (this.level <= LogLevel.ERROR) {
|
|
70
|
+
console.error(chalk.red(`✗ ${message}`), ...args);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 标题(带装饰)
|
|
75
|
+
*/
|
|
76
|
+
title(message) {
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(chalk.cyan.bold(`━━━ ${message} ━━━`));
|
|
79
|
+
console.log();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 步骤提示
|
|
83
|
+
*/
|
|
84
|
+
step(current, total, message) {
|
|
85
|
+
console.log(chalk.blue(`[${current}/${total}] ${message}`));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 提示用户输入
|
|
89
|
+
*/
|
|
90
|
+
prompt(message) {
|
|
91
|
+
console.log(chalk.cyan(`👉 ${message}`));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* AI 消息
|
|
95
|
+
*/
|
|
96
|
+
ai(message) {
|
|
97
|
+
console.log(chalk.magenta(`🤖 ${message}`));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 开始加载动画
|
|
101
|
+
*/
|
|
102
|
+
startSpinner(message) {
|
|
103
|
+
this.spinner = ora(message).start();
|
|
104
|
+
return this.spinner;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 更新加载动画文本
|
|
108
|
+
*/
|
|
109
|
+
updateSpinner(message) {
|
|
110
|
+
if (this.spinner) {
|
|
111
|
+
this.spinner.text = message;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 停止加载动画(成功)
|
|
116
|
+
*/
|
|
117
|
+
succeedSpinner(message) {
|
|
118
|
+
if (this.spinner) {
|
|
119
|
+
this.spinner.succeed(message);
|
|
120
|
+
this.spinner = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 停止加载动画(失败)
|
|
125
|
+
*/
|
|
126
|
+
failSpinner(message) {
|
|
127
|
+
if (this.spinner) {
|
|
128
|
+
this.spinner.fail(message);
|
|
129
|
+
this.spinner = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 停止加载动画
|
|
134
|
+
*/
|
|
135
|
+
stopSpinner() {
|
|
136
|
+
if (this.spinner) {
|
|
137
|
+
this.spinner.stop();
|
|
138
|
+
this.spinner = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 换行
|
|
143
|
+
*/
|
|
144
|
+
newLine() {
|
|
145
|
+
console.log();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 分隔线
|
|
149
|
+
*/
|
|
150
|
+
divider() {
|
|
151
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// 导出单例
|
|
155
|
+
const logger = new Logger();
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 自定义错误类型
|
|
159
|
+
*/
|
|
160
|
+
class SmartTestError extends Error {
|
|
161
|
+
constructor(message, code, details) {
|
|
162
|
+
super(message);
|
|
163
|
+
this.code = code;
|
|
164
|
+
this.details = details;
|
|
165
|
+
this.name = 'SmartTestError';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 配置错误
|
|
170
|
+
*/
|
|
171
|
+
class ConfigError extends SmartTestError {
|
|
172
|
+
constructor(message, details) {
|
|
173
|
+
super(message, 'CONFIG_ERROR', details);
|
|
174
|
+
this.name = 'ConfigError';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 网络错误
|
|
179
|
+
*/
|
|
180
|
+
class NetworkError extends SmartTestError {
|
|
181
|
+
constructor(message, details) {
|
|
182
|
+
super(message, 'NETWORK_ERROR', details);
|
|
183
|
+
this.name = 'NetworkError';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* AI 服务错误
|
|
188
|
+
*/
|
|
189
|
+
class AIServiceError extends SmartTestError {
|
|
190
|
+
constructor(message, details) {
|
|
191
|
+
super(message, 'AI_SERVICE_ERROR', details);
|
|
192
|
+
this.name = 'AIServiceError';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 文件系统错误
|
|
197
|
+
*/
|
|
198
|
+
class FileSystemError extends SmartTestError {
|
|
199
|
+
constructor(message, details) {
|
|
200
|
+
super(message, 'FILE_SYSTEM_ERROR', details);
|
|
201
|
+
this.name = 'FileSystemError';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 全局错误处理器
|
|
206
|
+
*/
|
|
207
|
+
function handleError(error) {
|
|
208
|
+
logger.newLine();
|
|
209
|
+
if (error instanceof SmartTestError) {
|
|
210
|
+
logger.error(error.message);
|
|
211
|
+
if (error.details) {
|
|
212
|
+
logger.debug('错误详情:', error.details);
|
|
213
|
+
}
|
|
214
|
+
// 根据不同的错误类型给出建议
|
|
215
|
+
if (error instanceof ConfigError) {
|
|
216
|
+
logger.info('提示: 请检查配置文件 .smarttestrc 是否正确');
|
|
217
|
+
}
|
|
218
|
+
else if (error instanceof AIServiceError) {
|
|
219
|
+
logger.info('提示: 请检查 AI API Key 是否配置正确');
|
|
220
|
+
logger.info(' 运行 smart-test init 重新配置');
|
|
221
|
+
}
|
|
222
|
+
else if (error instanceof NetworkError) {
|
|
223
|
+
logger.info('提示: 请检查网络连接是否正常');
|
|
224
|
+
}
|
|
225
|
+
else if (error instanceof FileSystemError) {
|
|
226
|
+
logger.info('提示: 请检查文件路径和权限是否正确');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
logger.error(`未知错误: ${error.message}`);
|
|
231
|
+
logger.debug('错误堆栈:', error.stack);
|
|
232
|
+
}
|
|
233
|
+
logger.newLine();
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* 包装异步函数,自动处理错误
|
|
238
|
+
*/
|
|
239
|
+
function withErrorHandling(fn) {
|
|
240
|
+
return (async (...args) => {
|
|
241
|
+
try {
|
|
242
|
+
return await fn(...args);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
handleError(error);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 初始化命令
|
|
252
|
+
*/
|
|
253
|
+
async function initCommand() {
|
|
254
|
+
logger.title('初始化 Smart Test 配置');
|
|
255
|
+
// 检查是否已存在配置文件
|
|
256
|
+
const configPath = path.join(process.cwd(), '.smarttestrc.json');
|
|
257
|
+
const exists = await fs
|
|
258
|
+
.access(configPath)
|
|
259
|
+
.then(() => true)
|
|
260
|
+
.catch(() => false);
|
|
261
|
+
if (exists) {
|
|
262
|
+
const { overwrite } = await inquirer.prompt([
|
|
263
|
+
{
|
|
264
|
+
type: 'confirm',
|
|
265
|
+
name: 'overwrite',
|
|
266
|
+
message: '配置文件已存在,是否覆盖?',
|
|
267
|
+
default: false,
|
|
268
|
+
},
|
|
269
|
+
]);
|
|
270
|
+
if (!overwrite) {
|
|
271
|
+
logger.info('已取消初始化');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// 交互式收集配置
|
|
276
|
+
const answers = await inquirer.prompt([
|
|
277
|
+
{
|
|
278
|
+
type: 'input',
|
|
279
|
+
name: 'aiApiKey',
|
|
280
|
+
message: '请输入 AI API Key:',
|
|
281
|
+
default: process.env.AI_API_KEY || '',
|
|
282
|
+
validate: (input) => {
|
|
283
|
+
if (!input.trim()) {
|
|
284
|
+
return '请输入有效的 API Key';
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
type: 'list',
|
|
291
|
+
name: 'aiModel',
|
|
292
|
+
message: '选择 AI 模型:',
|
|
293
|
+
choices: [
|
|
294
|
+
{ name: '通义千问 (qwen3-vl-plus) - 推荐', value: 'qwen3-vl-plus' },
|
|
295
|
+
{ name: 'OpenAI GPT-4 Vision', value: 'gpt-4-vision-preview' },
|
|
296
|
+
],
|
|
297
|
+
default: 'qwen3-vl-plus',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
type: 'input',
|
|
301
|
+
name: 'testsDir',
|
|
302
|
+
message: '测试文件目录:',
|
|
303
|
+
default: './tests/e2e',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
type: 'input',
|
|
307
|
+
name: 'helpersDir',
|
|
308
|
+
message: 'Helpers 目录:',
|
|
309
|
+
default: './tests/helpers',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: 'input',
|
|
313
|
+
name: 'baseURL',
|
|
314
|
+
message: 'Playwright Base URL:',
|
|
315
|
+
default: 'http://localhost:8080',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
type: 'list',
|
|
319
|
+
name: 'naming',
|
|
320
|
+
message: '文件命名风格:',
|
|
321
|
+
choices: [
|
|
322
|
+
{ name: 'kebab-case (推荐)', value: 'kebab-case' },
|
|
323
|
+
{ name: 'camelCase', value: 'camelCase' },
|
|
324
|
+
{ name: 'snake_case', value: 'snake_case' },
|
|
325
|
+
],
|
|
326
|
+
default: 'kebab-case',
|
|
327
|
+
},
|
|
328
|
+
]);
|
|
329
|
+
// 构建配置对象
|
|
330
|
+
const config = {
|
|
331
|
+
version: '1.0.0',
|
|
332
|
+
ai: {
|
|
333
|
+
model: answers.aiModel,
|
|
334
|
+
apiKey: answers.aiApiKey,
|
|
335
|
+
baseURL: answers.aiModel === 'qwen3-vl-plus'
|
|
336
|
+
? 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
|
337
|
+
: 'https://api.openai.com/v1',
|
|
338
|
+
timeout: 30000,
|
|
339
|
+
},
|
|
340
|
+
output: {
|
|
341
|
+
testsDir: answers.testsDir,
|
|
342
|
+
helpersDir: answers.helpersDir,
|
|
343
|
+
naming: answers.naming,
|
|
344
|
+
},
|
|
345
|
+
templates: {
|
|
346
|
+
mock: 'default',
|
|
347
|
+
test: 'default',
|
|
348
|
+
},
|
|
349
|
+
playwright: {
|
|
350
|
+
baseURL: answers.baseURL,
|
|
351
|
+
viewport: {
|
|
352
|
+
width: 375,
|
|
353
|
+
height: 667,
|
|
354
|
+
},
|
|
355
|
+
browser: 'chromium',
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
// 写入配置文件
|
|
359
|
+
try {
|
|
360
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
361
|
+
logger.success(`配置文件已创建: ${configPath}`);
|
|
362
|
+
logger.newLine();
|
|
363
|
+
logger.info('下一步,运行以下命令生成测试:');
|
|
364
|
+
logger.info(' smart-test generate --page <页面路径>');
|
|
365
|
+
logger.info(' smart-test record --url <页面URL>');
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
throw new FileSystemError(`写入配置文件失败: ${error.message}`, error);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* 默认配置
|
|
374
|
+
*/
|
|
375
|
+
const DEFAULT_CONFIG = {
|
|
376
|
+
version: '1.0.0',
|
|
377
|
+
ai: {
|
|
378
|
+
model: 'qwen3-vl-plus',
|
|
379
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
380
|
+
timeout: 30000,
|
|
381
|
+
},
|
|
382
|
+
output: {
|
|
383
|
+
testsDir: './tests/e2e',
|
|
384
|
+
helpersDir: './tests/helpers',
|
|
385
|
+
naming: 'kebab-case',
|
|
386
|
+
},
|
|
387
|
+
templates: {
|
|
388
|
+
mock: 'default',
|
|
389
|
+
test: 'default',
|
|
390
|
+
},
|
|
391
|
+
playwright: {
|
|
392
|
+
baseURL: 'http://localhost:8080',
|
|
393
|
+
viewport: {
|
|
394
|
+
width: 375,
|
|
395
|
+
height: 667,
|
|
396
|
+
},
|
|
397
|
+
browser: 'chromium',
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* 配置加载器
|
|
403
|
+
*/
|
|
404
|
+
class ConfigLoader {
|
|
405
|
+
constructor() {
|
|
406
|
+
this.config = null;
|
|
407
|
+
this.explorer = cosmiconfig('smarttest');
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 加载配置
|
|
411
|
+
*/
|
|
412
|
+
async load() {
|
|
413
|
+
if (this.config) {
|
|
414
|
+
return this.config;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const result = await this.explorer.search();
|
|
418
|
+
if (result && !result.isEmpty) {
|
|
419
|
+
logger.debug(`配置文件已加载: ${result.filepath}`);
|
|
420
|
+
this.config = this.mergeConfig(result.config);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
logger.debug('未找到配置文件,使用默认配置');
|
|
424
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
425
|
+
}
|
|
426
|
+
// 从环境变量读取 API Key
|
|
427
|
+
if (process.env.AI_API_KEY && !this.config.ai?.apiKey) {
|
|
428
|
+
this.config.ai = {
|
|
429
|
+
...this.config.ai,
|
|
430
|
+
apiKey: process.env.AI_API_KEY,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return this.config;
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
throw new ConfigError(`加载配置失败: ${error.message}`, error);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* 合并配置
|
|
441
|
+
*/
|
|
442
|
+
mergeConfig(userConfig) {
|
|
443
|
+
return {
|
|
444
|
+
version: userConfig.version || DEFAULT_CONFIG.version,
|
|
445
|
+
ai: {
|
|
446
|
+
...DEFAULT_CONFIG.ai,
|
|
447
|
+
...userConfig.ai,
|
|
448
|
+
},
|
|
449
|
+
output: {
|
|
450
|
+
...DEFAULT_CONFIG.output,
|
|
451
|
+
...userConfig.output,
|
|
452
|
+
},
|
|
453
|
+
templates: {
|
|
454
|
+
...DEFAULT_CONFIG.templates,
|
|
455
|
+
...userConfig.templates,
|
|
456
|
+
},
|
|
457
|
+
playwright: {
|
|
458
|
+
...DEFAULT_CONFIG.playwright,
|
|
459
|
+
...userConfig.playwright,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* 获取配置
|
|
465
|
+
*/
|
|
466
|
+
get() {
|
|
467
|
+
if (!this.config) {
|
|
468
|
+
throw new ConfigError('配置未加载,请先调用 load()');
|
|
469
|
+
}
|
|
470
|
+
return this.config;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* 更新配置
|
|
474
|
+
*/
|
|
475
|
+
update(updates) {
|
|
476
|
+
if (!this.config) {
|
|
477
|
+
throw new ConfigError('配置未加载,请先调用 load()');
|
|
478
|
+
}
|
|
479
|
+
this.config = this.mergeConfig({ ...this.config, ...updates });
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 重置配置
|
|
483
|
+
*/
|
|
484
|
+
reset() {
|
|
485
|
+
this.config = null;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* 验证配置
|
|
489
|
+
*/
|
|
490
|
+
validate(config) {
|
|
491
|
+
// AI 配置验证
|
|
492
|
+
if (!config.ai?.apiKey) {
|
|
493
|
+
logger.warn('未配置 AI API Key,某些功能将不可用');
|
|
494
|
+
}
|
|
495
|
+
// 输出目录验证
|
|
496
|
+
if (!config.output?.testsDir || !config.output?.helpersDir) {
|
|
497
|
+
throw new ConfigError('输出目录配置不完整');
|
|
498
|
+
}
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// 导出单例
|
|
503
|
+
const configLoader = new ConfigLoader();
|
|
504
|
+
|
|
505
|
+
var configLoader$1 = /*#__PURE__*/Object.freeze({
|
|
506
|
+
__proto__: null,
|
|
507
|
+
configLoader: configLoader
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* 生成命令
|
|
512
|
+
*/
|
|
513
|
+
async function generateCommand(options) {
|
|
514
|
+
logger.title('生成测试代码');
|
|
515
|
+
// 加载配置
|
|
516
|
+
const config = await configLoader.load();
|
|
517
|
+
logger.debug('配置已加载:', config);
|
|
518
|
+
// 确定生成模式
|
|
519
|
+
let mode = 'hybrid';
|
|
520
|
+
if (options.url) {
|
|
521
|
+
mode = 'record';
|
|
522
|
+
}
|
|
523
|
+
else if (options.page) {
|
|
524
|
+
mode = 'analyze';
|
|
525
|
+
}
|
|
526
|
+
else if (options.desc) {
|
|
527
|
+
mode = 'describe';
|
|
528
|
+
}
|
|
529
|
+
else if (options.interactive) {
|
|
530
|
+
mode = 'hybrid';
|
|
531
|
+
}
|
|
532
|
+
logger.info(`生成模式: ${mode}`);
|
|
533
|
+
// TODO: 实现不同的生成模式
|
|
534
|
+
switch (mode) {
|
|
535
|
+
case 'record':
|
|
536
|
+
logger.info('录制模式 - 开发中...');
|
|
537
|
+
logger.info(`将录制: ${options.url}`);
|
|
538
|
+
break;
|
|
539
|
+
case 'analyze':
|
|
540
|
+
logger.info('代码分析模式 - 开发中...');
|
|
541
|
+
logger.info(`将分析: ${options.page}`);
|
|
542
|
+
break;
|
|
543
|
+
case 'describe':
|
|
544
|
+
logger.info('描述生成模式 - 开发中...');
|
|
545
|
+
logger.info(`描述: ${options.desc}`);
|
|
546
|
+
break;
|
|
547
|
+
case 'hybrid':
|
|
548
|
+
logger.info('混合模式 - 开发中...');
|
|
549
|
+
logger.info('将进入交互式引导...');
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
logger.newLine();
|
|
553
|
+
logger.warn('此功能正在开发中,敬请期待!');
|
|
554
|
+
logger.info('当前版本仅支持 smart-test init 命令');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* 创建 CLI 程序
|
|
559
|
+
*/
|
|
560
|
+
function createCLI() {
|
|
561
|
+
const program = new Command();
|
|
562
|
+
program.name('smart-test').description('AI-powered visual testing for Playwright').version('1.0.0');
|
|
563
|
+
// 全局选项
|
|
564
|
+
program.option('-v, --verbose', '详细输出', () => {
|
|
565
|
+
logger.setLevel(LogLevel.DEBUG);
|
|
566
|
+
});
|
|
567
|
+
// ========== init 命令 ==========
|
|
568
|
+
program
|
|
569
|
+
.command('init')
|
|
570
|
+
.description('初始化 Smart Test 配置')
|
|
571
|
+
.action(withErrorHandling(async () => {
|
|
572
|
+
await initCommand();
|
|
573
|
+
}));
|
|
574
|
+
// ========== generate 命令 ==========
|
|
575
|
+
program
|
|
576
|
+
.command('generate')
|
|
577
|
+
.description('生成测试代码')
|
|
578
|
+
.option('-p, --page <path>', '页面文件路径(代码分析模式)')
|
|
579
|
+
.option('-u, --url <url>', '页面 URL(录制模式)')
|
|
580
|
+
.option('-d, --desc <description>', '页面描述(自然语言模式)')
|
|
581
|
+
.option('-o, --output <dir>', '输出目录')
|
|
582
|
+
.option('-i, --interactive', '交互式模式')
|
|
583
|
+
.option('--dry-run', '预览模式,不写入文件')
|
|
584
|
+
.action(withErrorHandling(async (options) => {
|
|
585
|
+
await generateCommand(options);
|
|
586
|
+
}));
|
|
587
|
+
// ========== record 命令(generate 的别名)==========
|
|
588
|
+
program
|
|
589
|
+
.command('record')
|
|
590
|
+
.description('录制模式生成测试(generate --url 的别名)')
|
|
591
|
+
.requiredOption('-u, --url <url>', '页面 URL')
|
|
592
|
+
.option('-o, --output <dir>', '输出目录')
|
|
593
|
+
.option('--manual', '手动操作模式')
|
|
594
|
+
.option('--interactive', '交互式引导')
|
|
595
|
+
.action(withErrorHandling(async (options) => {
|
|
596
|
+
await generateCommand({ url: options.url, ...options });
|
|
597
|
+
}));
|
|
598
|
+
// ========== config 命令 ==========
|
|
599
|
+
program
|
|
600
|
+
.command('config')
|
|
601
|
+
.description('查看当前配置')
|
|
602
|
+
.action(withErrorHandling(async () => {
|
|
603
|
+
const { configLoader } = await Promise.resolve().then(function () { return configLoader$1; });
|
|
604
|
+
const config = await configLoader.load();
|
|
605
|
+
logger.title('当前配置');
|
|
606
|
+
console.log(JSON.stringify(config, null, 2));
|
|
607
|
+
}));
|
|
608
|
+
return program;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* 运行 CLI
|
|
612
|
+
*/
|
|
613
|
+
async function run(argv = process.argv) {
|
|
614
|
+
const program = createCLI();
|
|
615
|
+
await program.parseAsync(argv);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export { createCLI, run };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SmartTestConfig } from './config-schema';
|
|
2
|
+
/**
|
|
3
|
+
* 配置加载器
|
|
4
|
+
*/
|
|
5
|
+
declare class ConfigLoader {
|
|
6
|
+
private config;
|
|
7
|
+
private explorer;
|
|
8
|
+
/**
|
|
9
|
+
* 加载配置
|
|
10
|
+
*/
|
|
11
|
+
load(): Promise<SmartTestConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* 合并配置
|
|
14
|
+
*/
|
|
15
|
+
private mergeConfig;
|
|
16
|
+
/**
|
|
17
|
+
* 获取配置
|
|
18
|
+
*/
|
|
19
|
+
get(): SmartTestConfig;
|
|
20
|
+
/**
|
|
21
|
+
* 更新配置
|
|
22
|
+
*/
|
|
23
|
+
update(updates: Partial<SmartTestConfig>): void;
|
|
24
|
+
/**
|
|
25
|
+
* 重置配置
|
|
26
|
+
*/
|
|
27
|
+
reset(): void;
|
|
28
|
+
/**
|
|
29
|
+
* 验证配置
|
|
30
|
+
*/
|
|
31
|
+
validate(config: SmartTestConfig): boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare const configLoader: ConfigLoader;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config/config-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAKvD;;GAEG;AACH,cAAM,YAAY;IAChB,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,QAAQ,CAA4B;IAE5C;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC;IA8BtC;;OAEG;IACH,OAAO,CAAC,WAAW;IAsBnB;;OAEG;IACH,GAAG,IAAI,eAAe;IAOtB;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI;IAO/C;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO;CAa3C;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|