csspilot 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +301 -23
- package/dist/index.js +301 -23
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -30388,7 +30388,11 @@ var ACTIONS = [
|
|
|
30388
30388
|
"assert",
|
|
30389
30389
|
"extract",
|
|
30390
30390
|
"screenshot",
|
|
30391
|
-
"request"
|
|
30391
|
+
"request",
|
|
30392
|
+
"callMethod",
|
|
30393
|
+
"mockWxApi",
|
|
30394
|
+
"trigger",
|
|
30395
|
+
"setData"
|
|
30392
30396
|
];
|
|
30393
30397
|
var locatorSchema = external_exports.strictObject({
|
|
30394
30398
|
text: external_exports.string().min(1).optional(),
|
|
@@ -30416,7 +30420,25 @@ var stepSchema = external_exports.strictObject({
|
|
|
30416
30420
|
variable: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "\u53D8\u91CF\u540D\u9700\u4EE5\u5B57\u6BCD\u6216\u4E0B\u5212\u7EBF\u5F00\u5934,\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF").optional(),
|
|
30417
30421
|
timeout: external_exports.number().int().positive().optional(),
|
|
30418
30422
|
/** UI 动作触发的网络请求精确次数断言 */
|
|
30419
|
-
expectRequests: external_exports.array(requestExpectationSchema).min(1).optional()
|
|
30423
|
+
expectRequests: external_exports.array(requestExpectationSchema).min(1).optional(),
|
|
30424
|
+
/** callMethod:要调用的 Page / 组件方法名(仅 miniapp;页面级调用不写 locator) */
|
|
30425
|
+
methodName: external_exports.string().min(1).optional(),
|
|
30426
|
+
/** callMethod 的入参(支持 ${var} 模板) */
|
|
30427
|
+
args: external_exports.array(external_exports.unknown()).optional(),
|
|
30428
|
+
/** mockWxApi:要 mock 的 wx.* API 名(如 showModal / request) */
|
|
30429
|
+
wxApi: external_exports.string().min(1).optional(),
|
|
30430
|
+
/** mockWxApi:mock 返回值(对象按原样注入;capture 时真实调用先上报再返回 mock 值) */
|
|
30431
|
+
result: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
30432
|
+
/** mockWxApi:捕获真实调用参数的变量名(${<capture>.options} / ${<capture>.called}) */
|
|
30433
|
+
capture: external_exports.string().min(1).optional(),
|
|
30434
|
+
/** trigger:触发的事件类型(如 getphonenumber;空 detail 即"用户拒绝/取消"类分支) */
|
|
30435
|
+
eventType: external_exports.string().min(1).optional(),
|
|
30436
|
+
/** trigger:作为事件 e.detail 注入(值支持 ${var} 模板) */
|
|
30437
|
+
detail: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
30438
|
+
/** setData:写入页面 data 的补丁(值支持 ${var} 模板) */
|
|
30439
|
+
patch: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
30440
|
+
/** assert:页面 data 路径断言(与 locator 互斥,仅 miniapp) */
|
|
30441
|
+
dataPath: external_exports.string().min(1).optional()
|
|
30420
30442
|
});
|
|
30421
30443
|
var datasetReferenceSchema = external_exports.strictObject({
|
|
30422
30444
|
file: external_exports.string().min(1),
|
|
@@ -30489,7 +30511,11 @@ var ADAPTER_SUPPORT = {
|
|
|
30489
30511
|
assert: ["web", "miniapp", "api"],
|
|
30490
30512
|
extract: ["web", "miniapp", "api"],
|
|
30491
30513
|
screenshot: ["web", "miniapp"],
|
|
30492
|
-
request: ["api"]
|
|
30514
|
+
request: ["api"],
|
|
30515
|
+
callMethod: ["miniapp"],
|
|
30516
|
+
mockWxApi: ["miniapp"],
|
|
30517
|
+
trigger: ["miniapp"],
|
|
30518
|
+
setData: ["miniapp"]
|
|
30493
30519
|
};
|
|
30494
30520
|
var LOCATOR_REQUIRED = /* @__PURE__ */ new Set(["click", "input", "select", "assert", "extract"]);
|
|
30495
30521
|
var VALUE_REQUIRED = /* @__PURE__ */ new Set(["input", "select"]);
|
|
@@ -30502,7 +30528,16 @@ var FIELD_ALLOWED = {
|
|
|
30502
30528
|
timeout: ["wait"],
|
|
30503
30529
|
method: ["request"],
|
|
30504
30530
|
headers: ["request"],
|
|
30505
|
-
body: ["request"]
|
|
30531
|
+
body: ["request"],
|
|
30532
|
+
methodName: ["callMethod"],
|
|
30533
|
+
args: ["callMethod"],
|
|
30534
|
+
wxApi: ["mockWxApi"],
|
|
30535
|
+
result: ["mockWxApi"],
|
|
30536
|
+
capture: ["mockWxApi"],
|
|
30537
|
+
eventType: ["trigger"],
|
|
30538
|
+
detail: ["trigger"],
|
|
30539
|
+
patch: ["setData"],
|
|
30540
|
+
dataPath: ["assert"]
|
|
30506
30541
|
};
|
|
30507
30542
|
var VARIABLE_REF = /\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g;
|
|
30508
30543
|
function validateCase(data) {
|
|
@@ -30566,6 +30601,23 @@ function checkStep(step, at, declared, issues) {
|
|
|
30566
30601
|
});
|
|
30567
30602
|
}
|
|
30568
30603
|
}
|
|
30604
|
+
if (step.action === "mockWxApi") {
|
|
30605
|
+
if (!step.wxApi) {
|
|
30606
|
+
issues.push({ path: `${at}.wxApi`, code: "semantic", message: "mockWxApi \u5FC5\u987B\u63D0\u4F9B wxApi(\u8981 mock \u7684 wx.* API \u540D)" });
|
|
30607
|
+
}
|
|
30608
|
+
if (!step.result) {
|
|
30609
|
+
issues.push({ path: `${at}.result`, code: "semantic", message: "mockWxApi \u5FC5\u987B\u63D0\u4F9B result(mock \u8FD4\u56DE\u503C)" });
|
|
30610
|
+
}
|
|
30611
|
+
}
|
|
30612
|
+
if (step.action === "trigger" && !step.eventType) {
|
|
30613
|
+
issues.push({ path: `${at}.eventType`, code: "semantic", message: "trigger \u5FC5\u987B\u63D0\u4F9B eventType(\u89E6\u53D1\u7684\u4E8B\u4EF6\u7C7B\u578B)" });
|
|
30614
|
+
}
|
|
30615
|
+
if (step.action === "setData" && (!step.patch || Object.keys(step.patch).length === 0)) {
|
|
30616
|
+
issues.push({ path: `${at}.patch`, code: "semantic", message: "setData \u5FC5\u987B\u63D0\u4F9B patch(\u5199\u5165\u9875\u9762 data \u7684\u8865\u4E01)" });
|
|
30617
|
+
}
|
|
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
|
+
}
|
|
30569
30621
|
if (step.action === "navigate" && !step.url) {
|
|
30570
30622
|
issues.push({ path: `${at}.url`, code: "semantic", message: "navigate \u5FC5\u987B\u63D0\u4F9B url" });
|
|
30571
30623
|
}
|
|
@@ -30597,6 +30649,13 @@ function checkStep(step, at, declared, issues) {
|
|
|
30597
30649
|
if (step.action === "wait" && !step.locator && step.timeout === void 0) {
|
|
30598
30650
|
issues.push({ path: at, code: "semantic", message: "wait \u9700\u8981\u63D0\u4F9B locator(\u7B49\u5F85\u5143\u7D20)\u6216 timeout(\u6BEB\u79D2)" });
|
|
30599
30651
|
}
|
|
30652
|
+
if (step.action === "callMethod" && !step.methodName) {
|
|
30653
|
+
issues.push({
|
|
30654
|
+
path: `${at}.methodName`,
|
|
30655
|
+
code: "semantic",
|
|
30656
|
+
message: "callMethod \u5FC5\u987B\u63D0\u4F9B methodName(\u9875\u9762\u6216\u7EC4\u4EF6\u7684\u65B9\u6CD5\u540D)"
|
|
30657
|
+
});
|
|
30658
|
+
}
|
|
30600
30659
|
if (step.expectRequests) {
|
|
30601
30660
|
if (step.target === "api" || !REQUEST_EXPECTATION_ACTIONS.has(step.action)) {
|
|
30602
30661
|
issues.push({
|
|
@@ -30638,11 +30697,17 @@ function checkVariableRefs(step, at, declared, issues) {
|
|
|
30638
30697
|
collect("value", step.value);
|
|
30639
30698
|
collect("expected", step.expected);
|
|
30640
30699
|
collect("url", step.url);
|
|
30700
|
+
collect("methodName", step.methodName);
|
|
30641
30701
|
collect("locator.text", step.locator?.text);
|
|
30642
30702
|
collect("locator.css", step.locator?.css);
|
|
30643
30703
|
if (typeof step.body === "string") collect("body", step.body);
|
|
30644
30704
|
else if (step.body) collect("body", JSON.stringify(step.body));
|
|
30645
30705
|
for (const headerValue of Object.values(step.headers ?? {})) collect("headers", headerValue);
|
|
30706
|
+
if (Array.isArray(step.args)) {
|
|
30707
|
+
step.args.forEach((arg, index) => {
|
|
30708
|
+
if (typeof arg === "string") collect(`args[${index}]`, arg);
|
|
30709
|
+
});
|
|
30710
|
+
}
|
|
30646
30711
|
for (const [index, expectation] of (step.expectRequests ?? []).entries()) {
|
|
30647
30712
|
collect(`expectRequests[${index}].urlContains`, expectation.urlContains);
|
|
30648
30713
|
}
|
|
@@ -31870,6 +31935,26 @@ var ExecutionContext = class {
|
|
|
31870
31935
|
}
|
|
31871
31936
|
this.variables.set("account", rawValue);
|
|
31872
31937
|
}
|
|
31938
|
+
/**
|
|
31939
|
+
* 注入结构化对象(如 mockWxApi 的捕获结果):按点号路径展开为平铺字符串变量。
|
|
31940
|
+
* 例如 seedObject('showModalCall', { called: true, args: { content: 'x' } }) 后,
|
|
31941
|
+
* `${showModalCall.called}` 与 `${showModalCall.args.content}` 均可解析。
|
|
31942
|
+
*/
|
|
31943
|
+
seedObject(name, value) {
|
|
31944
|
+
const flatten = (prefix, input2) => {
|
|
31945
|
+
if (input2 !== null && typeof input2 === "object" && !Array.isArray(input2)) {
|
|
31946
|
+
const entries = Object.entries(input2);
|
|
31947
|
+
if (entries.length === 0) {
|
|
31948
|
+
this.variables.set(prefix, "");
|
|
31949
|
+
return;
|
|
31950
|
+
}
|
|
31951
|
+
for (const [key, item] of entries) flatten(`${prefix}.${key}`, item);
|
|
31952
|
+
return;
|
|
31953
|
+
}
|
|
31954
|
+
this.variables.set(prefix, input2 === void 0 ? "" : String(input2));
|
|
31955
|
+
};
|
|
31956
|
+
flatten(name, value);
|
|
31957
|
+
}
|
|
31873
31958
|
};
|
|
31874
31959
|
|
|
31875
31960
|
// ../../packages/execution-engine/src/lifecycle/index.ts
|
|
@@ -32028,7 +32113,14 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32028
32113
|
case "wait":
|
|
32029
32114
|
await adapter.wait(locator, step.timeout);
|
|
32030
32115
|
break;
|
|
32031
|
-
case "assert":
|
|
32116
|
+
case "assert": {
|
|
32117
|
+
if (step.dataPath !== void 0) {
|
|
32118
|
+
if (!adapter.assertData) {
|
|
32119
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 dataPath \u65AD\u8A00`);
|
|
32120
|
+
}
|
|
32121
|
+
await adapter.assertData(context.resolve(step.dataPath), context.resolve(step.expected ?? ""));
|
|
32122
|
+
break;
|
|
32123
|
+
}
|
|
32032
32124
|
if (step.target === "api") {
|
|
32033
32125
|
if (!adapter.assertResponse) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api \u65AD\u8A00");
|
|
32034
32126
|
await adapter.assertResponse(context.resolve(step.expected ?? ""));
|
|
@@ -32036,6 +32128,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32036
32128
|
await adapter.assert(locator, context.resolve(step.expected ?? ""));
|
|
32037
32129
|
}
|
|
32038
32130
|
break;
|
|
32131
|
+
}
|
|
32039
32132
|
case "extract": {
|
|
32040
32133
|
const value = step.target === "api" ? await extractApiResponse(adapter, context.resolve(step.value ?? "")) : await adapter.extract(locator);
|
|
32041
32134
|
if (step.variable) {
|
|
@@ -32075,6 +32168,42 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32075
32168
|
}
|
|
32076
32169
|
break;
|
|
32077
32170
|
}
|
|
32171
|
+
case "callMethod": {
|
|
32172
|
+
if (typeof adapter.callMethod !== "function") {
|
|
32173
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 callMethod`);
|
|
32174
|
+
}
|
|
32175
|
+
const methodName = context.resolve(step.methodName ?? "");
|
|
32176
|
+
if (!methodName) throw new TestPilotError("MISSING_METHOD_NAME", "callMethod \u5FC5\u987B\u63D0\u4F9B methodName");
|
|
32177
|
+
const callArgs = (step.args ?? []).map((arg) => resolveDeep(arg, context));
|
|
32178
|
+
await adapter.callMethod(locator, methodName, callArgs);
|
|
32179
|
+
break;
|
|
32180
|
+
}
|
|
32181
|
+
case "mockWxApi": {
|
|
32182
|
+
if (typeof adapter.mockWxApi !== "function") {
|
|
32183
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 mockWxApi`);
|
|
32184
|
+
}
|
|
32185
|
+
await adapter.mockWxApi({
|
|
32186
|
+
api: context.resolve(step.wxApi ?? ""),
|
|
32187
|
+
result: step.result ? resolveDeep(step.result, context) : void 0,
|
|
32188
|
+
capture: step.capture
|
|
32189
|
+
});
|
|
32190
|
+
break;
|
|
32191
|
+
}
|
|
32192
|
+
case "trigger": {
|
|
32193
|
+
if (typeof adapter.trigger !== "function") {
|
|
32194
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 trigger`);
|
|
32195
|
+
}
|
|
32196
|
+
if (!locator) throw new TestPilotError("MISSING_LOCATOR", "trigger \u5FC5\u987B\u63D0\u4F9B locator(\u8981\u89E6\u53D1\u4E8B\u4EF6\u7684\u7EC4\u4EF6)");
|
|
32197
|
+
await adapter.trigger(locator, context.resolve(step.eventType ?? ""), step.detail ? resolveDeep(step.detail, context) : void 0);
|
|
32198
|
+
break;
|
|
32199
|
+
}
|
|
32200
|
+
case "setData": {
|
|
32201
|
+
if (typeof adapter.setData !== "function") {
|
|
32202
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 setData`);
|
|
32203
|
+
}
|
|
32204
|
+
await adapter.setData(resolveDeep(step.patch ?? {}, context));
|
|
32205
|
+
break;
|
|
32206
|
+
}
|
|
32078
32207
|
default: {
|
|
32079
32208
|
const exhaustive = step.action;
|
|
32080
32209
|
throw new TestPilotError("UNSUPPORTED_ACTION", `\u4E0D\u652F\u6301\u7684 action:${String(exhaustive)}`);
|
|
@@ -32263,6 +32392,12 @@ var ExecutionEngine = class {
|
|
|
32263
32392
|
result = await executeStep(step, index, adapter, context, artifacts, {
|
|
32264
32393
|
screenshotName: `${executionId}-${phase}-${String(index).padStart(2, "0")}`
|
|
32265
32394
|
});
|
|
32395
|
+
if (step.capture !== void 0 && typeof adapter.readWxApiCaptures === "function") {
|
|
32396
|
+
const captures = adapter.readWxApiCaptures();
|
|
32397
|
+
if (captures && captures.calls.length > 0) {
|
|
32398
|
+
context.seedObject(step.capture, captures.calls[0]?.options ?? null);
|
|
32399
|
+
}
|
|
32400
|
+
}
|
|
32266
32401
|
} catch (err) {
|
|
32267
32402
|
result = {
|
|
32268
32403
|
index,
|
|
@@ -32384,6 +32519,9 @@ var ExecutionEngine = class {
|
|
|
32384
32519
|
);
|
|
32385
32520
|
}
|
|
32386
32521
|
}
|
|
32522
|
+
for (const [, adapter] of evidenceAdapters) {
|
|
32523
|
+
await adapter.restoreWxMock?.().catch(() => void 0);
|
|
32524
|
+
}
|
|
32387
32525
|
await bus.emit({
|
|
32388
32526
|
type: "case-finished",
|
|
32389
32527
|
runId,
|
|
@@ -32547,6 +32685,10 @@ import { mkdtemp, readFile as readFile6, rm } from "node:fs/promises";
|
|
|
32547
32685
|
import { tmpdir } from "node:os";
|
|
32548
32686
|
import { join as join16 } from "node:path";
|
|
32549
32687
|
import { errors } from "playwright";
|
|
32688
|
+
var TEXT_WAIT_MS = 1e4;
|
|
32689
|
+
function delay2(ms) {
|
|
32690
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
32691
|
+
}
|
|
32550
32692
|
var PlaywrightAdapter = class {
|
|
32551
32693
|
constructor(bundle, options = {}) {
|
|
32552
32694
|
this.bundle = bundle;
|
|
@@ -32637,16 +32779,29 @@ var PlaywrightAdapter = class {
|
|
|
32637
32779
|
}
|
|
32638
32780
|
async assert(locator, expected) {
|
|
32639
32781
|
const target = await this.toLocator(locator);
|
|
32640
|
-
await this.coded(() => target.waitFor({ state: "visible" }));
|
|
32641
|
-
const actual = (await this.coded(() => target.innerText())).trim();
|
|
32642
32782
|
const wanted = expected.trim();
|
|
32643
|
-
|
|
32644
|
-
|
|
32783
|
+
const startedAt = Date.now();
|
|
32784
|
+
for (; ; ) {
|
|
32785
|
+
await this.coded(() => target.waitFor({ state: "visible" }));
|
|
32786
|
+
const actual = (await this.coded(() => target.innerText())).trim();
|
|
32787
|
+
if (actual.includes(wanted)) return;
|
|
32788
|
+
if (Date.now() - startedAt > TEXT_WAIT_MS) {
|
|
32789
|
+
throw new TestPilotError(
|
|
32790
|
+
"ASSERTION_FAILED",
|
|
32791
|
+
`\u65AD\u8A00\u5931\u8D25(\u7B49\u5F85 ${TEXT_WAIT_MS}ms):\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`
|
|
32792
|
+
);
|
|
32793
|
+
}
|
|
32794
|
+
await delay2(300);
|
|
32645
32795
|
}
|
|
32646
32796
|
}
|
|
32647
32797
|
async extract(locator) {
|
|
32648
32798
|
const target = await this.toLocator(locator);
|
|
32649
|
-
|
|
32799
|
+
const startedAt = Date.now();
|
|
32800
|
+
for (; ; ) {
|
|
32801
|
+
const text = (await this.coded(() => target.innerText())).trim();
|
|
32802
|
+
if (text.length > 0 || Date.now() - startedAt > TEXT_WAIT_MS) return text;
|
|
32803
|
+
await delay2(300);
|
|
32804
|
+
}
|
|
32650
32805
|
}
|
|
32651
32806
|
async screenshot() {
|
|
32652
32807
|
return (await this.ensurePage()).screenshot();
|
|
@@ -32711,6 +32866,8 @@ import net from "node:net";
|
|
|
32711
32866
|
var AUTO_PORT_FALLBACK_WAIT_MS = 9e4;
|
|
32712
32867
|
var NAVIGATE_RETRY_WINDOW_MS = 45e3;
|
|
32713
32868
|
var ELEMENT_WAIT_MS = 1e4;
|
|
32869
|
+
var TEXT_WAIT_MS2 = 1e4;
|
|
32870
|
+
var PAGE_READY_WAIT_MS = 3e4;
|
|
32714
32871
|
var CONNECTION_LOST = /connection closed|not connected/i;
|
|
32715
32872
|
var automatorLauncher = {
|
|
32716
32873
|
launch: async (options) => {
|
|
@@ -32724,7 +32881,7 @@ var automatorLauncher = {
|
|
|
32724
32881
|
}
|
|
32725
32882
|
}
|
|
32726
32883
|
};
|
|
32727
|
-
function
|
|
32884
|
+
function delay3(ms) {
|
|
32728
32885
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
32729
32886
|
}
|
|
32730
32887
|
async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
@@ -32735,7 +32892,7 @@ async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
|
32735
32892
|
return await automator.connect({ wsEndpoint });
|
|
32736
32893
|
} catch (err) {
|
|
32737
32894
|
lastError = err;
|
|
32738
|
-
await
|
|
32895
|
+
await delay3(1e3);
|
|
32739
32896
|
}
|
|
32740
32897
|
}
|
|
32741
32898
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -32804,14 +32961,31 @@ var MiniAppAdapter = class {
|
|
|
32804
32961
|
await this.runWithReconnect(async () => {
|
|
32805
32962
|
await (await this.getProgram()).reLaunch(url2);
|
|
32806
32963
|
});
|
|
32964
|
+
await this.waitForPageReady();
|
|
32807
32965
|
return;
|
|
32808
32966
|
} catch (err) {
|
|
32809
32967
|
lastError = err;
|
|
32810
|
-
await
|
|
32968
|
+
await delay3(3e3);
|
|
32811
32969
|
}
|
|
32812
32970
|
}
|
|
32813
32971
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
32814
32972
|
}
|
|
32973
|
+
/** 等待页面就绪:模拟器编译期间 currentPage / data 会失败或挂起,轮询至可响应 */
|
|
32974
|
+
async waitForPageReady(timeoutMs = PAGE_READY_WAIT_MS) {
|
|
32975
|
+
const startedAt = Date.now();
|
|
32976
|
+
for (; ; ) {
|
|
32977
|
+
const page = await (await this.getProgram()).currentPage();
|
|
32978
|
+
const data = await page.data().catch(() => void 0);
|
|
32979
|
+
if (data !== void 0 && data !== null) return;
|
|
32980
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
32981
|
+
throw new TestPilotError(
|
|
32982
|
+
"PAGE_READY_TIMEOUT",
|
|
32983
|
+
`\u7B49\u5F85\u9875\u9762\u5C31\u7EEA\u8D85\u65F6(${timeoutMs}ms):\u6A21\u62DF\u5668\u53EF\u80FD\u4ECD\u5728\u7F16\u8BD1,\u6216\u9879\u76EE\u672A\u88AB\u4FE1\u4EFB`
|
|
32984
|
+
);
|
|
32985
|
+
}
|
|
32986
|
+
await delay3(300);
|
|
32987
|
+
}
|
|
32988
|
+
}
|
|
32815
32989
|
async click(locator) {
|
|
32816
32990
|
await this.runWithReconnect(async () => (await this.element(locator)).tap());
|
|
32817
32991
|
}
|
|
@@ -32834,17 +33008,100 @@ var MiniAppAdapter = class {
|
|
|
32834
33008
|
async assert(locator, expected) {
|
|
32835
33009
|
await this.runWithReconnect(async () => {
|
|
32836
33010
|
const element = await this.element(locator);
|
|
32837
|
-
const actual = (await element.text()).trim();
|
|
32838
33011
|
const wanted = expected.trim();
|
|
32839
|
-
|
|
32840
|
-
|
|
33012
|
+
const startedAt = Date.now();
|
|
33013
|
+
for (; ; ) {
|
|
33014
|
+
const actual = (await element.text()).trim();
|
|
33015
|
+
if (actual.includes(wanted)) return;
|
|
33016
|
+
if (Date.now() - startedAt > TEXT_WAIT_MS2) {
|
|
33017
|
+
throw new TestPilotError(
|
|
33018
|
+
"ASSERTION_FAILED",
|
|
33019
|
+
`\u65AD\u8A00\u5931\u8D25(\u7B49\u5F85 ${TEXT_WAIT_MS2}ms):\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`
|
|
33020
|
+
);
|
|
33021
|
+
}
|
|
33022
|
+
await delay3(300);
|
|
32841
33023
|
}
|
|
32842
33024
|
});
|
|
32843
33025
|
}
|
|
32844
33026
|
async extract(locator) {
|
|
32845
33027
|
return this.runWithReconnect(async () => {
|
|
32846
33028
|
const element = await this.element(locator);
|
|
32847
|
-
|
|
33029
|
+
const startedAt = Date.now();
|
|
33030
|
+
for (; ; ) {
|
|
33031
|
+
const text = (await element.text()).trim();
|
|
33032
|
+
if (text.length > 0 || Date.now() - startedAt > TEXT_WAIT_MS2) return text;
|
|
33033
|
+
await delay3(300);
|
|
33034
|
+
}
|
|
33035
|
+
});
|
|
33036
|
+
}
|
|
33037
|
+
/** 调用 Page / 组件方法;不传 locator 时为页面级调用(Page 实例方法) */
|
|
33038
|
+
async callMethod(locator, methodName, args = []) {
|
|
33039
|
+
return this.runWithReconnect(async () => {
|
|
33040
|
+
if (locator) {
|
|
33041
|
+
const element = await this.element(locator);
|
|
33042
|
+
return element.callMethod(methodName, ...args);
|
|
33043
|
+
}
|
|
33044
|
+
const page = await (await this.getProgram()).currentPage();
|
|
33045
|
+
return page.callMethod(methodName, ...args);
|
|
33046
|
+
});
|
|
33047
|
+
}
|
|
33048
|
+
// ---- miniapp 深度测试(V0.2)----
|
|
33049
|
+
wxMock;
|
|
33050
|
+
/** mock 任意 wx.* API:拦截调用并返回 mock 值;capture 记录真实调用参数(最近调用在前) */
|
|
33051
|
+
async mockWxApi(options) {
|
|
33052
|
+
if (this.wxMock?.api === options.api) await this.restoreWxMock();
|
|
33053
|
+
const program2 = await this.getProgram();
|
|
33054
|
+
const captured = [];
|
|
33055
|
+
this.wxMock = { api: options.api, captured };
|
|
33056
|
+
const callbackName = options.capture ? `__tpWxCapture_${options.capture}` : void 0;
|
|
33057
|
+
if (callbackName) {
|
|
33058
|
+
await program2.exposeFunction(callbackName, (payload) => {
|
|
33059
|
+
if (payload !== null && typeof payload === "object") {
|
|
33060
|
+
captured.push(payload);
|
|
33061
|
+
}
|
|
33062
|
+
});
|
|
33063
|
+
}
|
|
33064
|
+
await program2.mockWxMethod(
|
|
33065
|
+
options.api,
|
|
33066
|
+
function(invokeOptions, cbName) {
|
|
33067
|
+
const callbacks = globalThis;
|
|
33068
|
+
callbacks[cbName]?.({ options: invokeOptions });
|
|
33069
|
+
return options.result ?? this.origin(invokeOptions);
|
|
33070
|
+
},
|
|
33071
|
+
callbackName
|
|
33072
|
+
);
|
|
33073
|
+
}
|
|
33074
|
+
/** 读取 mock 期间捕获的调用参数(最近调用在前) */
|
|
33075
|
+
readCapturedWxApiCalls(api) {
|
|
33076
|
+
return this.wxMock?.api === api ? [...this.wxMock.captured].reverse() : [];
|
|
33077
|
+
}
|
|
33078
|
+
/** 恢复 mock 的 wx API(用例结束由引擎调用) */
|
|
33079
|
+
async restoreWxMock() {
|
|
33080
|
+
const mock = this.wxMock;
|
|
33081
|
+
if (!mock) return;
|
|
33082
|
+
this.wxMock = void 0;
|
|
33083
|
+
if (!this.program) return;
|
|
33084
|
+
await this.program.restoreWxMethod(mock.api).catch(() => void 0);
|
|
33085
|
+
}
|
|
33086
|
+
/** 触发组件事件(trigger;detail 作为 e.detail 注入,空 detail 即"用户拒绝/取消"类分支) */
|
|
33087
|
+
async trigger(locator, eventType, detail) {
|
|
33088
|
+
await this.runWithReconnect(async () => {
|
|
33089
|
+
const element = await this.element(locator);
|
|
33090
|
+
await element.trigger(eventType, detail);
|
|
33091
|
+
});
|
|
33092
|
+
}
|
|
33093
|
+
/** 读取页面 data(点号路径,缺省为整个 data 对象) */
|
|
33094
|
+
async getData(dataPath) {
|
|
33095
|
+
return this.runWithReconnect(async () => {
|
|
33096
|
+
const page = await (await this.getProgram()).currentPage();
|
|
33097
|
+
return page.data(dataPath);
|
|
33098
|
+
});
|
|
33099
|
+
}
|
|
33100
|
+
/** 写入页面 data 补丁(setData 驱动 onShow/onPullDownRefresh 等真实链路) */
|
|
33101
|
+
async setData(patch) {
|
|
33102
|
+
await this.runWithReconnect(async () => {
|
|
33103
|
+
const page = await (await this.getProgram()).currentPage();
|
|
33104
|
+
await page.setData(patch);
|
|
32848
33105
|
});
|
|
32849
33106
|
}
|
|
32850
33107
|
async screenshot() {
|
|
@@ -32937,7 +33194,7 @@ var MiniAppAdapter = class {
|
|
|
32937
33194
|
if (Date.now() - startedAt > ELEMENT_WAIT_MS) {
|
|
32938
33195
|
throw new TestPilotError("ELEMENT_NOT_FOUND", `\u7B49\u5F85\u5143\u7D20\u8D85\u65F6(${ELEMENT_WAIT_MS}ms):${selector}`);
|
|
32939
33196
|
}
|
|
32940
|
-
await
|
|
33197
|
+
await delay3(500);
|
|
32941
33198
|
}
|
|
32942
33199
|
}
|
|
32943
33200
|
};
|
|
@@ -33417,7 +33674,7 @@ async function collectEnvVarRefs(root) {
|
|
|
33417
33674
|
}
|
|
33418
33675
|
|
|
33419
33676
|
// src/commands/init/project-detector.ts
|
|
33420
|
-
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
33677
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
33421
33678
|
import { basename, join as join22 } from "node:path";
|
|
33422
33679
|
|
|
33423
33680
|
// src/lib/git.ts
|
|
@@ -33473,10 +33730,31 @@ function detectProjectType(root) {
|
|
|
33473
33730
|
if (isWeb) return "web";
|
|
33474
33731
|
return "unknown";
|
|
33475
33732
|
}
|
|
33733
|
+
function detectLanguageFromSources(root) {
|
|
33734
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "miniprogram_npm", "dist", ".git"]);
|
|
33735
|
+
const found = { ts: false, js: false };
|
|
33736
|
+
const walk = (dir, depth) => {
|
|
33737
|
+
if (depth > 4 || found.ts && found.js) return;
|
|
33738
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
33739
|
+
if (entry.name.startsWith(".")) continue;
|
|
33740
|
+
const full = join22(dir, entry.name);
|
|
33741
|
+
if (entry.isDirectory()) {
|
|
33742
|
+
if (!SKIP_DIRS.has(entry.name)) walk(full, depth + 1);
|
|
33743
|
+
continue;
|
|
33744
|
+
}
|
|
33745
|
+
if (/\.(ts|tsx)$/.test(entry.name)) found.ts = true;
|
|
33746
|
+
else if (/\.(js|cjs|mjs|wxml)$/.test(entry.name)) found.js = true;
|
|
33747
|
+
}
|
|
33748
|
+
};
|
|
33749
|
+
walk(root, 0);
|
|
33750
|
+
if (found.ts) return "TypeScript";
|
|
33751
|
+
if (found.js) return "JavaScript";
|
|
33752
|
+
return "Unknown";
|
|
33753
|
+
}
|
|
33476
33754
|
async function detectProject(root) {
|
|
33477
33755
|
const pkg = readPackageJson(root);
|
|
33478
33756
|
const type = detectProjectType(root);
|
|
33479
|
-
const language = existsSync6(join22(root, "tsconfig.json")) ? "TypeScript" : pkg || existsSync6(join22(root, "src")) ? "JavaScript" :
|
|
33757
|
+
const language = existsSync6(join22(root, "tsconfig.json")) ? "TypeScript" : pkg || existsSync6(join22(root, "src")) ? "JavaScript" : detectLanguageFromSources(root);
|
|
33480
33758
|
const packageManager = existsSync6(join22(root, "pnpm-lock.yaml")) ? "pnpm" : existsSync6(join22(root, "yarn.lock")) ? "yarn" : existsSync6(join22(root, "package-lock.json")) ? "npm" : "npx";
|
|
33481
33759
|
const existingE2eDirs = E2E_DIR_CANDIDATES.filter((dir) => existsSync6(join22(root, dir)));
|
|
33482
33760
|
return {
|
|
@@ -33956,7 +34234,7 @@ function randomId() {
|
|
|
33956
34234
|
}
|
|
33957
34235
|
|
|
33958
34236
|
// src/commands/init/test-structure.ts
|
|
33959
|
-
import { existsSync as existsSync9, readdirSync as
|
|
34237
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
|
|
33960
34238
|
import { mkdir as mkdir14, writeFile as writeFile12 } from "node:fs/promises";
|
|
33961
34239
|
import { join as join26 } from "node:path";
|
|
33962
34240
|
async function initTestStructure(root, _info) {
|
|
@@ -33976,7 +34254,7 @@ async function initTestStructure(root, _info) {
|
|
|
33976
34254
|
await mkdir14(dir, { recursive: true });
|
|
33977
34255
|
}
|
|
33978
34256
|
for (const [name, dir] of Object.entries(dirs)) {
|
|
33979
|
-
if (!existed[name] &&
|
|
34257
|
+
if (!existed[name] && readdirSync3(dir).length === 0) {
|
|
33980
34258
|
await writeFile12(join26(dir, ".gitkeep"), "", "utf8").catch(() => void 0);
|
|
33981
34259
|
}
|
|
33982
34260
|
}
|
|
@@ -34351,7 +34629,7 @@ function makeValidateCommand() {
|
|
|
34351
34629
|
}
|
|
34352
34630
|
|
|
34353
34631
|
// src/cli.ts
|
|
34354
|
-
var CLI_VERSION = "0.7.
|
|
34632
|
+
var CLI_VERSION = "0.7.1";
|
|
34355
34633
|
async function createCli() {
|
|
34356
34634
|
const program2 = new Command();
|
|
34357
34635
|
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", () => {
|
package/dist/index.js
CHANGED
|
@@ -30387,7 +30387,11 @@ var ACTIONS = [
|
|
|
30387
30387
|
"assert",
|
|
30388
30388
|
"extract",
|
|
30389
30389
|
"screenshot",
|
|
30390
|
-
"request"
|
|
30390
|
+
"request",
|
|
30391
|
+
"callMethod",
|
|
30392
|
+
"mockWxApi",
|
|
30393
|
+
"trigger",
|
|
30394
|
+
"setData"
|
|
30391
30395
|
];
|
|
30392
30396
|
var locatorSchema = external_exports.strictObject({
|
|
30393
30397
|
text: external_exports.string().min(1).optional(),
|
|
@@ -30415,7 +30419,25 @@ var stepSchema = external_exports.strictObject({
|
|
|
30415
30419
|
variable: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "\u53D8\u91CF\u540D\u9700\u4EE5\u5B57\u6BCD\u6216\u4E0B\u5212\u7EBF\u5F00\u5934,\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF").optional(),
|
|
30416
30420
|
timeout: external_exports.number().int().positive().optional(),
|
|
30417
30421
|
/** UI 动作触发的网络请求精确次数断言 */
|
|
30418
|
-
expectRequests: external_exports.array(requestExpectationSchema).min(1).optional()
|
|
30422
|
+
expectRequests: external_exports.array(requestExpectationSchema).min(1).optional(),
|
|
30423
|
+
/** callMethod:要调用的 Page / 组件方法名(仅 miniapp;页面级调用不写 locator) */
|
|
30424
|
+
methodName: external_exports.string().min(1).optional(),
|
|
30425
|
+
/** callMethod 的入参(支持 ${var} 模板) */
|
|
30426
|
+
args: external_exports.array(external_exports.unknown()).optional(),
|
|
30427
|
+
/** mockWxApi:要 mock 的 wx.* API 名(如 showModal / request) */
|
|
30428
|
+
wxApi: external_exports.string().min(1).optional(),
|
|
30429
|
+
/** mockWxApi:mock 返回值(对象按原样注入;capture 时真实调用先上报再返回 mock 值) */
|
|
30430
|
+
result: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
30431
|
+
/** mockWxApi:捕获真实调用参数的变量名(${<capture>.options} / ${<capture>.called}) */
|
|
30432
|
+
capture: external_exports.string().min(1).optional(),
|
|
30433
|
+
/** trigger:触发的事件类型(如 getphonenumber;空 detail 即"用户拒绝/取消"类分支) */
|
|
30434
|
+
eventType: external_exports.string().min(1).optional(),
|
|
30435
|
+
/** trigger:作为事件 e.detail 注入(值支持 ${var} 模板) */
|
|
30436
|
+
detail: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
30437
|
+
/** setData:写入页面 data 的补丁(值支持 ${var} 模板) */
|
|
30438
|
+
patch: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
30439
|
+
/** assert:页面 data 路径断言(与 locator 互斥,仅 miniapp) */
|
|
30440
|
+
dataPath: external_exports.string().min(1).optional()
|
|
30419
30441
|
});
|
|
30420
30442
|
var datasetReferenceSchema = external_exports.strictObject({
|
|
30421
30443
|
file: external_exports.string().min(1),
|
|
@@ -30488,7 +30510,11 @@ var ADAPTER_SUPPORT = {
|
|
|
30488
30510
|
assert: ["web", "miniapp", "api"],
|
|
30489
30511
|
extract: ["web", "miniapp", "api"],
|
|
30490
30512
|
screenshot: ["web", "miniapp"],
|
|
30491
|
-
request: ["api"]
|
|
30513
|
+
request: ["api"],
|
|
30514
|
+
callMethod: ["miniapp"],
|
|
30515
|
+
mockWxApi: ["miniapp"],
|
|
30516
|
+
trigger: ["miniapp"],
|
|
30517
|
+
setData: ["miniapp"]
|
|
30492
30518
|
};
|
|
30493
30519
|
var LOCATOR_REQUIRED = /* @__PURE__ */ new Set(["click", "input", "select", "assert", "extract"]);
|
|
30494
30520
|
var VALUE_REQUIRED = /* @__PURE__ */ new Set(["input", "select"]);
|
|
@@ -30501,7 +30527,16 @@ var FIELD_ALLOWED = {
|
|
|
30501
30527
|
timeout: ["wait"],
|
|
30502
30528
|
method: ["request"],
|
|
30503
30529
|
headers: ["request"],
|
|
30504
|
-
body: ["request"]
|
|
30530
|
+
body: ["request"],
|
|
30531
|
+
methodName: ["callMethod"],
|
|
30532
|
+
args: ["callMethod"],
|
|
30533
|
+
wxApi: ["mockWxApi"],
|
|
30534
|
+
result: ["mockWxApi"],
|
|
30535
|
+
capture: ["mockWxApi"],
|
|
30536
|
+
eventType: ["trigger"],
|
|
30537
|
+
detail: ["trigger"],
|
|
30538
|
+
patch: ["setData"],
|
|
30539
|
+
dataPath: ["assert"]
|
|
30505
30540
|
};
|
|
30506
30541
|
var VARIABLE_REF = /\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g;
|
|
30507
30542
|
function validateCase(data) {
|
|
@@ -30565,6 +30600,23 @@ function checkStep(step, at, declared, issues) {
|
|
|
30565
30600
|
});
|
|
30566
30601
|
}
|
|
30567
30602
|
}
|
|
30603
|
+
if (step.action === "mockWxApi") {
|
|
30604
|
+
if (!step.wxApi) {
|
|
30605
|
+
issues.push({ path: `${at}.wxApi`, code: "semantic", message: "mockWxApi \u5FC5\u987B\u63D0\u4F9B wxApi(\u8981 mock \u7684 wx.* API \u540D)" });
|
|
30606
|
+
}
|
|
30607
|
+
if (!step.result) {
|
|
30608
|
+
issues.push({ path: `${at}.result`, code: "semantic", message: "mockWxApi \u5FC5\u987B\u63D0\u4F9B result(mock \u8FD4\u56DE\u503C)" });
|
|
30609
|
+
}
|
|
30610
|
+
}
|
|
30611
|
+
if (step.action === "trigger" && !step.eventType) {
|
|
30612
|
+
issues.push({ path: `${at}.eventType`, code: "semantic", message: "trigger \u5FC5\u987B\u63D0\u4F9B eventType(\u89E6\u53D1\u7684\u4E8B\u4EF6\u7C7B\u578B)" });
|
|
30613
|
+
}
|
|
30614
|
+
if (step.action === "setData" && (!step.patch || Object.keys(step.patch).length === 0)) {
|
|
30615
|
+
issues.push({ path: `${at}.patch`, code: "semantic", message: "setData \u5FC5\u987B\u63D0\u4F9B patch(\u5199\u5165\u9875\u9762 data \u7684\u8865\u4E01)" });
|
|
30616
|
+
}
|
|
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
|
+
}
|
|
30568
30620
|
if (step.action === "navigate" && !step.url) {
|
|
30569
30621
|
issues.push({ path: `${at}.url`, code: "semantic", message: "navigate \u5FC5\u987B\u63D0\u4F9B url" });
|
|
30570
30622
|
}
|
|
@@ -30596,6 +30648,13 @@ function checkStep(step, at, declared, issues) {
|
|
|
30596
30648
|
if (step.action === "wait" && !step.locator && step.timeout === void 0) {
|
|
30597
30649
|
issues.push({ path: at, code: "semantic", message: "wait \u9700\u8981\u63D0\u4F9B locator(\u7B49\u5F85\u5143\u7D20)\u6216 timeout(\u6BEB\u79D2)" });
|
|
30598
30650
|
}
|
|
30651
|
+
if (step.action === "callMethod" && !step.methodName) {
|
|
30652
|
+
issues.push({
|
|
30653
|
+
path: `${at}.methodName`,
|
|
30654
|
+
code: "semantic",
|
|
30655
|
+
message: "callMethod \u5FC5\u987B\u63D0\u4F9B methodName(\u9875\u9762\u6216\u7EC4\u4EF6\u7684\u65B9\u6CD5\u540D)"
|
|
30656
|
+
});
|
|
30657
|
+
}
|
|
30599
30658
|
if (step.expectRequests) {
|
|
30600
30659
|
if (step.target === "api" || !REQUEST_EXPECTATION_ACTIONS.has(step.action)) {
|
|
30601
30660
|
issues.push({
|
|
@@ -30637,11 +30696,17 @@ function checkVariableRefs(step, at, declared, issues) {
|
|
|
30637
30696
|
collect("value", step.value);
|
|
30638
30697
|
collect("expected", step.expected);
|
|
30639
30698
|
collect("url", step.url);
|
|
30699
|
+
collect("methodName", step.methodName);
|
|
30640
30700
|
collect("locator.text", step.locator?.text);
|
|
30641
30701
|
collect("locator.css", step.locator?.css);
|
|
30642
30702
|
if (typeof step.body === "string") collect("body", step.body);
|
|
30643
30703
|
else if (step.body) collect("body", JSON.stringify(step.body));
|
|
30644
30704
|
for (const headerValue of Object.values(step.headers ?? {})) collect("headers", headerValue);
|
|
30705
|
+
if (Array.isArray(step.args)) {
|
|
30706
|
+
step.args.forEach((arg, index) => {
|
|
30707
|
+
if (typeof arg === "string") collect(`args[${index}]`, arg);
|
|
30708
|
+
});
|
|
30709
|
+
}
|
|
30645
30710
|
for (const [index, expectation] of (step.expectRequests ?? []).entries()) {
|
|
30646
30711
|
collect(`expectRequests[${index}].urlContains`, expectation.urlContains);
|
|
30647
30712
|
}
|
|
@@ -31869,6 +31934,26 @@ var ExecutionContext = class {
|
|
|
31869
31934
|
}
|
|
31870
31935
|
this.variables.set("account", rawValue);
|
|
31871
31936
|
}
|
|
31937
|
+
/**
|
|
31938
|
+
* 注入结构化对象(如 mockWxApi 的捕获结果):按点号路径展开为平铺字符串变量。
|
|
31939
|
+
* 例如 seedObject('showModalCall', { called: true, args: { content: 'x' } }) 后,
|
|
31940
|
+
* `${showModalCall.called}` 与 `${showModalCall.args.content}` 均可解析。
|
|
31941
|
+
*/
|
|
31942
|
+
seedObject(name, value) {
|
|
31943
|
+
const flatten = (prefix, input2) => {
|
|
31944
|
+
if (input2 !== null && typeof input2 === "object" && !Array.isArray(input2)) {
|
|
31945
|
+
const entries = Object.entries(input2);
|
|
31946
|
+
if (entries.length === 0) {
|
|
31947
|
+
this.variables.set(prefix, "");
|
|
31948
|
+
return;
|
|
31949
|
+
}
|
|
31950
|
+
for (const [key, item] of entries) flatten(`${prefix}.${key}`, item);
|
|
31951
|
+
return;
|
|
31952
|
+
}
|
|
31953
|
+
this.variables.set(prefix, input2 === void 0 ? "" : String(input2));
|
|
31954
|
+
};
|
|
31955
|
+
flatten(name, value);
|
|
31956
|
+
}
|
|
31872
31957
|
};
|
|
31873
31958
|
|
|
31874
31959
|
// ../../packages/execution-engine/src/lifecycle/index.ts
|
|
@@ -32027,7 +32112,14 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32027
32112
|
case "wait":
|
|
32028
32113
|
await adapter.wait(locator, step.timeout);
|
|
32029
32114
|
break;
|
|
32030
|
-
case "assert":
|
|
32115
|
+
case "assert": {
|
|
32116
|
+
if (step.dataPath !== void 0) {
|
|
32117
|
+
if (!adapter.assertData) {
|
|
32118
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 dataPath \u65AD\u8A00`);
|
|
32119
|
+
}
|
|
32120
|
+
await adapter.assertData(context.resolve(step.dataPath), context.resolve(step.expected ?? ""));
|
|
32121
|
+
break;
|
|
32122
|
+
}
|
|
32031
32123
|
if (step.target === "api") {
|
|
32032
32124
|
if (!adapter.assertResponse) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api \u65AD\u8A00");
|
|
32033
32125
|
await adapter.assertResponse(context.resolve(step.expected ?? ""));
|
|
@@ -32035,6 +32127,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32035
32127
|
await adapter.assert(locator, context.resolve(step.expected ?? ""));
|
|
32036
32128
|
}
|
|
32037
32129
|
break;
|
|
32130
|
+
}
|
|
32038
32131
|
case "extract": {
|
|
32039
32132
|
const value = step.target === "api" ? await extractApiResponse(adapter, context.resolve(step.value ?? "")) : await adapter.extract(locator);
|
|
32040
32133
|
if (step.variable) {
|
|
@@ -32074,6 +32167,42 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32074
32167
|
}
|
|
32075
32168
|
break;
|
|
32076
32169
|
}
|
|
32170
|
+
case "callMethod": {
|
|
32171
|
+
if (typeof adapter.callMethod !== "function") {
|
|
32172
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 callMethod`);
|
|
32173
|
+
}
|
|
32174
|
+
const methodName = context.resolve(step.methodName ?? "");
|
|
32175
|
+
if (!methodName) throw new TestPilotError("MISSING_METHOD_NAME", "callMethod \u5FC5\u987B\u63D0\u4F9B methodName");
|
|
32176
|
+
const callArgs = (step.args ?? []).map((arg) => resolveDeep(arg, context));
|
|
32177
|
+
await adapter.callMethod(locator, methodName, callArgs);
|
|
32178
|
+
break;
|
|
32179
|
+
}
|
|
32180
|
+
case "mockWxApi": {
|
|
32181
|
+
if (typeof adapter.mockWxApi !== "function") {
|
|
32182
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 mockWxApi`);
|
|
32183
|
+
}
|
|
32184
|
+
await adapter.mockWxApi({
|
|
32185
|
+
api: context.resolve(step.wxApi ?? ""),
|
|
32186
|
+
result: step.result ? resolveDeep(step.result, context) : void 0,
|
|
32187
|
+
capture: step.capture
|
|
32188
|
+
});
|
|
32189
|
+
break;
|
|
32190
|
+
}
|
|
32191
|
+
case "trigger": {
|
|
32192
|
+
if (typeof adapter.trigger !== "function") {
|
|
32193
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 trigger`);
|
|
32194
|
+
}
|
|
32195
|
+
if (!locator) throw new TestPilotError("MISSING_LOCATOR", "trigger \u5FC5\u987B\u63D0\u4F9B locator(\u8981\u89E6\u53D1\u4E8B\u4EF6\u7684\u7EC4\u4EF6)");
|
|
32196
|
+
await adapter.trigger(locator, context.resolve(step.eventType ?? ""), step.detail ? resolveDeep(step.detail, context) : void 0);
|
|
32197
|
+
break;
|
|
32198
|
+
}
|
|
32199
|
+
case "setData": {
|
|
32200
|
+
if (typeof adapter.setData !== "function") {
|
|
32201
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301 setData`);
|
|
32202
|
+
}
|
|
32203
|
+
await adapter.setData(resolveDeep(step.patch ?? {}, context));
|
|
32204
|
+
break;
|
|
32205
|
+
}
|
|
32077
32206
|
default: {
|
|
32078
32207
|
const exhaustive = step.action;
|
|
32079
32208
|
throw new TestPilotError("UNSUPPORTED_ACTION", `\u4E0D\u652F\u6301\u7684 action:${String(exhaustive)}`);
|
|
@@ -32262,6 +32391,12 @@ var ExecutionEngine = class {
|
|
|
32262
32391
|
result = await executeStep(step, index, adapter, context, artifacts, {
|
|
32263
32392
|
screenshotName: `${executionId}-${phase}-${String(index).padStart(2, "0")}`
|
|
32264
32393
|
});
|
|
32394
|
+
if (step.capture !== void 0 && typeof adapter.readWxApiCaptures === "function") {
|
|
32395
|
+
const captures = adapter.readWxApiCaptures();
|
|
32396
|
+
if (captures && captures.calls.length > 0) {
|
|
32397
|
+
context.seedObject(step.capture, captures.calls[0]?.options ?? null);
|
|
32398
|
+
}
|
|
32399
|
+
}
|
|
32265
32400
|
} catch (err) {
|
|
32266
32401
|
result = {
|
|
32267
32402
|
index,
|
|
@@ -32383,6 +32518,9 @@ var ExecutionEngine = class {
|
|
|
32383
32518
|
);
|
|
32384
32519
|
}
|
|
32385
32520
|
}
|
|
32521
|
+
for (const [, adapter] of evidenceAdapters) {
|
|
32522
|
+
await adapter.restoreWxMock?.().catch(() => void 0);
|
|
32523
|
+
}
|
|
32386
32524
|
await bus.emit({
|
|
32387
32525
|
type: "case-finished",
|
|
32388
32526
|
runId,
|
|
@@ -32546,6 +32684,10 @@ import { mkdtemp, readFile as readFile6, rm } from "node:fs/promises";
|
|
|
32546
32684
|
import { tmpdir } from "node:os";
|
|
32547
32685
|
import { join as join16 } from "node:path";
|
|
32548
32686
|
import { errors } from "playwright";
|
|
32687
|
+
var TEXT_WAIT_MS = 1e4;
|
|
32688
|
+
function delay2(ms) {
|
|
32689
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
32690
|
+
}
|
|
32549
32691
|
var PlaywrightAdapter = class {
|
|
32550
32692
|
constructor(bundle, options = {}) {
|
|
32551
32693
|
this.bundle = bundle;
|
|
@@ -32636,16 +32778,29 @@ var PlaywrightAdapter = class {
|
|
|
32636
32778
|
}
|
|
32637
32779
|
async assert(locator, expected) {
|
|
32638
32780
|
const target = await this.toLocator(locator);
|
|
32639
|
-
await this.coded(() => target.waitFor({ state: "visible" }));
|
|
32640
|
-
const actual = (await this.coded(() => target.innerText())).trim();
|
|
32641
32781
|
const wanted = expected.trim();
|
|
32642
|
-
|
|
32643
|
-
|
|
32782
|
+
const startedAt = Date.now();
|
|
32783
|
+
for (; ; ) {
|
|
32784
|
+
await this.coded(() => target.waitFor({ state: "visible" }));
|
|
32785
|
+
const actual = (await this.coded(() => target.innerText())).trim();
|
|
32786
|
+
if (actual.includes(wanted)) return;
|
|
32787
|
+
if (Date.now() - startedAt > TEXT_WAIT_MS) {
|
|
32788
|
+
throw new TestPilotError(
|
|
32789
|
+
"ASSERTION_FAILED",
|
|
32790
|
+
`\u65AD\u8A00\u5931\u8D25(\u7B49\u5F85 ${TEXT_WAIT_MS}ms):\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`
|
|
32791
|
+
);
|
|
32792
|
+
}
|
|
32793
|
+
await delay2(300);
|
|
32644
32794
|
}
|
|
32645
32795
|
}
|
|
32646
32796
|
async extract(locator) {
|
|
32647
32797
|
const target = await this.toLocator(locator);
|
|
32648
|
-
|
|
32798
|
+
const startedAt = Date.now();
|
|
32799
|
+
for (; ; ) {
|
|
32800
|
+
const text = (await this.coded(() => target.innerText())).trim();
|
|
32801
|
+
if (text.length > 0 || Date.now() - startedAt > TEXT_WAIT_MS) return text;
|
|
32802
|
+
await delay2(300);
|
|
32803
|
+
}
|
|
32649
32804
|
}
|
|
32650
32805
|
async screenshot() {
|
|
32651
32806
|
return (await this.ensurePage()).screenshot();
|
|
@@ -32710,6 +32865,8 @@ import net from "node:net";
|
|
|
32710
32865
|
var AUTO_PORT_FALLBACK_WAIT_MS = 9e4;
|
|
32711
32866
|
var NAVIGATE_RETRY_WINDOW_MS = 45e3;
|
|
32712
32867
|
var ELEMENT_WAIT_MS = 1e4;
|
|
32868
|
+
var TEXT_WAIT_MS2 = 1e4;
|
|
32869
|
+
var PAGE_READY_WAIT_MS = 3e4;
|
|
32713
32870
|
var CONNECTION_LOST = /connection closed|not connected/i;
|
|
32714
32871
|
var automatorLauncher = {
|
|
32715
32872
|
launch: async (options) => {
|
|
@@ -32723,7 +32880,7 @@ var automatorLauncher = {
|
|
|
32723
32880
|
}
|
|
32724
32881
|
}
|
|
32725
32882
|
};
|
|
32726
|
-
function
|
|
32883
|
+
function delay3(ms) {
|
|
32727
32884
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
32728
32885
|
}
|
|
32729
32886
|
async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
@@ -32734,7 +32891,7 @@ async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
|
32734
32891
|
return await automator.connect({ wsEndpoint });
|
|
32735
32892
|
} catch (err) {
|
|
32736
32893
|
lastError = err;
|
|
32737
|
-
await
|
|
32894
|
+
await delay3(1e3);
|
|
32738
32895
|
}
|
|
32739
32896
|
}
|
|
32740
32897
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -32803,14 +32960,31 @@ var MiniAppAdapter = class {
|
|
|
32803
32960
|
await this.runWithReconnect(async () => {
|
|
32804
32961
|
await (await this.getProgram()).reLaunch(url2);
|
|
32805
32962
|
});
|
|
32963
|
+
await this.waitForPageReady();
|
|
32806
32964
|
return;
|
|
32807
32965
|
} catch (err) {
|
|
32808
32966
|
lastError = err;
|
|
32809
|
-
await
|
|
32967
|
+
await delay3(3e3);
|
|
32810
32968
|
}
|
|
32811
32969
|
}
|
|
32812
32970
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
32813
32971
|
}
|
|
32972
|
+
/** 等待页面就绪:模拟器编译期间 currentPage / data 会失败或挂起,轮询至可响应 */
|
|
32973
|
+
async waitForPageReady(timeoutMs = PAGE_READY_WAIT_MS) {
|
|
32974
|
+
const startedAt = Date.now();
|
|
32975
|
+
for (; ; ) {
|
|
32976
|
+
const page = await (await this.getProgram()).currentPage();
|
|
32977
|
+
const data = await page.data().catch(() => void 0);
|
|
32978
|
+
if (data !== void 0 && data !== null) return;
|
|
32979
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
32980
|
+
throw new TestPilotError(
|
|
32981
|
+
"PAGE_READY_TIMEOUT",
|
|
32982
|
+
`\u7B49\u5F85\u9875\u9762\u5C31\u7EEA\u8D85\u65F6(${timeoutMs}ms):\u6A21\u62DF\u5668\u53EF\u80FD\u4ECD\u5728\u7F16\u8BD1,\u6216\u9879\u76EE\u672A\u88AB\u4FE1\u4EFB`
|
|
32983
|
+
);
|
|
32984
|
+
}
|
|
32985
|
+
await delay3(300);
|
|
32986
|
+
}
|
|
32987
|
+
}
|
|
32814
32988
|
async click(locator) {
|
|
32815
32989
|
await this.runWithReconnect(async () => (await this.element(locator)).tap());
|
|
32816
32990
|
}
|
|
@@ -32833,17 +33007,100 @@ var MiniAppAdapter = class {
|
|
|
32833
33007
|
async assert(locator, expected) {
|
|
32834
33008
|
await this.runWithReconnect(async () => {
|
|
32835
33009
|
const element = await this.element(locator);
|
|
32836
|
-
const actual = (await element.text()).trim();
|
|
32837
33010
|
const wanted = expected.trim();
|
|
32838
|
-
|
|
32839
|
-
|
|
33011
|
+
const startedAt = Date.now();
|
|
33012
|
+
for (; ; ) {
|
|
33013
|
+
const actual = (await element.text()).trim();
|
|
33014
|
+
if (actual.includes(wanted)) return;
|
|
33015
|
+
if (Date.now() - startedAt > TEXT_WAIT_MS2) {
|
|
33016
|
+
throw new TestPilotError(
|
|
33017
|
+
"ASSERTION_FAILED",
|
|
33018
|
+
`\u65AD\u8A00\u5931\u8D25(\u7B49\u5F85 ${TEXT_WAIT_MS2}ms):\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`
|
|
33019
|
+
);
|
|
33020
|
+
}
|
|
33021
|
+
await delay3(300);
|
|
32840
33022
|
}
|
|
32841
33023
|
});
|
|
32842
33024
|
}
|
|
32843
33025
|
async extract(locator) {
|
|
32844
33026
|
return this.runWithReconnect(async () => {
|
|
32845
33027
|
const element = await this.element(locator);
|
|
32846
|
-
|
|
33028
|
+
const startedAt = Date.now();
|
|
33029
|
+
for (; ; ) {
|
|
33030
|
+
const text = (await element.text()).trim();
|
|
33031
|
+
if (text.length > 0 || Date.now() - startedAt > TEXT_WAIT_MS2) return text;
|
|
33032
|
+
await delay3(300);
|
|
33033
|
+
}
|
|
33034
|
+
});
|
|
33035
|
+
}
|
|
33036
|
+
/** 调用 Page / 组件方法;不传 locator 时为页面级调用(Page 实例方法) */
|
|
33037
|
+
async callMethod(locator, methodName, args = []) {
|
|
33038
|
+
return this.runWithReconnect(async () => {
|
|
33039
|
+
if (locator) {
|
|
33040
|
+
const element = await this.element(locator);
|
|
33041
|
+
return element.callMethod(methodName, ...args);
|
|
33042
|
+
}
|
|
33043
|
+
const page = await (await this.getProgram()).currentPage();
|
|
33044
|
+
return page.callMethod(methodName, ...args);
|
|
33045
|
+
});
|
|
33046
|
+
}
|
|
33047
|
+
// ---- miniapp 深度测试(V0.2)----
|
|
33048
|
+
wxMock;
|
|
33049
|
+
/** mock 任意 wx.* API:拦截调用并返回 mock 值;capture 记录真实调用参数(最近调用在前) */
|
|
33050
|
+
async mockWxApi(options) {
|
|
33051
|
+
if (this.wxMock?.api === options.api) await this.restoreWxMock();
|
|
33052
|
+
const program2 = await this.getProgram();
|
|
33053
|
+
const captured = [];
|
|
33054
|
+
this.wxMock = { api: options.api, captured };
|
|
33055
|
+
const callbackName = options.capture ? `__tpWxCapture_${options.capture}` : void 0;
|
|
33056
|
+
if (callbackName) {
|
|
33057
|
+
await program2.exposeFunction(callbackName, (payload) => {
|
|
33058
|
+
if (payload !== null && typeof payload === "object") {
|
|
33059
|
+
captured.push(payload);
|
|
33060
|
+
}
|
|
33061
|
+
});
|
|
33062
|
+
}
|
|
33063
|
+
await program2.mockWxMethod(
|
|
33064
|
+
options.api,
|
|
33065
|
+
function(invokeOptions, cbName) {
|
|
33066
|
+
const callbacks = globalThis;
|
|
33067
|
+
callbacks[cbName]?.({ options: invokeOptions });
|
|
33068
|
+
return options.result ?? this.origin(invokeOptions);
|
|
33069
|
+
},
|
|
33070
|
+
callbackName
|
|
33071
|
+
);
|
|
33072
|
+
}
|
|
33073
|
+
/** 读取 mock 期间捕获的调用参数(最近调用在前) */
|
|
33074
|
+
readCapturedWxApiCalls(api) {
|
|
33075
|
+
return this.wxMock?.api === api ? [...this.wxMock.captured].reverse() : [];
|
|
33076
|
+
}
|
|
33077
|
+
/** 恢复 mock 的 wx API(用例结束由引擎调用) */
|
|
33078
|
+
async restoreWxMock() {
|
|
33079
|
+
const mock = this.wxMock;
|
|
33080
|
+
if (!mock) return;
|
|
33081
|
+
this.wxMock = void 0;
|
|
33082
|
+
if (!this.program) return;
|
|
33083
|
+
await this.program.restoreWxMethod(mock.api).catch(() => void 0);
|
|
33084
|
+
}
|
|
33085
|
+
/** 触发组件事件(trigger;detail 作为 e.detail 注入,空 detail 即"用户拒绝/取消"类分支) */
|
|
33086
|
+
async trigger(locator, eventType, detail) {
|
|
33087
|
+
await this.runWithReconnect(async () => {
|
|
33088
|
+
const element = await this.element(locator);
|
|
33089
|
+
await element.trigger(eventType, detail);
|
|
33090
|
+
});
|
|
33091
|
+
}
|
|
33092
|
+
/** 读取页面 data(点号路径,缺省为整个 data 对象) */
|
|
33093
|
+
async getData(dataPath) {
|
|
33094
|
+
return this.runWithReconnect(async () => {
|
|
33095
|
+
const page = await (await this.getProgram()).currentPage();
|
|
33096
|
+
return page.data(dataPath);
|
|
33097
|
+
});
|
|
33098
|
+
}
|
|
33099
|
+
/** 写入页面 data 补丁(setData 驱动 onShow/onPullDownRefresh 等真实链路) */
|
|
33100
|
+
async setData(patch) {
|
|
33101
|
+
await this.runWithReconnect(async () => {
|
|
33102
|
+
const page = await (await this.getProgram()).currentPage();
|
|
33103
|
+
await page.setData(patch);
|
|
32847
33104
|
});
|
|
32848
33105
|
}
|
|
32849
33106
|
async screenshot() {
|
|
@@ -32936,7 +33193,7 @@ var MiniAppAdapter = class {
|
|
|
32936
33193
|
if (Date.now() - startedAt > ELEMENT_WAIT_MS) {
|
|
32937
33194
|
throw new TestPilotError("ELEMENT_NOT_FOUND", `\u7B49\u5F85\u5143\u7D20\u8D85\u65F6(${ELEMENT_WAIT_MS}ms):${selector}`);
|
|
32938
33195
|
}
|
|
32939
|
-
await
|
|
33196
|
+
await delay3(500);
|
|
32940
33197
|
}
|
|
32941
33198
|
}
|
|
32942
33199
|
};
|
|
@@ -33416,7 +33673,7 @@ async function collectEnvVarRefs(root) {
|
|
|
33416
33673
|
}
|
|
33417
33674
|
|
|
33418
33675
|
// src/commands/init/project-detector.ts
|
|
33419
|
-
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
33676
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
33420
33677
|
import { basename, join as join22 } from "node:path";
|
|
33421
33678
|
|
|
33422
33679
|
// src/lib/git.ts
|
|
@@ -33472,10 +33729,31 @@ function detectProjectType(root) {
|
|
|
33472
33729
|
if (isWeb) return "web";
|
|
33473
33730
|
return "unknown";
|
|
33474
33731
|
}
|
|
33732
|
+
function detectLanguageFromSources(root) {
|
|
33733
|
+
const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "miniprogram_npm", "dist", ".git"]);
|
|
33734
|
+
const found = { ts: false, js: false };
|
|
33735
|
+
const walk = (dir, depth) => {
|
|
33736
|
+
if (depth > 4 || found.ts && found.js) return;
|
|
33737
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
33738
|
+
if (entry.name.startsWith(".")) continue;
|
|
33739
|
+
const full = join22(dir, entry.name);
|
|
33740
|
+
if (entry.isDirectory()) {
|
|
33741
|
+
if (!SKIP_DIRS.has(entry.name)) walk(full, depth + 1);
|
|
33742
|
+
continue;
|
|
33743
|
+
}
|
|
33744
|
+
if (/\.(ts|tsx)$/.test(entry.name)) found.ts = true;
|
|
33745
|
+
else if (/\.(js|cjs|mjs|wxml)$/.test(entry.name)) found.js = true;
|
|
33746
|
+
}
|
|
33747
|
+
};
|
|
33748
|
+
walk(root, 0);
|
|
33749
|
+
if (found.ts) return "TypeScript";
|
|
33750
|
+
if (found.js) return "JavaScript";
|
|
33751
|
+
return "Unknown";
|
|
33752
|
+
}
|
|
33475
33753
|
async function detectProject(root) {
|
|
33476
33754
|
const pkg = readPackageJson(root);
|
|
33477
33755
|
const type = detectProjectType(root);
|
|
33478
|
-
const language = existsSync6(join22(root, "tsconfig.json")) ? "TypeScript" : pkg || existsSync6(join22(root, "src")) ? "JavaScript" :
|
|
33756
|
+
const language = existsSync6(join22(root, "tsconfig.json")) ? "TypeScript" : pkg || existsSync6(join22(root, "src")) ? "JavaScript" : detectLanguageFromSources(root);
|
|
33479
33757
|
const packageManager = existsSync6(join22(root, "pnpm-lock.yaml")) ? "pnpm" : existsSync6(join22(root, "yarn.lock")) ? "yarn" : existsSync6(join22(root, "package-lock.json")) ? "npm" : "npx";
|
|
33480
33758
|
const existingE2eDirs = E2E_DIR_CANDIDATES.filter((dir) => existsSync6(join22(root, dir)));
|
|
33481
33759
|
return {
|
|
@@ -33955,7 +34233,7 @@ function randomId() {
|
|
|
33955
34233
|
}
|
|
33956
34234
|
|
|
33957
34235
|
// src/commands/init/test-structure.ts
|
|
33958
|
-
import { existsSync as existsSync9, readdirSync as
|
|
34236
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
|
|
33959
34237
|
import { mkdir as mkdir14, writeFile as writeFile12 } from "node:fs/promises";
|
|
33960
34238
|
import { join as join26 } from "node:path";
|
|
33961
34239
|
async function initTestStructure(root, _info) {
|
|
@@ -33975,7 +34253,7 @@ async function initTestStructure(root, _info) {
|
|
|
33975
34253
|
await mkdir14(dir, { recursive: true });
|
|
33976
34254
|
}
|
|
33977
34255
|
for (const [name, dir] of Object.entries(dirs)) {
|
|
33978
|
-
if (!existed[name] &&
|
|
34256
|
+
if (!existed[name] && readdirSync3(dir).length === 0) {
|
|
33979
34257
|
await writeFile12(join26(dir, ".gitkeep"), "", "utf8").catch(() => void 0);
|
|
33980
34258
|
}
|
|
33981
34259
|
}
|
|
@@ -34350,7 +34628,7 @@ function makeValidateCommand() {
|
|
|
34350
34628
|
}
|
|
34351
34629
|
|
|
34352
34630
|
// src/cli.ts
|
|
34353
|
-
var CLI_VERSION = "0.7.
|
|
34631
|
+
var CLI_VERSION = "0.7.1";
|
|
34354
34632
|
async function createCli() {
|
|
34355
34633
|
const program2 = new Command();
|
|
34356
34634
|
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", () => {
|