csspilot 0.5.0 → 0.6.0
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 +76 -23
- package/dist/bin.js +727 -203
- package/dist/index.js +727 -203
- package/dist/skills/testpilot/SKILL.md +5 -1
- package/dist/skills/testpilot/manifest.yaml +1 -1
- package/dist/skills/testpilot/rules/assertion.md +8 -0
- package/dist/skills/testpilot/rules/case-design.md +13 -1
- package/dist/skills/testpilot/rules/dsl.md +62 -0
- package/dist/skills/testpilot/workflows/analyze-result.md +4 -2
- package/dist/skills/testpilot/workflows/create-case.md +4 -0
- package/dist/skills/testpilot/workflows/run-case.md +3 -0
- package/dist/skills/testpilot/workflows/test-requirements-review.md +46 -4
- package/dist/skills/testpilot/workflows/validate.md +4 -0
- package/package.json +11 -11
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -3094,7 +3094,7 @@ var require_visit = __commonJS({
|
|
|
3094
3094
|
var BREAK = Symbol("break visit");
|
|
3095
3095
|
var SKIP = Symbol("skip children");
|
|
3096
3096
|
var REMOVE = Symbol("remove node");
|
|
3097
|
-
function
|
|
3097
|
+
function visit3(node2, visitor) {
|
|
3098
3098
|
const visitor_ = initVisitor(visitor);
|
|
3099
3099
|
if (identity.isDocument(node2)) {
|
|
3100
3100
|
const cd = visit_(null, node2.contents, visitor_, Object.freeze([node2]));
|
|
@@ -3103,9 +3103,9 @@ var require_visit = __commonJS({
|
|
|
3103
3103
|
} else
|
|
3104
3104
|
visit_(null, node2, visitor_, Object.freeze([]));
|
|
3105
3105
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3106
|
+
visit3.BREAK = BREAK;
|
|
3107
|
+
visit3.SKIP = SKIP;
|
|
3108
|
+
visit3.REMOVE = REMOVE;
|
|
3109
3109
|
function visit_(key, node2, visitor, path) {
|
|
3110
3110
|
const ctrl = callVisitor(key, node2, visitor, path);
|
|
3111
3111
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
@@ -3239,7 +3239,7 @@ var require_visit = __commonJS({
|
|
|
3239
3239
|
throw new Error(`Cannot replace node with ${pt} parent`);
|
|
3240
3240
|
}
|
|
3241
3241
|
}
|
|
3242
|
-
exports.visit =
|
|
3242
|
+
exports.visit = visit3;
|
|
3243
3243
|
exports.visitAsync = visitAsync;
|
|
3244
3244
|
}
|
|
3245
3245
|
});
|
|
@@ -3249,7 +3249,7 @@ var require_directives = __commonJS({
|
|
|
3249
3249
|
"../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/doc/directives.js"(exports) {
|
|
3250
3250
|
"use strict";
|
|
3251
3251
|
var identity = require_identity();
|
|
3252
|
-
var
|
|
3252
|
+
var visit3 = require_visit();
|
|
3253
3253
|
var escapeChars = {
|
|
3254
3254
|
"!": "%21",
|
|
3255
3255
|
",": "%2C",
|
|
@@ -3393,7 +3393,7 @@ var require_directives = __commonJS({
|
|
|
3393
3393
|
let tagNames;
|
|
3394
3394
|
if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) {
|
|
3395
3395
|
const tags = {};
|
|
3396
|
-
|
|
3396
|
+
visit3.visit(doc.contents, (_key, node2) => {
|
|
3397
3397
|
if (identity.isNode(node2) && node2.tag)
|
|
3398
3398
|
tags[node2.tag] = true;
|
|
3399
3399
|
});
|
|
@@ -3420,7 +3420,7 @@ var require_anchors = __commonJS({
|
|
|
3420
3420
|
"../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/doc/anchors.js"(exports) {
|
|
3421
3421
|
"use strict";
|
|
3422
3422
|
var identity = require_identity();
|
|
3423
|
-
var
|
|
3423
|
+
var visit3 = require_visit();
|
|
3424
3424
|
function anchorIsValid(anchor2) {
|
|
3425
3425
|
if (/[\x00-\x19\s,[\]{}]/.test(anchor2)) {
|
|
3426
3426
|
const sa = JSON.stringify(anchor2);
|
|
@@ -3431,7 +3431,7 @@ var require_anchors = __commonJS({
|
|
|
3431
3431
|
}
|
|
3432
3432
|
function anchorNames(root) {
|
|
3433
3433
|
const anchors = /* @__PURE__ */ new Set();
|
|
3434
|
-
|
|
3434
|
+
visit3.visit(root, {
|
|
3435
3435
|
Value(_key, node2) {
|
|
3436
3436
|
if (node2.anchor)
|
|
3437
3437
|
anchors.add(node2.anchor);
|
|
@@ -3611,7 +3611,7 @@ var require_Alias = __commonJS({
|
|
|
3611
3611
|
"../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/Alias.js"(exports) {
|
|
3612
3612
|
"use strict";
|
|
3613
3613
|
var anchors = require_anchors();
|
|
3614
|
-
var
|
|
3614
|
+
var visit3 = require_visit();
|
|
3615
3615
|
var identity = require_identity();
|
|
3616
3616
|
var Node = require_Node();
|
|
3617
3617
|
var toJS = require_toJS();
|
|
@@ -3637,7 +3637,7 @@ var require_Alias = __commonJS({
|
|
|
3637
3637
|
nodes = ctx.aliasResolveCache;
|
|
3638
3638
|
} else {
|
|
3639
3639
|
nodes = [];
|
|
3640
|
-
|
|
3640
|
+
visit3.visit(doc, {
|
|
3641
3641
|
Node: (_key, node2) => {
|
|
3642
3642
|
if (identity.isAlias(node2) || identity.hasAnchor(node2))
|
|
3643
3643
|
nodes.push(node2);
|
|
@@ -8556,15 +8556,15 @@ var require_cst_visit = __commonJS({
|
|
|
8556
8556
|
var BREAK = Symbol("break visit");
|
|
8557
8557
|
var SKIP = Symbol("skip children");
|
|
8558
8558
|
var REMOVE = Symbol("remove item");
|
|
8559
|
-
function
|
|
8559
|
+
function visit3(cst, visitor) {
|
|
8560
8560
|
if ("type" in cst && cst.type === "document")
|
|
8561
8561
|
cst = { start: cst.start, value: cst.value };
|
|
8562
8562
|
_visit(Object.freeze([]), cst, visitor);
|
|
8563
8563
|
}
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8564
|
+
visit3.BREAK = BREAK;
|
|
8565
|
+
visit3.SKIP = SKIP;
|
|
8566
|
+
visit3.REMOVE = REMOVE;
|
|
8567
|
+
visit3.itemAtPath = (cst, path) => {
|
|
8568
8568
|
let item = cst;
|
|
8569
8569
|
for (const [field, index] of path) {
|
|
8570
8570
|
const tok = item?.[field];
|
|
@@ -8575,8 +8575,8 @@ var require_cst_visit = __commonJS({
|
|
|
8575
8575
|
}
|
|
8576
8576
|
return item;
|
|
8577
8577
|
};
|
|
8578
|
-
|
|
8579
|
-
const parent =
|
|
8578
|
+
visit3.parentCollection = (cst, path) => {
|
|
8579
|
+
const parent = visit3.itemAtPath(cst, path.slice(0, -1));
|
|
8580
8580
|
const field = path[path.length - 1][0];
|
|
8581
8581
|
const coll = parent?.[field];
|
|
8582
8582
|
if (coll && "items" in coll)
|
|
@@ -8607,7 +8607,7 @@ var require_cst_visit = __commonJS({
|
|
|
8607
8607
|
}
|
|
8608
8608
|
return typeof ctrl === "function" ? ctrl(item, path) : ctrl;
|
|
8609
8609
|
}
|
|
8610
|
-
exports.visit =
|
|
8610
|
+
exports.visit = visit3;
|
|
8611
8611
|
}
|
|
8612
8612
|
});
|
|
8613
8613
|
|
|
@@ -10323,7 +10323,7 @@ var require_dist = __commonJS({
|
|
|
10323
10323
|
var lineCounter = require_line_counter();
|
|
10324
10324
|
var parser = require_parser();
|
|
10325
10325
|
var publicApi = require_public_api();
|
|
10326
|
-
var
|
|
10326
|
+
var visit3 = require_visit();
|
|
10327
10327
|
exports.Composer = composer.Composer;
|
|
10328
10328
|
exports.Document = Document.Document;
|
|
10329
10329
|
exports.Schema = Schema.Schema;
|
|
@@ -10351,8 +10351,8 @@ var require_dist = __commonJS({
|
|
|
10351
10351
|
exports.parseAllDocuments = publicApi.parseAllDocuments;
|
|
10352
10352
|
exports.parseDocument = publicApi.parseDocument;
|
|
10353
10353
|
exports.stringify = publicApi.stringify;
|
|
10354
|
-
exports.visit =
|
|
10355
|
-
exports.visitAsync =
|
|
10354
|
+
exports.visit = visit3.visit;
|
|
10355
|
+
exports.visitAsync = visit3.visitAsync;
|
|
10356
10356
|
}
|
|
10357
10357
|
});
|
|
10358
10358
|
|
|
@@ -10449,7 +10449,7 @@ function makeCiCommand() {
|
|
|
10449
10449
|
}
|
|
10450
10450
|
|
|
10451
10451
|
// src/commands/init/doctor.ts
|
|
10452
|
-
import { existsSync as
|
|
10452
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
10453
10453
|
import { join as join24 } from "node:path";
|
|
10454
10454
|
|
|
10455
10455
|
// ../../packages/core/src/errors/index.ts
|
|
@@ -15513,7 +15513,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
15513
15513
|
var NONE = 0;
|
|
15514
15514
|
var ASSUMED = 1;
|
|
15515
15515
|
var PROVEN = 2;
|
|
15516
|
-
function isRecursive(inst, stack,
|
|
15516
|
+
function isRecursive(inst, stack, resolve7) {
|
|
15517
15517
|
const cached2 = recursive.get(inst);
|
|
15518
15518
|
if (cached2 !== void 0)
|
|
15519
15519
|
return cached2 ? PROVEN : NONE;
|
|
@@ -15523,7 +15523,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
15523
15523
|
let result = NONE;
|
|
15524
15524
|
const check2 = (child) => {
|
|
15525
15525
|
if (result !== PROVEN && child?._zod) {
|
|
15526
|
-
const answer = isRecursive(child, stack,
|
|
15526
|
+
const answer = isRecursive(child, stack, resolve7);
|
|
15527
15527
|
if (answer > result)
|
|
15528
15528
|
result = answer;
|
|
15529
15529
|
}
|
|
@@ -15534,7 +15534,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
15534
15534
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
15535
15535
|
if (spread && !desc.enumerable)
|
|
15536
15536
|
continue;
|
|
15537
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
15537
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve7) : NONE;
|
|
15538
15538
|
if (child > answer)
|
|
15539
15539
|
answer = child;
|
|
15540
15540
|
}
|
|
@@ -15601,7 +15601,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
15601
15601
|
break;
|
|
15602
15602
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
15603
15603
|
case "lazy": {
|
|
15604
|
-
const inner = def._cachedInner ?? (
|
|
15604
|
+
const inner = def._cachedInner ?? (resolve7 ? inst._zod.innerType : void 0);
|
|
15605
15605
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
15606
15606
|
break;
|
|
15607
15607
|
}
|
|
@@ -30167,6 +30167,8 @@ var configSchema = external_exports.strictObject({
|
|
|
30167
30167
|
}).optional(),
|
|
30168
30168
|
/** 环境配置:default 指定默认环境名,其余键为环境名 -> { baseUrl } */
|
|
30169
30169
|
environment: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().min(1), environmentEntrySchema])).optional(),
|
|
30170
|
+
/** Case 可见的项目变量白名单;值可通过 ${ENV_VAR} 从运行环境注入 */
|
|
30171
|
+
variables: external_exports.record(external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), external_exports.string()).optional(),
|
|
30170
30172
|
/** 项目声明的执行端(playwright / wechatide);仅作声明,实际可用性以 doctor 检测为准 */
|
|
30171
30173
|
adapters: external_exports.array(external_exports.string().min(1)).optional(),
|
|
30172
30174
|
workspace: external_exports.strictObject({ default: external_exports.string().min(1).optional() }).optional(),
|
|
@@ -30231,6 +30233,9 @@ async function loadTestpilotConfig(cwd = process.cwd()) {
|
|
|
30231
30233
|
testDirectory: parsed.test?.directory,
|
|
30232
30234
|
projectName: parsed.project?.name,
|
|
30233
30235
|
environment,
|
|
30236
|
+
variables: parsed.variables ? Object.fromEntries(
|
|
30237
|
+
Object.entries(parsed.variables).map(([name, value]) => [name, interpolateEnvRefs(value)])
|
|
30238
|
+
) : void 0,
|
|
30234
30239
|
adapters: parsed.adapters,
|
|
30235
30240
|
workspace: parsed.workspace,
|
|
30236
30241
|
server: parsed.server ? {
|
|
@@ -30341,11 +30346,11 @@ function execToString(command, args) {
|
|
|
30341
30346
|
}
|
|
30342
30347
|
|
|
30343
30348
|
// ../../packages/sdk/src/client.ts
|
|
30344
|
-
import { isAbsolute as
|
|
30349
|
+
import { isAbsolute as isAbsolute4, join as join19 } from "node:path";
|
|
30345
30350
|
|
|
30346
30351
|
// ../../packages/sdk/src/cases.ts
|
|
30347
|
-
import { mkdir as mkdir3, readFile as
|
|
30348
|
-
import { dirname, extname, isAbsolute, join as join6, resolve as
|
|
30352
|
+
import { mkdir as mkdir3, readFile as readFile4, readdir, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
|
|
30353
|
+
import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join6, resolve as resolve3 } from "node:path";
|
|
30349
30354
|
|
|
30350
30355
|
// ../../packages/dsl/src/schema/index.ts
|
|
30351
30356
|
var STEP_TARGETS = ["web", "miniapp", "api"];
|
|
@@ -30366,6 +30371,12 @@ var locatorSchema = external_exports.strictObject({
|
|
|
30366
30371
|
text: external_exports.string().min(1).optional(),
|
|
30367
30372
|
css: external_exports.string().min(1).optional()
|
|
30368
30373
|
}).describe("text \u4E0E css \u53EA\u80FD\u63D0\u4F9B\u5176\u4E00");
|
|
30374
|
+
var requestExpectationSchema = external_exports.strictObject({
|
|
30375
|
+
method: external_exports.enum(HTTP_METHODS).optional(),
|
|
30376
|
+
urlContains: external_exports.string().min(1),
|
|
30377
|
+
count: external_exports.number().int().nonnegative(),
|
|
30378
|
+
windowMs: external_exports.number().int().nonnegative().default(1e3)
|
|
30379
|
+
});
|
|
30369
30380
|
var stepSchema = external_exports.strictObject({
|
|
30370
30381
|
target: external_exports.enum(STEP_TARGETS),
|
|
30371
30382
|
action: external_exports.enum(ACTIONS),
|
|
@@ -30380,8 +30391,18 @@ var stepSchema = external_exports.strictObject({
|
|
|
30380
30391
|
/** api target:请求体;对象按 JSON 序列化,字符串原样发送(支持 ${var} 模板) */
|
|
30381
30392
|
body: external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.unknown())]).optional(),
|
|
30382
30393
|
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(),
|
|
30383
|
-
timeout: external_exports.number().int().positive().optional()
|
|
30394
|
+
timeout: external_exports.number().int().positive().optional(),
|
|
30395
|
+
/** UI 动作触发的网络请求精确次数断言 */
|
|
30396
|
+
expectRequests: external_exports.array(requestExpectationSchema).min(1).optional()
|
|
30397
|
+
});
|
|
30398
|
+
var datasetReferenceSchema = external_exports.strictObject({
|
|
30399
|
+
file: external_exports.string().min(1),
|
|
30400
|
+
idField: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "idField \u9700\u4E3A\u5408\u6CD5\u5B57\u6BB5\u540D").default("id")
|
|
30384
30401
|
});
|
|
30402
|
+
var dependencyPathSchema = external_exports.string().regex(
|
|
30403
|
+
/^(fixture|dataset|account|variable)(?:\.[A-Za-z_][A-Za-z0-9_]*)+$/,
|
|
30404
|
+
"requires \u8DEF\u5F84\u5FC5\u987B\u4EE5 fixture/dataset/account/variable \u5F00\u5934\u5E76\u5305\u542B\u5B57\u6BB5"
|
|
30405
|
+
);
|
|
30385
30406
|
var caseSchema = external_exports.strictObject({
|
|
30386
30407
|
id: external_exports.string().regex(/^[a-z0-9][a-z0-9-]*$/, "id \u9700\u4E3A kebab-case(\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26)"),
|
|
30387
30408
|
name: external_exports.string().min(1, "name \u4E0D\u80FD\u4E3A\u7A7A"),
|
|
@@ -30393,7 +30414,12 @@ var caseSchema = external_exports.strictObject({
|
|
|
30393
30414
|
/** 引用测试账号标识;凭据由 Environment + Server/CI Secret 管理,禁止写进 Case */
|
|
30394
30415
|
accountRef: external_exports.string().min(1).optional(),
|
|
30395
30416
|
tags: external_exports.array(external_exports.string().min(1)).optional(),
|
|
30396
|
-
|
|
30417
|
+
fixtures: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
30418
|
+
datasets: datasetReferenceSchema.optional(),
|
|
30419
|
+
requires: external_exports.array(dependencyPathSchema).min(1).optional(),
|
|
30420
|
+
setup: external_exports.array(stepSchema).min(1).optional(),
|
|
30421
|
+
steps: external_exports.array(stepSchema).min(1, "Case \u81F3\u5C11\u5305\u542B\u4E00\u4E2A step"),
|
|
30422
|
+
teardown: external_exports.array(stepSchema).min(1).optional()
|
|
30397
30423
|
});
|
|
30398
30424
|
|
|
30399
30425
|
// ../../packages/dsl/src/parser/index.ts
|
|
@@ -30444,6 +30470,7 @@ var ADAPTER_SUPPORT = {
|
|
|
30444
30470
|
};
|
|
30445
30471
|
var LOCATOR_REQUIRED = /* @__PURE__ */ new Set(["click", "input", "select", "assert", "extract"]);
|
|
30446
30472
|
var VALUE_REQUIRED = /* @__PURE__ */ new Set(["input", "select"]);
|
|
30473
|
+
var REQUEST_EXPECTATION_ACTIONS = /* @__PURE__ */ new Set(["navigate", "click", "input", "select"]);
|
|
30447
30474
|
var FIELD_ALLOWED = {
|
|
30448
30475
|
url: ["navigate", "request"],
|
|
30449
30476
|
value: ["input", "select", "extract"],
|
|
@@ -30458,12 +30485,21 @@ var VARIABLE_REF = /\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g;
|
|
|
30458
30485
|
function validateCase(data) {
|
|
30459
30486
|
const issues = [];
|
|
30460
30487
|
const declared = /* @__PURE__ */ new Set();
|
|
30488
|
+
if (data.fixtures) declared.add("fixture");
|
|
30489
|
+
if (data.datasets) declared.add("dataset");
|
|
30490
|
+
declared.add("variable");
|
|
30461
30491
|
if (data.accountRef) {
|
|
30462
30492
|
declared.add("account");
|
|
30463
30493
|
}
|
|
30494
|
+
data.setup?.forEach((step, index) => {
|
|
30495
|
+
checkStep(step, `setup[${index}]`, declared, issues);
|
|
30496
|
+
});
|
|
30464
30497
|
data.steps.forEach((step, index) => {
|
|
30465
30498
|
checkStep(step, `steps[${index}]`, declared, issues);
|
|
30466
30499
|
});
|
|
30500
|
+
data.teardown?.forEach((step, index) => {
|
|
30501
|
+
checkStep(step, `teardown[${index}]`, declared, issues);
|
|
30502
|
+
});
|
|
30467
30503
|
return issues;
|
|
30468
30504
|
}
|
|
30469
30505
|
function validateCaseSource(source) {
|
|
@@ -30538,6 +30574,15 @@ function checkStep(step, at, declared, issues) {
|
|
|
30538
30574
|
if (step.action === "wait" && !step.locator && step.timeout === void 0) {
|
|
30539
30575
|
issues.push({ path: at, code: "semantic", message: "wait \u9700\u8981\u63D0\u4F9B locator(\u7B49\u5F85\u5143\u7D20)\u6216 timeout(\u6BEB\u79D2)" });
|
|
30540
30576
|
}
|
|
30577
|
+
if (step.expectRequests) {
|
|
30578
|
+
if (step.target === "api" || !REQUEST_EXPECTATION_ACTIONS.has(step.action)) {
|
|
30579
|
+
issues.push({
|
|
30580
|
+
path: `${at}.expectRequests`,
|
|
30581
|
+
code: "semantic",
|
|
30582
|
+
message: "expectRequests \u4EC5\u7528\u4E8E web/miniapp \u7684 navigate/click/input/select"
|
|
30583
|
+
});
|
|
30584
|
+
}
|
|
30585
|
+
}
|
|
30541
30586
|
checkVariableRefs(step, at, declared, issues);
|
|
30542
30587
|
}
|
|
30543
30588
|
function checkLocator(step, at, issues) {
|
|
@@ -30570,9 +30615,14 @@ function checkVariableRefs(step, at, declared, issues) {
|
|
|
30570
30615
|
collect("value", step.value);
|
|
30571
30616
|
collect("expected", step.expected);
|
|
30572
30617
|
collect("url", step.url);
|
|
30618
|
+
collect("locator.text", step.locator?.text);
|
|
30619
|
+
collect("locator.css", step.locator?.css);
|
|
30573
30620
|
if (typeof step.body === "string") collect("body", step.body);
|
|
30574
30621
|
else if (step.body) collect("body", JSON.stringify(step.body));
|
|
30575
30622
|
for (const headerValue of Object.values(step.headers ?? {})) collect("headers", headerValue);
|
|
30623
|
+
for (const [index, expectation] of (step.expectRequests ?? []).entries()) {
|
|
30624
|
+
collect(`expectRequests[${index}].urlContains`, expectation.urlContains);
|
|
30625
|
+
}
|
|
30576
30626
|
for (const [field, name] of refs) {
|
|
30577
30627
|
const root = name.split(".")[0] ?? name;
|
|
30578
30628
|
if (!declared.has(root)) {
|
|
@@ -30587,9 +30637,20 @@ function checkVariableRefs(step, at, declared, issues) {
|
|
|
30587
30637
|
|
|
30588
30638
|
// ../../packages/sdk/src/project.ts
|
|
30589
30639
|
import { mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
30590
|
-
import {
|
|
30640
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
30641
|
+
import { dirname, join as join5, resolve } from "node:path";
|
|
30591
30642
|
function resolveRoot(root) {
|
|
30592
|
-
|
|
30643
|
+
const start = resolve(root ?? process.cwd());
|
|
30644
|
+
if (root) return start;
|
|
30645
|
+
let current = start;
|
|
30646
|
+
for (; ; ) {
|
|
30647
|
+
if (existsSync2(join5(current, "testpilot.yaml")) || existsSync2(join5(current, TESTPILOT_DIR))) {
|
|
30648
|
+
return current;
|
|
30649
|
+
}
|
|
30650
|
+
const parent = dirname(current);
|
|
30651
|
+
if (parent === current) return start;
|
|
30652
|
+
current = parent;
|
|
30653
|
+
}
|
|
30593
30654
|
}
|
|
30594
30655
|
async function loadProjectConfig(root) {
|
|
30595
30656
|
return loadTestpilotConfig(resolveRoot(root));
|
|
@@ -30638,6 +30699,164 @@ async function writeProjectLink(link, root) {
|
|
|
30638
30699
|
return file2;
|
|
30639
30700
|
}
|
|
30640
30701
|
|
|
30702
|
+
// ../../packages/sdk/src/case-data.ts
|
|
30703
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
30704
|
+
import { readFile as readFile3, realpath } from "node:fs/promises";
|
|
30705
|
+
import { extname, isAbsolute, relative, resolve as resolve2 } from "node:path";
|
|
30706
|
+
async function loadCaseDataFile(reference, options) {
|
|
30707
|
+
if (isAbsolute(reference)) {
|
|
30708
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u5FC5\u987B\u4F7F\u7528\u9879\u76EE\u76F8\u5BF9\u8DEF\u5F84:${reference}`);
|
|
30709
|
+
}
|
|
30710
|
+
const root = resolve2(options.root);
|
|
30711
|
+
const dataDir = resolve2(options.dataDir);
|
|
30712
|
+
const target = resolve2(root, reference);
|
|
30713
|
+
if (!isInside(dataDir, target)) {
|
|
30714
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u5FC5\u987B\u4F4D\u4E8E tests/e2e/data \u76EE\u5F55:${reference}`);
|
|
30715
|
+
}
|
|
30716
|
+
const [realDataDir, realTarget] = await Promise.all([
|
|
30717
|
+
realpath(dataDir).catch(() => dataDir),
|
|
30718
|
+
realpath(target).catch((error62) => {
|
|
30719
|
+
throw new Error(
|
|
30720
|
+
`\u8BFB\u53D6\u6570\u636E\u6587\u4EF6\u5931\u8D25:${reference}:${error62 instanceof Error ? error62.message : String(error62)}`
|
|
30721
|
+
);
|
|
30722
|
+
})
|
|
30723
|
+
]);
|
|
30724
|
+
if (!isInside(realDataDir, realTarget)) {
|
|
30725
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u89E3\u6790\u540E\u8D8A\u51FA tests/e2e/data \u76EE\u5F55:${reference}`);
|
|
30726
|
+
}
|
|
30727
|
+
const source = await readFile3(realTarget, "utf8");
|
|
30728
|
+
const extension = extname(realTarget).toLowerCase();
|
|
30729
|
+
try {
|
|
30730
|
+
if (extension === ".yaml" || extension === ".yml") return (0, import_yaml3.parse)(source);
|
|
30731
|
+
if (extension === ".json") return JSON.parse(source);
|
|
30732
|
+
} catch (error62) {
|
|
30733
|
+
throw new Error(
|
|
30734
|
+
`\u89E3\u6790\u6570\u636E\u6587\u4EF6\u5931\u8D25:${reference}:${error62 instanceof Error ? error62.message : String(error62)}`
|
|
30735
|
+
);
|
|
30736
|
+
}
|
|
30737
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u4EC5\u652F\u6301 .yaml/.yml/.json:${reference}`);
|
|
30738
|
+
}
|
|
30739
|
+
function isDataObject(value) {
|
|
30740
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30741
|
+
}
|
|
30742
|
+
function mergeFixture(target, source, at = "fixture") {
|
|
30743
|
+
const output2 = { ...target };
|
|
30744
|
+
for (const [key, value] of Object.entries(source)) {
|
|
30745
|
+
const path = `${at}.${key}`;
|
|
30746
|
+
if (!(key in output2)) {
|
|
30747
|
+
output2[key] = value;
|
|
30748
|
+
continue;
|
|
30749
|
+
}
|
|
30750
|
+
const current = output2[key];
|
|
30751
|
+
if (isDataObject(current) && isDataObject(value)) {
|
|
30752
|
+
output2[key] = mergeFixture(current, value, path);
|
|
30753
|
+
continue;
|
|
30754
|
+
}
|
|
30755
|
+
throw new Error(`fixture \u5B57\u6BB5\u51B2\u7A81:${path}`);
|
|
30756
|
+
}
|
|
30757
|
+
return output2;
|
|
30758
|
+
}
|
|
30759
|
+
function flattenVariables(prefix, value) {
|
|
30760
|
+
const output2 = {};
|
|
30761
|
+
visit2(prefix, value, output2);
|
|
30762
|
+
return output2;
|
|
30763
|
+
}
|
|
30764
|
+
function visit2(path, value, output2) {
|
|
30765
|
+
if (value === null || value === void 0) return;
|
|
30766
|
+
if (typeof value === "object") {
|
|
30767
|
+
output2[path] = JSON.stringify(value);
|
|
30768
|
+
if (Array.isArray(value)) {
|
|
30769
|
+
value.forEach((item, index) => visit2(`${path}.${index}`, item, output2));
|
|
30770
|
+
} else {
|
|
30771
|
+
for (const [key, item] of Object.entries(value)) {
|
|
30772
|
+
visit2(`${path}.${key}`, item, output2);
|
|
30773
|
+
}
|
|
30774
|
+
}
|
|
30775
|
+
return;
|
|
30776
|
+
}
|
|
30777
|
+
output2[path] = String(value);
|
|
30778
|
+
}
|
|
30779
|
+
function isInside(base, candidate) {
|
|
30780
|
+
const path = relative(base, candidate);
|
|
30781
|
+
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
30782
|
+
}
|
|
30783
|
+
|
|
30784
|
+
// ../../packages/sdk/src/case-preparation.ts
|
|
30785
|
+
async function prepareCaseExecutions(item, options) {
|
|
30786
|
+
const fixture = await loadFixtures(item.data.fixtures ?? [], options);
|
|
30787
|
+
const rows = await loadDataset(item.data, options);
|
|
30788
|
+
const projectVariables = flattenVariables("variable", options.variables ?? {});
|
|
30789
|
+
const accountVariables = accountValues(item.data, options.accounts);
|
|
30790
|
+
return rows.map(({ row, rowId }, rowIndex) => {
|
|
30791
|
+
const initialVariables = {
|
|
30792
|
+
...flattenVariables("fixture", fixture),
|
|
30793
|
+
...flattenVariables("dataset", row),
|
|
30794
|
+
...projectVariables,
|
|
30795
|
+
...accountVariables
|
|
30796
|
+
};
|
|
30797
|
+
const missingDependencies = (item.data.requires ?? []).filter(
|
|
30798
|
+
(path) => isUnavailable(initialVariables[path])
|
|
30799
|
+
);
|
|
30800
|
+
return {
|
|
30801
|
+
data: item.data,
|
|
30802
|
+
file: item.file,
|
|
30803
|
+
templateId: item.data.id,
|
|
30804
|
+
rowId,
|
|
30805
|
+
rowIndex,
|
|
30806
|
+
initialVariables,
|
|
30807
|
+
missingDependencies
|
|
30808
|
+
};
|
|
30809
|
+
});
|
|
30810
|
+
}
|
|
30811
|
+
async function loadFixtures(references, options) {
|
|
30812
|
+
let merged = {};
|
|
30813
|
+
for (const reference of references) {
|
|
30814
|
+
const value = await loadCaseDataFile(reference, options);
|
|
30815
|
+
if (!isDataObject(value)) {
|
|
30816
|
+
throw new Error(`fixture \u6587\u4EF6\u6839\u5FC5\u987B\u662F\u5BF9\u8C61:${reference}`);
|
|
30817
|
+
}
|
|
30818
|
+
merged = mergeFixture(merged, value);
|
|
30819
|
+
}
|
|
30820
|
+
return merged;
|
|
30821
|
+
}
|
|
30822
|
+
async function loadDataset(data, options) {
|
|
30823
|
+
if (!data.datasets) return [{ row: {}, rowId: "default" }];
|
|
30824
|
+
const value = await loadCaseDataFile(data.datasets.file, options);
|
|
30825
|
+
if (!Array.isArray(value)) {
|
|
30826
|
+
throw new Error(`dataset \u6587\u4EF6\u6839\u5FC5\u987B\u662F\u5BF9\u8C61\u6570\u7EC4:${data.datasets.file}`);
|
|
30827
|
+
}
|
|
30828
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30829
|
+
return value.map((row, index) => {
|
|
30830
|
+
if (!isDataObject(row)) {
|
|
30831
|
+
throw new Error(`dataset \u7B2C ${index + 1} \u884C\u5FC5\u987B\u662F\u5BF9\u8C61:${data.datasets.file}`);
|
|
30832
|
+
}
|
|
30833
|
+
const rawId = row[data.datasets.idField];
|
|
30834
|
+
if (typeof rawId !== "string" && typeof rawId !== "number" || String(rawId).trim() === "") {
|
|
30835
|
+
throw new Error(
|
|
30836
|
+
`dataset \u7B2C ${index + 1} \u884C\u7F3A\u5C11\u975E\u7A7A row id \u5B57\u6BB5 "${data.datasets.idField}"`
|
|
30837
|
+
);
|
|
30838
|
+
}
|
|
30839
|
+
const rowId = String(rawId);
|
|
30840
|
+
if (seen.has(rowId)) throw new Error(`dataset row id \u91CD\u590D:${rowId}`);
|
|
30841
|
+
seen.add(rowId);
|
|
30842
|
+
return { row, rowId };
|
|
30843
|
+
});
|
|
30844
|
+
}
|
|
30845
|
+
function accountValues(data, accounts) {
|
|
30846
|
+
if (!data.accountRef) return {};
|
|
30847
|
+
const raw = accounts?.[data.accountRef];
|
|
30848
|
+
if (raw === void 0) return {};
|
|
30849
|
+
try {
|
|
30850
|
+
const parsed = JSON.parse(raw);
|
|
30851
|
+
if (isDataObject(parsed)) return flattenVariables("account", parsed);
|
|
30852
|
+
} catch {
|
|
30853
|
+
}
|
|
30854
|
+
return { account: raw };
|
|
30855
|
+
}
|
|
30856
|
+
function isUnavailable(value) {
|
|
30857
|
+
return value === void 0 || value.trim() === "" || /\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(value);
|
|
30858
|
+
}
|
|
30859
|
+
|
|
30641
30860
|
// ../../packages/sdk/src/cases.ts
|
|
30642
30861
|
async function collectCaseFiles(paths) {
|
|
30643
30862
|
const files = [];
|
|
@@ -30666,19 +30885,20 @@ async function collectFromDir(dir) {
|
|
|
30666
30885
|
return found;
|
|
30667
30886
|
}
|
|
30668
30887
|
function isCaseFile(file2) {
|
|
30669
|
-
const ext =
|
|
30888
|
+
const ext = extname2(file2).toLowerCase();
|
|
30670
30889
|
return ext === ".yaml" || ext === ".yml";
|
|
30671
30890
|
}
|
|
30672
30891
|
function resolveAgainstRoot(root, file2) {
|
|
30673
|
-
return
|
|
30892
|
+
return isAbsolute2(file2) ? file2 : resolve3(resolveRoot(root), file2);
|
|
30674
30893
|
}
|
|
30675
30894
|
async function collectCases(paths, options = {}) {
|
|
30676
30895
|
const files = await collectCaseFiles(
|
|
30677
30896
|
paths.map((input2) => resolveAgainstRoot(options.root, input2))
|
|
30678
30897
|
);
|
|
30679
30898
|
const infos = [];
|
|
30899
|
+
const root = resolveRoot(options.root);
|
|
30680
30900
|
for (const file2 of files) {
|
|
30681
|
-
const validation = await validateCaseFile(file2);
|
|
30901
|
+
const validation = await validateCaseFile(file2, root);
|
|
30682
30902
|
infos.push({
|
|
30683
30903
|
file: file2,
|
|
30684
30904
|
valid: validation.ok,
|
|
@@ -30690,16 +30910,16 @@ async function collectCases(paths, options = {}) {
|
|
|
30690
30910
|
}
|
|
30691
30911
|
async function readCaseFile(file2, options = {}) {
|
|
30692
30912
|
const target = resolveAgainstRoot(options.root, file2);
|
|
30693
|
-
const source = await
|
|
30694
|
-
return { file: target, source, validation:
|
|
30913
|
+
const source = await readFile4(target, "utf8");
|
|
30914
|
+
return { file: target, source, validation: await validateCaseDataSource(source, target, resolveRoot(options.root)) };
|
|
30695
30915
|
}
|
|
30696
|
-
async function validateCaseFile(file2) {
|
|
30697
|
-
const source = await
|
|
30698
|
-
return
|
|
30916
|
+
async function validateCaseFile(file2, root) {
|
|
30917
|
+
const source = await readFile4(file2, "utf8");
|
|
30918
|
+
return validateCaseDataSource(source, file2, root);
|
|
30699
30919
|
}
|
|
30700
30920
|
async function writeCaseFile(file2, source, options = {}) {
|
|
30701
30921
|
const target = resolveAgainstRoot(options.root, file2);
|
|
30702
|
-
const validation =
|
|
30922
|
+
const validation = await validateCaseDataSource(source, target, resolveRoot(options.root));
|
|
30703
30923
|
if (!validation.ok) {
|
|
30704
30924
|
return { file: target, written: false, validation };
|
|
30705
30925
|
}
|
|
@@ -30709,18 +30929,41 @@ async function writeCaseFile(file2, source, options = {}) {
|
|
|
30709
30929
|
throw new Error(`\u7528\u4F8B\u6587\u4EF6\u5DF2\u5B58\u5728:${target}(\u8986\u76D6\u9700\u663E\u5F0F overwrite: true)`);
|
|
30710
30930
|
}
|
|
30711
30931
|
}
|
|
30712
|
-
await mkdir3(
|
|
30932
|
+
await mkdir3(dirname2(target), { recursive: true });
|
|
30713
30933
|
await writeFile3(target, source, "utf8");
|
|
30714
30934
|
return { file: target, written: true, validation };
|
|
30715
30935
|
}
|
|
30936
|
+
async function validateCaseDataSource(source, file2, root) {
|
|
30937
|
+
const validation = validateCaseSource(source);
|
|
30938
|
+
if (!validation.ok || !validation.data) return validation;
|
|
30939
|
+
try {
|
|
30940
|
+
await prepareCaseExecutions(
|
|
30941
|
+
{ data: validation.data, file: file2 },
|
|
30942
|
+
{ root, dataDir: join6(root, "tests", "e2e", "data") }
|
|
30943
|
+
);
|
|
30944
|
+
return validation;
|
|
30945
|
+
} catch (error62) {
|
|
30946
|
+
return {
|
|
30947
|
+
ok: false,
|
|
30948
|
+
data: validation.data,
|
|
30949
|
+
issues: [
|
|
30950
|
+
{
|
|
30951
|
+
path: "(root)",
|
|
30952
|
+
code: "semantic",
|
|
30953
|
+
message: error62 instanceof Error ? error62.message : String(error62)
|
|
30954
|
+
}
|
|
30955
|
+
]
|
|
30956
|
+
};
|
|
30957
|
+
}
|
|
30958
|
+
}
|
|
30716
30959
|
|
|
30717
30960
|
// ../../packages/sdk/src/reports.ts
|
|
30718
|
-
import { readFile as
|
|
30961
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
30719
30962
|
import { join as join18 } from "node:path";
|
|
30720
30963
|
|
|
30721
30964
|
// ../../packages/reporter/src/report.ts
|
|
30722
30965
|
import { writeFile as writeFile4 } from "node:fs/promises";
|
|
30723
|
-
import { dirname as
|
|
30966
|
+
import { dirname as dirname3, join as join8, resolve as resolve4 } from "node:path";
|
|
30724
30967
|
|
|
30725
30968
|
// ../../packages/reporter/src/classify.ts
|
|
30726
30969
|
var FAILURE_CATEGORIES = [
|
|
@@ -30750,14 +30993,14 @@ function classifyFailure(message) {
|
|
|
30750
30993
|
}
|
|
30751
30994
|
|
|
30752
30995
|
// ../../packages/reporter/src/history.ts
|
|
30753
|
-
import { readFile as
|
|
30996
|
+
import { readFile as readFile5, readdir as readdir2 } from "node:fs/promises";
|
|
30754
30997
|
import { join as join7 } from "node:path";
|
|
30755
30998
|
async function collectHistory(runsRoot2, skipRunId, limit = 12) {
|
|
30756
30999
|
const entries = (await readdir2(runsRoot2).catch(() => [])).filter((name) => /^\d{8}-\d{3}$/.test(name) && name !== skipRunId).sort().slice(-limit);
|
|
30757
31000
|
const points = [];
|
|
30758
31001
|
for (const runId of entries) {
|
|
30759
31002
|
try {
|
|
30760
|
-
const summary = JSON.parse(await
|
|
31003
|
+
const summary = JSON.parse(await readFile5(join7(runsRoot2, runId, "result.json"), "utf8"));
|
|
30761
31004
|
points.push({
|
|
30762
31005
|
runId,
|
|
30763
31006
|
startedAt: summary.startedAt,
|
|
@@ -30778,12 +31021,19 @@ function toSummaryView(summary) {
|
|
|
30778
31021
|
runId: summary.runId,
|
|
30779
31022
|
status: summary.status,
|
|
30780
31023
|
durationMs: summary.durationMs,
|
|
30781
|
-
cases: {
|
|
31024
|
+
cases: {
|
|
31025
|
+
templates: summary.totals.templates ?? summary.totals.cases,
|
|
31026
|
+
passed: summary.totals.passed,
|
|
31027
|
+
failed: summary.totals.failed,
|
|
31028
|
+
skipped: summary.totals.skipped ?? 0,
|
|
31029
|
+
warnings: summary.totals.warnings ?? 0
|
|
31030
|
+
},
|
|
30782
31031
|
steps: {
|
|
30783
31032
|
total: summary.totals.steps,
|
|
30784
31033
|
passed: summary.totals.stepsPassed,
|
|
30785
31034
|
failed: summary.totals.stepsFailed,
|
|
30786
|
-
skipped: summary.totals.stepsSkipped
|
|
31035
|
+
skipped: summary.totals.stepsSkipped,
|
|
31036
|
+
warning: summary.totals.stepsWarning ?? 0
|
|
30787
31037
|
}
|
|
30788
31038
|
};
|
|
30789
31039
|
}
|
|
@@ -30791,9 +31041,9 @@ function toSummaryView(summary) {
|
|
|
30791
31041
|
// ../../packages/reporter/src/report.ts
|
|
30792
31042
|
var HISTORY_LIMIT = 12;
|
|
30793
31043
|
async function writeReports(runDir, summary, options = {}) {
|
|
30794
|
-
const dir =
|
|
31044
|
+
const dir = resolve4(runDir);
|
|
30795
31045
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30796
|
-
const history = options.history ?? await collectHistory(
|
|
31046
|
+
const history = options.history ?? await collectHistory(dirname3(dir), summary.runId, options.historyLimit ?? HISTORY_LIMIT);
|
|
30797
31047
|
const failures = [];
|
|
30798
31048
|
const cases = summary.cases.map((item) => {
|
|
30799
31049
|
const categories = /* @__PURE__ */ new Set();
|
|
@@ -30852,7 +31102,7 @@ function renderHtml(data) {
|
|
|
30852
31102
|
const view = toSummaryView(data.summary);
|
|
30853
31103
|
const summary = data.summary;
|
|
30854
31104
|
const trendSvg = renderTrend(data);
|
|
30855
|
-
const casesHtml = summary.cases
|
|
31105
|
+
const casesHtml = groupedCasesHtml(summary.cases);
|
|
30856
31106
|
const chips = categoryChips(data);
|
|
30857
31107
|
return `<!doctype html>
|
|
30858
31108
|
<html lang="zh-CN">
|
|
@@ -30860,13 +31110,13 @@ function renderHtml(data) {
|
|
|
30860
31110
|
<meta charset="utf-8" />
|
|
30861
31111
|
<title>TestPilot Report ${escapeHtml(summary.runId)}</title>
|
|
30862
31112
|
<style>
|
|
30863
|
-
:root { --ok:#1a7f37; --fail:#cf222e; --skip:#6e7781; --line:#d0d7de; --muted:#59636e; }
|
|
31113
|
+
:root { --ok:#1a7f37; --fail:#cf222e; --warn:#9a6700; --skip:#6e7781; --line:#d0d7de; --muted:#59636e; }
|
|
30864
31114
|
* { box-sizing: border-box; }
|
|
30865
31115
|
body { font-family: system-ui, "Segoe UI", sans-serif; margin: 0; color: #1f2328; background: #f6f8fa; }
|
|
30866
31116
|
.wrap { max-width: 1080px; margin: 0 auto; padding: 20px 16px 48px; }
|
|
30867
31117
|
h1 { font-size: 20px; margin: 8px 0; } h2 { font-size: 15px; margin: 24px 0 8px; }
|
|
30868
31118
|
.badge { display: inline-block; padding: 2px 10px; border-radius: 10px; color: #fff; font-size: 12px; }
|
|
30869
|
-
.passed { background: var(--ok); } .failed { background: var(--fail); } .skipped { background: var(--skip); }
|
|
31119
|
+
.passed { background: var(--ok); } .failed { background: var(--fail); } .warning { background: var(--warn); } .skipped { background: var(--skip); }
|
|
30870
31120
|
code { background: #eef1f4; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
|
|
30871
31121
|
.cards { display: flex; gap: 12px; flex-wrap: wrap; margin: 14px 0; }
|
|
30872
31122
|
.card { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 10px 16px; min-width: 130px; }
|
|
@@ -30879,6 +31129,8 @@ function renderHtml(data) {
|
|
|
30879
31129
|
.btn { border: 1px solid var(--line); background: #fff; border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; }
|
|
30880
31130
|
#count { color: var(--muted); font-size: 12px; margin-left: auto; }
|
|
30881
31131
|
details.case { background: #fff; border: 1px solid var(--line); border-radius: 8px; margin: 10px 0; }
|
|
31132
|
+
section.template { border-left: 3px solid var(--line); padding-left: 12px; margin: 14px 0; }
|
|
31133
|
+
section.template > h3 { font-size: 14px; margin: 0 0 6px; }
|
|
30882
31134
|
details.case > summary { cursor: pointer; padding: 10px 14px; font-size: 14px; list-style: none; }
|
|
30883
31135
|
details.case > summary::-webkit-details-marker { display: none; }
|
|
30884
31136
|
details.case > summary:hover { background: #f6f8fa; }
|
|
@@ -30901,6 +31153,8 @@ function renderHtml(data) {
|
|
|
30901
31153
|
<div class="cards">
|
|
30902
31154
|
<div class="card"><b>${view.cases.passed}<span style="color:var(--ok)"> \u2713</span></b><span>\u901A\u8FC7\u7528\u4F8B</span></div>
|
|
30903
31155
|
<div class="card"><b>${view.cases.failed}<span style="color:var(--fail)"> \u2717</span></b><span>\u5931\u8D25\u7528\u4F8B</span></div>
|
|
31156
|
+
<div class="card"><b>${view.cases.skipped} \u21B7</b><span>\u8DF3\u8FC7\u6570\u636E\u884C</span></div>
|
|
31157
|
+
<div class="card"><b>${view.cases.warnings} \u26A0</b><span>\u6E05\u7406\u8B66\u544A</span></div>
|
|
30904
31158
|
<div class="card"><b>${view.steps.passed}<span style="color:var(--ok)"> \u2713</span></b><span>\u901A\u8FC7\u6B65\u9AA4</span></div>
|
|
30905
31159
|
<div class="card"><b>${view.steps.failed}<span style="color:var(--fail)"> \u2717</span></b><span>\u5931\u8D25\u6B65\u9AA4</span></div>
|
|
30906
31160
|
<div class="card"><b>${view.steps.skipped} \u21B7</b><span>\u8DF3\u8FC7\u6B65\u9AA4</span></div>
|
|
@@ -30987,26 +31241,44 @@ function renderHtml(data) {
|
|
|
30987
31241
|
</body>
|
|
30988
31242
|
</html>`;
|
|
30989
31243
|
}
|
|
31244
|
+
function groupedCasesHtml(cases) {
|
|
31245
|
+
const groups = /* @__PURE__ */ new Map();
|
|
31246
|
+
for (const item of cases) {
|
|
31247
|
+
const current = groups.get(item.caseId) ?? [];
|
|
31248
|
+
current.push(item);
|
|
31249
|
+
groups.set(item.caseId, current);
|
|
31250
|
+
}
|
|
31251
|
+
return [...groups.entries()].map(
|
|
31252
|
+
([caseId, rows]) => `<section class="template">
|
|
31253
|
+
<h3>${escapeHtml(caseId)} <span class="muted">\xB7 ${rows.length} \u4E2A\u6570\u636E\u884C</span></h3>
|
|
31254
|
+
${rows.map((item) => caseHtml(item)).join("\n")}
|
|
31255
|
+
</section>`
|
|
31256
|
+
).join("\n");
|
|
31257
|
+
}
|
|
30990
31258
|
function caseHtml(item) {
|
|
30991
31259
|
const rows = item.steps.map((step) => stepHtml(step)).join("\n");
|
|
30992
31260
|
const errorLine = item.error ? `<p class="error">${escapeHtml(item.error)}</p>` : "";
|
|
31261
|
+
const skipLine = item.skipReason ? `<p class="muted">${escapeHtml(item.skipReason)}${item.missingDependencies?.length ? `: ${escapeHtml(item.missingDependencies.join(", "))}` : ""}</p>` : "";
|
|
31262
|
+
const warningLine = item.warnings?.length ? `<p class="error">\u26A0 ${item.warnings.map(escapeHtml).join("<br />\u26A0 ")}</p>` : "";
|
|
30993
31263
|
const links = [];
|
|
30994
31264
|
if (item.video) links.push(`<a href="${escapeHtml(item.video)}">\u89C6\u9891</a>`);
|
|
30995
31265
|
if (item.trace) links.push(`<a href="${escapeHtml(item.trace)}">Trace</a>`);
|
|
30996
31266
|
const linksLine = links.length > 0 ? `<p>${links.join(" \xB7 ")}</p>` : "";
|
|
30997
|
-
const search = escapeHtml(`${item.caseId} ${item.caseName} ${item.file}`).replaceAll('"', """);
|
|
31267
|
+
const search = escapeHtml(`${item.caseId} ${item.rowId ?? "default"} ${item.caseName} ${item.file}`).replaceAll('"', """);
|
|
30998
31268
|
const cats = escapeHtml(item.categories.join(","));
|
|
30999
31269
|
return `
|
|
31000
31270
|
<details class="case" data-status="${item.status}" data-cats="${cats}" data-search="${search}">
|
|
31001
31271
|
<summary>
|
|
31002
31272
|
<span class="badge ${item.status}">${item.status}</span>
|
|
31003
|
-
<b
|
|
31273
|
+
<b>\u6570\u636E\u884C ${escapeHtml(item.rowId ?? "default")}</b> \xB7 ${escapeHtml(item.caseName)}
|
|
31004
31274
|
<span class="muted">\xB7 ${item.steps.length} \u6B65 \xB7 ${(item.durationMs / 1e3).toFixed(1)}s</span>
|
|
31005
31275
|
</summary>
|
|
31006
31276
|
${errorLine}
|
|
31277
|
+
${skipLine}
|
|
31278
|
+
${warningLine}
|
|
31007
31279
|
${linksLine}
|
|
31008
31280
|
<table>
|
|
31009
|
-
<tr><th>#</th><th>target</th><th>action</th><th>\u72B6\u6001</th><th>\u8017\u65F6</th><th>\u8BE6\u60C5</th></tr>
|
|
31281
|
+
<tr><th>#</th><th>\u9636\u6BB5</th><th>target</th><th>action</th><th>\u72B6\u6001</th><th>\u8017\u65F6</th><th>\u8BE6\u60C5</th></tr>
|
|
31010
31282
|
${rows}
|
|
31011
31283
|
</table>
|
|
31012
31284
|
</details>`;
|
|
@@ -31021,8 +31293,14 @@ function stepHtml(step) {
|
|
|
31021
31293
|
}
|
|
31022
31294
|
}
|
|
31023
31295
|
if (step.screenshot) details.push(`<a href="${escapeHtml(step.screenshot)}">\u622A\u56FE</a>`);
|
|
31296
|
+
for (const assertion of step.requestAssertions ?? []) {
|
|
31297
|
+
details.push(
|
|
31298
|
+
`\u8BF7\u6C42 ${escapeHtml(assertion.method ?? "*")} <code>${escapeHtml(assertion.urlContains)}</code>: ${assertion.actualCount}/${assertion.count}`
|
|
31299
|
+
);
|
|
31300
|
+
}
|
|
31024
31301
|
return `<tr>
|
|
31025
31302
|
<td class="num">${step.index}</td>
|
|
31303
|
+
<td>${escapeHtml(step.phase ?? "steps")}</td>
|
|
31026
31304
|
<td>${escapeHtml(step.target)}</td>
|
|
31027
31305
|
<td>${escapeHtml(step.action)}</td>
|
|
31028
31306
|
<td><span class="badge ${step.status}">${step.status}</span></td>
|
|
@@ -31079,7 +31357,7 @@ function embedJson(data) {
|
|
|
31079
31357
|
|
|
31080
31358
|
// ../../packages/reporter/src/summary-report.ts
|
|
31081
31359
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
31082
|
-
import { join as join9, resolve as
|
|
31360
|
+
import { join as join9, resolve as resolve5 } from "node:path";
|
|
31083
31361
|
function escapeHtml2(value) {
|
|
31084
31362
|
return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
31085
31363
|
}
|
|
@@ -31097,6 +31375,8 @@ function buildSummaryData(runs) {
|
|
|
31097
31375
|
executions.push({
|
|
31098
31376
|
runId,
|
|
31099
31377
|
caseId: item.caseId,
|
|
31378
|
+
rowId: item.rowId,
|
|
31379
|
+
identity: item.rowId && item.rowId !== "default" ? `${item.caseId}#${item.rowId}` : item.caseId,
|
|
31100
31380
|
caseName: item.caseName,
|
|
31101
31381
|
status: item.status,
|
|
31102
31382
|
category,
|
|
@@ -31114,10 +31394,11 @@ function buildSummaryData(runs) {
|
|
|
31114
31394
|
}
|
|
31115
31395
|
const byCase = /* @__PURE__ */ new Map();
|
|
31116
31396
|
for (const exec2 of executions) {
|
|
31117
|
-
let agg = byCase.get(exec2.
|
|
31397
|
+
let agg = byCase.get(exec2.identity);
|
|
31118
31398
|
if (!agg) {
|
|
31119
31399
|
agg = {
|
|
31120
31400
|
caseId: exec2.caseId,
|
|
31401
|
+
rowId: exec2.rowId,
|
|
31121
31402
|
caseName: exec2.caseName,
|
|
31122
31403
|
platforms: [],
|
|
31123
31404
|
runs: 0,
|
|
@@ -31127,17 +31408,18 @@ function buildSummaryData(runs) {
|
|
|
31127
31408
|
lastStatus: "unknown",
|
|
31128
31409
|
flaky: false
|
|
31129
31410
|
};
|
|
31130
|
-
byCase.set(exec2.
|
|
31411
|
+
byCase.set(exec2.identity, agg);
|
|
31131
31412
|
}
|
|
31132
31413
|
agg.runs++;
|
|
31133
31414
|
if (exec2.status === "passed") agg.passed++;
|
|
31134
|
-
else agg.failed++;
|
|
31415
|
+
else if (exec2.status === "failed") agg.failed++;
|
|
31135
31416
|
for (const platform of exec2.platforms) {
|
|
31136
31417
|
if (!agg.platforms.includes(platform)) agg.platforms.push(platform);
|
|
31137
31418
|
}
|
|
31138
31419
|
}
|
|
31139
31420
|
for (const agg of byCase.values()) {
|
|
31140
|
-
const
|
|
31421
|
+
const identity = agg.rowId && agg.rowId !== "default" ? `${agg.caseId}#${agg.rowId}` : agg.caseId;
|
|
31422
|
+
const last = [...executions].reverse().find((exec2) => exec2.identity === identity);
|
|
31141
31423
|
agg.lastStatus = last?.status ?? "unknown";
|
|
31142
31424
|
agg.lastCategory = last?.category;
|
|
31143
31425
|
agg.passRate = agg.runs > 0 ? agg.passed / agg.runs : 0;
|
|
@@ -31348,7 +31630,7 @@ function renderSummaryHtml(data) {
|
|
|
31348
31630
|
</html>`;
|
|
31349
31631
|
}
|
|
31350
31632
|
async function writeSummaryReport(artifactsDir, data) {
|
|
31351
|
-
const dir =
|
|
31633
|
+
const dir = resolve5(artifactsDir);
|
|
31352
31634
|
await mkdir4(dir, { recursive: true });
|
|
31353
31635
|
const htmlPath = join9(dir, "summary.html");
|
|
31354
31636
|
const jsonPath = join9(dir, "summary.json");
|
|
@@ -31358,8 +31640,8 @@ async function writeSummaryReport(artifactsDir, data) {
|
|
|
31358
31640
|
}
|
|
31359
31641
|
|
|
31360
31642
|
// ../../packages/sdk/src/runs.ts
|
|
31361
|
-
import { readFile as
|
|
31362
|
-
import { isAbsolute as
|
|
31643
|
+
import { readFile as readFile7, readdir as readdir4 } from "node:fs/promises";
|
|
31644
|
+
import { isAbsolute as isAbsolute3, join as join17 } from "node:path";
|
|
31363
31645
|
|
|
31364
31646
|
// ../../packages/execution-engine/src/artifacts/index.ts
|
|
31365
31647
|
import { mkdir as mkdir9 } from "node:fs/promises";
|
|
@@ -31367,7 +31649,7 @@ import { join as join13 } from "node:path";
|
|
|
31367
31649
|
|
|
31368
31650
|
// ../../packages/evidence/src/log.ts
|
|
31369
31651
|
import { appendFile, mkdir as mkdir5 } from "node:fs/promises";
|
|
31370
|
-
import { dirname as
|
|
31652
|
+
import { dirname as dirname4 } from "node:path";
|
|
31371
31653
|
var RunLogger = class {
|
|
31372
31654
|
constructor(filePath) {
|
|
31373
31655
|
this.filePath = filePath;
|
|
@@ -31382,7 +31664,7 @@ var RunLogger = class {
|
|
|
31382
31664
|
return this.write("ERROR", message);
|
|
31383
31665
|
}
|
|
31384
31666
|
async write(level, message) {
|
|
31385
|
-
await mkdir5(
|
|
31667
|
+
await mkdir5(dirname4(this.filePath), { recursive: true });
|
|
31386
31668
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${level} ${message}
|
|
31387
31669
|
`;
|
|
31388
31670
|
await appendFile(this.filePath, line, "utf8");
|
|
@@ -31391,30 +31673,30 @@ var RunLogger = class {
|
|
|
31391
31673
|
|
|
31392
31674
|
// ../../packages/evidence/src/screenshot.ts
|
|
31393
31675
|
import { mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
|
|
31394
|
-
import { dirname as
|
|
31676
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
31395
31677
|
async function saveScreenshot(dir, fileName, data) {
|
|
31396
31678
|
const file2 = join10(dir, fileName);
|
|
31397
|
-
await mkdir6(
|
|
31679
|
+
await mkdir6(dirname5(file2), { recursive: true });
|
|
31398
31680
|
await writeFile6(file2, data);
|
|
31399
31681
|
return file2;
|
|
31400
31682
|
}
|
|
31401
31683
|
|
|
31402
31684
|
// ../../packages/evidence/src/trace.ts
|
|
31403
31685
|
import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
|
|
31404
|
-
import { dirname as
|
|
31686
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
31405
31687
|
async function saveTrace(dir, fileName, data) {
|
|
31406
31688
|
const file2 = join11(dir, fileName);
|
|
31407
|
-
await mkdir7(
|
|
31689
|
+
await mkdir7(dirname6(file2), { recursive: true });
|
|
31408
31690
|
await writeFile7(file2, data);
|
|
31409
31691
|
return file2;
|
|
31410
31692
|
}
|
|
31411
31693
|
|
|
31412
31694
|
// ../../packages/evidence/src/video.ts
|
|
31413
31695
|
import { mkdir as mkdir8, writeFile as writeFile8 } from "node:fs/promises";
|
|
31414
|
-
import { dirname as
|
|
31696
|
+
import { dirname as dirname7, join as join12 } from "node:path";
|
|
31415
31697
|
async function saveVideo(dir, fileName, data) {
|
|
31416
31698
|
const file2 = join12(dir, fileName);
|
|
31417
|
-
await mkdir8(
|
|
31699
|
+
await mkdir8(dirname7(file2), { recursive: true });
|
|
31418
31700
|
await writeFile8(file2, data);
|
|
31419
31701
|
return file2;
|
|
31420
31702
|
}
|
|
@@ -31464,6 +31746,11 @@ var ExecutionContext = class {
|
|
|
31464
31746
|
get(name) {
|
|
31465
31747
|
return this.variables.get(name);
|
|
31466
31748
|
}
|
|
31749
|
+
seed(values) {
|
|
31750
|
+
for (const [name, value] of Object.entries(values)) {
|
|
31751
|
+
this.variables.set(name, value);
|
|
31752
|
+
}
|
|
31753
|
+
}
|
|
31467
31754
|
/**
|
|
31468
31755
|
* 将字符串中的 ${name} 替换为已提取的变量值;未定义的引用保持原样(校验阶段已拦截)。
|
|
31469
31756
|
* 名字允许点号,用于引擎注入的 account.username 一类凭据变量(用户 extract 变量名不含点号)。
|
|
@@ -31528,7 +31815,7 @@ async function ensureRunDir(runsRoot2, runId) {
|
|
|
31528
31815
|
|
|
31529
31816
|
// ../../packages/execution-engine/src/events/index.ts
|
|
31530
31817
|
import { appendFile as appendFile2, mkdir as mkdir11 } from "node:fs/promises";
|
|
31531
|
-
import { dirname as
|
|
31818
|
+
import { dirname as dirname8 } from "node:path";
|
|
31532
31819
|
var EventBus = class {
|
|
31533
31820
|
subscribers = [];
|
|
31534
31821
|
subscribe(subscriber) {
|
|
@@ -31542,7 +31829,7 @@ var EventBus = class {
|
|
|
31542
31829
|
};
|
|
31543
31830
|
function ndjsonSubscriber(filePath) {
|
|
31544
31831
|
return async (event) => {
|
|
31545
|
-
await mkdir11(
|
|
31832
|
+
await mkdir11(dirname8(filePath), { recursive: true });
|
|
31546
31833
|
await appendFile2(filePath, `${JSON.stringify(event)}
|
|
31547
31834
|
`, "utf8");
|
|
31548
31835
|
};
|
|
@@ -31623,11 +31910,19 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31623
31910
|
status: "passed",
|
|
31624
31911
|
durationMs: 0
|
|
31625
31912
|
};
|
|
31913
|
+
let captureStarted = false;
|
|
31626
31914
|
try {
|
|
31627
31915
|
const locator = step.locator ? {
|
|
31628
31916
|
text: step.locator.text !== void 0 ? context.resolve(step.locator.text) : void 0,
|
|
31629
31917
|
css: step.locator.css !== void 0 ? context.resolve(step.locator.css) : void 0
|
|
31630
31918
|
} : void 0;
|
|
31919
|
+
if (step.expectRequests) {
|
|
31920
|
+
if (!adapter.startRequestCapture || !adapter.stopRequestCapture) {
|
|
31921
|
+
throw new Error(`\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301\u8BF7\u6C42\u6B21\u6570\u65AD\u8A00`);
|
|
31922
|
+
}
|
|
31923
|
+
await adapter.startRequestCapture();
|
|
31924
|
+
captureStarted = true;
|
|
31925
|
+
}
|
|
31631
31926
|
switch (step.action) {
|
|
31632
31927
|
case "launch":
|
|
31633
31928
|
await adapter.launch();
|
|
@@ -31698,7 +31993,29 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31698
31993
|
throw new Error(`\u4E0D\u652F\u6301\u7684 action:${String(exhaustive)}`);
|
|
31699
31994
|
}
|
|
31700
31995
|
}
|
|
31996
|
+
if (step.expectRequests) {
|
|
31997
|
+
const windowMs = Math.max(...step.expectRequests.map((item) => item.windowMs));
|
|
31998
|
+
if (windowMs > 0) await delay(windowMs);
|
|
31999
|
+
const requests = await adapter.stopRequestCapture();
|
|
32000
|
+
captureStarted = false;
|
|
32001
|
+
result.requestAssertions = buildRequestAssertions(step, requests, context);
|
|
32002
|
+
const mismatches = result.requestAssertions.filter((item) => item.actualCount !== item.count);
|
|
32003
|
+
if (mismatches.length > 0) {
|
|
32004
|
+
throw new Error(
|
|
32005
|
+
mismatches.map(
|
|
32006
|
+
(item) => `\u8BF7\u6C42\u6B21\u6570\u4E0D\u5339\u914D:${item.method ?? "*"} ${item.urlContains},\u5B9E\u9645 ${item.actualCount},\u671F\u671B ${item.count}`
|
|
32007
|
+
).join("; ")
|
|
32008
|
+
);
|
|
32009
|
+
}
|
|
32010
|
+
}
|
|
31701
32011
|
} catch (err) {
|
|
32012
|
+
if (captureStarted) {
|
|
32013
|
+
try {
|
|
32014
|
+
await adapter.stopRequestCapture?.();
|
|
32015
|
+
} catch {
|
|
32016
|
+
}
|
|
32017
|
+
captureStarted = false;
|
|
32018
|
+
}
|
|
31702
32019
|
result.status = "failed";
|
|
31703
32020
|
result.error = err instanceof Error ? err.message : String(err);
|
|
31704
32021
|
try {
|
|
@@ -31743,92 +32060,195 @@ function resolveDeep(value, context) {
|
|
|
31743
32060
|
function snippet(text, limit = SNIPPET_LIMIT) {
|
|
31744
32061
|
return text.length > limit ? `${text.slice(0, limit)}\u2026` : text;
|
|
31745
32062
|
}
|
|
32063
|
+
function buildRequestAssertions(step, requests, context) {
|
|
32064
|
+
return (step.expectRequests ?? []).map((expectation) => {
|
|
32065
|
+
const method = expectation.method?.toUpperCase();
|
|
32066
|
+
const urlContains = context.resolve(expectation.urlContains);
|
|
32067
|
+
const matches = requests.filter(
|
|
32068
|
+
(request) => (!method || request.method.toUpperCase() === method) && request.url.includes(urlContains)
|
|
32069
|
+
);
|
|
32070
|
+
return {
|
|
32071
|
+
method,
|
|
32072
|
+
urlContains: expectation.urlContains,
|
|
32073
|
+
count: expectation.count,
|
|
32074
|
+
actualCount: matches.length,
|
|
32075
|
+
windowMs: expectation.windowMs,
|
|
32076
|
+
requests: matches.slice(0, 20).map((request) => ({
|
|
32077
|
+
method: request.method.toUpperCase(),
|
|
32078
|
+
url: sanitizeRequestUrl(request.url)
|
|
32079
|
+
}))
|
|
32080
|
+
};
|
|
32081
|
+
});
|
|
32082
|
+
}
|
|
32083
|
+
function sanitizeRequestUrl(url2) {
|
|
32084
|
+
const queryIndex = url2.indexOf("?");
|
|
32085
|
+
const safe = queryIndex >= 0 ? `${url2.slice(0, queryIndex)}?<redacted>` : url2;
|
|
32086
|
+
return snippet(safe, 300);
|
|
32087
|
+
}
|
|
32088
|
+
function delay(milliseconds) {
|
|
32089
|
+
return new Promise((resolve7) => setTimeout(resolve7, milliseconds));
|
|
32090
|
+
}
|
|
31746
32091
|
|
|
31747
32092
|
// ../../packages/execution-engine/src/engine/execution-engine.ts
|
|
31748
32093
|
var ExecutionEngine = class {
|
|
31749
32094
|
async runCase(data, options) {
|
|
31750
|
-
const { runId, file: file2, artifacts, bus, resolver } = options;
|
|
32095
|
+
const { runId, file: file2, artifacts, bus, resolver, rowId, rowIndex } = options;
|
|
31751
32096
|
const startedAt = /* @__PURE__ */ new Date();
|
|
31752
32097
|
const context = new ExecutionContext();
|
|
32098
|
+
context.seed(options.initialVariables);
|
|
31753
32099
|
if (data.accountRef && options.accounts?.[data.accountRef] !== void 0) {
|
|
31754
32100
|
context.seedAccount(options.accounts[data.accountRef]);
|
|
31755
32101
|
}
|
|
31756
32102
|
const steps = [];
|
|
32103
|
+
const warnings = [];
|
|
31757
32104
|
const evidenceAdapters = /* @__PURE__ */ new Map();
|
|
31758
32105
|
let caseError;
|
|
31759
|
-
|
|
31760
|
-
await
|
|
31761
|
-
|
|
32106
|
+
const executionId = safeArtifactName(`${data.id}-${rowId}`);
|
|
32107
|
+
await bus.emit({
|
|
32108
|
+
type: "case-started",
|
|
32109
|
+
runId,
|
|
32110
|
+
caseId: data.id,
|
|
32111
|
+
rowId,
|
|
32112
|
+
file: file2,
|
|
32113
|
+
ts: startedAt.toISOString()
|
|
32114
|
+
});
|
|
32115
|
+
await artifacts.logger.info(`case ${data.id}#${rowId} started (${file2})`);
|
|
32116
|
+
if (options.missingDependencies.length > 0) {
|
|
32117
|
+
const finishedAt2 = /* @__PURE__ */ new Date();
|
|
32118
|
+
const skipped = {
|
|
32119
|
+
caseId: data.id,
|
|
32120
|
+
caseName: data.name,
|
|
32121
|
+
file: file2,
|
|
32122
|
+
rowId,
|
|
32123
|
+
rowIndex,
|
|
32124
|
+
status: "skipped",
|
|
32125
|
+
skipReason: "dependency-not-ready",
|
|
32126
|
+
missingDependencies: options.missingDependencies,
|
|
32127
|
+
steps,
|
|
32128
|
+
startedAt: startedAt.toISOString(),
|
|
32129
|
+
finishedAt: finishedAt2.toISOString(),
|
|
32130
|
+
durationMs: finishedAt2.getTime() - startedAt.getTime()
|
|
32131
|
+
};
|
|
31762
32132
|
await bus.emit({
|
|
31763
|
-
type: "
|
|
32133
|
+
type: "case-finished",
|
|
31764
32134
|
runId,
|
|
31765
32135
|
caseId: data.id,
|
|
31766
|
-
|
|
31767
|
-
|
|
31768
|
-
|
|
31769
|
-
ts:
|
|
32136
|
+
rowId,
|
|
32137
|
+
status: "skipped",
|
|
32138
|
+
durationMs: skipped.durationMs,
|
|
32139
|
+
ts: finishedAt2.toISOString()
|
|
31770
32140
|
});
|
|
31771
|
-
|
|
31772
|
-
|
|
31773
|
-
|
|
31774
|
-
|
|
31775
|
-
|
|
31776
|
-
|
|
31777
|
-
|
|
31778
|
-
|
|
31779
|
-
|
|
31780
|
-
|
|
31781
|
-
|
|
32141
|
+
await artifacts.logger.info(
|
|
32142
|
+
`case ${data.id}#${rowId} skipped: missing ${options.missingDependencies.join(", ")}`
|
|
32143
|
+
);
|
|
32144
|
+
return skipped;
|
|
32145
|
+
}
|
|
32146
|
+
const runPhase = async (phase, phaseSteps) => {
|
|
32147
|
+
let passed = true;
|
|
32148
|
+
for (const [index, step] of phaseSteps.entries()) {
|
|
32149
|
+
await bus.emit({
|
|
32150
|
+
type: "step-started",
|
|
32151
|
+
runId,
|
|
32152
|
+
caseId: data.id,
|
|
32153
|
+
rowId,
|
|
32154
|
+
phase,
|
|
32155
|
+
index,
|
|
32156
|
+
target: step.target,
|
|
32157
|
+
action: step.action,
|
|
32158
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
32159
|
+
});
|
|
32160
|
+
let result;
|
|
32161
|
+
try {
|
|
32162
|
+
const adapter = await (await resolver.session(step.target)).get();
|
|
32163
|
+
if (adapter.startEvidence && !evidenceAdapters.has(step.target)) {
|
|
32164
|
+
try {
|
|
32165
|
+
await adapter.startEvidence(executionId);
|
|
32166
|
+
evidenceAdapters.set(step.target, adapter);
|
|
32167
|
+
} catch (err) {
|
|
32168
|
+
await artifacts.logger.info(
|
|
32169
|
+
`evidence start failed for ${step.target}: ${err instanceof Error ? err.message : String(err)}`
|
|
32170
|
+
);
|
|
32171
|
+
}
|
|
31782
32172
|
}
|
|
32173
|
+
result = await executeStep(step, index, adapter, context, artifacts, {
|
|
32174
|
+
screenshotName: `${executionId}-${phase}-${String(index).padStart(2, "0")}`
|
|
32175
|
+
});
|
|
32176
|
+
} catch (err) {
|
|
32177
|
+
result = {
|
|
32178
|
+
index,
|
|
32179
|
+
target: step.target,
|
|
32180
|
+
action: step.action,
|
|
32181
|
+
status: "failed",
|
|
32182
|
+
durationMs: 0,
|
|
32183
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32184
|
+
};
|
|
31783
32185
|
}
|
|
31784
|
-
result =
|
|
31785
|
-
|
|
31786
|
-
|
|
31787
|
-
|
|
31788
|
-
|
|
32186
|
+
result.phase = phase;
|
|
32187
|
+
if (phase === "teardown" && result.status === "failed") {
|
|
32188
|
+
result.status = "warning";
|
|
32189
|
+
warnings.push(result.error ?? `teardown step ${index} failed`);
|
|
32190
|
+
}
|
|
32191
|
+
steps.push(result);
|
|
32192
|
+
await artifacts.logger.info(
|
|
32193
|
+
`${phase} step ${index} ${step.action} -> ${result.status}${result.error ? ` - ${result.error}` : ""}`
|
|
32194
|
+
);
|
|
32195
|
+
await bus.emit({
|
|
32196
|
+
type: "step-finished",
|
|
32197
|
+
runId,
|
|
32198
|
+
caseId: data.id,
|
|
32199
|
+
rowId,
|
|
32200
|
+
phase,
|
|
31789
32201
|
index,
|
|
31790
32202
|
target: step.target,
|
|
31791
32203
|
action: step.action,
|
|
31792
|
-
status:
|
|
31793
|
-
durationMs:
|
|
31794
|
-
error:
|
|
31795
|
-
|
|
31796
|
-
|
|
31797
|
-
|
|
31798
|
-
|
|
31799
|
-
|
|
31800
|
-
|
|
31801
|
-
|
|
31802
|
-
|
|
31803
|
-
|
|
31804
|
-
|
|
31805
|
-
|
|
31806
|
-
|
|
31807
|
-
|
|
31808
|
-
|
|
31809
|
-
|
|
31810
|
-
|
|
31811
|
-
|
|
31812
|
-
caseError = result.error;
|
|
31813
|
-
for (const [skipIndex, skippedStep] of data.steps.entries()) {
|
|
31814
|
-
if (skipIndex <= index) continue;
|
|
31815
|
-
steps.push({
|
|
31816
|
-
index: skipIndex,
|
|
31817
|
-
target: skippedStep.target,
|
|
31818
|
-
action: skippedStep.action,
|
|
31819
|
-
status: "skipped",
|
|
31820
|
-
durationMs: 0
|
|
31821
|
-
});
|
|
32204
|
+
status: result.status,
|
|
32205
|
+
durationMs: result.durationMs,
|
|
32206
|
+
error: result.error,
|
|
32207
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
32208
|
+
});
|
|
32209
|
+
if (result.status === "failed") {
|
|
32210
|
+
passed = false;
|
|
32211
|
+
caseError = result.error;
|
|
32212
|
+
for (const [skipIndex, skippedStep] of phaseSteps.entries()) {
|
|
32213
|
+
if (skipIndex <= index) continue;
|
|
32214
|
+
steps.push({
|
|
32215
|
+
index: skipIndex,
|
|
32216
|
+
phase,
|
|
32217
|
+
target: skippedStep.target,
|
|
32218
|
+
action: skippedStep.action,
|
|
32219
|
+
status: "skipped",
|
|
32220
|
+
durationMs: 0
|
|
32221
|
+
});
|
|
32222
|
+
}
|
|
32223
|
+
break;
|
|
31822
32224
|
}
|
|
31823
|
-
|
|
32225
|
+
}
|
|
32226
|
+
return passed;
|
|
32227
|
+
};
|
|
32228
|
+
const setupPassed = await runPhase("setup", data.setup ?? []);
|
|
32229
|
+
if (setupPassed) {
|
|
32230
|
+
await runPhase("steps", data.steps);
|
|
32231
|
+
} else {
|
|
32232
|
+
for (const [index, step] of data.steps.entries()) {
|
|
32233
|
+
steps.push({
|
|
32234
|
+
index,
|
|
32235
|
+
phase: "steps",
|
|
32236
|
+
target: step.target,
|
|
32237
|
+
action: step.action,
|
|
32238
|
+
status: "skipped",
|
|
32239
|
+
durationMs: 0
|
|
32240
|
+
});
|
|
31824
32241
|
}
|
|
31825
32242
|
}
|
|
32243
|
+
await runPhase("teardown", data.teardown ?? []);
|
|
31826
32244
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
31827
32245
|
const status = caseError ? "failed" : "passed";
|
|
31828
32246
|
const caseResult = {
|
|
31829
32247
|
caseId: data.id,
|
|
31830
32248
|
caseName: data.name,
|
|
31831
32249
|
file: file2,
|
|
32250
|
+
rowId,
|
|
32251
|
+
rowIndex,
|
|
31832
32252
|
status,
|
|
31833
32253
|
steps,
|
|
31834
32254
|
startedAt: startedAt.toISOString(),
|
|
@@ -31836,15 +32256,16 @@ var ExecutionEngine = class {
|
|
|
31836
32256
|
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
31837
32257
|
};
|
|
31838
32258
|
if (caseError) caseResult.error = caseError;
|
|
32259
|
+
if (warnings.length > 0) caseResult.warnings = warnings;
|
|
31839
32260
|
for (const [target, adapter] of evidenceAdapters) {
|
|
31840
32261
|
if (!adapter.stopEvidence) continue;
|
|
31841
32262
|
try {
|
|
31842
|
-
const evidence = await adapter.stopEvidence(
|
|
32263
|
+
const evidence = await adapter.stopEvidence(executionId);
|
|
31843
32264
|
if (evidence.video) {
|
|
31844
|
-
caseResult.video = await artifacts.saveVideo(`${
|
|
32265
|
+
caseResult.video = await artifacts.saveVideo(`${executionId}-${target}.webm`, evidence.video);
|
|
31845
32266
|
}
|
|
31846
32267
|
if (evidence.trace) {
|
|
31847
|
-
caseResult.trace = await artifacts.saveTrace(`${
|
|
32268
|
+
caseResult.trace = await artifacts.saveTrace(`${executionId}-${target}.zip`, evidence.trace);
|
|
31848
32269
|
}
|
|
31849
32270
|
} catch (err) {
|
|
31850
32271
|
await artifacts.logger.info(
|
|
@@ -31856,33 +32277,45 @@ var ExecutionEngine = class {
|
|
|
31856
32277
|
type: "case-finished",
|
|
31857
32278
|
runId,
|
|
31858
32279
|
caseId: data.id,
|
|
32280
|
+
rowId,
|
|
31859
32281
|
status,
|
|
31860
32282
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
31861
32283
|
ts: finishedAt.toISOString()
|
|
31862
32284
|
});
|
|
31863
|
-
await artifacts.logger.info(`case ${data.id} ${status}`);
|
|
32285
|
+
await artifacts.logger.info(`case ${data.id}#${rowId} ${status}`);
|
|
31864
32286
|
return caseResult;
|
|
31865
32287
|
}
|
|
31866
32288
|
};
|
|
32289
|
+
function safeArtifactName(value) {
|
|
32290
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "case";
|
|
32291
|
+
}
|
|
31867
32292
|
|
|
31868
32293
|
// ../../packages/execution-engine/src/engine/result-collector.ts
|
|
31869
32294
|
function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
31870
32295
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
31871
32296
|
const totals = {
|
|
32297
|
+
templates: options.templates ?? new Set(cases.map((item) => item.caseId)).size,
|
|
31872
32298
|
cases: cases.length,
|
|
31873
32299
|
passed: cases.filter((item) => item.status === "passed").length,
|
|
31874
32300
|
failed: cases.filter((item) => item.status === "failed").length,
|
|
32301
|
+
skipped: cases.filter((item) => item.status === "skipped").length,
|
|
32302
|
+
warnings: 0,
|
|
31875
32303
|
steps: 0,
|
|
31876
32304
|
stepsPassed: 0,
|
|
31877
32305
|
stepsFailed: 0,
|
|
31878
|
-
stepsSkipped: 0
|
|
32306
|
+
stepsSkipped: 0,
|
|
32307
|
+
stepsWarning: 0
|
|
31879
32308
|
};
|
|
31880
32309
|
for (const item of cases) {
|
|
31881
32310
|
for (const step of item.steps) {
|
|
31882
32311
|
totals.steps++;
|
|
31883
32312
|
if (step.status === "passed") totals.stepsPassed++;
|
|
31884
32313
|
else if (step.status === "failed") totals.stepsFailed++;
|
|
31885
|
-
else totals.stepsSkipped++;
|
|
32314
|
+
else if (step.status === "skipped") totals.stepsSkipped++;
|
|
32315
|
+
else {
|
|
32316
|
+
totals.stepsWarning++;
|
|
32317
|
+
totals.warnings++;
|
|
32318
|
+
}
|
|
31886
32319
|
}
|
|
31887
32320
|
}
|
|
31888
32321
|
return {
|
|
@@ -31890,7 +32323,7 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
31890
32323
|
startedAt: startedAt.toISOString(),
|
|
31891
32324
|
finishedAt: finishedAt.toISOString(),
|
|
31892
32325
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
31893
|
-
status: totals.failed > 0 ? "failed" : "passed",
|
|
32326
|
+
status: totals.failed > 0 ? "failed" : totals.passed > 0 || totals.cases === 0 ? "passed" : "skipped",
|
|
31894
32327
|
...options.cancelled ? { cancelled: true } : {},
|
|
31895
32328
|
cases,
|
|
31896
32329
|
totals
|
|
@@ -31899,7 +32332,7 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
31899
32332
|
|
|
31900
32333
|
// ../../packages/execution-engine/src/engine/test-runner.ts
|
|
31901
32334
|
import { writeFile as writeFile9 } from "node:fs/promises";
|
|
31902
|
-
import { join as join15, resolve as
|
|
32335
|
+
import { join as join15, resolve as resolve6 } from "node:path";
|
|
31903
32336
|
var TestRunner = class {
|
|
31904
32337
|
resolver;
|
|
31905
32338
|
runsRoot;
|
|
@@ -31909,7 +32342,7 @@ var TestRunner = class {
|
|
|
31909
32342
|
accounts;
|
|
31910
32343
|
constructor(options) {
|
|
31911
32344
|
this.resolver = options.resolver;
|
|
31912
|
-
this.runsRoot =
|
|
32345
|
+
this.runsRoot = resolve6(options.runsRoot ?? join15(process.cwd(), ".testpilot", "artifacts", "runs"));
|
|
31913
32346
|
this.customRunId = options.runId;
|
|
31914
32347
|
this.extraSubscriber = options.onEvent;
|
|
31915
32348
|
this.signal = options.signal;
|
|
@@ -31937,6 +32370,11 @@ var TestRunner = class {
|
|
|
31937
32370
|
await engine.runCase(item.data, {
|
|
31938
32371
|
runId,
|
|
31939
32372
|
file: item.file,
|
|
32373
|
+
templateId: item.templateId ?? item.data.id,
|
|
32374
|
+
rowId: item.rowId ?? "default",
|
|
32375
|
+
rowIndex: item.rowIndex ?? 0,
|
|
32376
|
+
initialVariables: item.initialVariables ?? {},
|
|
32377
|
+
missingDependencies: item.missingDependencies ?? [],
|
|
31940
32378
|
artifacts,
|
|
31941
32379
|
bus,
|
|
31942
32380
|
resolver: this.resolver,
|
|
@@ -31946,7 +32384,8 @@ var TestRunner = class {
|
|
|
31946
32384
|
}
|
|
31947
32385
|
const cancelled = this.signal?.aborted === true;
|
|
31948
32386
|
await this.resolver.closeAll();
|
|
31949
|
-
const
|
|
32387
|
+
const templates = new Set(cases.map((item) => item.templateId ?? item.data.id)).size;
|
|
32388
|
+
const summary = buildRunSummary(runId, startedAt, results, { cancelled, templates });
|
|
31950
32389
|
await bus.emit({
|
|
31951
32390
|
type: "run-finished",
|
|
31952
32391
|
runId,
|
|
@@ -31988,8 +32427,8 @@ async function openCaseContext(bundle, options, evidenceTmpDir) {
|
|
|
31988
32427
|
}
|
|
31989
32428
|
|
|
31990
32429
|
// ../../packages/adapter-playwright/src/page.ts
|
|
31991
|
-
import { existsSync as
|
|
31992
|
-
import { mkdtemp, readFile as
|
|
32430
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
32431
|
+
import { mkdtemp, readFile as readFile6, rm } from "node:fs/promises";
|
|
31993
32432
|
import { tmpdir } from "node:os";
|
|
31994
32433
|
import { join as join16 } from "node:path";
|
|
31995
32434
|
var PlaywrightAdapter = class {
|
|
@@ -31999,6 +32438,7 @@ var PlaywrightAdapter = class {
|
|
|
31999
32438
|
}
|
|
32000
32439
|
target = "web";
|
|
32001
32440
|
current;
|
|
32441
|
+
requestCapture;
|
|
32002
32442
|
/** 浏览器在 create() 时已启动;context/page 按用例惰性创建,launch 保持幂等 */
|
|
32003
32443
|
async launch() {
|
|
32004
32444
|
}
|
|
@@ -32013,6 +32453,7 @@ var PlaywrightAdapter = class {
|
|
|
32013
32453
|
}
|
|
32014
32454
|
/** 结束取证:trace 落 zip、录屏在 context 关闭后完成,返回二者字节 */
|
|
32015
32455
|
async stopEvidence(_caseId) {
|
|
32456
|
+
await this.stopRequestCapture().catch(() => []);
|
|
32016
32457
|
if (!this.current) return {};
|
|
32017
32458
|
const { context, page, evidenceTmpDir } = this.current;
|
|
32018
32459
|
this.current = void 0;
|
|
@@ -32020,11 +32461,28 @@ var PlaywrightAdapter = class {
|
|
|
32020
32461
|
await context.tracing.stop({ path: traceZipPath }).catch(() => void 0);
|
|
32021
32462
|
const recordedVideoPath = await page.video()?.path().catch(() => void 0);
|
|
32022
32463
|
await context.close().catch(() => void 0);
|
|
32023
|
-
const video = recordedVideoPath &&
|
|
32024
|
-
const trace = evidenceTmpDir &&
|
|
32464
|
+
const video = recordedVideoPath && existsSync3(recordedVideoPath) ? await readFile6(recordedVideoPath) : void 0;
|
|
32465
|
+
const trace = evidenceTmpDir && existsSync3(traceZipPath) ? await readFile6(traceZipPath) : void 0;
|
|
32025
32466
|
if (evidenceTmpDir) await rm(evidenceTmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
32026
32467
|
return { video, trace };
|
|
32027
32468
|
}
|
|
32469
|
+
async startRequestCapture() {
|
|
32470
|
+
if (this.requestCapture) await this.stopRequestCapture();
|
|
32471
|
+
const page = await this.ensurePage();
|
|
32472
|
+
const requests = [];
|
|
32473
|
+
const listener = (request) => {
|
|
32474
|
+
requests.push({ method: request.method(), url: request.url() });
|
|
32475
|
+
};
|
|
32476
|
+
page.on("request", listener);
|
|
32477
|
+
this.requestCapture = { page, listener, requests };
|
|
32478
|
+
}
|
|
32479
|
+
async stopRequestCapture() {
|
|
32480
|
+
if (!this.requestCapture) return [];
|
|
32481
|
+
const capture = this.requestCapture;
|
|
32482
|
+
this.requestCapture = void 0;
|
|
32483
|
+
capture.page.off("request", capture.listener);
|
|
32484
|
+
return capture.requests;
|
|
32485
|
+
}
|
|
32028
32486
|
async navigate(url2) {
|
|
32029
32487
|
const options = this.options;
|
|
32030
32488
|
const page = await this.ensurePage();
|
|
@@ -32086,6 +32544,7 @@ var PlaywrightAdapter = class {
|
|
|
32086
32544
|
throw new Error(`\u65E0\u6548\u7684 locator:${JSON.stringify(locator)}`);
|
|
32087
32545
|
}
|
|
32088
32546
|
async closeContextQuietly() {
|
|
32547
|
+
await this.stopRequestCapture().catch(() => []);
|
|
32089
32548
|
if (!this.current) return;
|
|
32090
32549
|
const { context } = this.current;
|
|
32091
32550
|
this.current = void 0;
|
|
@@ -32124,8 +32583,8 @@ var automatorLauncher = {
|
|
|
32124
32583
|
}
|
|
32125
32584
|
}
|
|
32126
32585
|
};
|
|
32127
|
-
function
|
|
32128
|
-
return new Promise((
|
|
32586
|
+
function delay2(ms) {
|
|
32587
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
32129
32588
|
}
|
|
32130
32589
|
async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
32131
32590
|
const startedAt = Date.now();
|
|
@@ -32135,7 +32594,7 @@ async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
|
32135
32594
|
return await automator.connect({ wsEndpoint });
|
|
32136
32595
|
} catch (err) {
|
|
32137
32596
|
lastError = err;
|
|
32138
|
-
await
|
|
32597
|
+
await delay2(1e3);
|
|
32139
32598
|
}
|
|
32140
32599
|
}
|
|
32141
32600
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -32186,6 +32645,7 @@ var MiniAppAdapter = class {
|
|
|
32186
32645
|
}
|
|
32187
32646
|
target = "miniapp";
|
|
32188
32647
|
program;
|
|
32648
|
+
requestCapture;
|
|
32189
32649
|
/** 拉起开发者工具并建立自动化连接;需幂等 */
|
|
32190
32650
|
async launch() {
|
|
32191
32651
|
if (this.program) return;
|
|
@@ -32206,7 +32666,7 @@ var MiniAppAdapter = class {
|
|
|
32206
32666
|
return;
|
|
32207
32667
|
} catch (err) {
|
|
32208
32668
|
lastError = err;
|
|
32209
|
-
await
|
|
32669
|
+
await delay2(3e3);
|
|
32210
32670
|
}
|
|
32211
32671
|
}
|
|
32212
32672
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -32252,7 +32712,43 @@ var MiniAppAdapter = class {
|
|
|
32252
32712
|
return Buffer.from(base643, "base64");
|
|
32253
32713
|
});
|
|
32254
32714
|
}
|
|
32715
|
+
async startRequestCapture() {
|
|
32716
|
+
if (this.requestCapture) await this.stopRequestCapture();
|
|
32717
|
+
const program2 = await this.getProgram();
|
|
32718
|
+
const requests = [];
|
|
32719
|
+
const exposedName = `__testpilotRequest_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
32720
|
+
await program2.exposeFunction(exposedName, (value) => {
|
|
32721
|
+
if (value === null || typeof value !== "object") return;
|
|
32722
|
+
const request = value;
|
|
32723
|
+
if (typeof request.url !== "string") return;
|
|
32724
|
+
requests.push({
|
|
32725
|
+
method: typeof request.method === "string" ? request.method : "GET",
|
|
32726
|
+
url: request.url
|
|
32727
|
+
});
|
|
32728
|
+
});
|
|
32729
|
+
await program2.mockWxMethod(
|
|
32730
|
+
"request",
|
|
32731
|
+
function(options, callbackName) {
|
|
32732
|
+
const callbacks = globalThis;
|
|
32733
|
+
callbacks[callbackName]?.({
|
|
32734
|
+
method: typeof options.method === "string" ? options.method : "GET",
|
|
32735
|
+
url: typeof options.url === "string" ? options.url : ""
|
|
32736
|
+
});
|
|
32737
|
+
return this.origin(options);
|
|
32738
|
+
},
|
|
32739
|
+
exposedName
|
|
32740
|
+
);
|
|
32741
|
+
this.requestCapture = { program: program2, exposedName, requests };
|
|
32742
|
+
}
|
|
32743
|
+
async stopRequestCapture() {
|
|
32744
|
+
if (!this.requestCapture) return [];
|
|
32745
|
+
const capture = this.requestCapture;
|
|
32746
|
+
this.requestCapture = void 0;
|
|
32747
|
+
await capture.program.restoreWxMethod("request");
|
|
32748
|
+
return capture.requests;
|
|
32749
|
+
}
|
|
32255
32750
|
async close() {
|
|
32751
|
+
await this.stopRequestCapture().catch(() => []);
|
|
32256
32752
|
if (!this.program) return;
|
|
32257
32753
|
const program2 = this.program;
|
|
32258
32754
|
this.program = void 0;
|
|
@@ -32300,17 +32796,17 @@ var MiniAppAdapter = class {
|
|
|
32300
32796
|
if (Date.now() - startedAt > ELEMENT_WAIT_MS) {
|
|
32301
32797
|
throw new Error(`\u7B49\u5F85\u5143\u7D20\u8D85\u65F6(${ELEMENT_WAIT_MS}ms):${selector}`);
|
|
32302
32798
|
}
|
|
32303
|
-
await
|
|
32799
|
+
await delay2(500);
|
|
32304
32800
|
}
|
|
32305
32801
|
}
|
|
32306
32802
|
};
|
|
32307
32803
|
|
|
32308
32804
|
// ../../packages/adapter-wechatide/src/project.ts
|
|
32309
|
-
import { existsSync as
|
|
32805
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
32310
32806
|
async function resolveMiniappConfig(input2) {
|
|
32311
32807
|
const projectPath = input2.projectPath ?? process.env.TESTPILOT_MINIPROGRAM_PROJECT_PATH;
|
|
32312
32808
|
if (!projectPath) return void 0;
|
|
32313
|
-
if (!
|
|
32809
|
+
if (!existsSync4(projectPath)) {
|
|
32314
32810
|
throw new Error(`\u5C0F\u7A0B\u5E8F\u9879\u76EE\u8DEF\u5F84\u4E0D\u5B58\u5728:${projectPath}`);
|
|
32315
32811
|
}
|
|
32316
32812
|
const cliPath = input2.cliPath ?? process.env.WECHAT_DEVTOOLS_CLI ?? await findWechatDevToolsCli();
|
|
@@ -32485,7 +32981,7 @@ function createDefaultAdapters(config2) {
|
|
|
32485
32981
|
async function runCases(options = {}) {
|
|
32486
32982
|
const root = resolveRoot(options.root);
|
|
32487
32983
|
const config2 = await loadProjectConfig(root);
|
|
32488
|
-
const inputs = options.paths && options.paths.length > 0 ? options.paths.map((input2) =>
|
|
32984
|
+
const inputs = options.paths && options.paths.length > 0 ? options.paths.map((input2) => isAbsolute3(input2) ? input2 : join17(root, input2)) : [join17(root, config2.casesDir)];
|
|
32489
32985
|
const cases = await collectCases(inputs, { root });
|
|
32490
32986
|
if (cases.length === 0) {
|
|
32491
32987
|
throw new RunError("ENO_CASES", `\u672A\u627E\u5230\u7528\u4F8B\u6587\u4EF6(\u67E5\u627E:${inputs.join(", ")})`);
|
|
@@ -32517,8 +33013,23 @@ async function runCases(options = {}) {
|
|
|
32517
33013
|
signal: options.signal,
|
|
32518
33014
|
accounts: options.accounts
|
|
32519
33015
|
});
|
|
33016
|
+
const prepared = (await Promise.all(
|
|
33017
|
+
runnable.map(
|
|
33018
|
+
(item) => prepareCaseExecutions(
|
|
33019
|
+
{ data: item.case, file: item.file },
|
|
33020
|
+
{
|
|
33021
|
+
root,
|
|
33022
|
+
dataDir: join17(root, "tests", "e2e", "data"),
|
|
33023
|
+
variables: config2.variables,
|
|
33024
|
+
accounts: options.accounts
|
|
33025
|
+
}
|
|
33026
|
+
)
|
|
33027
|
+
)
|
|
33028
|
+
)).flat();
|
|
32520
33029
|
const summary = await runner.run(
|
|
32521
|
-
|
|
33030
|
+
prepared.map(
|
|
33031
|
+
(item) => item
|
|
33032
|
+
)
|
|
32522
33033
|
);
|
|
32523
33034
|
return { summary, invalid, tagFiltered, missingAccounts };
|
|
32524
33035
|
}
|
|
@@ -32542,20 +33053,20 @@ async function getRun(runId, options = {}) {
|
|
|
32542
33053
|
const base = runsRoot(options.root);
|
|
32543
33054
|
const file2 = join17(base, runId, RESULT_FILE);
|
|
32544
33055
|
try {
|
|
32545
|
-
return JSON.parse(await
|
|
33056
|
+
return JSON.parse(await readFile7(file2, "utf8"));
|
|
32546
33057
|
} catch {
|
|
32547
33058
|
throw new RunError("ENO_CASES", `\u8BFB\u53D6\u8FD0\u884C\u7ED3\u679C\u5931\u8D25:${file2}(\u8FD0\u884C\u672A\u5B8C\u6210\u6216 runId \u4E0D\u5B58\u5728)`);
|
|
32548
33059
|
}
|
|
32549
33060
|
}
|
|
32550
33061
|
async function getRunEvents(runId, options = {}) {
|
|
32551
33062
|
const file2 = join17(runsRoot(options.root), runId, "events.ndjson");
|
|
32552
|
-
const source = await
|
|
33063
|
+
const source = await readFile7(file2, "utf8").catch(() => "");
|
|
32553
33064
|
return source.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
32554
33065
|
}
|
|
32555
33066
|
async function readRunMeta(base, runId) {
|
|
32556
33067
|
try {
|
|
32557
33068
|
const summary = JSON.parse(
|
|
32558
|
-
await
|
|
33069
|
+
await readFile7(join17(base, runId, RESULT_FILE), "utf8")
|
|
32559
33070
|
);
|
|
32560
33071
|
return {
|
|
32561
33072
|
status: summary.status,
|
|
@@ -32579,7 +33090,7 @@ async function generateReport(runId, options = {}) {
|
|
|
32579
33090
|
const runDir = join18(resolveRoot(options.root), RUNS_DIR, id);
|
|
32580
33091
|
let summary;
|
|
32581
33092
|
try {
|
|
32582
|
-
summary = JSON.parse(await
|
|
33093
|
+
summary = JSON.parse(await readFile8(join18(runDir, "result.json"), "utf8"));
|
|
32583
33094
|
} catch {
|
|
32584
33095
|
throw new Error(`\u8BFB\u53D6\u8FD0\u884C\u7ED3\u679C\u5931\u8D25:${join18(runDir, "result.json")}(\u8FD0\u884C\u672A\u5B8C\u6210\u6216 runId \u4E0D\u5B58\u5728)`);
|
|
32585
33096
|
}
|
|
@@ -32610,13 +33121,13 @@ async function generateSummaryReport(options = {}) {
|
|
|
32610
33121
|
}
|
|
32611
33122
|
async function readReport(runId, options = {}) {
|
|
32612
33123
|
const file2 = join18(resolveRoot(options.root), RUNS_DIR, runId, "report.json");
|
|
32613
|
-
const source = await
|
|
33124
|
+
const source = await readFile8(file2, "utf8").catch(() => void 0);
|
|
32614
33125
|
return source ? JSON.parse(source) : void 0;
|
|
32615
33126
|
}
|
|
32616
33127
|
|
|
32617
33128
|
// ../../packages/sdk/src/client.ts
|
|
32618
33129
|
function resolveInput(root, input2) {
|
|
32619
|
-
return
|
|
33130
|
+
return isAbsolute4(input2) ? input2 : join19(root, input2);
|
|
32620
33131
|
}
|
|
32621
33132
|
var TestPilotClient = class {
|
|
32622
33133
|
/** 业务项目根目录(绝对路径) */
|
|
@@ -32692,13 +33203,13 @@ var TestPilotClient = class {
|
|
|
32692
33203
|
};
|
|
32693
33204
|
|
|
32694
33205
|
// src/commands/init/adapter-detector.ts
|
|
32695
|
-
import { existsSync as
|
|
33206
|
+
import { existsSync as existsSync5, readFileSync } from "node:fs";
|
|
32696
33207
|
import { join as join20 } from "node:path";
|
|
32697
33208
|
async function detectAdapters(root, projectType) {
|
|
32698
33209
|
const required2 = projectType === "hybrid" ? ["playwright", "wechatide"] : projectType === "wechat-miniapp" ? ["wechatide"] : projectType === "web" ? ["playwright"] : [];
|
|
32699
33210
|
const pkg = readPackageJsonDeps(root);
|
|
32700
33211
|
const hasPlaywrightDep = pkg !== void 0 && ("playwright" in pkg || "@playwright/test" in pkg);
|
|
32701
|
-
const playwrightInstalled = hasPlaywrightDep ||
|
|
33212
|
+
const playwrightInstalled = hasPlaywrightDep || existsSync5(join20(root, "node_modules", "playwright"));
|
|
32702
33213
|
const devtoolsCli = await findWechatDevToolsCli().catch(() => void 0);
|
|
32703
33214
|
return [
|
|
32704
33215
|
{
|
|
@@ -32717,7 +33228,7 @@ async function detectAdapters(root, projectType) {
|
|
|
32717
33228
|
}
|
|
32718
33229
|
function readPackageJsonDeps(root) {
|
|
32719
33230
|
const file2 = join20(root, "package.json");
|
|
32720
|
-
if (!
|
|
33231
|
+
if (!existsSync5(file2)) return void 0;
|
|
32721
33232
|
try {
|
|
32722
33233
|
const pkg = JSON.parse(readFileSync(file2, "utf8"));
|
|
32723
33234
|
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
@@ -32727,13 +33238,13 @@ function readPackageJsonDeps(root) {
|
|
|
32727
33238
|
}
|
|
32728
33239
|
|
|
32729
33240
|
// src/commands/init/environment-initializer.ts
|
|
32730
|
-
import { readFile as
|
|
33241
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
32731
33242
|
import { join as join21 } from "node:path";
|
|
32732
33243
|
var ENV_REF_PATTERN = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
32733
33244
|
async function collectEnvVarRefs(root) {
|
|
32734
33245
|
let text;
|
|
32735
33246
|
try {
|
|
32736
|
-
text = await
|
|
33247
|
+
text = await readFile9(join21(root, "testpilot.yaml"), "utf8");
|
|
32737
33248
|
} catch {
|
|
32738
33249
|
return [];
|
|
32739
33250
|
}
|
|
@@ -32749,7 +33260,7 @@ async function collectEnvVarRefs(root) {
|
|
|
32749
33260
|
}
|
|
32750
33261
|
|
|
32751
33262
|
// src/commands/init/project-detector.ts
|
|
32752
|
-
import { existsSync as
|
|
33263
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
32753
33264
|
import { basename, join as join22 } from "node:path";
|
|
32754
33265
|
|
|
32755
33266
|
// src/lib/git.ts
|
|
@@ -32788,7 +33299,7 @@ var WEB_DEP_MARKERS = [
|
|
|
32788
33299
|
var E2E_DIR_CANDIDATES = ["tests/e2e", "test/e2e", "e2e", "cypress"];
|
|
32789
33300
|
function readPackageJson(root) {
|
|
32790
33301
|
const file2 = join22(root, "package.json");
|
|
32791
|
-
if (!
|
|
33302
|
+
if (!existsSync6(file2)) return void 0;
|
|
32792
33303
|
try {
|
|
32793
33304
|
return JSON.parse(readFileSync2(file2, "utf8"));
|
|
32794
33305
|
} catch {
|
|
@@ -32798,8 +33309,8 @@ function readPackageJson(root) {
|
|
|
32798
33309
|
function detectProjectType(root) {
|
|
32799
33310
|
const pkg = readPackageJson(root);
|
|
32800
33311
|
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
32801
|
-
const isWechat =
|
|
32802
|
-
const isWeb =
|
|
33312
|
+
const isWechat = existsSync6(join22(root, "project.config.json")) || existsSync6(join22(root, "app.json")) || existsSync6(join22(root, "miniprogram", "app.json"));
|
|
33313
|
+
const isWeb = existsSync6(join22(root, "index.html")) || WEB_DEP_MARKERS.some((marker) => deps?.[marker] !== void 0) || [".ts", ".tsx", ".js", ".jsx"].some((ext) => existsSync6(join22(root, "src", `index${ext}`)));
|
|
32803
33314
|
if (isWechat && isWeb) return "hybrid";
|
|
32804
33315
|
if (isWechat) return "wechat-miniapp";
|
|
32805
33316
|
if (isWeb) return "web";
|
|
@@ -32808,9 +33319,9 @@ function detectProjectType(root) {
|
|
|
32808
33319
|
async function detectProject(root) {
|
|
32809
33320
|
const pkg = readPackageJson(root);
|
|
32810
33321
|
const type = detectProjectType(root);
|
|
32811
|
-
const language =
|
|
32812
|
-
const packageManager =
|
|
32813
|
-
const existingE2eDirs = E2E_DIR_CANDIDATES.filter((dir) =>
|
|
33322
|
+
const language = existsSync6(join22(root, "tsconfig.json")) ? "TypeScript" : pkg || existsSync6(join22(root, "src")) ? "JavaScript" : "Unknown";
|
|
33323
|
+
const packageManager = existsSync6(join22(root, "pnpm-lock.yaml")) ? "pnpm" : existsSync6(join22(root, "yarn.lock")) ? "yarn" : existsSync6(join22(root, "package-lock.json")) ? "npm" : "npx";
|
|
33324
|
+
const existingE2eDirs = E2E_DIR_CANDIDATES.filter((dir) => existsSync6(join22(root, dir)));
|
|
32814
33325
|
return {
|
|
32815
33326
|
root,
|
|
32816
33327
|
name: pkg?.name ?? basename(root),
|
|
@@ -32820,8 +33331,8 @@ async function detectProject(root) {
|
|
|
32820
33331
|
git: await readGitInfo(root),
|
|
32821
33332
|
hasTestsE2e: existingE2eDirs.includes("tests/e2e"),
|
|
32822
33333
|
existingE2eDirs,
|
|
32823
|
-
hasTestpilotConfig:
|
|
32824
|
-
hasProjectLink:
|
|
33334
|
+
hasTestpilotConfig: existsSync6(join22(root, "testpilot.yaml")),
|
|
33335
|
+
hasProjectLink: existsSync6(join22(root, ".testpilot", "project.json"))
|
|
32825
33336
|
};
|
|
32826
33337
|
}
|
|
32827
33338
|
function defaultAdapterFor(type) {
|
|
@@ -32836,9 +33347,9 @@ function adaptersFor(type) {
|
|
|
32836
33347
|
}
|
|
32837
33348
|
|
|
32838
33349
|
// src/commands/init/skill-installer.ts
|
|
32839
|
-
import { existsSync as
|
|
33350
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
|
|
32840
33351
|
import { access as access2, cp, mkdir as mkdir12, writeFile as writeFile10 } from "node:fs/promises";
|
|
32841
|
-
import { dirname as
|
|
33352
|
+
import { dirname as dirname9, join as join23 } from "node:path";
|
|
32842
33353
|
import { fileURLToPath } from "node:url";
|
|
32843
33354
|
var CORE_FILES = ["SKILL.md", "manifest.yaml"];
|
|
32844
33355
|
var CORE_DIRS = ["rules", "workflows"];
|
|
@@ -32852,23 +33363,23 @@ async function exists3(path) {
|
|
|
32852
33363
|
}
|
|
32853
33364
|
function readManifestVersion(dir) {
|
|
32854
33365
|
const manifestPath = join23(dir, "manifest.yaml");
|
|
32855
|
-
if (!
|
|
33366
|
+
if (!existsSync7(manifestPath)) return void 0;
|
|
32856
33367
|
return /^version:\s*([^\s#]+)/m.exec(readFileSync3(manifestPath, "utf8"))?.[1];
|
|
32857
33368
|
}
|
|
32858
33369
|
function resolveSkillSourceDir() {
|
|
32859
33370
|
const fromEnv = process.env.TESTPILOT_SKILL_DIR;
|
|
32860
33371
|
if (fromEnv) return fromEnv;
|
|
32861
33372
|
const bundled = fileURLToPath(new URL("./skills/testpilot", import.meta.url));
|
|
32862
|
-
if (
|
|
33373
|
+
if (existsSync7(bundled)) return bundled;
|
|
32863
33374
|
const inRepo = fileURLToPath(new URL("../../../../../skills/testpilot", import.meta.url));
|
|
32864
|
-
return
|
|
33375
|
+
return existsSync7(inRepo) ? inRepo : void 0;
|
|
32865
33376
|
}
|
|
32866
33377
|
async function copyCoreFiles(src, dest) {
|
|
32867
33378
|
for (const name of CORE_FILES) {
|
|
32868
33379
|
await cp(join23(src, name), join23(dest, name), { recursive: true, force: true });
|
|
32869
33380
|
}
|
|
32870
33381
|
for (const dir of CORE_DIRS) {
|
|
32871
|
-
if (
|
|
33382
|
+
if (existsSync7(join23(src, dir))) {
|
|
32872
33383
|
await cp(join23(src, dir), join23(dest, dir), { recursive: true, force: true });
|
|
32873
33384
|
}
|
|
32874
33385
|
}
|
|
@@ -32883,7 +33394,7 @@ async function syncSkillFiles(root, options = {}) {
|
|
|
32883
33394
|
"\u65E0\u6CD5\u5B9A\u4F4D TestPilot Skill \u6E90\u76EE\u5F55;\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF TESTPILOT_SKILL_DIR \u6307\u5B9A skills/testpilot \u8DEF\u5F84"
|
|
32884
33395
|
);
|
|
32885
33396
|
}
|
|
32886
|
-
await mkdir12(
|
|
33397
|
+
await mkdir12(dirname9(dest), { recursive: true });
|
|
32887
33398
|
await cp(src, dest, { recursive: true });
|
|
32888
33399
|
return { core: "created", bundledVersion, dest };
|
|
32889
33400
|
}
|
|
@@ -32957,6 +33468,12 @@ function renderTestConventionsMd() {
|
|
|
32957
33468
|
6. \u4F18\u5148\u590D\u7528\u5DF2\u6709 Case
|
|
32958
33469
|
7. \u65B0\u589E Case \u5FC5\u987B\u901A\u8FC7 csspilot validate
|
|
32959
33470
|
8. Case ID \u5FC5\u987B\u552F\u4E00
|
|
33471
|
+
9. fixture/dataset \u4F7F\u7528 YAML \u6216 JSON,\u7EDF\u4E00\u4F4D\u4E8E tests/e2e/data
|
|
33472
|
+
10. \u9700\u6C42\u89C4\u5219\u77E9\u9635\u4E0E QA \u8986\u76D6\u6620\u5C04\u5199\u5165 tests/e2e/reviews/<\u9700\u6C42>-coverage.md
|
|
33473
|
+
11. \u53EA\u5728\u6B65\u9AA4\u6A21\u677F\u76F8\u540C\u65F6\u4F7F\u7528 data-driven Case,\u6BCF\u884C\u4FDD\u6301\u4E00\u4E2A\u53EF\u5224\u5B9A\u89C4\u5219/\u6570\u636E\u70B9
|
|
33474
|
+
12. requires \u53EA\u58F0\u660E\u9879\u76EE\u8BC1\u636E\u652F\u6301\u7684\u4F9D\u8D56,\u4E0D\u5F97\u5957\u7528\u8D26\u53F7/\u4F18\u60E0\u5238/PPM \u7B49\u9ED8\u8BA4\u5B57\u6BB5
|
|
33475
|
+
13. Case \u4E0D\u5F97\u8BFB\u53D6\u4EFB\u610F\u73AF\u5883\u53D8\u91CF;\u5148\u5728 testpilot.yaml variables \u767D\u540D\u5355\u6620\u5C04\u540E\u4F7F\u7528 \${variable.*}
|
|
33476
|
+
14. setup/teardown \u4F18\u5148 API;teardown \u5931\u8D25\u4F5C\u4E3A warning,\u4E0D\u6539\u53D8\u4E3B\u6D41\u7A0B\u7ED3\u679C
|
|
32960
33477
|
`;
|
|
32961
33478
|
}
|
|
32962
33479
|
function renderAdaptersMd(adapters) {
|
|
@@ -32997,8 +33514,8 @@ async function runDoctor(root) {
|
|
|
32997
33514
|
const projectItems = [
|
|
32998
33515
|
ok(`Project ${info.name}(${info.type})`),
|
|
32999
33516
|
info.git.commit ? ok(`Git repository(${info.git.branch ?? "detached"} \xB7 ${info.git.commit.slice(0, 7)})`) : warn("Git repository \u672A\u68C0\u6D4B\u5230", "Case Metadata \u65E0\u6CD5\u540C\u6B65\u5230 Server"),
|
|
33000
|
-
|
|
33001
|
-
|
|
33517
|
+
existsSync8(join24(root, ".testpilot", "project.json")) ? ok(".testpilot/project.json") : error62(".testpilot/project.json", "\u8FD0\u884C npx csspilot init \u751F\u6210"),
|
|
33518
|
+
existsSync8(join24(root, "testpilot.yaml")) ? ok("testpilot.yaml") : error62("testpilot.yaml", "\u8FD0\u884C npx csspilot init \u751F\u6210")
|
|
33002
33519
|
];
|
|
33003
33520
|
push("Project", projectItems);
|
|
33004
33521
|
const config2 = await loadTestpilotConfig(root).catch(() => void 0);
|
|
@@ -33007,8 +33524,8 @@ async function runDoctor(root) {
|
|
|
33007
33524
|
const invalidCount = cases.filter((item) => !item.valid).length;
|
|
33008
33525
|
const testDir = join24(root, "tests", "e2e");
|
|
33009
33526
|
push("Test", [
|
|
33010
|
-
|
|
33011
|
-
|
|
33527
|
+
existsSync8(testDir) ? ok("tests/e2e") : error62("tests/e2e", "\u8FD0\u884C npx csspilot init \u521B\u5EFA"),
|
|
33528
|
+
existsSync8(join24(root, casesDir)) ? ok(`cases directory(${casesDir})`) : error62(`cases directory(${casesDir})`, "\u8FD0\u884C npx csspilot init \u521B\u5EFA"),
|
|
33012
33529
|
cases.length === 0 ? warn("Case validation \u8DF3\u8FC7(\u672A\u627E\u5230\u7528\u4F8B)", "\u5728 tests/e2e/cases \u521B\u5EFA Case") : invalidCount === 0 ? ok(`Case validation(${cases.length} \u4E2A\u7528\u4F8B\u5168\u90E8\u6709\u6548)`) : error62(`Case validation(${invalidCount}/${cases.length} \u4E2A\u7528\u4F8B\u6821\u9A8C\u5931\u8D25)`, "\u8FD0\u884C npx csspilot validate \u67E5\u770B\u8BE6\u60C5")
|
|
33013
33530
|
]);
|
|
33014
33531
|
const skillDir = join24(root, ".agents", "skills", "testpilot");
|
|
@@ -33017,7 +33534,7 @@ async function runDoctor(root) {
|
|
|
33017
33534
|
const bundledSkillVersion = skillSource ? readManifestVersion(skillSource) : void 0;
|
|
33018
33535
|
const skillItems = [
|
|
33019
33536
|
installedSkillVersion ? ok(`TestPilot Skill(.agents/skills/testpilot,v${installedSkillVersion})`) : error62("TestPilot Skill", "\u8FD0\u884C npx csspilot init \u5B89\u88C5"),
|
|
33020
|
-
|
|
33537
|
+
existsSync8(join24(skillDir, "references", "project.md")) ? ok("Project testing guide(references/)") : warn("Project testing guide \u7F3A\u5931", "references/ \u7531 init \u751F\u6210;\u53EF\u91CD\u65B0\u8FD0\u884C npx csspilot init")
|
|
33021
33538
|
];
|
|
33022
33539
|
if (installedSkillVersion && bundledSkillVersion && installedSkillVersion !== bundledSkillVersion) {
|
|
33023
33540
|
skillItems.push(
|
|
@@ -33112,7 +33629,7 @@ function makeDoctorCommand() {
|
|
|
33112
33629
|
import { join as join27 } from "node:path";
|
|
33113
33630
|
|
|
33114
33631
|
// src/commands/init/config-initializer.ts
|
|
33115
|
-
import { access as access3, mkdir as mkdir13, readFile as
|
|
33632
|
+
import { access as access3, mkdir as mkdir13, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
|
|
33116
33633
|
import { basename as basename2, join as join25 } from "node:path";
|
|
33117
33634
|
async function exists4(path) {
|
|
33118
33635
|
try {
|
|
@@ -33144,6 +33661,10 @@ environment:
|
|
|
33144
33661
|
staging:
|
|
33145
33662
|
baseUrl: \${STAGING_BASE_URL}
|
|
33146
33663
|
|
|
33664
|
+
# Case \u53EF\u89C1\u7684\u9879\u76EE\u53D8\u91CF\u767D\u540D\u5355(\u503C\u6765\u81EA .env / CI Secret;Case \u7528 \${variable.<name>} \u5F15\u7528)
|
|
33665
|
+
# variables:
|
|
33666
|
+
# ppmProjectId: \${PPM_PROJECT_ID}
|
|
33667
|
+
|
|
33147
33668
|
# \u9879\u76EE\u58F0\u660E\u7684\u6267\u884C\u7AEF(\u5B9E\u9645\u53EF\u7528\u6027\u4EE5 csspilot doctor \u68C0\u6D4B\u4E3A\u51C6)
|
|
33148
33669
|
adapters:
|
|
33149
33670
|
${adapters}
|
|
@@ -33194,7 +33715,7 @@ ${entry}
|
|
|
33194
33715
|
`, "utf8");
|
|
33195
33716
|
return "created";
|
|
33196
33717
|
}
|
|
33197
|
-
const content = await
|
|
33718
|
+
const content = await readFile10(gitignorePath, "utf8");
|
|
33198
33719
|
if (content.split(/\r?\n/).some((item) => item.trim() === entry)) {
|
|
33199
33720
|
return "exists";
|
|
33200
33721
|
}
|
|
@@ -33278,19 +33799,21 @@ function randomId() {
|
|
|
33278
33799
|
}
|
|
33279
33800
|
|
|
33280
33801
|
// src/commands/init/test-structure.ts
|
|
33281
|
-
import { existsSync as
|
|
33802
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2 } from "node:fs";
|
|
33282
33803
|
import { mkdir as mkdir14, writeFile as writeFile12 } from "node:fs/promises";
|
|
33283
33804
|
import { join as join26 } from "node:path";
|
|
33284
33805
|
async function initTestStructure(root, _info) {
|
|
33285
33806
|
const dirs = {
|
|
33286
33807
|
cases: join26(root, "tests", "e2e", "cases"),
|
|
33287
33808
|
fixtures: join26(root, "tests", "e2e", "fixtures"),
|
|
33288
|
-
data: join26(root, "tests", "e2e", "data")
|
|
33809
|
+
data: join26(root, "tests", "e2e", "data"),
|
|
33810
|
+
reviews: join26(root, "tests", "e2e", "reviews")
|
|
33289
33811
|
};
|
|
33290
33812
|
const existed = {
|
|
33291
|
-
cases:
|
|
33292
|
-
fixtures:
|
|
33293
|
-
data:
|
|
33813
|
+
cases: existsSync9(dirs.cases),
|
|
33814
|
+
fixtures: existsSync9(dirs.fixtures),
|
|
33815
|
+
data: existsSync9(dirs.data),
|
|
33816
|
+
reviews: existsSync9(dirs.reviews)
|
|
33294
33817
|
};
|
|
33295
33818
|
for (const dir of Object.values(dirs)) {
|
|
33296
33819
|
await mkdir14(dir, { recursive: true });
|
|
@@ -33303,7 +33826,8 @@ async function initTestStructure(root, _info) {
|
|
|
33303
33826
|
return {
|
|
33304
33827
|
cases: existed.cases ? "reused" : "created",
|
|
33305
33828
|
fixtures: existed.fixtures ? "reused" : "created",
|
|
33306
|
-
data: existed.data ? "reused" : "created"
|
|
33829
|
+
data: existed.data ? "reused" : "created",
|
|
33830
|
+
reviews: existed.reviews ? "reused" : "created"
|
|
33307
33831
|
};
|
|
33308
33832
|
}
|
|
33309
33833
|
|
|
@@ -33343,7 +33867,7 @@ async function runInit(root, options = {}) {
|
|
|
33343
33867
|
const testStructure = await initTestStructure(root, project);
|
|
33344
33868
|
const reuseNote = project.existingE2eDirs.filter((dir) => dir !== "tests/e2e");
|
|
33345
33869
|
console.log(
|
|
33346
|
-
testStructure.cases === "reused" ? "\u2713 \u6D4B\u8BD5\u76EE\u5F55 tests/e2e(\u5DF2\u5B58\u5728,\u590D\u7528)" : "\u2713 \u6D4B\u8BD5\u76EE\u5F55 tests/e2e/{cases,fixtures,data}"
|
|
33870
|
+
testStructure.cases === "reused" ? "\u2713 \u6D4B\u8BD5\u76EE\u5F55 tests/e2e(\u5DF2\u5B58\u5728,\u590D\u7528)" : "\u2713 \u6D4B\u8BD5\u76EE\u5F55 tests/e2e/{cases,fixtures,data,reviews}"
|
|
33347
33871
|
);
|
|
33348
33872
|
if (reuseNote.length > 0) {
|
|
33349
33873
|
console.log(` \u26A0 \u68C0\u6D4B\u5230\u5176\u4ED6 E2E \u76EE\u5F55:${reuseNote.join(", ")}(\u4FDD\u7559\u4E0D\u52A8,\u672A\u5F3A\u5236\u8FC1\u79FB)`);
|
|
@@ -33486,7 +34010,7 @@ function makeRunCommand() {
|
|
|
33486
34010
|
].filter(Boolean).join(",");
|
|
33487
34011
|
console.log("");
|
|
33488
34012
|
console.log(
|
|
33489
|
-
`Run ${summary.runId}${summary.cancelled ? "(\u5DF2\u53D6\u6D88)" : ""}: ${summary.status.toUpperCase()} \u2014 \
|
|
34013
|
+
`Run ${summary.runId}${summary.cancelled ? "(\u5DF2\u53D6\u6D88)" : ""}: ${summary.status.toUpperCase()} \u2014 \u6A21\u677F ${summary.totals.templates ?? summary.totals.cases},\u6570\u636E\u884C \u2713${summary.totals.passed} \u2717${summary.totals.failed} \u21B7${summary.totals.skipped ?? 0} \u26A0${summary.totals.warnings ?? 0},\u6B65\u9AA4 \u2713${summary.totals.stepsPassed} \u2717${summary.totals.stepsFailed} \u21B7${summary.totals.stepsSkipped} \u26A0${summary.totals.stepsWarning ?? 0}${notes ? `(${notes})` : ""}`
|
|
33490
34014
|
);
|
|
33491
34015
|
console.log(`\u4EA7\u7269\u76EE\u5F55:${join28(".testpilot", "artifacts", "runs", summary.runId)}`);
|
|
33492
34016
|
console.log("\u67E5\u770B\u62A5\u544A:npx csspilot report");
|
|
@@ -33513,12 +34037,12 @@ function consoleEventHandler(event) {
|
|
|
33513
34037
|
`);
|
|
33514
34038
|
break;
|
|
33515
34039
|
case "case-started":
|
|
33516
|
-
console.log(`\u25B6 ${event.caseId} (${event.file})`);
|
|
34040
|
+
console.log(`\u25B6 ${event.caseId}#${event.rowId ?? "default"} (${event.file})`);
|
|
33517
34041
|
break;
|
|
33518
34042
|
case "step-finished": {
|
|
33519
|
-
const mark = event.status === "passed" ? " \u2713" : event.status === "failed" ? " \u2717" : " \u21B7";
|
|
34043
|
+
const mark = event.status === "passed" ? " \u2713" : event.status === "failed" ? " \u2717" : event.status === "warning" ? " \u26A0" : " \u21B7";
|
|
33520
34044
|
console.log(
|
|
33521
|
-
`${mark} ${event.target}/${event.action} ${event.durationMs}ms${event.error ? ` \u2014 ${event.error}` : ""}`
|
|
34045
|
+
`${mark} ${event.phase ?? "steps"} ${event.target}/${event.action} ${event.durationMs}ms${event.error ? ` \u2014 ${event.error}` : ""}`
|
|
33522
34046
|
);
|
|
33523
34047
|
break;
|
|
33524
34048
|
}
|
|
@@ -33528,7 +34052,7 @@ function consoleEventHandler(event) {
|
|
|
33528
34052
|
}
|
|
33529
34053
|
|
|
33530
34054
|
// src/commands/sync-metadata.ts
|
|
33531
|
-
import { relative } from "node:path";
|
|
34055
|
+
import { relative as relative2 } from "node:path";
|
|
33532
34056
|
function makeSyncMetadataCommand() {
|
|
33533
34057
|
return new Command("sync-metadata").description("\u540C\u6B65 Case Metadata \u5FEB\u7167\u5230 TestPilot Server(CI / git push \u540E\u4F7F\u7528)").option("--server <url>", "TestPilot Server \u5730\u5740(\u9ED8\u8BA4\u53D6 project.json \u6216 testpilot.yaml)").action(async (options) => {
|
|
33534
34058
|
const client = new TestPilotClient();
|
|
@@ -33555,7 +34079,7 @@ function makeSyncMetadataCommand() {
|
|
|
33555
34079
|
const cases = infos.filter((item) => item.valid && item.case).map((item) => ({
|
|
33556
34080
|
id: item.case.id,
|
|
33557
34081
|
title: item.case.name,
|
|
33558
|
-
file:
|
|
34082
|
+
file: relative2(process.cwd(), item.file).split("\\").join("/"),
|
|
33559
34083
|
tags: item.case.tags ?? []
|
|
33560
34084
|
}));
|
|
33561
34085
|
const skipped = infos.length - cases.length;
|
|
@@ -33665,7 +34189,7 @@ function makeValidateCommand() {
|
|
|
33665
34189
|
}
|
|
33666
34190
|
|
|
33667
34191
|
// src/cli.ts
|
|
33668
|
-
var CLI_VERSION = "0.
|
|
34192
|
+
var CLI_VERSION = "0.6.0";
|
|
33669
34193
|
async function createCli() {
|
|
33670
34194
|
const program2 = new Command();
|
|
33671
34195
|
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", () => {
|