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