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/README.md +8 -7
- package/dist/bin.js +596 -87
- package/dist/index.js +596 -87
- package/dist/skills/testpilot/manifest.yaml +4 -1
- package/dist/skills/testpilot/rules/case-design.md +47 -47
- package/dist/skills/testpilot/rules/dsl.md +121 -13
- package/dist/skills/testpilot/workflows/create-case.md +33 -33
- package/dist/skills/testpilot/workflows/run-case.md +1 -1
- package/dist/skills/testpilot/workflows/test-requirements-review.md +43 -6
- package/package.json +1 -1
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,
|
|
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([
|
|
30521
|
-
|
|
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) &&
|
|
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
|
|
31533
|
-
let agg = byCase.get(
|
|
31671
|
+
for (const exec3 of executions) {
|
|
31672
|
+
let agg = byCase.get(exec3.identity);
|
|
31534
31673
|
if (!agg) {
|
|
31535
31674
|
agg = {
|
|
31536
|
-
caseId:
|
|
31537
|
-
rowId:
|
|
31538
|
-
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(
|
|
31686
|
+
byCase.set(exec3.identity, agg);
|
|
31548
31687
|
}
|
|
31549
31688
|
agg.runs++;
|
|
31550
|
-
if (
|
|
31551
|
-
else if (
|
|
31552
|
-
for (const platform of
|
|
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((
|
|
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((
|
|
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((
|
|
31575
|
-
inconclusive: executions.filter((
|
|
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((
|
|
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,
|
|
32263
|
+
await adapter.input(locator, resolveValueText(step.value, context));
|
|
32109
32264
|
break;
|
|
32110
32265
|
case "select":
|
|
32111
|
-
await adapter.select(locator,
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
33786
|
-
import { access as access2, cp, mkdir as mkdir12, writeFile as
|
|
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(
|
|
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
|
|
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\
|
|
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
|
|
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
|
|
34127
|
-
return `# TestPilot \u73AF\u5883\u53D8\u91CF\
|
|
34128
|
-
# \
|
|
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
|
|
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
|
|
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
|
|
34660
|
+
await writeFile12(yamlPath, renderTestpilotYaml(info), "utf8");
|
|
34168
34661
|
yaml = "created";
|
|
34169
34662
|
}
|
|
34170
|
-
const
|
|
34171
|
-
let
|
|
34172
|
-
if (!await exists4(
|
|
34173
|
-
await
|
|
34174
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
34586
|
-
|
|
34587
|
-
const
|
|
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" ?
|
|
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/ \
|
|
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.
|
|
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", () => {
|