@be-link/smart-test 1.0.1-beta.20 → 1.0.1-beta.21

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.
@@ -3,8 +3,8 @@
3
3
 
4
4
  var fs$4 = require('node:fs/promises');
5
5
  var path = require('node:path');
6
- var prompts = require('@inquirer/prompts');
7
6
  var jiti = require('jiti');
7
+ var prompts = require('@inquirer/prompts');
8
8
  require('node:child_process');
9
9
  var openai = require('openai');
10
10
  var test = require('@playwright/test');
@@ -135,29 +135,33 @@ async function loadLocalEnv(cwd) {
135
135
 
136
136
  /**
137
137
  * 查找并加载配置文件
138
- * 支持 smart-test.config.json
138
+ * 支持 smart-test.config.ts/js(优先级递减)
139
139
  */
140
140
  async function loadConfig$1(cwd) {
141
- // 加载 JSON 配置文件
142
- const content = await fs$4.readFile(path.resolve(cwd, 'tests/smart-test.config.json'), 'utf-8');
143
- const config = JSON.parse(content);
144
- // 处理环境变量引用 ${ENV_VAR}
145
- replaceEnvVars(config);
146
- return config;
141
+ const testsDir = path.resolve(cwd, 'tests');
142
+ // 按优先级查找配置文件:.ts > .js
143
+ const configFiles = [path.resolve(testsDir, 'smart-test.config.ts'), path.resolve(testsDir, 'smart-test.config.js')];
144
+ for (const configPath of configFiles) {
145
+ if (await fileExists$1(configPath)) {
146
+ const jiti$1 = jiti.createJiti(cwd, {
147
+ interopDefault: true, // 自动处理 default export
148
+ });
149
+ const config = jiti$1(configPath);
150
+ return config;
151
+ }
152
+ }
153
+ throw new Error('未找到配置文件 smart-test.config.{ts,js}');
147
154
  }
148
155
  /**
149
- * 替换配置中的环境变量引用
156
+ * 检查文件是否存在
150
157
  */
151
- function replaceEnvVars(obj) {
152
- for (const key in obj) {
153
- if (typeof obj[key] === 'string') {
154
- obj[key] = obj[key].replace(/\$\{([^}]+)\}/g, (_, envVar) => {
155
- return process.env[envVar] || '';
156
- });
157
- }
158
- else if (typeof obj[key] === 'object' && obj[key] !== null) {
159
- replaceEnvVars(obj[key]);
160
- }
158
+ async function fileExists$1(filePath) {
159
+ try {
160
+ await fs$4.access(filePath);
161
+ return true;
162
+ }
163
+ catch {
164
+ return false;
161
165
  }
162
166
  }
163
167
 
@@ -231,6 +235,44 @@ async function extractMockFunctionName(mockFilePath) {
231
235
  }
232
236
  }
233
237
 
238
+ /**
239
+ * 校验开发服务器状态(用于 DOM 提取前的检查)
240
+ * @returns PlaywrightConfigInfo 如果校验成功,否则退出进程
241
+ */
242
+ async function validateServerForDomExtraction(cwd) {
243
+ console.log('\n🔍 检测开发服务器状态...\n');
244
+ // 读取 Playwright 配置
245
+ const playwrightConfig = await loadPlaywrightConfig(cwd);
246
+ if (!playwrightConfig || !playwrightConfig.baseURL) {
247
+ console.error('❌ 未找到 Playwright 配置或 baseURL!');
248
+ console.log('\n💡 请确保项目中存在 playwright.config.ts/js 文件,');
249
+ console.log(' 并配置了 use.baseURL 字段\n');
250
+ process.exit(1);
251
+ }
252
+ // 检测服务器是否运行
253
+ const serverRunning = await isServerRunning(playwrightConfig.baseURL);
254
+ if (!serverRunning) {
255
+ console.log(`⚠️ 开发服务器未运行!`);
256
+ console.log(` URL: ${playwrightConfig.baseURL}`);
257
+ console.log(`\n💡 请启动开发服务器:`);
258
+ if (playwrightConfig.webServer?.command) {
259
+ console.log(` $ ${playwrightConfig.webServer.command}`);
260
+ }
261
+ console.log(`\n⏳ 等待服务器启动中(最长等待 120 秒)...\n`);
262
+ // 等待服务器启动,超时时间 120 秒
263
+ const isReady = await waitForServer(playwrightConfig.baseURL, 120000);
264
+ if (!isReady) {
265
+ console.error(`\n❌ 服务器启动超时!`);
266
+ console.log(` 请确保服务器正常启动后重新运行: smart-test gen\n`);
267
+ process.exit(1);
268
+ }
269
+ console.log(`✅ 服务器已启动成功!\n`);
270
+ }
271
+ else {
272
+ console.log(`✅ 开发服务器正在运行 (${playwrightConfig.baseURL})\n`);
273
+ }
274
+ return playwrightConfig;
275
+ }
234
276
  /**
235
277
  * 检查服务器是否可访问
236
278
  */
@@ -243,12 +285,30 @@ async function isServerRunning(url) {
243
285
  return false;
244
286
  }
245
287
  }
288
+ /**
289
+ * 等待服务器启动
290
+ */
291
+ async function waitForServer(url, timeout) {
292
+ const startTime = Date.now();
293
+ const checkInterval = 500; // 每 500ms 检查一次
294
+ while (Date.now() - startTime < timeout) {
295
+ if (await isServerRunning(url)) {
296
+ return true;
297
+ }
298
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
299
+ }
300
+ return false;
301
+ }
246
302
 
247
303
  /**
248
304
  * 启动交互式问答
249
305
  */
250
306
  async function runInteractive(config, cwd) {
251
307
  console.log('\n👋 欢迎使用 Smart Test Generator!\n');
308
+ if (!config?.ai?.apiKey) {
309
+ console.error('缺少必需的环境变量: apiKey;请在配置文件 (smart-test.config.json) 配置');
310
+ process.exit(1);
311
+ }
252
312
  // 阶段 1: 选择模式
253
313
  const mode = await selectMode();
254
314
  // 阶段 2: 根据模式询问参数
@@ -350,36 +410,27 @@ async function askMockQuestions(config) {
350
410
  */
351
411
  async function askTestCaseQuestions(config, cwd) {
352
412
  console.log('\n📝 测试用例配置:\n');
353
- // 🎯 第一步:询问是否提取 DOM
413
+ // 访问页面URL
414
+ const pageUrl = await prompts.input({
415
+ message: '页面 URL 路径:',
416
+ validate: (value) => value.startsWith('/') || 'URL 应以 / 开头',
417
+ });
418
+ // 询问是否提取 DOM
354
419
  const extractDom = await prompts.confirm({
355
420
  message: '是否启动浏览器提取页面真实结构?(推荐,生成更精确的选择器)',
356
421
  default: true,
357
422
  });
358
- let mockFilePath;
359
423
  // 如果需要提取 DOM,立即检测服务器
360
424
  if (extractDom) {
361
- console.log('\n🔍 检测开发服务器状态...\n');
362
- // 读取 Playwright 配置
363
- const playwrightConfig = await loadPlaywrightConfig(cwd);
364
- if (!playwrightConfig || !playwrightConfig.baseURL) {
365
- console.error(' 未找到 Playwright 配置或 baseURL!');
366
- console.log('\n💡 请确保项目中存在 playwright.config.ts/js 文件,');
367
- console.log(' 并配置了 use.baseURL 字段\n');
368
- process.exit(1);
369
- }
370
- // 检测服务器是否运行
371
- const serverRunning = await isServerRunning(playwrightConfig.baseURL);
372
- if (!serverRunning) {
373
- console.error(`❌ 开发服务器未运行!`);
374
- console.log(` URL: ${playwrightConfig.baseURL}`);
375
- console.log(`\n💡 请先启动开发服务器:`);
376
- if (playwrightConfig.webServer?.command) {
377
- console.log(` $ ${playwrightConfig.webServer.command}`);
378
- }
379
- console.log(`\n 然后重新运行: smart-test gen\n`);
380
- process.exit(1);
381
- }
382
- console.log(`✅ 开发服务器正在运行 (${playwrightConfig.baseURL})\n`);
425
+ // 使用公共校验方法
426
+ await validateServerForDomExtraction(cwd);
427
+ }
428
+ const loadMockFile = await prompts.confirm({
429
+ message: '是否加载 Mock 文件?',
430
+ default: true,
431
+ });
432
+ let mockFilePath;
433
+ if (loadMockFile) {
383
434
  // 询问 mock 文件路径
384
435
  mockFilePath = await prompts.input({
385
436
  message: 'Mock 文件路径(相对于项目根目录,留空则不加载 Mock):',
@@ -390,11 +441,6 @@ async function askTestCaseQuestions(config, cwd) {
390
441
  mockFilePath = undefined;
391
442
  }
392
443
  }
393
- // 继续询问其他参数
394
- const pageUrl = await prompts.input({
395
- message: '页面 URL 路径:',
396
- validate: (value) => value.startsWith('/') || 'URL 应以 / 开头',
397
- });
398
444
  const scenario = await prompts.input({
399
445
  message: '测试场景描述:',
400
446
  validate: (value) => value.length > 0 || '场景描述不能为空',
@@ -1221,22 +1267,8 @@ async function executeGenerateTestCase(interactiveResult, apiKey, cwd) {
1221
1267
  console.log(` ✅ 配置加载成功`);
1222
1268
  console.log(` 文件: ${playwrightConfig.configPath}`);
1223
1269
  console.log(` 地址: ${playwrightConfig.baseURL}\n`);
1224
- // 2. 检测开发服务器状态
1225
- console.log(' 🌐 [2/6] 检测开发服务器状态...');
1226
- const serverRunning = await isServerRunning(playwrightConfig.baseURL);
1227
- if (!serverRunning) {
1228
- console.error(`\n ❌ 开发服务器未运行!`);
1229
- console.log(` URL: ${playwrightConfig.baseURL}`);
1230
- console.log(`\n 💡 请先启动开发服务器:`);
1231
- if (playwrightConfig.webServer?.command) {
1232
- console.log(` $ ${playwrightConfig.webServer.command}`);
1233
- }
1234
- console.log(`\n 然后重新运行: smart-test gen\n`);
1235
- process.exit(1);
1236
- }
1237
- console.log(' ✅ 开发服务器正在运行\n');
1238
- // 3. 处理 Mock 文件
1239
- console.log(' 📦 [3/6] 准备 Mock 数据...');
1270
+ // 2. 处理 Mock 文件
1271
+ console.log(' 📦 [2/5] 准备 Mock 数据...');
1240
1272
  let mockFile = null;
1241
1273
  let mockFunctionName = null;
1242
1274
  if (testcaseParams.mockFilePath) {
@@ -1264,8 +1296,8 @@ async function executeGenerateTestCase(interactiveResult, apiKey, cwd) {
1264
1296
  else {
1265
1297
  console.log(' ℹ️未提供 Mock 文件(页面可能需要真实数据)\n');
1266
1298
  }
1267
- // 4. 启动浏览器
1268
- console.log(' 🌐 [4/6] 启动浏览器...');
1299
+ // 3. 启动浏览器
1300
+ console.log(' 🌐 [3/5] 启动浏览器...');
1269
1301
  const browser = await test.chromium.launch({ headless: true });
1270
1302
  const context = await browser.newContext({
1271
1303
  baseURL: playwrightConfig.baseURL,
@@ -1275,9 +1307,9 @@ async function executeGenerateTestCase(interactiveResult, apiKey, cwd) {
1275
1307
  const page = await context.newPage();
1276
1308
  console.log(' ✅浏览器已启动(无头模式)\n');
1277
1309
  try {
1278
- // 5. 加载 Mock
1310
+ // 4. 加载 Mock
1279
1311
  if (mockFile && mockFunctionName) {
1280
- console.log(' 🔄 [5/6] 应用 Mock 数据...');
1312
+ console.log(' 🔄 [4/5] 应用 Mock 数据...');
1281
1313
  try {
1282
1314
  // 使用 jiti 加载 Mock 文件(支持 TypeScript)
1283
1315
  const jiti$1 = jiti.createJiti(cwd, {
@@ -1299,11 +1331,11 @@ async function executeGenerateTestCase(interactiveResult, apiKey, cwd) {
1299
1331
  }
1300
1332
  }
1301
1333
  else {
1302
- console.log(' ⏭️[5/6] 跳过 Mock 数据应用\n');
1334
+ console.log(' ⏭️[4/5] 跳过 Mock 数据应用\n');
1303
1335
  }
1304
- // 6. 访问页面并提取 DOM
1336
+ // 5. 访问页面并提取 DOM
1305
1337
  const fullUrl = `${playwrightConfig.baseURL}${testcaseParams.pageUrl}`;
1306
- console.log(' 🔗 [6/6] 访问页面并提取结构...');
1338
+ console.log(' 🔗 [5/5] 访问页面并提取结构...');
1307
1339
  console.log(` URL: ${fullUrl}`);
1308
1340
  await page.goto(testcaseParams.pageUrl);
1309
1341
  console.log(' ⏳ 等待页面加载完成...');
@@ -1389,11 +1421,7 @@ async function executeGenCommand(args) {
1389
1421
  // 启动交互式模式
1390
1422
  const interactiveResult = await runInteractive(fileConfig, cwd);
1391
1423
  // 从配置文件或环境变量获取 API Key
1392
- const apiKey = fileConfig.ai?.apiKey;
1393
- if (!apiKey) {
1394
- console.error('缺少必需的环境变量: apiKey;请在配置文件 (smart-test.config.json) 配置');
1395
- process.exit(1);
1396
- }
1424
+ const apiKey = fileConfig.ai.apiKey;
1397
1425
  // 配置 AI(使用配置文件中的设置或默认值)
1398
1426
  configure({
1399
1427
  apiKey,
@@ -1433,11 +1461,11 @@ function printGenHelp() {
1433
1461
  `);
1434
1462
  }
1435
1463
 
1436
- const SMART_TEST_CONFIG_FILENAME = 'smart-test.config.json';
1464
+ const SMART_TEST_CONFIG_FILENAME = 'smart-test.config.ts';
1437
1465
  const PLAYWRIGHT_CONFIG_FILENAME = 'playwright.config.ts';
1438
1466
  /**
1439
1467
  * 执行 init 命令:在项目中生成配置文件
1440
- * - smart-test.config.json 在 tests 目录
1468
+ * - smart-test.config.ts 在 tests 目录
1441
1469
  * - playwright.config.ts 在项目根目录
1442
1470
  */
1443
1471
  async function executeInitCommand() {
@@ -1446,7 +1474,7 @@ async function executeInitCommand() {
1446
1474
  // 配置文件路径
1447
1475
  const smartTestConfigPath = path.resolve(testsDir, SMART_TEST_CONFIG_FILENAME);
1448
1476
  const playwrightConfigPath = path.resolve(cwd, PLAYWRIGHT_CONFIG_FILENAME);
1449
- // 检查并处理 smart-test.config.json
1477
+ // 检查并处理 smart-test.config.ts
1450
1478
  const smartTestExists = await checkFileExists(smartTestConfigPath);
1451
1479
  if (smartTestExists) {
1452
1480
  const shouldOverwrite = await prompts.confirm({
@@ -1457,12 +1485,12 @@ async function executeInitCommand() {
1457
1485
  console.log('已取消创建 smart-test 配置文件');
1458
1486
  }
1459
1487
  else {
1460
- await createConfigFile('smart-test.config.template.json', smartTestConfigPath, testsDir);
1488
+ await createConfigFile('smart-test.config.template.ts', smartTestConfigPath, testsDir);
1461
1489
  console.log(`✅ 配置文件已创建: tests/${SMART_TEST_CONFIG_FILENAME}`);
1462
1490
  }
1463
1491
  }
1464
1492
  else {
1465
- await createConfigFile('smart-test.config.template.json', smartTestConfigPath, testsDir);
1493
+ await createConfigFile('smart-test.config.template.ts', smartTestConfigPath, testsDir);
1466
1494
  console.log(`✅ 配置文件已创建: tests/${SMART_TEST_CONFIG_FILENAME}`);
1467
1495
  }
1468
1496
  // 检查并处理 playwright.config.ts
@@ -1485,7 +1513,7 @@ async function executeInitCommand() {
1485
1513
  console.log(`✅ 配置文件已创建: ${PLAYWRIGHT_CONFIG_FILENAME}`);
1486
1514
  }
1487
1515
  console.log('\n📝 接下来你可以:');
1488
- console.log(' 1. 编辑 smart-test.config.json,设置 AI API 密钥和默认参数');
1516
+ console.log(' 1. 编辑 smart-test.config.ts,设置 AI API 密钥(推荐使用环境变量)');
1489
1517
  console.log(' 2. 根据需要调整 playwright.config.ts 中的测试配置');
1490
1518
  console.log(' 3. 运行 smart-test gen 命令时会自动读取配置\n');
1491
1519
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gen.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/gen.ts"],"names":[],"mappings":"AAOA,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CrE"}
1
+ {"version":3,"file":"gen.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/gen.ts"],"names":[],"mappings":"AAOA,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-testcase.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-testcase.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,iBAAiB,EAAE,iBAAiB,EACpC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAsLf"}
1
+ {"version":3,"file":"generate-testcase.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-testcase.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAK3D;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,iBAAiB,EAAE,iBAAiB,EACpC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAsKf"}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 执行 init 命令:在项目中生成配置文件
3
- * - smart-test.config.json 在 tests 目录
3
+ * - smart-test.config.ts 在 tests 目录
4
4
  * - playwright.config.ts 在项目根目录
5
5
  */
6
6
  export declare function executeInitCommand(): Promise<void>;
@@ -63,7 +63,7 @@ export interface SmartTestConfig {
63
63
  }
64
64
  /**
65
65
  * 查找并加载配置文件
66
- * 支持 smart-test.config.json
66
+ * 支持 smart-test.config.ts/js(优先级递减)
67
67
  */
68
68
  export declare function loadConfig(cwd: string): Promise<SmartTestConfig>;
69
69
  //# sourceMappingURL=config-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/cli/core/config-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,EAAE,CAAC,EAAE,QAAQ,CAAC;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAE1B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAOtE"}
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/cli/core/config-loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,EAAE,CAAC,EAAE,QAAQ,CAAC;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAE1B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAiBtE"}
@@ -0,0 +1,31 @@
1
+ // @ts-nocheck
2
+ import type { SmartTestConfig } from '@be-link/smart-test';
3
+
4
+ /**
5
+ * Smart Test 配置文件
6
+ *
7
+ * 💡 提示:
8
+ * 1. 可以使用 process.env 读取环境变量,避免硬编码敏感信息
9
+ * 2. 例如:apiKey: process.env.VITE_AI_API_KEY || ''
10
+ * 3. 此文件在用户项目中使用时,需要先安装 @be-link/smart-test 包
11
+ */
12
+ const config: SmartTestConfig = {
13
+ ai: {
14
+ // 从环境变量读取 API Key(推荐)
15
+ // 例如:在 .env 文件中设置 VITE_AI_API_KEY=your_api_key
16
+ apiKey: process.env.VITE_AI_API_KEY || '',
17
+ baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
18
+ visionModel: 'qwen3-vl-plus',
19
+ codeModel: 'qwen3-coder-plus',
20
+ },
21
+ mock: {
22
+ mockDir: 'tests/mocks',
23
+ typeFile: '',
24
+ },
25
+ testCase: {
26
+ caseDir: 'tests/e2e',
27
+ mockDir: 'tests/mocks',
28
+ },
29
+ };
30
+
31
+ export default config;
@@ -1 +1 @@
1
- {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/interactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI7E;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqBrG"}
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/interactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG7E;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0BrG"}
@@ -1,6 +1,7 @@
1
1
  import { OpenAI } from 'openai';
2
- import { readFile as readFile$1, mkdir, writeFile } from 'node:fs/promises';
2
+ import { access, readFile as readFile$1, mkdir, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import { createJiti } from 'jiti';
4
5
  import { createRequire, builtinModules } from 'module';
5
6
  import * as url from 'url';
6
7
  import url__default, { fileURLToPath, pathToFileURL, URL as URL$1 } from 'url';
@@ -16,29 +17,33 @@ import { format, inspect } from 'util';
16
17
 
17
18
  /**
18
19
  * 查找并加载配置文件
19
- * 支持 smart-test.config.json
20
+ * 支持 smart-test.config.ts/js(优先级递减)
20
21
  */
21
22
  async function loadConfig$1(cwd) {
22
- // 加载 JSON 配置文件
23
- const content = await readFile$1(path.resolve(cwd, 'tests/smart-test.config.json'), 'utf-8');
24
- const config = JSON.parse(content);
25
- // 处理环境变量引用 ${ENV_VAR}
26
- replaceEnvVars(config);
27
- return config;
23
+ const testsDir = path.resolve(cwd, 'tests');
24
+ // 按优先级查找配置文件:.ts > .js
25
+ const configFiles = [path.resolve(testsDir, 'smart-test.config.ts'), path.resolve(testsDir, 'smart-test.config.js')];
26
+ for (const configPath of configFiles) {
27
+ if (await fileExists$1(configPath)) {
28
+ const jiti = createJiti(cwd, {
29
+ interopDefault: true, // 自动处理 default export
30
+ });
31
+ const config = jiti(configPath);
32
+ return config;
33
+ }
34
+ }
35
+ throw new Error('未找到配置文件 smart-test.config.{ts,js}');
28
36
  }
29
37
  /**
30
- * 替换配置中的环境变量引用
38
+ * 检查文件是否存在
31
39
  */
32
- function replaceEnvVars(obj) {
33
- for (const key in obj) {
34
- if (typeof obj[key] === 'string') {
35
- obj[key] = obj[key].replace(/\$\{([^}]+)\}/g, (_, envVar) => {
36
- return process.env[envVar] || '';
37
- });
38
- }
39
- else if (typeof obj[key] === 'object' && obj[key] !== null) {
40
- replaceEnvVars(obj[key]);
41
- }
40
+ async function fileExists$1(filePath) {
41
+ try {
42
+ await access(filePath);
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
42
47
  }
43
48
  }
44
49
 
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var openai = require('openai');
4
4
  var promises = require('node:fs/promises');
5
5
  var path = require('node:path');
6
+ var jiti = require('jiti');
6
7
  var module$1 = require('module');
7
8
  var url = require('url');
8
9
  var path10 = require('path');
@@ -37,29 +38,33 @@ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$4);
37
38
 
38
39
  /**
39
40
  * 查找并加载配置文件
40
- * 支持 smart-test.config.json
41
+ * 支持 smart-test.config.ts/js(优先级递减)
41
42
  */
42
43
  async function loadConfig$1(cwd) {
43
- // 加载 JSON 配置文件
44
- const content = await promises.readFile(path.resolve(cwd, 'tests/smart-test.config.json'), 'utf-8');
45
- const config = JSON.parse(content);
46
- // 处理环境变量引用 ${ENV_VAR}
47
- replaceEnvVars(config);
48
- return config;
44
+ const testsDir = path.resolve(cwd, 'tests');
45
+ // 按优先级查找配置文件:.ts > .js
46
+ const configFiles = [path.resolve(testsDir, 'smart-test.config.ts'), path.resolve(testsDir, 'smart-test.config.js')];
47
+ for (const configPath of configFiles) {
48
+ if (await fileExists$1(configPath)) {
49
+ const jiti$1 = jiti.createJiti(cwd, {
50
+ interopDefault: true, // 自动处理 default export
51
+ });
52
+ const config = jiti$1(configPath);
53
+ return config;
54
+ }
55
+ }
56
+ throw new Error('未找到配置文件 smart-test.config.{ts,js}');
49
57
  }
50
58
  /**
51
- * 替换配置中的环境变量引用
59
+ * 检查文件是否存在
52
60
  */
53
- function replaceEnvVars(obj) {
54
- for (const key in obj) {
55
- if (typeof obj[key] === 'string') {
56
- obj[key] = obj[key].replace(/\$\{([^}]+)\}/g, (_, envVar) => {
57
- return process.env[envVar] || '';
58
- });
59
- }
60
- else if (typeof obj[key] === 'object' && obj[key] !== null) {
61
- replaceEnvVars(obj[key]);
62
- }
61
+ async function fileExists$1(filePath) {
62
+ try {
63
+ await promises.access(filePath);
64
+ return true;
65
+ }
66
+ catch {
67
+ return false;
63
68
  }
64
69
  }
65
70
 
@@ -1,5 +1,10 @@
1
1
  import type { ChildProcess } from 'node:child_process';
2
- import type { WebServerConfig } from './playwright-config-loader';
2
+ import type { WebServerConfig, PlaywrightConfigInfo } from './playwright-config-loader';
3
+ /**
4
+ * 校验开发服务器状态(用于 DOM 提取前的检查)
5
+ * @returns PlaywrightConfigInfo 如果校验成功,否则退出进程
6
+ */
7
+ export declare function validateServerForDomExtraction(cwd: string): Promise<PlaywrightConfigInfo>;
3
8
  /**
4
9
  * 检查服务器是否可访问
5
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"web-server-manager.d.ts","sourceRoot":"","sources":["../../src/utils/web-server-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYlF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAE,CAAC,CAkDvE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAMtE"}
1
+ {"version":3,"file":"web-server-manager.d.ts","sourceRoot":"","sources":["../../src/utils/web-server-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGxF;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAuC/F;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYlF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAE,CAAC,CAkDvE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAMtE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@be-link/smart-test",
3
- "version": "1.0.1-beta.20",
3
+ "version": "1.0.1-beta.21",
4
4
  "description": "AI-powered visual testing for Playwright - 基于 AI 视觉模型的 Playwright 测试工具",
5
5
  "homepage": "https://github.com/snowmountain-top/be-link",
6
6
  "author": "shian",
@@ -55,7 +55,7 @@
55
55
  "clean": "rimraf ./dist && rimraf .rollup.cache",
56
56
  "build:js": "rollup --config rollup.config.ts --configPlugin typescript",
57
57
  "build:types": "tsc --project tsconfig.json",
58
- "build:copy-templates": "mkdir -p dist/cli/templates && cp src/cli/templates/*.json src/cli/templates/*.ts dist/cli/templates/",
58
+ "build:copy-templates": "mkdir -p dist/cli/templates && cp src/cli/templates/*.ts dist/cli/templates/",
59
59
  "build": "pnpm clean && pnpm build:js && pnpm build:types && pnpm build:copy-templates",
60
60
  "lint": "eslint src --ext .ts --fix"
61
61
  }
@@ -1,7 +0,0 @@
1
- /**
2
- * Playwright 测试配置
3
- * @see https://playwright.dev/docs/test-configuration
4
- */
5
- declare const _default: import("@playwright/test").PlaywrightTestConfig<{}, {}>;
6
- export default _default;
7
- //# sourceMappingURL=playwright.config.template.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"playwright.config.template.d.ts","sourceRoot":"","sources":["../../../src/cli/templates/playwright.config.template.ts"],"names":[],"mappings":"AAEA;;;GAGG;;AACH,wBAwCG"}
@@ -1,17 +0,0 @@
1
- {
2
- "ai": {
3
- "apiKey": "",
4
- "baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1",
5
- "visionModel": "qwen3-vl-plus",
6
- "codeModel": "qwen3-coder-plus"
7
- },
8
- "mock": {
9
- "mockDir": "tests/mocks",
10
- "typeFile": "",
11
- "types": []
12
- },
13
- "testCase": {
14
- "caseDir": "tests/e2e",
15
- "mockDir": "tests/mocks"
16
- }
17
- }