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