csspilot 0.7.1 → 0.7.2

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/dist/index.js CHANGED
@@ -30210,9 +30210,10 @@ var configSchema = external_exports.strictObject({
30210
30210
  function interpolateEnvRefs(value) {
30211
30211
  return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (match, name) => process.env[name] ?? match);
30212
30212
  }
30213
+ var ENV_TESTING_FILE = ".env.testing";
30213
30214
  function loadProjectEnvFile(root = process.cwd()) {
30214
30215
  try {
30215
- process.loadEnvFile(join3(root, ".env"));
30216
+ process.loadEnvFile(join3(root, ENV_TESTING_FILE));
30216
30217
  } catch {
30217
30218
  }
30218
30219
  }
@@ -30278,8 +30279,8 @@ async function loadTestpilotConfig(cwd = process.cwd()) {
30278
30279
  }
30279
30280
 
30280
30281
  // ../../packages/core/src/wechat-devtools.ts
30281
- import { execFile } from "node:child_process";
30282
- import { existsSync, readdirSync } from "node:fs";
30282
+ import { exec, execFile } from "node:child_process";
30283
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
30283
30284
  import { join as join4 } from "node:path";
30284
30285
  var CLI_NAMES = ["cli.bat", "cli"];
30285
30286
  var REGISTRY_UNINSTALL_KEYS = [
@@ -30366,6 +30367,42 @@ function execToString(command, args) {
30366
30367
  });
30367
30368
  });
30368
30369
  }
30370
+ async function isWechatDevToolsLoggedIn(cliPath) {
30371
+ const output2 = await execCliToString(cliPath, ["islogin"]).catch(() => void 0);
30372
+ if (output2 === void 0) return void 0;
30373
+ if (/is\s*login\s*[:=]?\s*(true|yes|1)\b/i.test(output2)) return true;
30374
+ if (/is\s*login\s*[:=]?\s*(false|no|0)\b/i.test(output2)) return false;
30375
+ if (/\btrue\b/i.test(output2)) return true;
30376
+ if (/\bfalse\b/i.test(output2)) return false;
30377
+ return void 0;
30378
+ }
30379
+ function execCliToString(cliPath, args) {
30380
+ return new Promise((resolvePromise, rejectPromise) => {
30381
+ const quote = (value) => /\s/.test(value) ? `"${value}"` : value;
30382
+ exec(
30383
+ `"${cliPath}" ${args.map(quote).join(" ")}`,
30384
+ { windowsHide: true, timeout: 3e4, encoding: "buffer" },
30385
+ (err, stdout, stderr) => {
30386
+ const text = `${stdout.toString("utf8")}${stderr.toString("utf8")}`.trim();
30387
+ if (err && !text) {
30388
+ rejectPromise(err);
30389
+ return;
30390
+ }
30391
+ resolvePromise(text);
30392
+ }
30393
+ );
30394
+ });
30395
+ }
30396
+ function readMiniappProjectAppid(projectPath) {
30397
+ const configPath = join4(projectPath, "project.config.json");
30398
+ if (!existsSync(configPath)) return void 0;
30399
+ try {
30400
+ const parsed = JSON.parse(readFileSync(configPath, "utf8"));
30401
+ return { appid: typeof parsed.appid === "string" ? parsed.appid : void 0, configPath };
30402
+ } catch {
30403
+ return void 0;
30404
+ }
30405
+ }
30369
30406
 
30370
30407
  // ../../packages/sdk/src/client.ts
30371
30408
  import { isAbsolute as isAbsolute4, join as join19 } from "node:path";
@@ -30391,8 +30428,16 @@ var ACTIONS = [
30391
30428
  "callMethod",
30392
30429
  "mockWxApi",
30393
30430
  "trigger",
30394
- "setData"
30431
+ "setData",
30432
+ "longpress",
30433
+ "callWxApi",
30434
+ "touch",
30435
+ "scroll",
30436
+ "swipeTo",
30437
+ "slideTo",
30438
+ "moveTo"
30395
30439
  ];
30440
+ var TOUCH_PHASES = ["touchstart", "touchmove", "touchend"];
30396
30441
  var locatorSchema = external_exports.strictObject({
30397
30442
  text: external_exports.string().min(1).optional(),
30398
30443
  css: external_exports.string().min(1).optional()
@@ -30408,7 +30453,7 @@ var stepSchema = external_exports.strictObject({
30408
30453
  action: external_exports.enum(ACTIONS),
30409
30454
  locator: locatorSchema.optional(),
30410
30455
  url: external_exports.string().min(1).optional(),
30411
- value: external_exports.string().optional(),
30456
+ value: external_exports.union([external_exports.string(), external_exports.number()]).optional(),
30412
30457
  expected: external_exports.string().optional(),
30413
30458
  /** api target:request 的 HTTP 方法(默认 GET) */
30414
30459
  method: external_exports.enum(HTTP_METHODS).optional(),
@@ -30434,10 +30479,29 @@ var stepSchema = external_exports.strictObject({
30434
30479
  eventType: external_exports.string().min(1).optional(),
30435
30480
  /** trigger:作为事件 e.detail 注入(值支持 ${var} 模板) */
30436
30481
  detail: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
30437
- /** setData:写入页面 data 的补丁(值支持 ${var} 模板) */
30482
+ /** setData:写入页面 data 的补丁(值支持 ${var} 模板);提供 locator 时为组件级 setData */
30438
30483
  patch: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
30439
- /** assert:页面 data 路径断言(与 locator 互斥,仅 miniapp) */
30440
- dataPath: external_exports.string().min(1).optional()
30484
+ /** assert:页面 data 路径断言(与 locator/attribute/property/count 互斥,仅 miniapp) */
30485
+ dataPath: external_exports.string().min(1).optional(),
30486
+ /** assert:元素 attribute 名(与 expected 组合做属性断言;web/miniapp) */
30487
+ attribute: external_exports.string().min(1).optional(),
30488
+ /** assert:元素 property 名(自定义组件实例属性;仅 miniapp) */
30489
+ property: external_exports.string().min(1).optional(),
30490
+ /** assert:匹配元素的精确数量(0 即"元素不存在"断言;与 expected/attribute/property/dataPath 互斥) */
30491
+ count: external_exports.number().int().nonnegative().optional(),
30492
+ /** touch:触摸阶段 */
30493
+ phase: external_exports.enum(TOUCH_PHASES).optional(),
30494
+ /** touch:触摸点数组(元素如 [{identifier: 0, x: 100, y: 200}],值支持 ${var} 模板) */
30495
+ touches: external_exports.array(external_exports.record(external_exports.string(), external_exports.unknown())).min(1).optional(),
30496
+ /** touch:changedTouches 触摸点数组;缺省复用 touches */
30497
+ changedTouches: external_exports.array(external_exports.record(external_exports.string(), external_exports.unknown())).optional(),
30498
+ /** scroll:滚动位置(scroll-view 内滚动提供 locator;缺省滚动页面) */
30499
+ scrollTop: external_exports.number().optional(),
30500
+ /** swipeTo:swiper 目标页索引 */
30501
+ index: external_exports.number().int().nonnegative().optional(),
30502
+ /** moveTo:movable-view 目标坐标 */
30503
+ x: external_exports.number().optional(),
30504
+ y: external_exports.number().optional()
30441
30505
  });
30442
30506
  var datasetReferenceSchema = external_exports.strictObject({
30443
30507
  file: external_exports.string().min(1),
@@ -30514,29 +30578,57 @@ var ADAPTER_SUPPORT = {
30514
30578
  callMethod: ["miniapp"],
30515
30579
  mockWxApi: ["miniapp"],
30516
30580
  trigger: ["miniapp"],
30517
- setData: ["miniapp"]
30581
+ setData: ["miniapp"],
30582
+ longpress: ["web", "miniapp"],
30583
+ callWxApi: ["miniapp"],
30584
+ touch: ["miniapp"],
30585
+ scroll: ["miniapp"],
30586
+ swipeTo: ["miniapp"],
30587
+ slideTo: ["miniapp"],
30588
+ moveTo: ["miniapp"]
30518
30589
  };
30519
- var LOCATOR_REQUIRED = /* @__PURE__ */ new Set(["click", "input", "select", "assert", "extract"]);
30520
- var VALUE_REQUIRED = /* @__PURE__ */ new Set(["input", "select"]);
30590
+ var LOCATOR_REQUIRED = /* @__PURE__ */ new Set([
30591
+ "click",
30592
+ "input",
30593
+ "select",
30594
+ "assert",
30595
+ "extract",
30596
+ "longpress",
30597
+ "touch",
30598
+ "swipeTo",
30599
+ "slideTo",
30600
+ "moveTo"
30601
+ ]);
30602
+ var VALUE_REQUIRED = /* @__PURE__ */ new Set(["input", "select", "slideTo"]);
30521
30603
  var REQUEST_EXPECTATION_ACTIONS = /* @__PURE__ */ new Set(["navigate", "click", "input", "select"]);
30522
30604
  var FIELD_ALLOWED = {
30523
30605
  url: ["navigate", "request"],
30524
- value: ["input", "select", "extract"],
30606
+ value: ["input", "select", "extract", "slideTo"],
30525
30607
  expected: ["assert", "request"],
30526
- variable: ["extract"],
30608
+ variable: ["extract", "callWxApi"],
30527
30609
  timeout: ["wait"],
30528
30610
  method: ["request"],
30529
30611
  headers: ["request"],
30530
30612
  body: ["request"],
30531
30613
  methodName: ["callMethod"],
30532
- args: ["callMethod"],
30533
- wxApi: ["mockWxApi"],
30614
+ args: ["callMethod", "callWxApi"],
30615
+ wxApi: ["mockWxApi", "callWxApi"],
30534
30616
  result: ["mockWxApi"],
30535
30617
  capture: ["mockWxApi"],
30536
30618
  eventType: ["trigger"],
30537
30619
  detail: ["trigger"],
30538
30620
  patch: ["setData"],
30539
- dataPath: ["assert"]
30621
+ dataPath: ["assert"],
30622
+ attribute: ["assert"],
30623
+ property: ["assert"],
30624
+ count: ["assert"],
30625
+ phase: ["touch"],
30626
+ touches: ["touch"],
30627
+ changedTouches: ["touch"],
30628
+ scrollTop: ["scroll"],
30629
+ index: ["swipeTo"],
30630
+ x: ["moveTo"],
30631
+ y: ["moveTo"]
30540
30632
  };
30541
30633
  var VARIABLE_REF = /\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g;
30542
30634
  function validateCase(data) {
@@ -30600,6 +30692,55 @@ function checkStep(step, at, declared, issues) {
30600
30692
  });
30601
30693
  }
30602
30694
  }
30695
+ if (step.action === "callWxApi" && !step.wxApi) {
30696
+ issues.push({ path: `${at}.wxApi`, code: "semantic", message: "callWxApi \u5FC5\u987B\u63D0\u4F9B wxApi(\u8981\u4E3B\u52A8\u8C03\u7528\u7684 wx.* API \u540D,\u5982 setStorageSync)" });
30697
+ }
30698
+ if (step.action === "touch") {
30699
+ if (!step.phase) {
30700
+ issues.push({ path: `${at}.phase`, code: "semantic", message: "touch \u5FC5\u987B\u63D0\u4F9B phase(touchstart / touchmove / touchend)" });
30701
+ }
30702
+ if (!step.touches || step.touches.length === 0) {
30703
+ issues.push({ path: `${at}.touches`, code: "semantic", message: "touch \u5FC5\u987B\u63D0\u4F9B touches(\u89E6\u6478\u70B9\u6570\u7EC4,\u5982 [{identifier: 0, x: 0, y: 0}])" });
30704
+ }
30705
+ }
30706
+ if (step.action === "scroll" && step.scrollTop === void 0) {
30707
+ issues.push({ path: `${at}.scrollTop`, code: "semantic", message: "scroll \u5FC5\u987B\u63D0\u4F9B scrollTop(\u6EDA\u52A8\u4F4D\u7F6E\u50CF\u7D20)" });
30708
+ }
30709
+ if (step.action === "swipeTo" && step.index === void 0) {
30710
+ issues.push({ path: `${at}.index`, code: "semantic", message: "swipeTo \u5FC5\u987B\u63D0\u4F9B index(swiper \u76EE\u6807\u9875\u7D22\u5F15)" });
30711
+ }
30712
+ if (step.action === "moveTo" && (step.x === void 0 || step.y === void 0)) {
30713
+ issues.push({ path: `${at}.x`, code: "semantic", message: "moveTo \u5FC5\u987B\u63D0\u4F9B x \u4E0E y(movable-view \u76EE\u6807\u5750\u6807)" });
30714
+ }
30715
+ if (step.action === "assert") {
30716
+ const modes = [
30717
+ step.dataPath !== void 0,
30718
+ step.attribute !== void 0,
30719
+ step.property !== void 0,
30720
+ step.count !== void 0
30721
+ ].filter(Boolean).length;
30722
+ if (modes > 1) {
30723
+ issues.push({ path: `${at}.assert`, code: "semantic", message: "assert \u7684 dataPath / attribute / property / count \u53EA\u80FD\u63D0\u4F9B\u5176\u4E00(\u7F3A\u7701\u4E3A\u5143\u7D20\u6587\u672C\u65AD\u8A00)" });
30724
+ }
30725
+ if (step.property !== void 0 && step.target !== "miniapp") {
30726
+ issues.push({ path: `${at}.property`, code: "semantic", message: "property \u65AD\u8A00\u4EC5 miniapp \u652F\u6301(\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u5B9E\u4F8B\u5C5E\u6027)" });
30727
+ }
30728
+ if ((step.attribute !== void 0 || step.property !== void 0) && !step.locator) {
30729
+ issues.push({ path: `${at}.locator`, code: "semantic", message: "attribute / property \u65AD\u8A00\u5FC5\u987B\u63D0\u4F9B locator" });
30730
+ }
30731
+ if (step.count !== void 0 && !step.locator) {
30732
+ issues.push({ path: `${at}.locator`, code: "semantic", message: "count \u65AD\u8A00\u5FC5\u987B\u63D0\u4F9B locator(\u7EDF\u8BA1\u5339\u914D\u5143\u7D20)" });
30733
+ }
30734
+ if (step.count !== void 0 && step.expected !== void 0) {
30735
+ issues.push({ path: `${at}.assert`, code: "semantic", message: "count \u65AD\u8A00\u4EE5 count \u4E3A\u671F\u671B\u503C,\u4E0D\u9700\u8981 expected" });
30736
+ }
30737
+ if (step.target === "api" && (step.attribute !== void 0 || step.property !== void 0 || step.count !== void 0)) {
30738
+ issues.push({ path: `${at}.assert`, code: "semantic", message: "api \u7AEF assert \u4EC5\u652F\u6301 expected(\u54CD\u5E94\u4F53\u5305\u542B)" });
30739
+ }
30740
+ if (step.dataPath !== void 0 && step.target !== "miniapp") {
30741
+ issues.push({ path: `${at}.dataPath`, code: "semantic", message: "dataPath \u65AD\u8A00\u4EC5 miniapp \u652F\u6301(web \u7AEF\u7528 locator)" });
30742
+ }
30743
+ }
30603
30744
  if (step.action === "mockWxApi") {
30604
30745
  if (!step.wxApi) {
30605
30746
  issues.push({ path: `${at}.wxApi`, code: "semantic", message: "mockWxApi \u5FC5\u987B\u63D0\u4F9B wxApi(\u8981 mock \u7684 wx.* API \u540D)" });
@@ -30614,22 +30755,19 @@ function checkStep(step, at, declared, issues) {
30614
30755
  if (step.action === "setData" && (!step.patch || Object.keys(step.patch).length === 0)) {
30615
30756
  issues.push({ path: `${at}.patch`, code: "semantic", message: "setData \u5FC5\u987B\u63D0\u4F9B patch(\u5199\u5165\u9875\u9762 data \u7684\u8865\u4E01)" });
30616
30757
  }
30617
- if (step.dataPath !== void 0 && step.target !== "miniapp") {
30618
- issues.push({ path: `${at}.dataPath`, code: "semantic", message: "dataPath \u65AD\u8A00\u4EC5 miniapp \u652F\u6301(web \u7AEF\u7528 locator)" });
30619
- }
30620
30758
  if (step.action === "navigate" && !step.url) {
30621
30759
  issues.push({ path: `${at}.url`, code: "semantic", message: "navigate \u5FC5\u987B\u63D0\u4F9B url" });
30622
30760
  }
30623
30761
  if (step.action === "request" && !step.url) {
30624
30762
  issues.push({ path: `${at}.url`, code: "semantic", message: "request \u5FC5\u987B\u63D0\u4F9B url(\u76F8\u5BF9\u8DEF\u5F84\u6309 api.baseUrl \u89E3\u6790)" });
30625
30763
  }
30626
- if (VALUE_REQUIRED.has(step.action) && !step.value) {
30764
+ if (VALUE_REQUIRED.has(step.action) && (step.value === void 0 || step.value === "")) {
30627
30765
  issues.push({ path: `${at}.value`, code: "semantic", message: `action "${step.action}" \u5FC5\u987B\u63D0\u4F9B value` });
30628
30766
  }
30629
30767
  if (step.action === "extract" && step.target === "api" && !step.value) {
30630
30768
  issues.push({ path: `${at}.value`, code: "semantic", message: "api \u7AEF extract \u5FC5\u987B\u63D0\u4F9B value(\u70B9\u53F7 JSON path,\u5982 data.orderId)" });
30631
30769
  }
30632
- if (step.action === "assert" && !step.expected) {
30770
+ if (step.action === "assert" && step.count === void 0 && !step.expected) {
30633
30771
  issues.push({ path: `${at}.expected`, code: "semantic", message: "assert \u5FC5\u987B\u63D0\u4F9B expected" });
30634
30772
  }
30635
30773
  if (step.action === "extract") {
@@ -30669,6 +30807,7 @@ function checkStep(step, at, declared, issues) {
30669
30807
  function checkLocator(step, at, issues) {
30670
30808
  const locator = step.locator;
30671
30809
  if (!locator) {
30810
+ if (step.action === "assert" && step.dataPath !== void 0) return;
30672
30811
  if (LOCATOR_REQUIRED.has(step.action)) {
30673
30812
  issues.push({
30674
30813
  path: `${at}.locator`,
@@ -30693,7 +30832,7 @@ function checkVariableRefs(step, at, declared, issues) {
30693
30832
  if (match[1]) refs.push([field, match[1]]);
30694
30833
  }
30695
30834
  };
30696
- collect("value", step.value);
30835
+ collect("value", step.value === void 0 ? void 0 : String(step.value));
30697
30836
  collect("expected", step.expected);
30698
30837
  collect("url", step.url);
30699
30838
  collect("methodName", step.methodName);
@@ -31528,13 +31667,13 @@ function buildSummaryData(runs) {
31528
31667
  });
31529
31668
  }
31530
31669
  const byCase = /* @__PURE__ */ new Map();
31531
- for (const exec2 of executions) {
31532
- let agg = byCase.get(exec2.identity);
31670
+ for (const exec3 of executions) {
31671
+ let agg = byCase.get(exec3.identity);
31533
31672
  if (!agg) {
31534
31673
  agg = {
31535
- caseId: exec2.caseId,
31536
- rowId: exec2.rowId,
31537
- caseName: exec2.caseName,
31674
+ caseId: exec3.caseId,
31675
+ rowId: exec3.rowId,
31676
+ caseName: exec3.caseName,
31538
31677
  platforms: [],
31539
31678
  runs: 0,
31540
31679
  passed: 0,
@@ -31543,18 +31682,18 @@ function buildSummaryData(runs) {
31543
31682
  lastStatus: "unknown",
31544
31683
  flaky: false
31545
31684
  };
31546
- byCase.set(exec2.identity, agg);
31685
+ byCase.set(exec3.identity, agg);
31547
31686
  }
31548
31687
  agg.runs++;
31549
- if (exec2.status === "passed") agg.passed++;
31550
- else if (exec2.status === "failed") agg.failed++;
31551
- for (const platform of exec2.platforms) {
31688
+ if (exec3.status === "passed") agg.passed++;
31689
+ else if (exec3.status === "failed") agg.failed++;
31690
+ for (const platform of exec3.platforms) {
31552
31691
  if (!agg.platforms.includes(platform)) agg.platforms.push(platform);
31553
31692
  }
31554
31693
  }
31555
31694
  for (const agg of byCase.values()) {
31556
31695
  const identity = agg.rowId && agg.rowId !== "default" ? `${agg.caseId}#${agg.rowId}` : agg.caseId;
31557
- const last = [...executions].reverse().find((exec2) => exec2.identity === identity);
31696
+ const last = [...executions].reverse().find((exec3) => exec3.identity === identity);
31558
31697
  agg.lastStatus = last?.status ?? "unknown";
31559
31698
  agg.lastCategory = last?.category;
31560
31699
  agg.passRate = agg.runs > 0 ? agg.passed / agg.runs : 0;
@@ -31565,13 +31704,13 @@ function buildSummaryData(runs) {
31565
31704
  const categories = FAILURE_CATEGORIES.map((item) => ({
31566
31705
  id: item.id,
31567
31706
  label: item.label,
31568
- count: executions.filter((exec2) => exec2.status === "failed" && exec2.category === item.id).length
31707
+ count: executions.filter((exec3) => exec3.status === "failed" && exec3.category === item.id).length
31569
31708
  })).filter((item) => item.count > 0);
31570
31709
  const durations = runs.map((item) => item.summary.durationMs).filter((value) => typeof value === "number");
31571
31710
  return {
31572
31711
  buckets: {
31573
- blocked: executions.filter((exec2) => exec2.status === "blocked").length,
31574
- inconclusive: executions.filter((exec2) => exec2.status === "inconclusive").length
31712
+ blocked: executions.filter((exec3) => exec3.status === "blocked").length,
31713
+ inconclusive: executions.filter((exec3) => exec3.status === "inconclusive").length
31575
31714
  },
31576
31715
  window: {
31577
31716
  runs: runs.length,
@@ -31581,7 +31720,7 @@ function buildSummaryData(runs) {
31581
31720
  kpis: {
31582
31721
  runs: runs.length,
31583
31722
  executions: executions.length,
31584
- overallRate: executions.length > 0 ? executions.filter((exec2) => exec2.status === "passed").length / executions.length : 0,
31723
+ overallRate: executions.length > 0 ? executions.filter((exec3) => exec3.status === "passed").length / executions.length : 0,
31585
31724
  avgDurationMs: durations.length > 0 ? durations.reduce((sum, value) => sum + value, 0) / durations.length : 0,
31586
31725
  flakyCount: flaky.length
31587
31726
  },
@@ -31785,7 +31924,7 @@ import { readFile as readFile7, readdir as readdir4 } from "node:fs/promises";
31785
31924
  import { isAbsolute as isAbsolute3, join as join17 } from "node:path";
31786
31925
 
31787
31926
  // ../../packages/execution-engine/src/artifacts/index.ts
31788
- import { mkdir as mkdir9 } from "node:fs/promises";
31927
+ import { mkdir as mkdir9, writeFile as writeFile9 } from "node:fs/promises";
31789
31928
  import { join as join13 } from "node:path";
31790
31929
 
31791
31930
  // ../../packages/evidence/src/log.ts
@@ -31876,6 +32015,12 @@ var ArtifactManager = class {
31876
32015
  await saveTrace(this.tracesDir, fileName, data);
31877
32016
  return `traces/${fileName}`;
31878
32017
  }
32018
+ /** 保存失败用例的运行时诊断(console 尾部/异常/页面栈/systemInfo),返回相对 run 目录的路径 */
32019
+ async saveDiagnostics(fileName, data) {
32020
+ await mkdir9(this.logsDir, { recursive: true });
32021
+ await writeFile9(join13(this.logsDir, fileName), JSON.stringify(data, null, 2), "utf8");
32022
+ return `logs/${fileName}`;
32023
+ }
31879
32024
  };
31880
32025
 
31881
32026
  // ../../packages/execution-engine/src/context/index.ts
@@ -32015,6 +32160,14 @@ function redactHeaders(headers) {
32015
32160
  return redacted;
32016
32161
  }
32017
32162
 
32163
+ // ../../packages/adapter-core/src/locator.ts
32164
+ function describeLocator(locator) {
32165
+ if (!locator) return "-";
32166
+ if (locator.text !== void 0) return `text="${locator.text}"`;
32167
+ if (locator.css !== void 0) return `css="${locator.css}"`;
32168
+ return "-";
32169
+ }
32170
+
32018
32171
  // ../../packages/adapter-core/src/session.ts
32019
32172
  var AdapterSession = class {
32020
32173
  constructor(target, factory) {
@@ -32071,6 +32224,8 @@ var AdapterResolver = class {
32071
32224
 
32072
32225
  // ../../packages/execution-engine/src/engine/step-executor.ts
32073
32226
  var SNIPPET_LIMIT = 400;
32227
+ var ASSERT_POLL_MS = 1e4;
32228
+ var ABSENT_SETTLE_MS = 2e3;
32074
32229
  async function executeStep(step, index, adapter, context, artifacts, options) {
32075
32230
  const startedAt = Date.now();
32076
32231
  const result = {
@@ -32104,15 +32259,26 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
32104
32259
  await adapter.click(locator);
32105
32260
  break;
32106
32261
  case "input":
32107
- await adapter.input(locator, context.resolve(step.value ?? ""));
32262
+ await adapter.input(locator, resolveValueText(step.value, context));
32108
32263
  break;
32109
32264
  case "select":
32110
- await adapter.select(locator, context.resolve(step.value ?? ""));
32265
+ await adapter.select(locator, resolveValueText(step.value, context));
32111
32266
  break;
32112
32267
  case "wait":
32113
32268
  await adapter.wait(locator, step.timeout);
32114
32269
  break;
32115
32270
  case "assert": {
32271
+ if (step.count !== void 0) {
32272
+ if (!adapter.countElements) {
32273
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 count \u65AD\u8A00`);
32274
+ }
32275
+ await assertElementCount(adapter, locator, step.count);
32276
+ break;
32277
+ }
32278
+ if (step.attribute !== void 0 || step.property !== void 0) {
32279
+ await assertElementAttribute(adapter, locator, step, context);
32280
+ break;
32281
+ }
32116
32282
  if (step.dataPath !== void 0) {
32117
32283
  if (!adapter.assertData) {
32118
32284
  throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 dataPath \u65AD\u8A00`);
@@ -32129,7 +32295,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
32129
32295
  break;
32130
32296
  }
32131
32297
  case "extract": {
32132
- const value = step.target === "api" ? await extractApiResponse(adapter, context.resolve(step.value ?? "")) : await adapter.extract(locator);
32298
+ const value = step.target === "api" ? await extractApiResponse(adapter, resolveValueText(step.value, context)) : await adapter.extract(locator);
32133
32299
  if (step.variable) {
32134
32300
  context.set(step.variable, value);
32135
32301
  result.extracted = { [step.variable]: value };
@@ -32200,7 +32366,69 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
32200
32366
  if (typeof adapter.setData !== "function") {
32201
32367
  throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 setData`);
32202
32368
  }
32203
- await adapter.setData(resolveDeep(step.patch ?? {}, context));
32369
+ await adapter.setData(resolveDeep(step.patch ?? {}, context), locator);
32370
+ break;
32371
+ }
32372
+ case "longpress": {
32373
+ if (typeof adapter.longpress !== "function") {
32374
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 longpress`);
32375
+ }
32376
+ await adapter.longpress(locator);
32377
+ break;
32378
+ }
32379
+ case "callWxApi": {
32380
+ if (typeof adapter.callWxApi !== "function") {
32381
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 callWxApi`);
32382
+ }
32383
+ const wxApi = context.resolve(step.wxApi ?? "");
32384
+ if (!wxApi) throw new TestPilotError("MISSING_WX_API", "callWxApi \u5FC5\u987B\u63D0\u4F9B wxApi");
32385
+ const callArgs = (step.args ?? []).map((arg) => resolveDeep(arg, context));
32386
+ const returned = await adapter.callWxApi(wxApi, callArgs);
32387
+ if (step.variable) {
32388
+ const captured = typeof returned === "string" ? returned : JSON.stringify(returned) ?? "";
32389
+ context.set(step.variable, captured);
32390
+ result.extracted = { [step.variable]: captured };
32391
+ }
32392
+ break;
32393
+ }
32394
+ case "touch": {
32395
+ if (typeof adapter.touch !== "function") {
32396
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 touch`);
32397
+ }
32398
+ if (!locator) throw new TestPilotError("MISSING_LOCATOR", "touch \u5FC5\u987B\u63D0\u4F9B locator");
32399
+ const touches = (step.touches ?? []).map((point) => resolveDeep(point, context));
32400
+ const changed = (step.changedTouches ?? step.touches ?? []).map(
32401
+ (point) => resolveDeep(point, context)
32402
+ );
32403
+ await adapter.touch(locator, step.phase ?? "", touches, changed);
32404
+ break;
32405
+ }
32406
+ case "scroll": {
32407
+ if (typeof adapter.scroll !== "function") {
32408
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 scroll`);
32409
+ }
32410
+ await adapter.scroll(locator, step.scrollTop ?? 0);
32411
+ break;
32412
+ }
32413
+ case "swipeTo": {
32414
+ if (typeof adapter.swipeTo !== "function") {
32415
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 swipeTo`);
32416
+ }
32417
+ await adapter.swipeTo(locator, step.index ?? 0);
32418
+ break;
32419
+ }
32420
+ case "slideTo": {
32421
+ if (typeof adapter.slideTo !== "function") {
32422
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 slideTo`);
32423
+ }
32424
+ await adapter.slideTo(locator, Number(resolveValueText(step.value, context)));
32425
+ break;
32426
+ }
32427
+ case "moveTo": {
32428
+ if (typeof adapter.moveTo !== "function") {
32429
+ throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 moveTo`);
32430
+ }
32431
+ await adapter.moveTo(locator, step.x ?? 0, step.y ?? 0);
32204
32432
  break;
32205
32433
  }
32206
32434
  default: {
@@ -32249,6 +32477,42 @@ async function extractApiResponse(adapter, path) {
32249
32477
  if (!path) throw new TestPilotError("UNSUPPORTED_ACTION", "api \u7AEF extract \u9700\u8981\u63D0\u4F9B value(\u70B9\u53F7 JSON path)");
32250
32478
  return adapter.extractResponse(path);
32251
32479
  }
32480
+ function resolveValueText(value, context) {
32481
+ return value === void 0 ? "" : context.resolve(String(value));
32482
+ }
32483
+ async function assertElementCount(adapter, locator, expected) {
32484
+ const windowMs = expected > 0 ? ASSERT_POLL_MS : ABSENT_SETTLE_MS;
32485
+ const startedAt = Date.now();
32486
+ for (; ; ) {
32487
+ const actual = await adapter.countElements(locator);
32488
+ if (actual === expected) return;
32489
+ if (Date.now() - startedAt > windowMs) {
32490
+ throw new TestPilotError(
32491
+ "ASSERTION_FAILED",
32492
+ `\u5143\u7D20\u6570\u91CF\u65AD\u8A00\u5931\u8D25(\u7B49\u5F85 ${windowMs}ms):${describeLocator(locator)} \u5B9E\u9645 ${actual},\u671F\u671B ${expected}`
32493
+ );
32494
+ }
32495
+ await delay(300);
32496
+ }
32497
+ }
32498
+ async function assertElementAttribute(adapter, locator, step, context) {
32499
+ const useProperty = step.property !== void 0;
32500
+ const name = (useProperty ? step.property : step.attribute) ?? "";
32501
+ const wanted = context.resolve(step.expected ?? "").trim();
32502
+ const startedAt = Date.now();
32503
+ for (; ; ) {
32504
+ const actual = useProperty ? await adapter.getElementProperty(locator, name) : await adapter.getElementAttribute(locator, name);
32505
+ const actualText = actual === void 0 || actual === null ? "<absent>" : String(actual);
32506
+ if (actualText.trim() === wanted) return;
32507
+ if (Date.now() - startedAt > ASSERT_POLL_MS) {
32508
+ throw new TestPilotError(
32509
+ "ASSERTION_FAILED",
32510
+ `\u5C5E\u6027\u65AD\u8A00\u5931\u8D25(\u7B49\u5F85 ${ASSERT_POLL_MS}ms):${describeLocator(locator)} \u7684 ${useProperty ? "property" : "attribute"} "${name}" \u671F\u671B "${wanted}",\u5B9E\u9645\u4E3A "${actualText.slice(0, 200)}"`
32511
+ );
32512
+ }
32513
+ await delay(300);
32514
+ }
32515
+ }
32252
32516
  function resolveHeaders(headers, context) {
32253
32517
  if (!headers) return void 0;
32254
32518
  const resolved = {};
@@ -32521,6 +32785,24 @@ var ExecutionEngine = class {
32521
32785
  for (const [, adapter] of evidenceAdapters) {
32522
32786
  await adapter.restoreWxMock?.().catch(() => void 0);
32523
32787
  }
32788
+ if (status !== "passed") {
32789
+ for (const [target, adapter] of evidenceAdapters) {
32790
+ if (typeof adapter.readDiagnostics !== "function") continue;
32791
+ try {
32792
+ const diagnostics = await adapter.readDiagnostics();
32793
+ const hasContent = diagnostics.consoleLogs.length > 0 || diagnostics.exceptions.length > 0 || (diagnostics.pageStack?.length ?? 0) > 0 || diagnostics.systemInfo !== void 0;
32794
+ if (!hasContent) continue;
32795
+ caseResult.diagnostics = await artifacts.saveDiagnostics(
32796
+ `${executionId}-${target}.json`,
32797
+ diagnostics
32798
+ );
32799
+ } catch (err) {
32800
+ await artifacts.logger.info(
32801
+ `diagnostics dump failed for ${target}: ${err instanceof Error ? err.message : String(err)}`
32802
+ );
32803
+ }
32804
+ }
32805
+ }
32524
32806
  await bus.emit({
32525
32807
  type: "case-finished",
32526
32808
  runId,
@@ -32582,7 +32864,7 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
32582
32864
  }
32583
32865
 
32584
32866
  // ../../packages/execution-engine/src/engine/test-runner.ts
32585
- import { writeFile as writeFile9 } from "node:fs/promises";
32867
+ import { writeFile as writeFile10 } from "node:fs/promises";
32586
32868
  import { join as join15, resolve as resolve6 } from "node:path";
32587
32869
  var TestRunner = class {
32588
32870
  resolver;
@@ -32646,7 +32928,7 @@ var TestRunner = class {
32646
32928
  ts: (/* @__PURE__ */ new Date()).toISOString()
32647
32929
  });
32648
32930
  await artifacts.logger.info(`run ${runId} finished: ${summary.status}`);
32649
- await writeFile9(join15(runDir, RESULT_FILE), JSON.stringify(summary, null, 2), "utf8");
32931
+ await writeFile10(join15(runDir, RESULT_FILE), JSON.stringify(summary, null, 2), "utf8");
32650
32932
  return summary;
32651
32933
  }
32652
32934
  };
@@ -32685,6 +32967,7 @@ import { tmpdir } from "node:os";
32685
32967
  import { join as join16 } from "node:path";
32686
32968
  import { errors } from "playwright";
32687
32969
  var TEXT_WAIT_MS = 1e4;
32970
+ var LONG_PRESS_MS = 600;
32688
32971
  function delay2(ms) {
32689
32972
  return new Promise((resolve7) => setTimeout(resolve7, ms));
32690
32973
  }
@@ -32760,6 +33043,21 @@ var PlaywrightAdapter = class {
32760
33043
  const target = await this.toLocator(locator);
32761
33044
  await this.coded(() => target.click());
32762
33045
  }
33046
+ /** 长按元素:web 端没有原生 longpress,以按住 LONG_PRESS_MS 的 click 近似 */
33047
+ async longpress(locator) {
33048
+ const target = await this.toLocator(locator);
33049
+ await this.coded(() => target.click({ delay: LONG_PRESS_MS }));
33050
+ }
33051
+ /** 统计匹配元素数量(单次读取,不等待;重试语义由引擎负责) */
33052
+ async countElements(locator) {
33053
+ const target = await this.toLocator(locator);
33054
+ return target.count();
33055
+ }
33056
+ /** 读取元素 attribute(不存在时为 null) */
33057
+ async getElementAttribute(locator, name) {
33058
+ const target = await this.toLocator(locator);
33059
+ return this.coded(() => target.getAttribute(name));
33060
+ }
32763
33061
  async input(locator, value) {
32764
33062
  const target = await this.toLocator(locator);
32765
33063
  await this.coded(() => target.fill(value));
@@ -32868,6 +33166,8 @@ var ELEMENT_WAIT_MS = 1e4;
32868
33166
  var TEXT_WAIT_MS2 = 1e4;
32869
33167
  var PAGE_READY_WAIT_MS = 3e4;
32870
33168
  var CONNECTION_LOST = /connection closed|not connected/i;
33169
+ var DIAGNOSTICS_BUFFER_MAX = 200;
33170
+ var CONSOLE_TEXT_LIMIT = 500;
32871
33171
  var automatorLauncher = {
32872
33172
  launch: async (options) => {
32873
33173
  try {
@@ -32883,6 +33183,20 @@ var automatorLauncher = {
32883
33183
  function delay3(ms) {
32884
33184
  return new Promise((resolve7) => setTimeout(resolve7, ms));
32885
33185
  }
33186
+ function serializeConsoleArg(value) {
33187
+ if (typeof value === "string") return value;
33188
+ try {
33189
+ return JSON.stringify(value) ?? String(value);
33190
+ } catch {
33191
+ return String(value);
33192
+ }
33193
+ }
33194
+ function stringifyDataValue(value) {
33195
+ if (value === void 0 || value === null) return "";
33196
+ if (typeof value === "string") return value;
33197
+ if (typeof value === "object") return JSON.stringify(value);
33198
+ return String(value);
33199
+ }
32886
33200
  async function connectWithRetry(wsEndpoint, timeoutMs) {
32887
33201
  const startedAt = Date.now();
32888
33202
  let lastError;
@@ -32943,6 +33257,10 @@ var MiniAppAdapter = class {
32943
33257
  target = "miniapp";
32944
33258
  program;
32945
33259
  requestCapture;
33260
+ /** console / exception 环形缓冲(失败取证的数据源);重连后继续累积 */
33261
+ consoleLogs = [];
33262
+ exceptions = [];
33263
+ diagnosticsAttachedTo;
32946
33264
  /** 拉起开发者工具并建立自动化连接;需幂等 */
32947
33265
  async launch() {
32948
33266
  if (this.program) return;
@@ -32951,6 +33269,7 @@ var MiniAppAdapter = class {
32951
33269
  };
32952
33270
  if (this.config.cliPath) launchOptions.cliPath = this.config.cliPath;
32953
33271
  this.program = await this.launcher.launch(launchOptions);
33272
+ this.attachDiagnostics(this.program);
32954
33273
  }
32955
33274
  async navigate(url2) {
32956
33275
  const startedAt = Date.now();
@@ -33070,7 +33389,12 @@ var MiniAppAdapter = class {
33070
33389
  callbackName
33071
33390
  );
33072
33391
  }
33073
- /** 读取 mock 期间捕获的调用参数(最近调用在前) */
33392
+ /** 读取 mock 期间捕获的调用参数(引擎在带 capture 的 mockWxApi step 后调用;最近调用在前) */
33393
+ readWxApiCaptures() {
33394
+ if (!this.wxMock) return void 0;
33395
+ return { name: this.wxMock.api, calls: [...this.wxMock.captured].reverse() };
33396
+ }
33397
+ /** @deprecated 引擎已改用 readWxApiCaptures();保留按 API 名读取的旧入口 */
33074
33398
  readCapturedWxApiCalls(api) {
33075
33399
  return this.wxMock?.api === api ? [...this.wxMock.captured].reverse() : [];
33076
33400
  }
@@ -33096,13 +33420,146 @@ var MiniAppAdapter = class {
33096
33420
  return page.data(dataPath);
33097
33421
  });
33098
33422
  }
33099
- /** 写入页面 data 补丁(setData 驱动 onShow/onPullDownRefresh 等真实链路) */
33100
- async setData(patch) {
33423
+ /** 写入 data 补丁(setData 驱动 onShow/onPullDownRefresh 等真实链路);locator 提供时为组件级 */
33424
+ async setData(patch, locator) {
33101
33425
  await this.runWithReconnect(async () => {
33426
+ if (locator) {
33427
+ await (await this.element(locator)).setData(patch);
33428
+ return;
33429
+ }
33102
33430
  const page = await (await this.getProgram()).currentPage();
33103
33431
  await page.setData(patch);
33104
33432
  });
33105
33433
  }
33434
+ /** 断言页面 data 路径的值包含 expected(数据绑定未完成时轮询等待) */
33435
+ async assertData(dataPath, expected) {
33436
+ await this.runWithReconnect(async () => {
33437
+ const page = await (await this.getProgram()).currentPage();
33438
+ const wanted = expected.trim();
33439
+ const startedAt = Date.now();
33440
+ for (; ; ) {
33441
+ const actual = stringifyDataValue(await page.data(dataPath));
33442
+ if (actual.includes(wanted)) return;
33443
+ if (Date.now() - startedAt > TEXT_WAIT_MS2) {
33444
+ throw new TestPilotError(
33445
+ "ASSERTION_FAILED",
33446
+ `\u9875\u9762 data \u65AD\u8A00\u5931\u8D25(\u7B49\u5F85 ${TEXT_WAIT_MS2}ms):data.${dataPath} \u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`
33447
+ );
33448
+ }
33449
+ await delay3(300);
33450
+ }
33451
+ });
33452
+ }
33453
+ // ---- miniapp 交互扩展(对照官方 automator Element / MiniProgram API)----
33454
+ /** 长按元素 */
33455
+ async longpress(locator) {
33456
+ await this.runWithReconnect(async () => (await this.element(locator)).longpress());
33457
+ }
33458
+ /** 主动调用 wx.* API(如 setStorageSync / clearStorageSync 造前置状态) */
33459
+ async callWxApi(api, args = []) {
33460
+ return this.runWithReconnect(async () => {
33461
+ const program2 = await this.getProgram();
33462
+ if (typeof program2.callWxMethod !== "function") {
33463
+ throw new TestPilotError(
33464
+ "UNSUPPORTED_ACTION",
33465
+ "\u5F53\u524D miniprogram-automator \u4E0D\u652F\u6301 callWxMethod,\u8BF7\u5347\u7EA7\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u540E\u91CD\u8BD5"
33466
+ );
33467
+ }
33468
+ return program2.callWxMethod(api, ...args);
33469
+ });
33470
+ }
33471
+ /** 派发触摸事件(touchstart / touchmove / touchend) */
33472
+ async touch(locator, phase, touches, changedTouches) {
33473
+ await this.runWithReconnect(async () => {
33474
+ const element = await this.element(locator);
33475
+ const options = { touches: [...touches], changedTouches: [...changedTouches] };
33476
+ if (phase === "touchstart") await element.touchstart(options);
33477
+ else if (phase === "touchmove") await element.touchmove(options);
33478
+ else if (phase === "touchend") await element.touchend(options);
33479
+ else throw new TestPilotError("UNSUPPORTED_ACTION", `\u672A\u77E5\u89E6\u6478\u9636\u6BB5:${phase}`);
33480
+ });
33481
+ }
33482
+ /** 滚动:locator 提供时滚动 scroll-view 元素,缺省滚动页面 */
33483
+ async scroll(locator, scrollTop) {
33484
+ await this.runWithReconnect(async () => {
33485
+ if (locator) {
33486
+ await (await this.element(locator)).scrollTo(scrollTop);
33487
+ return;
33488
+ }
33489
+ const program2 = await this.getProgram();
33490
+ if (typeof program2.pageScrollTo !== "function") {
33491
+ throw new TestPilotError(
33492
+ "UNSUPPORTED_ACTION",
33493
+ "\u5F53\u524D miniprogram-automator \u4E0D\u652F\u6301 pageScrollTo,\u8BF7\u5347\u7EA7\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u540E\u91CD\u8BD5"
33494
+ );
33495
+ }
33496
+ await program2.pageScrollTo(scrollTop);
33497
+ });
33498
+ }
33499
+ /** swiper 切换到指定页 */
33500
+ async swipeTo(locator, index) {
33501
+ await this.runWithReconnect(async () => (await this.element(locator)).swipeTo(index));
33502
+ }
33503
+ /** slider 滑到指定值 */
33504
+ async slideTo(locator, value) {
33505
+ await this.runWithReconnect(async () => (await this.element(locator)).slideTo(value));
33506
+ }
33507
+ /** movable-view 移动到指定坐标 */
33508
+ async moveTo(locator, x, y) {
33509
+ await this.runWithReconnect(async () => (await this.element(locator)).moveTo(x, y));
33510
+ }
33511
+ /** 统计匹配元素数量(单次读取;存在性/计数断言的重试语义由引擎负责) */
33512
+ async countElements(locator) {
33513
+ return this.runWithReconnect(async () => {
33514
+ const page = await (await this.getProgram()).currentPage();
33515
+ const elements = await page.$$(this.cssSelector(locator));
33516
+ return elements.length;
33517
+ });
33518
+ }
33519
+ /** 读取元素 attribute(不存在时为 undefined/null) */
33520
+ async getElementAttribute(locator, name) {
33521
+ return this.runWithReconnect(async () => (await this.element(locator)).attribute(name));
33522
+ }
33523
+ /** 读取元素 property(miniapp 自定义组件实例属性) */
33524
+ async getElementProperty(locator, name) {
33525
+ return this.runWithReconnect(async () => (await this.element(locator)).property(name));
33526
+ }
33527
+ /** 导出运行时诊断:console 尾部 / 异常 / 页面栈 / systemInfo(失败取证由引擎落盘) */
33528
+ async readDiagnostics() {
33529
+ const program2 = this.program;
33530
+ const pageStack = await program2?.pageStack?.().catch(() => void 0);
33531
+ const systemInfo = await program2?.systemInfo?.().catch(() => void 0);
33532
+ return {
33533
+ consoleLogs: [...this.consoleLogs],
33534
+ exceptions: [...this.exceptions],
33535
+ pageStack,
33536
+ systemInfo
33537
+ };
33538
+ }
33539
+ /** 订阅小程序 console / exception 事件(每个 program 实例只挂一次) */
33540
+ attachDiagnostics(program2) {
33541
+ if (this.diagnosticsAttachedTo === program2 || typeof program2.on !== "function") return;
33542
+ this.diagnosticsAttachedTo = program2;
33543
+ program2.on("console", (payload) => {
33544
+ const record2 = payload ?? {};
33545
+ const args = Array.isArray(record2.args) ? record2.args : [];
33546
+ this.pushConsoleLog({
33547
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
33548
+ type: typeof record2.type === "string" ? record2.type : "log",
33549
+ text: args.map(serializeConsoleArg).join(" ").slice(0, CONSOLE_TEXT_LIMIT)
33550
+ });
33551
+ });
33552
+ program2.on("exception", (payload) => {
33553
+ const record2 = payload ?? {};
33554
+ const message = typeof record2.message === "string" ? record2.message : String(payload);
33555
+ this.exceptions.push(message.slice(0, CONSOLE_TEXT_LIMIT));
33556
+ if (this.exceptions.length > DIAGNOSTICS_BUFFER_MAX) this.exceptions.shift();
33557
+ });
33558
+ }
33559
+ pushConsoleLog(entry) {
33560
+ this.consoleLogs.push(entry);
33561
+ if (this.consoleLogs.length > DIAGNOSTICS_BUFFER_MAX) this.consoleLogs.shift();
33562
+ }
33106
33563
  async screenshot() {
33107
33564
  return this.runWithReconnect(async () => {
33108
33565
  const base643 = await (await this.getProgram()).screenshot();
@@ -33616,7 +34073,7 @@ var TestPilotClient = class {
33616
34073
  };
33617
34074
 
33618
34075
  // src/commands/init/adapter-detector.ts
33619
- import { existsSync as existsSync5, readFileSync } from "node:fs";
34076
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
33620
34077
  import { join as join20 } from "node:path";
33621
34078
  async function detectAdapters(root, projectType) {
33622
34079
  const required2 = projectType === "hybrid" ? ["playwright", "wechatide"] : projectType === "wechat-miniapp" ? ["wechatide"] : projectType === "web" ? ["playwright"] : [];
@@ -33643,7 +34100,7 @@ function readPackageJsonDeps(root) {
33643
34100
  const file2 = join20(root, "package.json");
33644
34101
  if (!existsSync5(file2)) return void 0;
33645
34102
  try {
33646
- const pkg = JSON.parse(readFileSync(file2, "utf8"));
34103
+ const pkg = JSON.parse(readFileSync2(file2, "utf8"));
33647
34104
  return { ...pkg.dependencies, ...pkg.devDependencies };
33648
34105
  } catch {
33649
34106
  return void 0;
@@ -33673,7 +34130,7 @@ async function collectEnvVarRefs(root) {
33673
34130
  }
33674
34131
 
33675
34132
  // src/commands/init/project-detector.ts
33676
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
34133
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
33677
34134
  import { basename, join as join22 } from "node:path";
33678
34135
 
33679
34136
  // src/lib/git.ts
@@ -33714,7 +34171,7 @@ function readPackageJson(root) {
33714
34171
  const file2 = join22(root, "package.json");
33715
34172
  if (!existsSync6(file2)) return void 0;
33716
34173
  try {
33717
- return JSON.parse(readFileSync2(file2, "utf8"));
34174
+ return JSON.parse(readFileSync3(file2, "utf8"));
33718
34175
  } catch {
33719
34176
  return void 0;
33720
34177
  }
@@ -33781,8 +34238,8 @@ function adaptersFor(type) {
33781
34238
  }
33782
34239
 
33783
34240
  // src/commands/init/skill-installer.ts
33784
- import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
33785
- import { access as access2, cp, mkdir as mkdir12, writeFile as writeFile10 } from "node:fs/promises";
34241
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
34242
+ import { access as access2, cp, mkdir as mkdir12, writeFile as writeFile11 } from "node:fs/promises";
33786
34243
  import { dirname as dirname9, join as join23 } from "node:path";
33787
34244
  import { fileURLToPath } from "node:url";
33788
34245
  var CORE_FILES = ["SKILL.md", "manifest.yaml"];
@@ -33798,7 +34255,7 @@ async function exists3(path) {
33798
34255
  function readManifestVersion(dir) {
33799
34256
  const manifestPath = join23(dir, "manifest.yaml");
33800
34257
  if (!existsSync7(manifestPath)) return void 0;
33801
- return /^version:\s*([^\s#]+)/m.exec(readFileSync3(manifestPath, "utf8"))?.[1];
34258
+ return /^version:\s*([^\s#]+)/m.exec(readFileSync4(manifestPath, "utf8"))?.[1];
33802
34259
  }
33803
34260
  function resolveSkillSourceDir() {
33804
34261
  const fromEnv = process.env.TESTPILOT_SKILL_DIR;
@@ -33867,7 +34324,7 @@ async function installSkill(root, info, adapters) {
33867
34324
  const file2 = join23(refsDir, name);
33868
34325
  if (await exists3(file2)) continue;
33869
34326
  await mkdir12(refsDir, { recursive: true });
33870
- await writeFile10(file2, render(), "utf8");
34327
+ await writeFile11(file2, render(), "utf8");
33871
34328
  references = "created";
33872
34329
  }
33873
34330
  return coreVersion ? { core, coreVersion, references, dest } : { core, references, dest };
@@ -33986,9 +34443,44 @@ async function runDoctor(root) {
33986
34443
  (item) => item.available ? ok(`${item.id}(${item.detail})`) : item.required ? warn(`${item.id} \u4E0D\u53EF\u7528`, item.detail) : warn(`${item.id} \u672A\u5B89\u88C5(\u53EF\u9009)`, item.detail)
33987
34444
  )
33988
34445
  );
34446
+ const miniappProjectPath = config2?.miniapp?.projectPath;
34447
+ if (miniappProjectPath) {
34448
+ const miniappItems = [];
34449
+ const cliPath = process.env.WECHAT_DEVTOOLS_CLI ?? config2?.miniapp?.cliPath ?? await findWechatDevToolsCli();
34450
+ if (!cliPath) {
34451
+ miniappItems.push(
34452
+ warn("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177 CLI \u672A\u627E\u5230", "\u5B89\u88C5\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177,\u6216\u8BBE\u7F6E WECHAT_DEVTOOLS_CLI \u6307\u5411 cli")
34453
+ );
34454
+ } else {
34455
+ miniappItems.push(ok(`\u5F00\u53D1\u8005\u5DE5\u5177 CLI(${cliPath})`));
34456
+ const loggedIn = await isWechatDevToolsLoggedIn(cliPath);
34457
+ if (loggedIn === true) miniappItems.push(ok("\u5F00\u53D1\u8005\u5DE5\u5177\u5DF2\u767B\u5F55"));
34458
+ else if (loggedIn === false)
34459
+ miniappItems.push(
34460
+ warn("\u5F00\u53D1\u8005\u5DE5\u5177\u672A\u767B\u5F55", "\u6253\u5F00\u5DE5\u5177\u626B\u7801\u767B\u5F55;CI \u65E0\u4EBA\u503C\u5B88\u7684\u767B\u5F55\u6001\u7EF4\u62A4\u89C1 docs/miniapp-testing.md")
34461
+ );
34462
+ else
34463
+ miniappItems.push(
34464
+ warn("\u5F00\u53D1\u8005\u5DE5\u5177\u767B\u5F55\u6001\u672A\u77E5", "cli islogin \u65E0\u6709\u6548\u8F93\u51FA;\u8BF7\u786E\u8BA4\u5DE5\u5177\u5DF2\u767B\u5F55\u4E14 \u5B89\u5168\u8BBE\u7F6E->\u670D\u52A1\u7AEF\u53E3 \u5DF2\u5F00\u542F")
34465
+ );
34466
+ }
34467
+ const appidInfo = readMiniappProjectAppid(miniappProjectPath);
34468
+ if (!appidInfo) {
34469
+ miniappItems.push(warn("project.config.json \u7F3A\u5931", `${miniappProjectPath} \u4E0B\u672A\u627E\u5230,\u81EA\u52A8\u5316\u65E0\u6CD5\u5B9A\u4F4D\u9879\u76EE`));
34470
+ } else if (!appidInfo.appid) {
34471
+ miniappItems.push(warn("project.config.json \u7F3A\u5C11 appid", "\u81EA\u52A8\u5316\u4E0E\u9884\u89C8\u80FD\u529B\u53D7\u9650,\u8BF7\u586B\u5199\u771F\u5B9E appid"));
34472
+ } else if (appidInfo.appid === "touristappid") {
34473
+ miniappItems.push(
34474
+ warn("appid \u4E3A touristappid(\u6E38\u5BA2\u6A21\u5F0F)", "\u90E8\u5206\u81EA\u52A8\u5316/\u9884\u89C8\u80FD\u529B\u53D7\u9650,\u8BF7\u5728 project.config.json \u586B\u5199\u771F\u5B9E appid")
34475
+ );
34476
+ } else {
34477
+ miniappItems.push(ok(`\u5C0F\u7A0B\u5E8F appid(${appidInfo.appid})`));
34478
+ }
34479
+ push("Miniapp IDE", miniappItems);
34480
+ }
33989
34481
  const refs = await collectEnvVarRefs(root);
33990
34482
  const envItems = refs.map(
33991
- (name) => process.env[name] !== void 0 ? ok(name) : error62(`${name} missing`, "\u5728\u8FD0\u884C\u73AF\u5883(.env / CI Secret)\u4E2D\u8BBE\u7F6E")
34483
+ (name) => process.env[name] !== void 0 ? ok(name) : error62(`${name} missing`, "\u5728 .env.testing(\u65E7\u7248\u672C\u4E3A .env,\u8BF7\u8FC1\u79FB)\u6216 CI Secret \u4E2D\u8BBE\u7F6E")
33992
34484
  );
33993
34485
  const serverConfigured = (process.env.TESTPILOT_SERVER_URL ?? config2?.server?.baseUrl) !== void 0;
33994
34486
  const accountRefs = [
@@ -34063,7 +34555,7 @@ function makeDoctorCommand() {
34063
34555
  import { join as join27 } from "node:path";
34064
34556
 
34065
34557
  // src/commands/init/config-initializer.ts
34066
- import { access as access3, mkdir as mkdir13, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
34558
+ import { access as access3, mkdir as mkdir13, readFile as readFile10, writeFile as writeFile12 } from "node:fs/promises";
34067
34559
  import { basename as basename2, join as join25 } from "node:path";
34068
34560
  async function exists4(path) {
34069
34561
  try {
@@ -34095,7 +34587,7 @@ environment:
34095
34587
  staging:
34096
34588
  baseUrl: \${STAGING_BASE_URL}
34097
34589
 
34098
- # Case \u53EF\u89C1\u7684\u9879\u76EE\u53D8\u91CF\u767D\u540D\u5355(\u503C\u6765\u81EA .env / CI Secret;Case \u7528 \${variable.<name>} \u5F15\u7528)
34590
+ # Case \u53EF\u89C1\u7684\u9879\u76EE\u53D8\u91CF\u767D\u540D\u5355(\u503C\u6765\u81EA .env.testing / CI Secret;Case \u7528 \${variable.<name>} \u5F15\u7528)
34099
34591
  # variables:
34100
34592
  # ppmProjectId: \${PPM_PROJECT_ID}
34101
34593
 
@@ -34112,19 +34604,20 @@ ${adapters}
34112
34604
  # server:
34113
34605
  # baseUrl: http://127.0.0.1:3000
34114
34606
 
34115
- # Web \u7AEF:navigate \u76F8\u5BF9 url \u7684\u57FA\u51C6\u5730\u5740(\u6BCF\u4EBA\u7684\u5730\u5740\u4E0D\u540C,\u503C\u653E .env)
34607
+ # Web \u7AEF:navigate \u76F8\u5BF9 url \u7684\u57FA\u51C6\u5730\u5740(\u6BCF\u4EBA\u7684\u5730\u5740\u4E0D\u540C,\u503C\u653E .env.testing)
34116
34608
  # web:
34117
34609
  # baseUrl: \${WEB_BASE_URL}
34118
34610
 
34119
- # \u5C0F\u7A0B\u5E8F\u7AEF:\u586B\u5165\u5C0F\u7A0B\u5E8F\u9879\u76EE\u76EE\u5F55\u540E\u5373\u53EF\u6267\u884C target: miniapp \u7684\u6B65\u9AA4(\u8DEF\u5F84\u6BCF\u4EBA\u4E0D\u540C,\u8D70 .env)
34611
+ # \u5C0F\u7A0B\u5E8F\u7AEF:\u586B\u5165\u5C0F\u7A0B\u5E8F\u9879\u76EE\u76EE\u5F55\u540E\u5373\u53EF\u6267\u884C target: miniapp \u7684\u6B65\u9AA4(\u8DEF\u5F84\u6BCF\u4EBA\u4E0D\u540C,\u8D70 .env.testing)
34120
34612
  # miniapp:
34121
34613
  # projectPath: \${MINIAPP_PROJECT_PATH}
34122
34614
  # cliPath: \${MINIAPP_CLI_PATH}
34123
34615
  `;
34124
34616
  }
34125
- function renderDotEnvExample() {
34126
- return `# TestPilot \u73AF\u5883\u53D8\u91CF\u793A\u4F8B:\u590D\u5236\u4E3A .env \u5E76\u586B\u5165\u771F\u5B9E\u503C(.env \u542B\u654F\u611F\u4FE1\u606F,\u4E0D\u8981\u63D0\u4EA4\u5230 Git)
34127
- # \u4F18\u5148\u7EA7:\u771F\u5B9E\u73AF\u5883\u53D8\u91CF > .env \u6587\u4EF6;testpilot.yaml \u4E2D\u7684 \${VAR} \u5F15\u7528\u8FD0\u884C\u65F6\u81EA\u52A8\u6CE8\u5165
34617
+ function renderDotEnvTesting() {
34618
+ return `# TestPilot \u6D4B\u8BD5\u73AF\u5883\u53D8\u91CF(CLI \u81EA\u52A8\u52A0\u8F7D\u672C\u6587\u4EF6,\u76F4\u63A5\u586B\u5165\u771F\u5B9E\u503C\u5373\u53EF,\u65E0\u9700\u590D\u5236)
34619
+ # \u53EA\u653E\u6D4B\u8BD5\u76F8\u5173\u53D8\u91CF,\u4E0E\u524D\u7AEF\u9879\u76EE\u81EA\u8EAB\u7684 .env \u4E92\u4E0D\u5F71\u54CD;\u672C\u6587\u4EF6\u542B\u6D4B\u8BD5\u51ED\u636E,\u5DF2\u52A0\u5165 .gitignore
34620
+ # \u4F18\u5148\u7EA7:\u771F\u5B9E\u73AF\u5883\u53D8\u91CF > \u672C\u6587\u4EF6;testpilot.yaml \u4E2D\u7684 \${VAR} \u5F15\u7528\u8FD0\u884C\u65F6\u81EA\u52A8\u6CE8\u5165
34128
34621
 
34129
34622
  # \u73AF\u5883\u914D\u7F6E(environment \u6BB5)
34130
34623
  TEST_BASE_URL=http://127.0.0.1:8080
@@ -34144,7 +34637,7 @@ STAGING_BASE_URL=https://staging.example.com
34144
34637
  async function ensureGitignoreEntry(root, entry, comment = "TestPilot \u672C\u5730\u73AF\u5883\u53D8\u91CF") {
34145
34638
  const gitignorePath = join25(root, ".gitignore");
34146
34639
  if (!await exists4(gitignorePath)) {
34147
- await writeFile11(gitignorePath, `# ${comment}
34640
+ await writeFile12(gitignorePath, `# ${comment}
34148
34641
  ${entry}
34149
34642
  `, "utf8");
34150
34643
  return "created";
@@ -34154,7 +34647,7 @@ ${entry}
34154
34647
  return "exists";
34155
34648
  }
34156
34649
  const separator = content.endsWith("\n") || content === "" ? "" : "\n";
34157
- await writeFile11(gitignorePath, `${content}${separator}# ${comment}
34650
+ await writeFile12(gitignorePath, `${content}${separator}# ${comment}
34158
34651
  ${entry}
34159
34652
  `, "utf8");
34160
34653
  return "updated";
@@ -34163,21 +34656,22 @@ async function initTestPilotConfig(root, info, adapters) {
34163
34656
  const yamlPath = join25(root, "testpilot.yaml");
34164
34657
  let yaml = "reused";
34165
34658
  if (!await exists4(yamlPath)) {
34166
- await writeFile11(yamlPath, renderTestpilotYaml(info), "utf8");
34659
+ await writeFile12(yamlPath, renderTestpilotYaml(info), "utf8");
34167
34660
  yaml = "created";
34168
34661
  }
34169
- const envExamplePath = join25(root, ".env.example");
34170
- let envExample = "reused";
34171
- if (!await exists4(envExamplePath)) {
34172
- await writeFile11(envExamplePath, renderDotEnvExample(), "utf8");
34173
- envExample = "created";
34662
+ const envTestingPath = join25(root, ENV_TESTING_FILE);
34663
+ let envTesting = "reused";
34664
+ if (!await exists4(envTestingPath)) {
34665
+ await writeFile12(envTestingPath, renderDotEnvTesting(), "utf8");
34666
+ envTesting = "created";
34174
34667
  }
34175
- const gitignore = await ensureGitignoreEntry(root, ".env");
34668
+ const gitignore = await ensureGitignoreEntry(root, ENV_TESTING_FILE);
34669
+ await ensureGitignoreEntry(root, ".env");
34176
34670
  const runtimeDir = join25(root, ".testpilot", "artifacts");
34177
34671
  await mkdir13(runtimeDir, { recursive: true });
34178
34672
  const keep = join25(runtimeDir, ".gitkeep");
34179
34673
  if (!await exists4(keep)) {
34180
- await writeFile11(keep, "", "utf8");
34674
+ await writeFile12(keep, "", "utf8");
34181
34675
  }
34182
34676
  const existing = await readProjectLink(root);
34183
34677
  const defaultAdapter = defaultAdapterFor(info.type);
@@ -34201,7 +34695,7 @@ async function initTestPilotConfig(root, info, adapters) {
34201
34695
  runtimeDir: join25(root, ".testpilot"),
34202
34696
  projectLink: link,
34203
34697
  projectLinkFile,
34204
- envExample,
34698
+ envTesting,
34205
34699
  gitignore
34206
34700
  };
34207
34701
  }
@@ -34234,7 +34728,7 @@ function randomId() {
34234
34728
 
34235
34729
  // src/commands/init/test-structure.ts
34236
34730
  import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
34237
- import { mkdir as mkdir14, writeFile as writeFile12 } from "node:fs/promises";
34731
+ import { mkdir as mkdir14, writeFile as writeFile13 } from "node:fs/promises";
34238
34732
  import { join as join26 } from "node:path";
34239
34733
  async function initTestStructure(root, _info) {
34240
34734
  const dirs = {
@@ -34254,7 +34748,7 @@ async function initTestStructure(root, _info) {
34254
34748
  }
34255
34749
  for (const [name, dir] of Object.entries(dirs)) {
34256
34750
  if (!existed[name] && readdirSync3(dir).length === 0) {
34257
- await writeFile12(join26(dir, ".gitkeep"), "", "utf8").catch(() => void 0);
34751
+ await writeFile13(join26(dir, ".gitkeep"), "", "utf8").catch(() => void 0);
34258
34752
  }
34259
34753
  }
34260
34754
  return {
@@ -34293,10 +34787,10 @@ async function runInit(root, options = {}) {
34293
34787
  const serverMode = config2.projectLink.serverUrl ? `Server:${config2.projectLink.serverUrl}` : "\u672C\u5730\u6A21\u5F0F";
34294
34788
  console.log(`\u2713 \u9879\u76EE\u5173\u8054 ${config2.projectLink.projectId}(${serverMode})`);
34295
34789
  console.log(
34296
- config2.envExample === "created" ? "\u2713 \u73AF\u5883\u53D8\u91CF\u793A\u4F8B .env.example(\u590D\u5236\u4E3A .env \u586B\u5165\u771F\u5B9E\u503C)" : "\u2713 \u73AF\u5883\u53D8\u91CF\u793A\u4F8B .env.example(\u5DF2\u5B58\u5728,\u4FDD\u7559)"
34790
+ config2.envTesting === "created" ? "\u2713 \u6D4B\u8BD5\u73AF\u5883\u53D8\u91CF .env.testing(\u76F4\u63A5\u586B\u5165\u771F\u5B9E\u503C,CLI \u81EA\u52A8\u52A0\u8F7D)" : "\u2713 \u6D4B\u8BD5\u73AF\u5883\u53D8\u91CF .env.testing(\u5DF2\u5B58\u5728,\u4FDD\u7559)"
34297
34791
  );
34298
34792
  console.log(
34299
- config2.gitignore === "exists" ? "\u2713 Git \u5FFD\u7565 .env(\u5DF2\u5728 .gitignore)" : `\u2713 Git \u5FFD\u7565 .env(\u5DF2\u5199\u5165 .gitignore)`
34793
+ config2.gitignore === "exists" ? "\u2713 Git \u5FFD\u7565 .env.testing(\u5DF2\u5728 .gitignore)" : `\u2713 Git \u5FFD\u7565 .env.testing(\u5DF2\u5199\u5165 .gitignore)`
34300
34794
  );
34301
34795
  const testStructure = await initTestStructure(root, project);
34302
34796
  const reuseNote = project.existingE2eDirs.filter((dir) => dir !== "tests/e2e");
@@ -34388,7 +34882,7 @@ function makeListCommand() {
34388
34882
  }
34389
34883
 
34390
34884
  // src/commands/report.ts
34391
- import { exec } from "node:child_process";
34885
+ import { exec as exec2 } from "node:child_process";
34392
34886
  function makeReportCommand() {
34393
34887
  return new Command("report").description("\u751F\u6210\u6D4B\u8BD5\u62A5\u544A(JSON + HTML)").option("--run <id>", "\u8FD0\u884C\u8BB0\u5F55 ID,\u9ED8\u8BA4\u6700\u8FD1\u4E00\u6B21").option("--open", "\u751F\u6210\u540E\u5728\u6D4F\u89C8\u5668\u6253\u5F00", false).option("--summary", "\u751F\u6210\u8DE8 run \u6C47\u603B\u62A5\u544A(summary.html + summary.json)", false).option("--last <n>", "\u6C47\u603B\u7EDF\u8BA1\u7A97\u53E3:\u6700\u8FD1 N \u6B21\u8FD0\u884C", "30").action(async (options) => {
34394
34888
  const client = new TestPilotClient();
@@ -34417,7 +34911,7 @@ function makeReportCommand() {
34417
34911
  function openInBrowser(file2) {
34418
34912
  const command = process.platform === "win32" ? `start "" "${file2}"` : process.platform === "darwin" ? `open "${file2}"` : `xdg-open "${file2}"`;
34419
34913
  return new Promise((resolvePromise) => {
34420
- exec(command, () => resolvePromise());
34914
+ exec2(command, () => resolvePromise());
34421
34915
  });
34422
34916
  }
34423
34917
 
@@ -34562,10 +35056,13 @@ function makeSyncMetadataCommand() {
34562
35056
  }
34563
35057
 
34564
35058
  // src/commands/update.ts
34565
- import { writeFile as writeFile13 } from "node:fs/promises";
35059
+ import { readFile as readFile11, writeFile as writeFile14 } from "node:fs/promises";
34566
35060
  import { join as join29 } from "node:path";
35061
+ function varNames(content) {
35062
+ return new Set([...content.matchAll(/^#?\s*([A-Z][A-Z0-9_]+)=/gm)].map((match) => match[1]));
35063
+ }
34567
35064
  function makeUpdateCommand() {
34568
- const update = new Command("update").description("\u540C\u6B65 TestPilot \u63A5\u5165\u7269\u5230\u5F53\u524D CLI \u7248\u672C(Skill / .env.example;\u4E0D\u89E6\u78B0 .env \u4E0E\u7528\u4F8B)").option("--force", "Skill \u7248\u672C\u4E00\u81F4\u65F6\u4E5F\u5F3A\u5236\u8986\u76D6\u672C\u5730\u4FEE\u6539", false).action(async (options) => {
35065
+ const update = new Command("update").description("\u540C\u6B65 TestPilot \u63A5\u5165\u7269\u5230\u5F53\u524D CLI \u7248\u672C(Skill / .env.testing;\u4E0D\u89E6\u78B0 .env \u4E0E\u7528\u4F8B)").option("--force", "Skill \u7248\u672C\u4E00\u81F4\u65F6\u4E5F\u5F3A\u5236\u8986\u76D6\u672C\u5730\u4FEE\u6539", false).action(async (options) => {
34569
35066
  const root = process.cwd();
34570
35067
  const dest = join29(".agents", "skills", "testpilot");
34571
35068
  const skill = await syncSkillFiles(root, { force: options.force });
@@ -34581,13 +35078,25 @@ function makeUpdateCommand() {
34581
35078
  default:
34582
35079
  console.log(`\u2713 Skill \u5DF2\u662F\u6700\u65B0 v${skill.bundledVersion ?? "?"}`);
34583
35080
  }
34584
- await writeFile13(join29(root, ".env.example"), renderDotEnvExample(), "utf8");
34585
- console.log("\u2713 .env.example \u5DF2\u540C\u6B65\u4E3A\u5F53\u524D\u7248\u672C\u7684\u793A\u4F8B(.env \u4E0D\u53D7\u5F71\u54CD)");
34586
- const gitignore = await ensureGitignoreEntry(root, ".env");
35081
+ const envPath = join29(root, ENV_TESTING_FILE);
35082
+ const template = renderDotEnvTesting();
35083
+ const existing = await readFile11(envPath, "utf8").catch(() => void 0);
35084
+ if (existing === void 0) {
35085
+ await writeFile14(envPath, template, "utf8");
35086
+ console.log(`\u2713 ${ENV_TESTING_FILE} \u5DF2\u751F\u6210(\u76F4\u63A5\u586B\u5165\u771F\u5B9E\u503C,CLI \u81EA\u52A8\u52A0\u8F7D)`);
35087
+ } else {
35088
+ console.log(`\u2713 ${ENV_TESTING_FILE} \u5DF2\u5B58\u5728,\u4FDD\u7559\u7528\u6237\u5185\u5BB9`);
35089
+ const missing = [...varNames(template)].filter((name) => !varNames(existing).has(name));
35090
+ if (missing.length > 0) {
35091
+ console.log(` \u63D0\u793A:\u5F53\u524D\u7248\u672C\u6A21\u677F\u65B0\u589E\u53D8\u91CF,\u53EF\u6309\u9700\u8865\u5165:${missing.join(", ")}`);
35092
+ }
35093
+ }
35094
+ const gitignore = await ensureGitignoreEntry(root, ENV_TESTING_FILE);
35095
+ await ensureGitignoreEntry(root, ".env");
34587
35096
  console.log(
34588
- gitignore === "exists" ? "\u2713 .gitignore \u5DF2\u5FFD\u7565 .env" : "\u2713 .gitignore \u5DF2\u8FFD\u52A0\u5FFD\u7565 .env"
35097
+ gitignore === "exists" ? `\u2713 .gitignore \u5DF2\u5FFD\u7565 ${ENV_TESTING_FILE}` : `\u2713 .gitignore \u5DF2\u8FFD\u52A0\u5FFD\u7565 ${ENV_TESTING_FILE}`
34589
35098
  );
34590
- console.log("\u63D0\u793A:\u63D0\u4EA4 .agents/skills/ \u4E0E .env.example \u7684\u53D8\u66F4,\u56E2\u961F\u4E0E AI \u5373\u53EF\u4F7F\u7528\u6700\u65B0\u80FD\u529B");
35099
+ console.log("\u63D0\u793A:\u63D0\u4EA4 .agents/skills/ \u7684\u53D8\u66F4,\u56E2\u961F\u4E0E AI \u5373\u53EF\u4F7F\u7528\u6700\u65B0\u80FD\u529B");
34591
35100
  });
34592
35101
  return update;
34593
35102
  }
@@ -34628,7 +35137,7 @@ function makeValidateCommand() {
34628
35137
  }
34629
35138
 
34630
35139
  // src/cli.ts
34631
- var CLI_VERSION = "0.7.1";
35140
+ var CLI_VERSION = "0.7.2";
34632
35141
  async function createCli() {
34633
35142
  const program2 = new Command();
34634
35143
  program2.name("csspilot").description("TestPilot \u2014 AI Native \u8DE8\u7AEF\u4E1A\u52A1\u6D4B\u8BD5\u57FA\u7840\u8BBE\u65BD CLI").version(CLI_VERSION, "-v, --version", "output the current version").option("-V", "output the current version", () => {