csspilot 0.5.0 → 0.7.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 +934 -249
- package/dist/index.js +934 -249
- package/dist/skills/testpilot/SKILL.md +84 -78
- 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 +47 -29
- 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 +33 -16
- package/dist/skills/testpilot/workflows/run-case.md +3 -0
- package/dist/skills/testpilot/workflows/test-requirements-review.md +143 -61
- 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);
|
|
@@ -8102,7 +8102,7 @@ var require_composer = __commonJS({
|
|
|
8102
8102
|
var node_process = __require("process");
|
|
8103
8103
|
var directives = require_directives();
|
|
8104
8104
|
var Document = require_Document();
|
|
8105
|
-
var
|
|
8105
|
+
var errors2 = require_errors();
|
|
8106
8106
|
var identity = require_identity();
|
|
8107
8107
|
var composeDoc = require_compose_doc();
|
|
8108
8108
|
var resolveEnd = require_resolve_end();
|
|
@@ -8149,9 +8149,9 @@ var require_composer = __commonJS({
|
|
|
8149
8149
|
this.onError = (source, code, message, warning) => {
|
|
8150
8150
|
const pos = getErrorPos(source);
|
|
8151
8151
|
if (warning)
|
|
8152
|
-
this.warnings.push(new
|
|
8152
|
+
this.warnings.push(new errors2.YAMLWarning(pos, code, message));
|
|
8153
8153
|
else
|
|
8154
|
-
this.errors.push(new
|
|
8154
|
+
this.errors.push(new errors2.YAMLParseError(pos, code, message));
|
|
8155
8155
|
};
|
|
8156
8156
|
this.directives = new directives.Directives({ version: options.version || "1.2" });
|
|
8157
8157
|
this.options = options;
|
|
@@ -8249,7 +8249,7 @@ ${cb}` : comment;
|
|
|
8249
8249
|
break;
|
|
8250
8250
|
case "error": {
|
|
8251
8251
|
const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
|
|
8252
|
-
const error62 = new
|
|
8252
|
+
const error62 = new errors2.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
|
|
8253
8253
|
if (this.atDirectives || !this.doc)
|
|
8254
8254
|
this.errors.push(error62);
|
|
8255
8255
|
else
|
|
@@ -8259,7 +8259,7 @@ ${cb}` : comment;
|
|
|
8259
8259
|
case "doc-end": {
|
|
8260
8260
|
if (!this.doc) {
|
|
8261
8261
|
const msg = "Unexpected doc-end without preceding document";
|
|
8262
|
-
this.errors.push(new
|
|
8262
|
+
this.errors.push(new errors2.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
|
|
8263
8263
|
break;
|
|
8264
8264
|
}
|
|
8265
8265
|
this.doc.directives.docEnd = true;
|
|
@@ -8274,7 +8274,7 @@ ${end.comment}` : end.comment;
|
|
|
8274
8274
|
break;
|
|
8275
8275
|
}
|
|
8276
8276
|
default:
|
|
8277
|
-
this.errors.push(new
|
|
8277
|
+
this.errors.push(new errors2.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
|
|
8278
8278
|
}
|
|
8279
8279
|
}
|
|
8280
8280
|
/**
|
|
@@ -8309,7 +8309,7 @@ var require_cst_scalar = __commonJS({
|
|
|
8309
8309
|
"use strict";
|
|
8310
8310
|
var resolveBlockScalar = require_resolve_block_scalar();
|
|
8311
8311
|
var resolveFlowScalar = require_resolve_flow_scalar();
|
|
8312
|
-
var
|
|
8312
|
+
var errors2 = require_errors();
|
|
8313
8313
|
var stringifyString = require_stringifyString();
|
|
8314
8314
|
function resolveAsScalar(token, strict = true, onError) {
|
|
8315
8315
|
if (token) {
|
|
@@ -8318,7 +8318,7 @@ var require_cst_scalar = __commonJS({
|
|
|
8318
8318
|
if (onError)
|
|
8319
8319
|
onError(offset, code, message);
|
|
8320
8320
|
else
|
|
8321
|
-
throw new
|
|
8321
|
+
throw new errors2.YAMLParseError([offset, offset + 1], code, message);
|
|
8322
8322
|
};
|
|
8323
8323
|
switch (token.type) {
|
|
8324
8324
|
case "scalar":
|
|
@@ -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
|
|
|
@@ -10213,7 +10213,7 @@ var require_public_api = __commonJS({
|
|
|
10213
10213
|
"use strict";
|
|
10214
10214
|
var composer = require_composer();
|
|
10215
10215
|
var Document = require_Document();
|
|
10216
|
-
var
|
|
10216
|
+
var errors2 = require_errors();
|
|
10217
10217
|
var log = require_log();
|
|
10218
10218
|
var identity = require_identity();
|
|
10219
10219
|
var lineCounter = require_line_counter();
|
|
@@ -10230,8 +10230,8 @@ var require_public_api = __commonJS({
|
|
|
10230
10230
|
const docs = Array.from(composer$1.compose(parser$1.parse(source)));
|
|
10231
10231
|
if (prettyErrors && lineCounter2)
|
|
10232
10232
|
for (const doc of docs) {
|
|
10233
|
-
doc.errors.forEach(
|
|
10234
|
-
doc.warnings.forEach(
|
|
10233
|
+
doc.errors.forEach(errors2.prettifyError(source, lineCounter2));
|
|
10234
|
+
doc.warnings.forEach(errors2.prettifyError(source, lineCounter2));
|
|
10235
10235
|
}
|
|
10236
10236
|
if (docs.length > 0)
|
|
10237
10237
|
return docs;
|
|
@@ -10246,13 +10246,13 @@ var require_public_api = __commonJS({
|
|
|
10246
10246
|
if (!doc)
|
|
10247
10247
|
doc = _doc;
|
|
10248
10248
|
else if (doc.options.logLevel !== "silent") {
|
|
10249
|
-
doc.errors.push(new
|
|
10249
|
+
doc.errors.push(new errors2.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
|
|
10250
10250
|
break;
|
|
10251
10251
|
}
|
|
10252
10252
|
}
|
|
10253
10253
|
if (prettyErrors && lineCounter2) {
|
|
10254
|
-
doc.errors.forEach(
|
|
10255
|
-
doc.warnings.forEach(
|
|
10254
|
+
doc.errors.forEach(errors2.prettifyError(source, lineCounter2));
|
|
10255
|
+
doc.warnings.forEach(errors2.prettifyError(source, lineCounter2));
|
|
10256
10256
|
}
|
|
10257
10257
|
return doc;
|
|
10258
10258
|
}
|
|
@@ -10311,7 +10311,7 @@ var require_dist = __commonJS({
|
|
|
10311
10311
|
var composer = require_composer();
|
|
10312
10312
|
var Document = require_Document();
|
|
10313
10313
|
var Schema = require_Schema();
|
|
10314
|
-
var
|
|
10314
|
+
var errors2 = require_errors();
|
|
10315
10315
|
var Alias = require_Alias();
|
|
10316
10316
|
var identity = require_identity();
|
|
10317
10317
|
var Pair = require_Pair();
|
|
@@ -10323,13 +10323,13 @@ 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;
|
|
10330
|
-
exports.YAMLError =
|
|
10331
|
-
exports.YAMLParseError =
|
|
10332
|
-
exports.YAMLWarning =
|
|
10330
|
+
exports.YAMLError = errors2.YAMLError;
|
|
10331
|
+
exports.YAMLParseError = errors2.YAMLParseError;
|
|
10332
|
+
exports.YAMLWarning = errors2.YAMLWarning;
|
|
10333
10333
|
exports.Alias = Alias.Alias;
|
|
10334
10334
|
exports.isAlias = identity.isAlias;
|
|
10335
10335
|
exports.isCollection = identity.isCollection;
|
|
@@ -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
|
|
@@ -10462,6 +10462,28 @@ var TestPilotError = class extends Error {
|
|
|
10462
10462
|
}
|
|
10463
10463
|
};
|
|
10464
10464
|
|
|
10465
|
+
// ../../packages/core/src/verdict.ts
|
|
10466
|
+
var INFRA_CODES = /* @__PURE__ */ new Set([
|
|
10467
|
+
"ENVIRONMENT_UNAVAILABLE",
|
|
10468
|
+
"ADAPTER_NOT_FOUND",
|
|
10469
|
+
"UNSUPPORTED_ACTION",
|
|
10470
|
+
"FIXTURE_MISSING",
|
|
10471
|
+
"ACCOUNT_UNAVAILABLE",
|
|
10472
|
+
"CONFIG_MISSING"
|
|
10473
|
+
]);
|
|
10474
|
+
var UNVERIFIED_CODES = /* @__PURE__ */ new Set(["PREREQ_VARIABLE_MISSING"]);
|
|
10475
|
+
function verdictFromErrorCode(code) {
|
|
10476
|
+
if (code !== void 0 && INFRA_CODES.has(code)) return "infra";
|
|
10477
|
+
if (code !== void 0 && UNVERIFIED_CODES.has(code)) return "unverified";
|
|
10478
|
+
return "product";
|
|
10479
|
+
}
|
|
10480
|
+
function caseStatusFromErrorCode(code) {
|
|
10481
|
+
const verdict = verdictFromErrorCode(code);
|
|
10482
|
+
if (verdict === "infra") return "blocked";
|
|
10483
|
+
if (verdict === "unverified") return "inconclusive";
|
|
10484
|
+
return "failed";
|
|
10485
|
+
}
|
|
10486
|
+
|
|
10465
10487
|
// ../../packages/core/src/constants/index.ts
|
|
10466
10488
|
import { join as join2 } from "node:path";
|
|
10467
10489
|
var TESTPILOT_DIR = ".testpilot";
|
|
@@ -15513,7 +15535,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
15513
15535
|
var NONE = 0;
|
|
15514
15536
|
var ASSUMED = 1;
|
|
15515
15537
|
var PROVEN = 2;
|
|
15516
|
-
function isRecursive(inst, stack,
|
|
15538
|
+
function isRecursive(inst, stack, resolve7) {
|
|
15517
15539
|
const cached2 = recursive.get(inst);
|
|
15518
15540
|
if (cached2 !== void 0)
|
|
15519
15541
|
return cached2 ? PROVEN : NONE;
|
|
@@ -15523,7 +15545,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
15523
15545
|
let result = NONE;
|
|
15524
15546
|
const check2 = (child) => {
|
|
15525
15547
|
if (result !== PROVEN && child?._zod) {
|
|
15526
|
-
const answer = isRecursive(child, stack,
|
|
15548
|
+
const answer = isRecursive(child, stack, resolve7);
|
|
15527
15549
|
if (answer > result)
|
|
15528
15550
|
result = answer;
|
|
15529
15551
|
}
|
|
@@ -15534,7 +15556,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
15534
15556
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
15535
15557
|
if (spread && !desc.enumerable)
|
|
15536
15558
|
continue;
|
|
15537
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
15559
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve7) : NONE;
|
|
15538
15560
|
if (child > answer)
|
|
15539
15561
|
answer = child;
|
|
15540
15562
|
}
|
|
@@ -15601,7 +15623,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
15601
15623
|
break;
|
|
15602
15624
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
15603
15625
|
case "lazy": {
|
|
15604
|
-
const inner = def._cachedInner ?? (
|
|
15626
|
+
const inner = def._cachedInner ?? (resolve7 ? inst._zod.innerType : void 0);
|
|
15605
15627
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
15606
15628
|
break;
|
|
15607
15629
|
}
|
|
@@ -30167,6 +30189,8 @@ var configSchema = external_exports.strictObject({
|
|
|
30167
30189
|
}).optional(),
|
|
30168
30190
|
/** 环境配置:default 指定默认环境名,其余键为环境名 -> { baseUrl } */
|
|
30169
30191
|
environment: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().min(1), environmentEntrySchema])).optional(),
|
|
30192
|
+
/** Case 可见的项目变量白名单;值可通过 ${ENV_VAR} 从运行环境注入 */
|
|
30193
|
+
variables: external_exports.record(external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), external_exports.string()).optional(),
|
|
30170
30194
|
/** 项目声明的执行端(playwright / wechatide);仅作声明,实际可用性以 doctor 检测为准 */
|
|
30171
30195
|
adapters: external_exports.array(external_exports.string().min(1)).optional(),
|
|
30172
30196
|
workspace: external_exports.strictObject({ default: external_exports.string().min(1).optional() }).optional(),
|
|
@@ -30231,6 +30255,9 @@ async function loadTestpilotConfig(cwd = process.cwd()) {
|
|
|
30231
30255
|
testDirectory: parsed.test?.directory,
|
|
30232
30256
|
projectName: parsed.project?.name,
|
|
30233
30257
|
environment,
|
|
30258
|
+
variables: parsed.variables ? Object.fromEntries(
|
|
30259
|
+
Object.entries(parsed.variables).map(([name, value]) => [name, interpolateEnvRefs(value)])
|
|
30260
|
+
) : void 0,
|
|
30234
30261
|
adapters: parsed.adapters,
|
|
30235
30262
|
workspace: parsed.workspace,
|
|
30236
30263
|
server: parsed.server ? {
|
|
@@ -30341,11 +30368,11 @@ function execToString(command, args) {
|
|
|
30341
30368
|
}
|
|
30342
30369
|
|
|
30343
30370
|
// ../../packages/sdk/src/client.ts
|
|
30344
|
-
import { isAbsolute as
|
|
30371
|
+
import { isAbsolute as isAbsolute4, join as join19 } from "node:path";
|
|
30345
30372
|
|
|
30346
30373
|
// ../../packages/sdk/src/cases.ts
|
|
30347
|
-
import { mkdir as mkdir3, readFile as
|
|
30348
|
-
import { dirname, extname, isAbsolute, join as join6, resolve as
|
|
30374
|
+
import { mkdir as mkdir3, readFile as readFile4, readdir, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
|
|
30375
|
+
import { dirname as dirname2, extname as extname2, isAbsolute as isAbsolute2, join as join6, resolve as resolve3 } from "node:path";
|
|
30349
30376
|
|
|
30350
30377
|
// ../../packages/dsl/src/schema/index.ts
|
|
30351
30378
|
var STEP_TARGETS = ["web", "miniapp", "api"];
|
|
@@ -30366,6 +30393,12 @@ var locatorSchema = external_exports.strictObject({
|
|
|
30366
30393
|
text: external_exports.string().min(1).optional(),
|
|
30367
30394
|
css: external_exports.string().min(1).optional()
|
|
30368
30395
|
}).describe("text \u4E0E css \u53EA\u80FD\u63D0\u4F9B\u5176\u4E00");
|
|
30396
|
+
var requestExpectationSchema = external_exports.strictObject({
|
|
30397
|
+
method: external_exports.enum(HTTP_METHODS).optional(),
|
|
30398
|
+
urlContains: external_exports.string().min(1),
|
|
30399
|
+
count: external_exports.number().int().nonnegative(),
|
|
30400
|
+
windowMs: external_exports.number().int().nonnegative().default(1e3)
|
|
30401
|
+
});
|
|
30369
30402
|
var stepSchema = external_exports.strictObject({
|
|
30370
30403
|
target: external_exports.enum(STEP_TARGETS),
|
|
30371
30404
|
action: external_exports.enum(ACTIONS),
|
|
@@ -30380,8 +30413,18 @@ var stepSchema = external_exports.strictObject({
|
|
|
30380
30413
|
/** api target:请求体;对象按 JSON 序列化,字符串原样发送(支持 ${var} 模板) */
|
|
30381
30414
|
body: external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.unknown())]).optional(),
|
|
30382
30415
|
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()
|
|
30416
|
+
timeout: external_exports.number().int().positive().optional(),
|
|
30417
|
+
/** UI 动作触发的网络请求精确次数断言 */
|
|
30418
|
+
expectRequests: external_exports.array(requestExpectationSchema).min(1).optional()
|
|
30419
|
+
});
|
|
30420
|
+
var datasetReferenceSchema = external_exports.strictObject({
|
|
30421
|
+
file: external_exports.string().min(1),
|
|
30422
|
+
idField: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "idField \u9700\u4E3A\u5408\u6CD5\u5B57\u6BB5\u540D").default("id")
|
|
30384
30423
|
});
|
|
30424
|
+
var dependencyPathSchema = external_exports.string().regex(
|
|
30425
|
+
/^(fixture|dataset|account|variable)(?:\.[A-Za-z_][A-Za-z0-9_]*)+$/,
|
|
30426
|
+
"requires \u8DEF\u5F84\u5FC5\u987B\u4EE5 fixture/dataset/account/variable \u5F00\u5934\u5E76\u5305\u542B\u5B57\u6BB5"
|
|
30427
|
+
);
|
|
30385
30428
|
var caseSchema = external_exports.strictObject({
|
|
30386
30429
|
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
30430
|
name: external_exports.string().min(1, "name \u4E0D\u80FD\u4E3A\u7A7A"),
|
|
@@ -30393,7 +30436,12 @@ var caseSchema = external_exports.strictObject({
|
|
|
30393
30436
|
/** 引用测试账号标识;凭据由 Environment + Server/CI Secret 管理,禁止写进 Case */
|
|
30394
30437
|
accountRef: external_exports.string().min(1).optional(),
|
|
30395
30438
|
tags: external_exports.array(external_exports.string().min(1)).optional(),
|
|
30396
|
-
|
|
30439
|
+
fixtures: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
30440
|
+
datasets: datasetReferenceSchema.optional(),
|
|
30441
|
+
requires: external_exports.array(dependencyPathSchema).min(1).optional(),
|
|
30442
|
+
setup: external_exports.array(stepSchema).min(1).optional(),
|
|
30443
|
+
steps: external_exports.array(stepSchema).min(1, "Case \u81F3\u5C11\u5305\u542B\u4E00\u4E2A step"),
|
|
30444
|
+
teardown: external_exports.array(stepSchema).min(1).optional()
|
|
30397
30445
|
});
|
|
30398
30446
|
|
|
30399
30447
|
// ../../packages/dsl/src/parser/index.ts
|
|
@@ -30444,6 +30492,7 @@ var ADAPTER_SUPPORT = {
|
|
|
30444
30492
|
};
|
|
30445
30493
|
var LOCATOR_REQUIRED = /* @__PURE__ */ new Set(["click", "input", "select", "assert", "extract"]);
|
|
30446
30494
|
var VALUE_REQUIRED = /* @__PURE__ */ new Set(["input", "select"]);
|
|
30495
|
+
var REQUEST_EXPECTATION_ACTIONS = /* @__PURE__ */ new Set(["navigate", "click", "input", "select"]);
|
|
30447
30496
|
var FIELD_ALLOWED = {
|
|
30448
30497
|
url: ["navigate", "request"],
|
|
30449
30498
|
value: ["input", "select", "extract"],
|
|
@@ -30458,12 +30507,21 @@ var VARIABLE_REF = /\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g;
|
|
|
30458
30507
|
function validateCase(data) {
|
|
30459
30508
|
const issues = [];
|
|
30460
30509
|
const declared = /* @__PURE__ */ new Set();
|
|
30510
|
+
if (data.fixtures) declared.add("fixture");
|
|
30511
|
+
if (data.datasets) declared.add("dataset");
|
|
30512
|
+
declared.add("variable");
|
|
30461
30513
|
if (data.accountRef) {
|
|
30462
30514
|
declared.add("account");
|
|
30463
30515
|
}
|
|
30516
|
+
data.setup?.forEach((step, index) => {
|
|
30517
|
+
checkStep(step, `setup[${index}]`, declared, issues);
|
|
30518
|
+
});
|
|
30464
30519
|
data.steps.forEach((step, index) => {
|
|
30465
30520
|
checkStep(step, `steps[${index}]`, declared, issues);
|
|
30466
30521
|
});
|
|
30522
|
+
data.teardown?.forEach((step, index) => {
|
|
30523
|
+
checkStep(step, `teardown[${index}]`, declared, issues);
|
|
30524
|
+
});
|
|
30467
30525
|
return issues;
|
|
30468
30526
|
}
|
|
30469
30527
|
function validateCaseSource(source) {
|
|
@@ -30538,6 +30596,15 @@ function checkStep(step, at, declared, issues) {
|
|
|
30538
30596
|
if (step.action === "wait" && !step.locator && step.timeout === void 0) {
|
|
30539
30597
|
issues.push({ path: at, code: "semantic", message: "wait \u9700\u8981\u63D0\u4F9B locator(\u7B49\u5F85\u5143\u7D20)\u6216 timeout(\u6BEB\u79D2)" });
|
|
30540
30598
|
}
|
|
30599
|
+
if (step.expectRequests) {
|
|
30600
|
+
if (step.target === "api" || !REQUEST_EXPECTATION_ACTIONS.has(step.action)) {
|
|
30601
|
+
issues.push({
|
|
30602
|
+
path: `${at}.expectRequests`,
|
|
30603
|
+
code: "semantic",
|
|
30604
|
+
message: "expectRequests \u4EC5\u7528\u4E8E web/miniapp \u7684 navigate/click/input/select"
|
|
30605
|
+
});
|
|
30606
|
+
}
|
|
30607
|
+
}
|
|
30541
30608
|
checkVariableRefs(step, at, declared, issues);
|
|
30542
30609
|
}
|
|
30543
30610
|
function checkLocator(step, at, issues) {
|
|
@@ -30570,9 +30637,14 @@ function checkVariableRefs(step, at, declared, issues) {
|
|
|
30570
30637
|
collect("value", step.value);
|
|
30571
30638
|
collect("expected", step.expected);
|
|
30572
30639
|
collect("url", step.url);
|
|
30640
|
+
collect("locator.text", step.locator?.text);
|
|
30641
|
+
collect("locator.css", step.locator?.css);
|
|
30573
30642
|
if (typeof step.body === "string") collect("body", step.body);
|
|
30574
30643
|
else if (step.body) collect("body", JSON.stringify(step.body));
|
|
30575
30644
|
for (const headerValue of Object.values(step.headers ?? {})) collect("headers", headerValue);
|
|
30645
|
+
for (const [index, expectation] of (step.expectRequests ?? []).entries()) {
|
|
30646
|
+
collect(`expectRequests[${index}].urlContains`, expectation.urlContains);
|
|
30647
|
+
}
|
|
30576
30648
|
for (const [field, name] of refs) {
|
|
30577
30649
|
const root = name.split(".")[0] ?? name;
|
|
30578
30650
|
if (!declared.has(root)) {
|
|
@@ -30587,9 +30659,20 @@ function checkVariableRefs(step, at, declared, issues) {
|
|
|
30587
30659
|
|
|
30588
30660
|
// ../../packages/sdk/src/project.ts
|
|
30589
30661
|
import { mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
30590
|
-
import {
|
|
30662
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
30663
|
+
import { dirname, join as join5, resolve } from "node:path";
|
|
30591
30664
|
function resolveRoot(root) {
|
|
30592
|
-
|
|
30665
|
+
const start = resolve(root ?? process.cwd());
|
|
30666
|
+
if (root) return start;
|
|
30667
|
+
let current = start;
|
|
30668
|
+
for (; ; ) {
|
|
30669
|
+
if (existsSync2(join5(current, "testpilot.yaml")) || existsSync2(join5(current, TESTPILOT_DIR))) {
|
|
30670
|
+
return current;
|
|
30671
|
+
}
|
|
30672
|
+
const parent = dirname(current);
|
|
30673
|
+
if (parent === current) return start;
|
|
30674
|
+
current = parent;
|
|
30675
|
+
}
|
|
30593
30676
|
}
|
|
30594
30677
|
async function loadProjectConfig(root) {
|
|
30595
30678
|
return loadTestpilotConfig(resolveRoot(root));
|
|
@@ -30638,6 +30721,167 @@ async function writeProjectLink(link, root) {
|
|
|
30638
30721
|
return file2;
|
|
30639
30722
|
}
|
|
30640
30723
|
|
|
30724
|
+
// ../../packages/sdk/src/case-data.ts
|
|
30725
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
30726
|
+
import { readFile as readFile3, realpath } from "node:fs/promises";
|
|
30727
|
+
import { extname, isAbsolute, relative, resolve as resolve2 } from "node:path";
|
|
30728
|
+
async function loadCaseDataFile(reference, options) {
|
|
30729
|
+
if (isAbsolute(reference)) {
|
|
30730
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u5FC5\u987B\u4F7F\u7528\u9879\u76EE\u76F8\u5BF9\u8DEF\u5F84:${reference}`);
|
|
30731
|
+
}
|
|
30732
|
+
const root = resolve2(options.root);
|
|
30733
|
+
const dataDir = resolve2(options.dataDir);
|
|
30734
|
+
const target = resolve2(root, reference);
|
|
30735
|
+
if (!isInside(dataDir, target)) {
|
|
30736
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u5FC5\u987B\u4F4D\u4E8E tests/e2e/data \u76EE\u5F55:${reference}`);
|
|
30737
|
+
}
|
|
30738
|
+
const [realDataDir, realTarget] = await Promise.all([
|
|
30739
|
+
realpath(dataDir).catch(() => dataDir),
|
|
30740
|
+
realpath(target).catch((error62) => {
|
|
30741
|
+
if (error62 instanceof Error && error62.code === "ENOENT") {
|
|
30742
|
+
throw new TestPilotError("FIXTURE_MISSING", `\u6570\u636E\u6587\u4EF6\u4E0D\u5B58\u5728:${reference}`);
|
|
30743
|
+
}
|
|
30744
|
+
throw new Error(
|
|
30745
|
+
`\u8BFB\u53D6\u6570\u636E\u6587\u4EF6\u5931\u8D25:${reference}:${error62 instanceof Error ? error62.message : String(error62)}`
|
|
30746
|
+
);
|
|
30747
|
+
})
|
|
30748
|
+
]);
|
|
30749
|
+
if (!isInside(realDataDir, realTarget)) {
|
|
30750
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u89E3\u6790\u540E\u8D8A\u51FA tests/e2e/data \u76EE\u5F55:${reference}`);
|
|
30751
|
+
}
|
|
30752
|
+
const source = await readFile3(realTarget, "utf8");
|
|
30753
|
+
const extension = extname(realTarget).toLowerCase();
|
|
30754
|
+
try {
|
|
30755
|
+
if (extension === ".yaml" || extension === ".yml") return (0, import_yaml3.parse)(source);
|
|
30756
|
+
if (extension === ".json") return JSON.parse(source);
|
|
30757
|
+
} catch (error62) {
|
|
30758
|
+
throw new Error(
|
|
30759
|
+
`\u89E3\u6790\u6570\u636E\u6587\u4EF6\u5931\u8D25:${reference}:${error62 instanceof Error ? error62.message : String(error62)}`
|
|
30760
|
+
);
|
|
30761
|
+
}
|
|
30762
|
+
throw new Error(`\u6570\u636E\u6587\u4EF6\u4EC5\u652F\u6301 .yaml/.yml/.json:${reference}`);
|
|
30763
|
+
}
|
|
30764
|
+
function isDataObject(value) {
|
|
30765
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30766
|
+
}
|
|
30767
|
+
function mergeFixture(target, source, at = "fixture") {
|
|
30768
|
+
const output2 = { ...target };
|
|
30769
|
+
for (const [key, value] of Object.entries(source)) {
|
|
30770
|
+
const path = `${at}.${key}`;
|
|
30771
|
+
if (!(key in output2)) {
|
|
30772
|
+
output2[key] = value;
|
|
30773
|
+
continue;
|
|
30774
|
+
}
|
|
30775
|
+
const current = output2[key];
|
|
30776
|
+
if (isDataObject(current) && isDataObject(value)) {
|
|
30777
|
+
output2[key] = mergeFixture(current, value, path);
|
|
30778
|
+
continue;
|
|
30779
|
+
}
|
|
30780
|
+
throw new Error(`fixture \u5B57\u6BB5\u51B2\u7A81:${path}`);
|
|
30781
|
+
}
|
|
30782
|
+
return output2;
|
|
30783
|
+
}
|
|
30784
|
+
function flattenVariables(prefix, value) {
|
|
30785
|
+
const output2 = {};
|
|
30786
|
+
visit2(prefix, value, output2);
|
|
30787
|
+
return output2;
|
|
30788
|
+
}
|
|
30789
|
+
function visit2(path, value, output2) {
|
|
30790
|
+
if (value === null || value === void 0) return;
|
|
30791
|
+
if (typeof value === "object") {
|
|
30792
|
+
output2[path] = JSON.stringify(value);
|
|
30793
|
+
if (Array.isArray(value)) {
|
|
30794
|
+
value.forEach((item, index) => visit2(`${path}.${index}`, item, output2));
|
|
30795
|
+
} else {
|
|
30796
|
+
for (const [key, item] of Object.entries(value)) {
|
|
30797
|
+
visit2(`${path}.${key}`, item, output2);
|
|
30798
|
+
}
|
|
30799
|
+
}
|
|
30800
|
+
return;
|
|
30801
|
+
}
|
|
30802
|
+
output2[path] = String(value);
|
|
30803
|
+
}
|
|
30804
|
+
function isInside(base, candidate) {
|
|
30805
|
+
const path = relative(base, candidate);
|
|
30806
|
+
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
30807
|
+
}
|
|
30808
|
+
|
|
30809
|
+
// ../../packages/sdk/src/case-preparation.ts
|
|
30810
|
+
async function prepareCaseExecutions(item, options) {
|
|
30811
|
+
const fixture = await loadFixtures(item.data.fixtures ?? [], options);
|
|
30812
|
+
const rows = await loadDataset(item.data, options);
|
|
30813
|
+
const projectVariables = flattenVariables("variable", options.variables ?? {});
|
|
30814
|
+
const accountVariables = accountValues(item.data, options.accounts);
|
|
30815
|
+
return rows.map(({ row, rowId }, rowIndex) => {
|
|
30816
|
+
const initialVariables = {
|
|
30817
|
+
...flattenVariables("fixture", fixture),
|
|
30818
|
+
...flattenVariables("dataset", row),
|
|
30819
|
+
...projectVariables,
|
|
30820
|
+
...accountVariables
|
|
30821
|
+
};
|
|
30822
|
+
const missingDependencies = (item.data.requires ?? []).filter(
|
|
30823
|
+
(path) => isUnavailable(initialVariables[path])
|
|
30824
|
+
);
|
|
30825
|
+
return {
|
|
30826
|
+
data: item.data,
|
|
30827
|
+
file: item.file,
|
|
30828
|
+
templateId: item.data.id,
|
|
30829
|
+
rowId,
|
|
30830
|
+
rowIndex,
|
|
30831
|
+
initialVariables,
|
|
30832
|
+
missingDependencies
|
|
30833
|
+
};
|
|
30834
|
+
});
|
|
30835
|
+
}
|
|
30836
|
+
async function loadFixtures(references, options) {
|
|
30837
|
+
let merged = {};
|
|
30838
|
+
for (const reference of references) {
|
|
30839
|
+
const value = await loadCaseDataFile(reference, options);
|
|
30840
|
+
if (!isDataObject(value)) {
|
|
30841
|
+
throw new Error(`fixture \u6587\u4EF6\u6839\u5FC5\u987B\u662F\u5BF9\u8C61:${reference}`);
|
|
30842
|
+
}
|
|
30843
|
+
merged = mergeFixture(merged, value);
|
|
30844
|
+
}
|
|
30845
|
+
return merged;
|
|
30846
|
+
}
|
|
30847
|
+
async function loadDataset(data, options) {
|
|
30848
|
+
if (!data.datasets) return [{ row: {}, rowId: "default" }];
|
|
30849
|
+
const value = await loadCaseDataFile(data.datasets.file, options);
|
|
30850
|
+
if (!Array.isArray(value)) {
|
|
30851
|
+
throw new Error(`dataset \u6587\u4EF6\u6839\u5FC5\u987B\u662F\u5BF9\u8C61\u6570\u7EC4:${data.datasets.file}`);
|
|
30852
|
+
}
|
|
30853
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30854
|
+
return value.map((row, index) => {
|
|
30855
|
+
if (!isDataObject(row)) {
|
|
30856
|
+
throw new Error(`dataset \u7B2C ${index + 1} \u884C\u5FC5\u987B\u662F\u5BF9\u8C61:${data.datasets.file}`);
|
|
30857
|
+
}
|
|
30858
|
+
const rawId = row[data.datasets.idField];
|
|
30859
|
+
if (typeof rawId !== "string" && typeof rawId !== "number" || String(rawId).trim() === "") {
|
|
30860
|
+
throw new Error(
|
|
30861
|
+
`dataset \u7B2C ${index + 1} \u884C\u7F3A\u5C11\u975E\u7A7A row id \u5B57\u6BB5 "${data.datasets.idField}"`
|
|
30862
|
+
);
|
|
30863
|
+
}
|
|
30864
|
+
const rowId = String(rawId);
|
|
30865
|
+
if (seen.has(rowId)) throw new Error(`dataset row id \u91CD\u590D:${rowId}`);
|
|
30866
|
+
seen.add(rowId);
|
|
30867
|
+
return { row, rowId };
|
|
30868
|
+
});
|
|
30869
|
+
}
|
|
30870
|
+
function accountValues(data, accounts) {
|
|
30871
|
+
if (!data.accountRef) return {};
|
|
30872
|
+
const raw = accounts?.[data.accountRef];
|
|
30873
|
+
if (raw === void 0) return {};
|
|
30874
|
+
try {
|
|
30875
|
+
const parsed = JSON.parse(raw);
|
|
30876
|
+
if (isDataObject(parsed)) return flattenVariables("account", parsed);
|
|
30877
|
+
} catch {
|
|
30878
|
+
}
|
|
30879
|
+
return { account: raw };
|
|
30880
|
+
}
|
|
30881
|
+
function isUnavailable(value) {
|
|
30882
|
+
return value === void 0 || value.trim() === "" || /\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(value);
|
|
30883
|
+
}
|
|
30884
|
+
|
|
30641
30885
|
// ../../packages/sdk/src/cases.ts
|
|
30642
30886
|
async function collectCaseFiles(paths) {
|
|
30643
30887
|
const files = [];
|
|
@@ -30666,19 +30910,20 @@ async function collectFromDir(dir) {
|
|
|
30666
30910
|
return found;
|
|
30667
30911
|
}
|
|
30668
30912
|
function isCaseFile(file2) {
|
|
30669
|
-
const ext =
|
|
30913
|
+
const ext = extname2(file2).toLowerCase();
|
|
30670
30914
|
return ext === ".yaml" || ext === ".yml";
|
|
30671
30915
|
}
|
|
30672
30916
|
function resolveAgainstRoot(root, file2) {
|
|
30673
|
-
return
|
|
30917
|
+
return isAbsolute2(file2) ? file2 : resolve3(resolveRoot(root), file2);
|
|
30674
30918
|
}
|
|
30675
30919
|
async function collectCases(paths, options = {}) {
|
|
30676
30920
|
const files = await collectCaseFiles(
|
|
30677
30921
|
paths.map((input2) => resolveAgainstRoot(options.root, input2))
|
|
30678
30922
|
);
|
|
30679
30923
|
const infos = [];
|
|
30924
|
+
const root = resolveRoot(options.root);
|
|
30680
30925
|
for (const file2 of files) {
|
|
30681
|
-
const validation = await validateCaseFile(file2);
|
|
30926
|
+
const validation = await validateCaseFile(file2, root);
|
|
30682
30927
|
infos.push({
|
|
30683
30928
|
file: file2,
|
|
30684
30929
|
valid: validation.ok,
|
|
@@ -30690,16 +30935,16 @@ async function collectCases(paths, options = {}) {
|
|
|
30690
30935
|
}
|
|
30691
30936
|
async function readCaseFile(file2, options = {}) {
|
|
30692
30937
|
const target = resolveAgainstRoot(options.root, file2);
|
|
30693
|
-
const source = await
|
|
30694
|
-
return { file: target, source, validation:
|
|
30938
|
+
const source = await readFile4(target, "utf8");
|
|
30939
|
+
return { file: target, source, validation: await validateCaseDataSource(source, target, resolveRoot(options.root)) };
|
|
30695
30940
|
}
|
|
30696
|
-
async function validateCaseFile(file2) {
|
|
30697
|
-
const source = await
|
|
30698
|
-
return
|
|
30941
|
+
async function validateCaseFile(file2, root) {
|
|
30942
|
+
const source = await readFile4(file2, "utf8");
|
|
30943
|
+
return validateCaseDataSource(source, file2, root);
|
|
30699
30944
|
}
|
|
30700
30945
|
async function writeCaseFile(file2, source, options = {}) {
|
|
30701
30946
|
const target = resolveAgainstRoot(options.root, file2);
|
|
30702
|
-
const validation =
|
|
30947
|
+
const validation = await validateCaseDataSource(source, target, resolveRoot(options.root));
|
|
30703
30948
|
if (!validation.ok) {
|
|
30704
30949
|
return { file: target, written: false, validation };
|
|
30705
30950
|
}
|
|
@@ -30709,18 +30954,44 @@ async function writeCaseFile(file2, source, options = {}) {
|
|
|
30709
30954
|
throw new Error(`\u7528\u4F8B\u6587\u4EF6\u5DF2\u5B58\u5728:${target}(\u8986\u76D6\u9700\u663E\u5F0F overwrite: true)`);
|
|
30710
30955
|
}
|
|
30711
30956
|
}
|
|
30712
|
-
await mkdir3(
|
|
30957
|
+
await mkdir3(dirname2(target), { recursive: true });
|
|
30713
30958
|
await writeFile3(target, source, "utf8");
|
|
30714
30959
|
return { file: target, written: true, validation };
|
|
30715
30960
|
}
|
|
30961
|
+
async function validateCaseDataSource(source, file2, root) {
|
|
30962
|
+
const validation = validateCaseSource(source);
|
|
30963
|
+
if (!validation.ok || !validation.data) return validation;
|
|
30964
|
+
try {
|
|
30965
|
+
await prepareCaseExecutions(
|
|
30966
|
+
{ data: validation.data, file: file2 },
|
|
30967
|
+
{ root, dataDir: join6(root, "tests", "e2e", "data") }
|
|
30968
|
+
);
|
|
30969
|
+
return validation;
|
|
30970
|
+
} catch (error62) {
|
|
30971
|
+
if (error62 instanceof TestPilotError && error62.code === "FIXTURE_MISSING") {
|
|
30972
|
+
return validation;
|
|
30973
|
+
}
|
|
30974
|
+
return {
|
|
30975
|
+
ok: false,
|
|
30976
|
+
data: validation.data,
|
|
30977
|
+
issues: [
|
|
30978
|
+
{
|
|
30979
|
+
path: "(root)",
|
|
30980
|
+
code: "semantic",
|
|
30981
|
+
message: error62 instanceof Error ? error62.message : String(error62)
|
|
30982
|
+
}
|
|
30983
|
+
]
|
|
30984
|
+
};
|
|
30985
|
+
}
|
|
30986
|
+
}
|
|
30716
30987
|
|
|
30717
30988
|
// ../../packages/sdk/src/reports.ts
|
|
30718
|
-
import { readFile as
|
|
30989
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
30719
30990
|
import { join as join18 } from "node:path";
|
|
30720
30991
|
|
|
30721
30992
|
// ../../packages/reporter/src/report.ts
|
|
30722
30993
|
import { writeFile as writeFile4 } from "node:fs/promises";
|
|
30723
|
-
import { dirname as
|
|
30994
|
+
import { dirname as dirname3, join as join8, resolve as resolve4 } from "node:path";
|
|
30724
30995
|
|
|
30725
30996
|
// ../../packages/reporter/src/classify.ts
|
|
30726
30997
|
var FAILURE_CATEGORIES = [
|
|
@@ -30748,16 +31019,34 @@ function classifyFailure(message) {
|
|
|
30748
31019
|
}
|
|
30749
31020
|
return "other";
|
|
30750
31021
|
}
|
|
31022
|
+
var CODE_CATEGORIES = {
|
|
31023
|
+
ASSERTION_FAILED: "assert",
|
|
31024
|
+
REQUEST_COUNT_MISMATCH: "assert",
|
|
31025
|
+
STATUS_MISMATCH: "assert",
|
|
31026
|
+
STEP_TIMEOUT: "assert",
|
|
31027
|
+
ELEMENT_NOT_FOUND: "element",
|
|
31028
|
+
ADAPTER_NOT_FOUND: "adapter",
|
|
31029
|
+
UNSUPPORTED_ACTION: "adapter",
|
|
31030
|
+
CONFIG_MISSING: "adapter",
|
|
31031
|
+
ENVIRONMENT_UNAVAILABLE: "env",
|
|
31032
|
+
FIXTURE_MISSING: "env",
|
|
31033
|
+
ACCOUNT_UNAVAILABLE: "env",
|
|
31034
|
+
PREREQ_VARIABLE_MISSING: "other"
|
|
31035
|
+
};
|
|
31036
|
+
function classifyFromCode(code) {
|
|
31037
|
+
if (code === void 0) return void 0;
|
|
31038
|
+
return CODE_CATEGORIES[code];
|
|
31039
|
+
}
|
|
30751
31040
|
|
|
30752
31041
|
// ../../packages/reporter/src/history.ts
|
|
30753
|
-
import { readFile as
|
|
31042
|
+
import { readFile as readFile5, readdir as readdir2 } from "node:fs/promises";
|
|
30754
31043
|
import { join as join7 } from "node:path";
|
|
30755
31044
|
async function collectHistory(runsRoot2, skipRunId, limit = 12) {
|
|
30756
31045
|
const entries = (await readdir2(runsRoot2).catch(() => [])).filter((name) => /^\d{8}-\d{3}$/.test(name) && name !== skipRunId).sort().slice(-limit);
|
|
30757
31046
|
const points = [];
|
|
30758
31047
|
for (const runId of entries) {
|
|
30759
31048
|
try {
|
|
30760
|
-
const summary = JSON.parse(await
|
|
31049
|
+
const summary = JSON.parse(await readFile5(join7(runsRoot2, runId, "result.json"), "utf8"));
|
|
30761
31050
|
points.push({
|
|
30762
31051
|
runId,
|
|
30763
31052
|
startedAt: summary.startedAt,
|
|
@@ -30778,12 +31067,19 @@ function toSummaryView(summary) {
|
|
|
30778
31067
|
runId: summary.runId,
|
|
30779
31068
|
status: summary.status,
|
|
30780
31069
|
durationMs: summary.durationMs,
|
|
30781
|
-
cases: {
|
|
31070
|
+
cases: {
|
|
31071
|
+
templates: summary.totals.templates ?? summary.totals.cases,
|
|
31072
|
+
passed: summary.totals.passed,
|
|
31073
|
+
failed: summary.totals.failed,
|
|
31074
|
+
skipped: summary.totals.skipped ?? 0,
|
|
31075
|
+
warnings: summary.totals.warnings ?? 0
|
|
31076
|
+
},
|
|
30782
31077
|
steps: {
|
|
30783
31078
|
total: summary.totals.steps,
|
|
30784
31079
|
passed: summary.totals.stepsPassed,
|
|
30785
31080
|
failed: summary.totals.stepsFailed,
|
|
30786
|
-
skipped: summary.totals.stepsSkipped
|
|
31081
|
+
skipped: summary.totals.stepsSkipped,
|
|
31082
|
+
warning: summary.totals.stepsWarning ?? 0
|
|
30787
31083
|
}
|
|
30788
31084
|
};
|
|
30789
31085
|
}
|
|
@@ -30791,14 +31087,14 @@ function toSummaryView(summary) {
|
|
|
30791
31087
|
// ../../packages/reporter/src/report.ts
|
|
30792
31088
|
var HISTORY_LIMIT = 12;
|
|
30793
31089
|
async function writeReports(runDir, summary, options = {}) {
|
|
30794
|
-
const dir =
|
|
31090
|
+
const dir = resolve4(runDir);
|
|
30795
31091
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30796
|
-
const history = options.history ?? await collectHistory(
|
|
31092
|
+
const history = options.history ?? await collectHistory(dirname3(dir), summary.runId, options.historyLimit ?? HISTORY_LIMIT);
|
|
30797
31093
|
const failures = [];
|
|
30798
31094
|
const cases = summary.cases.map((item) => {
|
|
30799
31095
|
const categories = /* @__PURE__ */ new Set();
|
|
30800
31096
|
const steps = item.steps.map((step) => {
|
|
30801
|
-
const category = step.status === "failed" ? classifyFailure(step.error) : void 0;
|
|
31097
|
+
const category = classifyFromCode(step.errorCode) ?? (step.status === "failed" ? classifyFailure(step.error) : void 0);
|
|
30802
31098
|
if (category) {
|
|
30803
31099
|
categories.add(category);
|
|
30804
31100
|
failures.push({
|
|
@@ -30806,7 +31102,8 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
30806
31102
|
label: categoryLabel(category),
|
|
30807
31103
|
caseId: item.caseId,
|
|
30808
31104
|
index: step.index,
|
|
30809
|
-
message: step.error ?? ""
|
|
31105
|
+
message: step.error ?? "",
|
|
31106
|
+
verdict: verdictFromErrorCode(step.errorCode ?? item.errorCode)
|
|
30810
31107
|
});
|
|
30811
31108
|
}
|
|
30812
31109
|
return { ...step, category };
|
|
@@ -30829,6 +31126,20 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
30829
31126
|
await writeFile4(htmlPath, renderHtml(data), "utf8");
|
|
30830
31127
|
return { json: jsonPath, html: htmlPath };
|
|
30831
31128
|
}
|
|
31129
|
+
var VERDICT_GROUPS = [
|
|
31130
|
+
{ verdict: "product", title: "\u4EA7\u54C1\u5931\u8D25" },
|
|
31131
|
+
{ verdict: "infra", title: "\u73AF\u5883\u4E0E\u524D\u7F6E(\u975E\u4EA7\u54C1\u56DE\u5F52)" },
|
|
31132
|
+
{ verdict: "unverified", title: "\u672A\u9A8C\u8BC1" }
|
|
31133
|
+
];
|
|
31134
|
+
function verdictGroupsHtml(data) {
|
|
31135
|
+
return VERDICT_GROUPS.map((group) => {
|
|
31136
|
+
const items = data.failures.filter((failure2) => failure2.verdict === group.verdict);
|
|
31137
|
+
const list = items.length > 0 ? items.map(
|
|
31138
|
+
(failure2) => `<li><code>${escapeHtml(failure2.caseId)}</code> #${failure2.index} ${escapeHtml(failure2.message.slice(0, 160))}</li>`
|
|
31139
|
+
).join("\n") : '<li class="muted">\u65E0</li>';
|
|
31140
|
+
return `<div class="verdict-group"><h3>${escapeHtml(group.title)}(${items.length})</h3><ul>${list}</ul></div>`;
|
|
31141
|
+
}).join("\n");
|
|
31142
|
+
}
|
|
30832
31143
|
function categoryChips(data) {
|
|
30833
31144
|
const counts = /* @__PURE__ */ new Map();
|
|
30834
31145
|
for (const failure2 of data.failures) {
|
|
@@ -30852,21 +31163,22 @@ function renderHtml(data) {
|
|
|
30852
31163
|
const view = toSummaryView(data.summary);
|
|
30853
31164
|
const summary = data.summary;
|
|
30854
31165
|
const trendSvg = renderTrend(data);
|
|
30855
|
-
const casesHtml = summary.cases
|
|
31166
|
+
const casesHtml = groupedCasesHtml(summary.cases);
|
|
30856
31167
|
const chips = categoryChips(data);
|
|
31168
|
+
const verdictHtml = verdictGroupsHtml(data);
|
|
30857
31169
|
return `<!doctype html>
|
|
30858
31170
|
<html lang="zh-CN">
|
|
30859
31171
|
<head>
|
|
30860
31172
|
<meta charset="utf-8" />
|
|
30861
31173
|
<title>TestPilot Report ${escapeHtml(summary.runId)}</title>
|
|
30862
31174
|
<style>
|
|
30863
|
-
:root { --ok:#1a7f37; --fail:#cf222e; --skip:#6e7781; --line:#d0d7de; --muted:#59636e; }
|
|
31175
|
+
:root { --ok:#1a7f37; --fail:#cf222e; --warn:#9a6700; --skip:#6e7781; --blocked:#8250df; --inconclusive:#9a6700; --line:#d0d7de; --muted:#59636e; }
|
|
30864
31176
|
* { box-sizing: border-box; }
|
|
30865
31177
|
body { font-family: system-ui, "Segoe UI", sans-serif; margin: 0; color: #1f2328; background: #f6f8fa; }
|
|
30866
31178
|
.wrap { max-width: 1080px; margin: 0 auto; padding: 20px 16px 48px; }
|
|
30867
31179
|
h1 { font-size: 20px; margin: 8px 0; } h2 { font-size: 15px; margin: 24px 0 8px; }
|
|
30868
31180
|
.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); }
|
|
31181
|
+
.passed { background: var(--ok); } .failed { background: var(--fail); } .warning { background: var(--warn); } .skipped { background: var(--skip); } .blocked { background: var(--blocked); } .inconclusive { background: var(--inconclusive); }
|
|
30870
31182
|
code { background: #eef1f4; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
|
|
30871
31183
|
.cards { display: flex; gap: 12px; flex-wrap: wrap; margin: 14px 0; }
|
|
30872
31184
|
.card { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 10px 16px; min-width: 130px; }
|
|
@@ -30879,6 +31191,8 @@ function renderHtml(data) {
|
|
|
30879
31191
|
.btn { border: 1px solid var(--line); background: #fff; border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; }
|
|
30880
31192
|
#count { color: var(--muted); font-size: 12px; margin-left: auto; }
|
|
30881
31193
|
details.case { background: #fff; border: 1px solid var(--line); border-radius: 8px; margin: 10px 0; }
|
|
31194
|
+
section.template { border-left: 3px solid var(--line); padding-left: 12px; margin: 14px 0; }
|
|
31195
|
+
section.template > h3 { font-size: 14px; margin: 0 0 6px; }
|
|
30882
31196
|
details.case > summary { cursor: pointer; padding: 10px 14px; font-size: 14px; list-style: none; }
|
|
30883
31197
|
details.case > summary::-webkit-details-marker { display: none; }
|
|
30884
31198
|
details.case > summary:hover { background: #f6f8fa; }
|
|
@@ -30891,6 +31205,11 @@ function renderHtml(data) {
|
|
|
30891
31205
|
.muted { color: var(--muted); }
|
|
30892
31206
|
.hide { display: none !important; }
|
|
30893
31207
|
.meta { color: var(--muted); font-size: 12px; margin: 6px 0; }
|
|
31208
|
+
.verdicts { display: flex; gap: 14px; flex-wrap: wrap; }
|
|
31209
|
+
.verdict-group { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 10px 14px; flex: 1; min-width: 220px; }
|
|
31210
|
+
.verdict-group h3 { font-size: 13px; margin: 0 0 6px; }
|
|
31211
|
+
.verdict-group ul { margin: 0; padding-left: 18px; font-size: 12px; }
|
|
31212
|
+
.verdict-group li { margin: 3px 0; }
|
|
30894
31213
|
</style>
|
|
30895
31214
|
</head>
|
|
30896
31215
|
<body>
|
|
@@ -30901,6 +31220,8 @@ function renderHtml(data) {
|
|
|
30901
31220
|
<div class="cards">
|
|
30902
31221
|
<div class="card"><b>${view.cases.passed}<span style="color:var(--ok)"> \u2713</span></b><span>\u901A\u8FC7\u7528\u4F8B</span></div>
|
|
30903
31222
|
<div class="card"><b>${view.cases.failed}<span style="color:var(--fail)"> \u2717</span></b><span>\u5931\u8D25\u7528\u4F8B</span></div>
|
|
31223
|
+
<div class="card"><b>${view.cases.skipped} \u21B7</b><span>\u8DF3\u8FC7\u6570\u636E\u884C</span></div>
|
|
31224
|
+
<div class="card"><b>${view.cases.warnings} \u26A0</b><span>\u6E05\u7406\u8B66\u544A</span></div>
|
|
30904
31225
|
<div class="card"><b>${view.steps.passed}<span style="color:var(--ok)"> \u2713</span></b><span>\u901A\u8FC7\u6B65\u9AA4</span></div>
|
|
30905
31226
|
<div class="card"><b>${view.steps.failed}<span style="color:var(--fail)"> \u2717</span></b><span>\u5931\u8D25\u6B65\u9AA4</span></div>
|
|
30906
31227
|
<div class="card"><b>${view.steps.skipped} \u21B7</b><span>\u8DF3\u8FC7\u6B65\u9AA4</span></div>
|
|
@@ -30909,6 +31230,9 @@ function renderHtml(data) {
|
|
|
30909
31230
|
<h2>\u5386\u53F2\u8D8B\u52BF(\u6700\u8FD1 ${data.history.length + 1} \u6B21\u8FD0\u884C)</h2>
|
|
30910
31231
|
<div class="trend">${trendSvg}</div>
|
|
30911
31232
|
|
|
31233
|
+
<h2>\u5931\u8D25\u4E09\u6876</h2>
|
|
31234
|
+
<div class="verdicts">${verdictHtml}</div>
|
|
31235
|
+
|
|
30912
31236
|
<h2>\u5931\u8D25\u5206\u7C7B</h2>
|
|
30913
31237
|
<div class="toolbar" id="cat-chips">${chips}</div>
|
|
30914
31238
|
|
|
@@ -30987,26 +31311,44 @@ function renderHtml(data) {
|
|
|
30987
31311
|
</body>
|
|
30988
31312
|
</html>`;
|
|
30989
31313
|
}
|
|
31314
|
+
function groupedCasesHtml(cases) {
|
|
31315
|
+
const groups = /* @__PURE__ */ new Map();
|
|
31316
|
+
for (const item of cases) {
|
|
31317
|
+
const current = groups.get(item.caseId) ?? [];
|
|
31318
|
+
current.push(item);
|
|
31319
|
+
groups.set(item.caseId, current);
|
|
31320
|
+
}
|
|
31321
|
+
return [...groups.entries()].map(
|
|
31322
|
+
([caseId, rows]) => `<section class="template">
|
|
31323
|
+
<h3>${escapeHtml(caseId)} <span class="muted">\xB7 ${rows.length} \u4E2A\u6570\u636E\u884C</span></h3>
|
|
31324
|
+
${rows.map((item) => caseHtml(item)).join("\n")}
|
|
31325
|
+
</section>`
|
|
31326
|
+
).join("\n");
|
|
31327
|
+
}
|
|
30990
31328
|
function caseHtml(item) {
|
|
30991
31329
|
const rows = item.steps.map((step) => stepHtml(step)).join("\n");
|
|
30992
31330
|
const errorLine = item.error ? `<p class="error">${escapeHtml(item.error)}</p>` : "";
|
|
31331
|
+
const skipLine = item.skipReason ? `<p class="muted">${escapeHtml(item.skipReason)}${item.missingDependencies?.length ? `: ${escapeHtml(item.missingDependencies.join(", "))}` : ""}</p>` : "";
|
|
31332
|
+
const warningLine = item.warnings?.length ? `<p class="error">\u26A0 ${item.warnings.map(escapeHtml).join("<br />\u26A0 ")}</p>` : "";
|
|
30993
31333
|
const links = [];
|
|
30994
31334
|
if (item.video) links.push(`<a href="${escapeHtml(item.video)}">\u89C6\u9891</a>`);
|
|
30995
31335
|
if (item.trace) links.push(`<a href="${escapeHtml(item.trace)}">Trace</a>`);
|
|
30996
31336
|
const linksLine = links.length > 0 ? `<p>${links.join(" \xB7 ")}</p>` : "";
|
|
30997
|
-
const search = escapeHtml(`${item.caseId} ${item.caseName} ${item.file}`).replaceAll('"', """);
|
|
31337
|
+
const search = escapeHtml(`${item.caseId} ${item.rowId ?? "default"} ${item.caseName} ${item.file}`).replaceAll('"', """);
|
|
30998
31338
|
const cats = escapeHtml(item.categories.join(","));
|
|
30999
31339
|
return `
|
|
31000
31340
|
<details class="case" data-status="${item.status}" data-cats="${cats}" data-search="${search}">
|
|
31001
31341
|
<summary>
|
|
31002
31342
|
<span class="badge ${item.status}">${item.status}</span>
|
|
31003
|
-
<b
|
|
31343
|
+
<b>\u6570\u636E\u884C ${escapeHtml(item.rowId ?? "default")}</b> \xB7 ${escapeHtml(item.caseName)}
|
|
31004
31344
|
<span class="muted">\xB7 ${item.steps.length} \u6B65 \xB7 ${(item.durationMs / 1e3).toFixed(1)}s</span>
|
|
31005
31345
|
</summary>
|
|
31006
31346
|
${errorLine}
|
|
31347
|
+
${skipLine}
|
|
31348
|
+
${warningLine}
|
|
31007
31349
|
${linksLine}
|
|
31008
31350
|
<table>
|
|
31009
|
-
<tr><th>#</th><th>target</th><th>action</th><th>\u72B6\u6001</th><th>\u8017\u65F6</th><th>\u8BE6\u60C5</th></tr>
|
|
31351
|
+
<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
31352
|
${rows}
|
|
31011
31353
|
</table>
|
|
31012
31354
|
</details>`;
|
|
@@ -31021,8 +31363,14 @@ function stepHtml(step) {
|
|
|
31021
31363
|
}
|
|
31022
31364
|
}
|
|
31023
31365
|
if (step.screenshot) details.push(`<a href="${escapeHtml(step.screenshot)}">\u622A\u56FE</a>`);
|
|
31366
|
+
for (const assertion of step.requestAssertions ?? []) {
|
|
31367
|
+
details.push(
|
|
31368
|
+
`\u8BF7\u6C42 ${escapeHtml(assertion.method ?? "*")} <code>${escapeHtml(assertion.urlContains)}</code>: ${assertion.actualCount}/${assertion.count}`
|
|
31369
|
+
);
|
|
31370
|
+
}
|
|
31024
31371
|
return `<tr>
|
|
31025
31372
|
<td class="num">${step.index}</td>
|
|
31373
|
+
<td>${escapeHtml(step.phase ?? "steps")}</td>
|
|
31026
31374
|
<td>${escapeHtml(step.target)}</td>
|
|
31027
31375
|
<td>${escapeHtml(step.action)}</td>
|
|
31028
31376
|
<td><span class="badge ${step.status}">${step.status}</span></td>
|
|
@@ -31079,7 +31427,7 @@ function embedJson(data) {
|
|
|
31079
31427
|
|
|
31080
31428
|
// ../../packages/reporter/src/summary-report.ts
|
|
31081
31429
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
31082
|
-
import { join as join9, resolve as
|
|
31430
|
+
import { join as join9, resolve as resolve5 } from "node:path";
|
|
31083
31431
|
function escapeHtml2(value) {
|
|
31084
31432
|
return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
31085
31433
|
}
|
|
@@ -31093,10 +31441,12 @@ function buildSummaryData(runs) {
|
|
|
31093
31441
|
for (const item of summary.cases) {
|
|
31094
31442
|
const platforms = [...new Set(item.steps.map((step) => step.target))];
|
|
31095
31443
|
const failedStep = item.steps.find((step) => step.status === "failed");
|
|
31096
|
-
const category = item.status === "failed" ? classifyFailure(failedStep?.error) : void 0;
|
|
31444
|
+
const category = classifyFromCode(item.errorCode ?? failedStep?.errorCode) ?? (item.status === "failed" ? classifyFailure(failedStep?.error) : void 0);
|
|
31097
31445
|
executions.push({
|
|
31098
31446
|
runId,
|
|
31099
31447
|
caseId: item.caseId,
|
|
31448
|
+
rowId: item.rowId,
|
|
31449
|
+
identity: item.rowId && item.rowId !== "default" ? `${item.caseId}#${item.rowId}` : item.caseId,
|
|
31100
31450
|
caseName: item.caseName,
|
|
31101
31451
|
status: item.status,
|
|
31102
31452
|
category,
|
|
@@ -31114,10 +31464,11 @@ function buildSummaryData(runs) {
|
|
|
31114
31464
|
}
|
|
31115
31465
|
const byCase = /* @__PURE__ */ new Map();
|
|
31116
31466
|
for (const exec2 of executions) {
|
|
31117
|
-
let agg = byCase.get(exec2.
|
|
31467
|
+
let agg = byCase.get(exec2.identity);
|
|
31118
31468
|
if (!agg) {
|
|
31119
31469
|
agg = {
|
|
31120
31470
|
caseId: exec2.caseId,
|
|
31471
|
+
rowId: exec2.rowId,
|
|
31121
31472
|
caseName: exec2.caseName,
|
|
31122
31473
|
platforms: [],
|
|
31123
31474
|
runs: 0,
|
|
@@ -31127,17 +31478,18 @@ function buildSummaryData(runs) {
|
|
|
31127
31478
|
lastStatus: "unknown",
|
|
31128
31479
|
flaky: false
|
|
31129
31480
|
};
|
|
31130
|
-
byCase.set(exec2.
|
|
31481
|
+
byCase.set(exec2.identity, agg);
|
|
31131
31482
|
}
|
|
31132
31483
|
agg.runs++;
|
|
31133
31484
|
if (exec2.status === "passed") agg.passed++;
|
|
31134
|
-
else agg.failed++;
|
|
31485
|
+
else if (exec2.status === "failed") agg.failed++;
|
|
31135
31486
|
for (const platform of exec2.platforms) {
|
|
31136
31487
|
if (!agg.platforms.includes(platform)) agg.platforms.push(platform);
|
|
31137
31488
|
}
|
|
31138
31489
|
}
|
|
31139
31490
|
for (const agg of byCase.values()) {
|
|
31140
|
-
const
|
|
31491
|
+
const identity = agg.rowId && agg.rowId !== "default" ? `${agg.caseId}#${agg.rowId}` : agg.caseId;
|
|
31492
|
+
const last = [...executions].reverse().find((exec2) => exec2.identity === identity);
|
|
31141
31493
|
agg.lastStatus = last?.status ?? "unknown";
|
|
31142
31494
|
agg.lastCategory = last?.category;
|
|
31143
31495
|
agg.passRate = agg.runs > 0 ? agg.passed / agg.runs : 0;
|
|
@@ -31152,6 +31504,10 @@ function buildSummaryData(runs) {
|
|
|
31152
31504
|
})).filter((item) => item.count > 0);
|
|
31153
31505
|
const durations = runs.map((item) => item.summary.durationMs).filter((value) => typeof value === "number");
|
|
31154
31506
|
return {
|
|
31507
|
+
buckets: {
|
|
31508
|
+
blocked: executions.filter((exec2) => exec2.status === "blocked").length,
|
|
31509
|
+
inconclusive: executions.filter((exec2) => exec2.status === "inconclusive").length
|
|
31510
|
+
},
|
|
31155
31511
|
window: {
|
|
31156
31512
|
runs: runs.length,
|
|
31157
31513
|
from: runPoints[0]?.startedAt,
|
|
@@ -31236,7 +31592,7 @@ function renderSummaryHtml(data) {
|
|
|
31236
31592
|
th.num, td.num { text-align: right; }
|
|
31237
31593
|
code { background: #eef1f4; padding: 1px 5px; border-radius: 4px; font-size: 12px; cursor: pointer; }
|
|
31238
31594
|
.badge { display: inline-block; padding: 1px 9px; border-radius: 10px; color: #fff; font-size: 11px; }
|
|
31239
|
-
.passed { background: #1a7f37; } .failed { background: #cf222e; } .unknown { background: #6e7781; }
|
|
31595
|
+
.passed { background: #1a7f37; } .failed { background: #cf222e; } .unknown { background: #6e7781; } .blocked { background: #8250df; } .inconclusive { background: #9a6700; }
|
|
31240
31596
|
.toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 10px 0; }
|
|
31241
31597
|
.chip { border: 1px solid var(--line); background: #fff; border-radius: 14px; padding: 3px 12px; font-size: 12px; cursor: pointer; }
|
|
31242
31598
|
.chip.active { background: #0969da; color: #fff; border-color: #0969da; }
|
|
@@ -31262,6 +31618,8 @@ function renderSummaryHtml(data) {
|
|
|
31262
31618
|
<div class="card"><b>${rateText}</b><span>\u6574\u4F53\u901A\u8FC7\u7387</span></div>
|
|
31263
31619
|
<div class="card"><b>${(kpis.avgDurationMs / 1e3).toFixed(1)}s</b><span>\u5E73\u5747\u8FD0\u884C\u8017\u65F6</span></div>
|
|
31264
31620
|
<div class="card"><b>${kpis.flakyCount}</b><span>Flaky \u7528\u4F8B</span></div>
|
|
31621
|
+
${data.buckets.blocked > 0 ? `<div class="card"><b>${data.buckets.blocked} \u2298</b><span>\u73AF\u5883/\u524D\u7F6E\u963B\u585E(\u975E\u4EA7\u54C1)</span></div>` : ""}
|
|
31622
|
+
${data.buckets.inconclusive > 0 ? `<div class="card"><b>${data.buckets.inconclusive} ?</b><span>\u672A\u9A8C\u8BC1(\u8BC1\u636E\u4E0D\u8DB3)</span></div>` : ""}
|
|
31265
31623
|
</div>
|
|
31266
31624
|
|
|
31267
31625
|
<h2>\u8D8B\u52BF(\u6BCF\u6B21\u8FD0\u884C\u7ED3\u679C\u4E0E\u901A\u8FC7\u7387,\u60AC\u6D6E\u770B\u8BE6\u60C5)</h2>
|
|
@@ -31348,7 +31706,7 @@ function renderSummaryHtml(data) {
|
|
|
31348
31706
|
</html>`;
|
|
31349
31707
|
}
|
|
31350
31708
|
async function writeSummaryReport(artifactsDir, data) {
|
|
31351
|
-
const dir =
|
|
31709
|
+
const dir = resolve5(artifactsDir);
|
|
31352
31710
|
await mkdir4(dir, { recursive: true });
|
|
31353
31711
|
const htmlPath = join9(dir, "summary.html");
|
|
31354
31712
|
const jsonPath = join9(dir, "summary.json");
|
|
@@ -31358,8 +31716,8 @@ async function writeSummaryReport(artifactsDir, data) {
|
|
|
31358
31716
|
}
|
|
31359
31717
|
|
|
31360
31718
|
// ../../packages/sdk/src/runs.ts
|
|
31361
|
-
import { readFile as
|
|
31362
|
-
import { isAbsolute as
|
|
31719
|
+
import { readFile as readFile7, readdir as readdir4 } from "node:fs/promises";
|
|
31720
|
+
import { isAbsolute as isAbsolute3, join as join17 } from "node:path";
|
|
31363
31721
|
|
|
31364
31722
|
// ../../packages/execution-engine/src/artifacts/index.ts
|
|
31365
31723
|
import { mkdir as mkdir9 } from "node:fs/promises";
|
|
@@ -31367,7 +31725,7 @@ import { join as join13 } from "node:path";
|
|
|
31367
31725
|
|
|
31368
31726
|
// ../../packages/evidence/src/log.ts
|
|
31369
31727
|
import { appendFile, mkdir as mkdir5 } from "node:fs/promises";
|
|
31370
|
-
import { dirname as
|
|
31728
|
+
import { dirname as dirname4 } from "node:path";
|
|
31371
31729
|
var RunLogger = class {
|
|
31372
31730
|
constructor(filePath) {
|
|
31373
31731
|
this.filePath = filePath;
|
|
@@ -31382,7 +31740,7 @@ var RunLogger = class {
|
|
|
31382
31740
|
return this.write("ERROR", message);
|
|
31383
31741
|
}
|
|
31384
31742
|
async write(level, message) {
|
|
31385
|
-
await mkdir5(
|
|
31743
|
+
await mkdir5(dirname4(this.filePath), { recursive: true });
|
|
31386
31744
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${level} ${message}
|
|
31387
31745
|
`;
|
|
31388
31746
|
await appendFile(this.filePath, line, "utf8");
|
|
@@ -31391,30 +31749,30 @@ var RunLogger = class {
|
|
|
31391
31749
|
|
|
31392
31750
|
// ../../packages/evidence/src/screenshot.ts
|
|
31393
31751
|
import { mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
|
|
31394
|
-
import { dirname as
|
|
31752
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
31395
31753
|
async function saveScreenshot(dir, fileName, data) {
|
|
31396
31754
|
const file2 = join10(dir, fileName);
|
|
31397
|
-
await mkdir6(
|
|
31755
|
+
await mkdir6(dirname5(file2), { recursive: true });
|
|
31398
31756
|
await writeFile6(file2, data);
|
|
31399
31757
|
return file2;
|
|
31400
31758
|
}
|
|
31401
31759
|
|
|
31402
31760
|
// ../../packages/evidence/src/trace.ts
|
|
31403
31761
|
import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
|
|
31404
|
-
import { dirname as
|
|
31762
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
31405
31763
|
async function saveTrace(dir, fileName, data) {
|
|
31406
31764
|
const file2 = join11(dir, fileName);
|
|
31407
|
-
await mkdir7(
|
|
31765
|
+
await mkdir7(dirname6(file2), { recursive: true });
|
|
31408
31766
|
await writeFile7(file2, data);
|
|
31409
31767
|
return file2;
|
|
31410
31768
|
}
|
|
31411
31769
|
|
|
31412
31770
|
// ../../packages/evidence/src/video.ts
|
|
31413
31771
|
import { mkdir as mkdir8, writeFile as writeFile8 } from "node:fs/promises";
|
|
31414
|
-
import { dirname as
|
|
31772
|
+
import { dirname as dirname7, join as join12 } from "node:path";
|
|
31415
31773
|
async function saveVideo(dir, fileName, data) {
|
|
31416
31774
|
const file2 = join12(dir, fileName);
|
|
31417
|
-
await mkdir8(
|
|
31775
|
+
await mkdir8(dirname7(file2), { recursive: true });
|
|
31418
31776
|
await writeFile8(file2, data);
|
|
31419
31777
|
return file2;
|
|
31420
31778
|
}
|
|
@@ -31464,15 +31822,29 @@ var ExecutionContext = class {
|
|
|
31464
31822
|
get(name) {
|
|
31465
31823
|
return this.variables.get(name);
|
|
31466
31824
|
}
|
|
31825
|
+
seed(values) {
|
|
31826
|
+
for (const [name, value] of Object.entries(values)) {
|
|
31827
|
+
this.variables.set(name, value);
|
|
31828
|
+
}
|
|
31829
|
+
}
|
|
31467
31830
|
/**
|
|
31468
|
-
* 将字符串中的 ${name}
|
|
31469
|
-
*
|
|
31831
|
+
* 将字符串中的 ${name} 替换为已提取的变量值;未定义的引用抛 TestPilotError——
|
|
31832
|
+
* 校验阶段已静态拦截"从未声明"的引用,运行时未命中即数据未兑现
|
|
31833
|
+
* (前置 extract 未执行或 fixture/dataset/凭据缺失),按三态归 unverified/account 桶。
|
|
31834
|
+
* 凭据桶仅限 `account` 整值或 `account.*` 字段;用户变量撞 account 前缀(如 accountId)
|
|
31835
|
+
* 不误归凭据桶。名字允许点号,用于引擎注入的 account.username 一类凭据变量
|
|
31836
|
+
* (用户 extract 变量名不含点号)。
|
|
31470
31837
|
*/
|
|
31471
31838
|
resolve(template) {
|
|
31472
|
-
return template.replace(
|
|
31473
|
-
|
|
31474
|
-
(
|
|
31475
|
-
|
|
31839
|
+
return template.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (_match, name) => {
|
|
31840
|
+
const value = this.variables.get(name);
|
|
31841
|
+
if (value !== void 0) return value;
|
|
31842
|
+
const isAccount = name === "account" || name.startsWith("account.");
|
|
31843
|
+
throw new TestPilotError(
|
|
31844
|
+
isAccount ? "ACCOUNT_UNAVAILABLE" : "PREREQ_VARIABLE_MISSING",
|
|
31845
|
+
`\u53D8\u91CF "${name}" \u672A\u5B9A\u4E49(\u524D\u7F6E extract \u672A\u6267\u884C\u6216 fixture/dataset/\u51ED\u636E\u7F3A\u5931)`
|
|
31846
|
+
);
|
|
31847
|
+
});
|
|
31476
31848
|
}
|
|
31477
31849
|
/**
|
|
31478
31850
|
* 注入 accountRef 凭据:值为 JSON 对象时按 `${account.<字段>}` 展开为字符串,
|
|
@@ -31528,7 +31900,7 @@ async function ensureRunDir(runsRoot2, runId) {
|
|
|
31528
31900
|
|
|
31529
31901
|
// ../../packages/execution-engine/src/events/index.ts
|
|
31530
31902
|
import { appendFile as appendFile2, mkdir as mkdir11 } from "node:fs/promises";
|
|
31531
|
-
import { dirname as
|
|
31903
|
+
import { dirname as dirname8 } from "node:path";
|
|
31532
31904
|
var EventBus = class {
|
|
31533
31905
|
subscribers = [];
|
|
31534
31906
|
subscribe(subscriber) {
|
|
@@ -31542,7 +31914,7 @@ var EventBus = class {
|
|
|
31542
31914
|
};
|
|
31543
31915
|
function ndjsonSubscriber(filePath) {
|
|
31544
31916
|
return async (event) => {
|
|
31545
|
-
await mkdir11(
|
|
31917
|
+
await mkdir11(dirname8(filePath), { recursive: true });
|
|
31546
31918
|
await appendFile2(filePath, `${JSON.stringify(event)}
|
|
31547
31919
|
`, "utf8");
|
|
31548
31920
|
};
|
|
@@ -31623,11 +31995,19 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31623
31995
|
status: "passed",
|
|
31624
31996
|
durationMs: 0
|
|
31625
31997
|
};
|
|
31998
|
+
let captureStarted = false;
|
|
31626
31999
|
try {
|
|
31627
32000
|
const locator = step.locator ? {
|
|
31628
32001
|
text: step.locator.text !== void 0 ? context.resolve(step.locator.text) : void 0,
|
|
31629
32002
|
css: step.locator.css !== void 0 ? context.resolve(step.locator.css) : void 0
|
|
31630
32003
|
} : void 0;
|
|
32004
|
+
if (step.expectRequests) {
|
|
32005
|
+
if (!adapter.startRequestCapture || !adapter.stopRequestCapture) {
|
|
32006
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301\u8BF7\u6C42\u6B21\u6570\u65AD\u8A00`);
|
|
32007
|
+
}
|
|
32008
|
+
await adapter.startRequestCapture();
|
|
32009
|
+
captureStarted = true;
|
|
32010
|
+
}
|
|
31631
32011
|
switch (step.action) {
|
|
31632
32012
|
case "launch":
|
|
31633
32013
|
await adapter.launch();
|
|
@@ -31649,7 +32029,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31649
32029
|
break;
|
|
31650
32030
|
case "assert":
|
|
31651
32031
|
if (step.target === "api") {
|
|
31652
|
-
if (!adapter.assertResponse) throw new
|
|
32032
|
+
if (!adapter.assertResponse) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api \u65AD\u8A00");
|
|
31653
32033
|
await adapter.assertResponse(context.resolve(step.expected ?? ""));
|
|
31654
32034
|
} else {
|
|
31655
32035
|
await adapter.assert(locator, context.resolve(step.expected ?? ""));
|
|
@@ -31669,7 +32049,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31669
32049
|
break;
|
|
31670
32050
|
}
|
|
31671
32051
|
case "request": {
|
|
31672
|
-
if (!adapter.request) throw new
|
|
32052
|
+
if (!adapter.request) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api request");
|
|
31673
32053
|
const method = step.method ?? "GET";
|
|
31674
32054
|
const url2 = context.resolve(step.url ?? "");
|
|
31675
32055
|
const headers = resolveHeaders(step.headers, context);
|
|
@@ -31686,7 +32066,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31686
32066
|
if (step.expected !== void 0) {
|
|
31687
32067
|
const expectedStatus = context.resolve(step.expected);
|
|
31688
32068
|
if (String(response.status) !== expectedStatus) {
|
|
31689
|
-
throw new
|
|
32069
|
+
throw new TestPilotError(
|
|
32070
|
+
"STATUS_MISMATCH",
|
|
31690
32071
|
`HTTP \u72B6\u6001\u7801 ${response.status} ${response.statusText},\u671F\u671B ${expectedStatus}:${snippet(response.bodyText, 200)}`
|
|
31691
32072
|
);
|
|
31692
32073
|
}
|
|
@@ -31695,12 +32076,36 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31695
32076
|
}
|
|
31696
32077
|
default: {
|
|
31697
32078
|
const exhaustive = step.action;
|
|
31698
|
-
throw new
|
|
32079
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u4E0D\u652F\u6301\u7684 action:${String(exhaustive)}`);
|
|
32080
|
+
}
|
|
32081
|
+
}
|
|
32082
|
+
if (step.expectRequests) {
|
|
32083
|
+
const windowMs = Math.max(...step.expectRequests.map((item) => item.windowMs));
|
|
32084
|
+
if (windowMs > 0) await delay(windowMs);
|
|
32085
|
+
const requests = await adapter.stopRequestCapture();
|
|
32086
|
+
captureStarted = false;
|
|
32087
|
+
result.requestAssertions = buildRequestAssertions(step, requests, context);
|
|
32088
|
+
const mismatches = result.requestAssertions.filter((item) => item.actualCount !== item.count);
|
|
32089
|
+
if (mismatches.length > 0) {
|
|
32090
|
+
throw new TestPilotError(
|
|
32091
|
+
"REQUEST_COUNT_MISMATCH",
|
|
32092
|
+
mismatches.map(
|
|
32093
|
+
(item) => `\u8BF7\u6C42\u6B21\u6570\u4E0D\u5339\u914D:${item.method ?? "*"} ${item.urlContains},\u5B9E\u9645 ${item.actualCount},\u671F\u671B ${item.count}`
|
|
32094
|
+
).join("; ")
|
|
32095
|
+
);
|
|
31699
32096
|
}
|
|
31700
32097
|
}
|
|
31701
32098
|
} catch (err) {
|
|
32099
|
+
if (captureStarted) {
|
|
32100
|
+
try {
|
|
32101
|
+
await adapter.stopRequestCapture?.();
|
|
32102
|
+
} catch {
|
|
32103
|
+
}
|
|
32104
|
+
captureStarted = false;
|
|
32105
|
+
}
|
|
31702
32106
|
result.status = "failed";
|
|
31703
32107
|
result.error = err instanceof Error ? err.message : String(err);
|
|
32108
|
+
result.errorCode = err instanceof TestPilotError ? err.code : void 0;
|
|
31704
32109
|
try {
|
|
31705
32110
|
const data = await adapter.screenshot();
|
|
31706
32111
|
result.screenshot = await artifacts.saveStepScreenshot(`${options.screenshotName}-failed.png`, data);
|
|
@@ -31711,8 +32116,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31711
32116
|
return result;
|
|
31712
32117
|
}
|
|
31713
32118
|
async function extractApiResponse(adapter, path) {
|
|
31714
|
-
if (!adapter.extractResponse) throw new
|
|
31715
|
-
if (!path) throw new
|
|
32119
|
+
if (!adapter.extractResponse) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api \u63D0\u53D6");
|
|
32120
|
+
if (!path) throw new TestPilotError("UNSUPPORTED_ACTION", "api \u7AEF extract \u9700\u8981\u63D0\u4F9B value(\u70B9\u53F7 JSON path)");
|
|
31716
32121
|
return adapter.extractResponse(path);
|
|
31717
32122
|
}
|
|
31718
32123
|
function resolveHeaders(headers, context) {
|
|
@@ -31743,92 +32148,216 @@ function resolveDeep(value, context) {
|
|
|
31743
32148
|
function snippet(text, limit = SNIPPET_LIMIT) {
|
|
31744
32149
|
return text.length > limit ? `${text.slice(0, limit)}\u2026` : text;
|
|
31745
32150
|
}
|
|
32151
|
+
function buildRequestAssertions(step, requests, context) {
|
|
32152
|
+
return (step.expectRequests ?? []).map((expectation) => {
|
|
32153
|
+
const method = expectation.method?.toUpperCase();
|
|
32154
|
+
const urlContains = context.resolve(expectation.urlContains);
|
|
32155
|
+
const matches = requests.filter(
|
|
32156
|
+
(request) => (!method || request.method.toUpperCase() === method) && request.url.includes(urlContains)
|
|
32157
|
+
);
|
|
32158
|
+
return {
|
|
32159
|
+
method,
|
|
32160
|
+
urlContains: expectation.urlContains,
|
|
32161
|
+
count: expectation.count,
|
|
32162
|
+
actualCount: matches.length,
|
|
32163
|
+
windowMs: expectation.windowMs,
|
|
32164
|
+
requests: matches.slice(0, 20).map((request) => ({
|
|
32165
|
+
method: request.method.toUpperCase(),
|
|
32166
|
+
url: sanitizeRequestUrl(request.url)
|
|
32167
|
+
}))
|
|
32168
|
+
};
|
|
32169
|
+
});
|
|
32170
|
+
}
|
|
32171
|
+
function sanitizeRequestUrl(url2) {
|
|
32172
|
+
const queryIndex = url2.indexOf("?");
|
|
32173
|
+
const safe = queryIndex >= 0 ? `${url2.slice(0, queryIndex)}?<redacted>` : url2;
|
|
32174
|
+
return snippet(safe, 300);
|
|
32175
|
+
}
|
|
32176
|
+
function delay(milliseconds) {
|
|
32177
|
+
return new Promise((resolve7) => setTimeout(resolve7, milliseconds));
|
|
32178
|
+
}
|
|
31746
32179
|
|
|
31747
32180
|
// ../../packages/execution-engine/src/engine/execution-engine.ts
|
|
31748
32181
|
var ExecutionEngine = class {
|
|
31749
32182
|
async runCase(data, options) {
|
|
31750
|
-
const { runId, file: file2, artifacts, bus, resolver } = options;
|
|
32183
|
+
const { runId, file: file2, artifacts, bus, resolver, rowId, rowIndex } = options;
|
|
31751
32184
|
const startedAt = /* @__PURE__ */ new Date();
|
|
31752
32185
|
const context = new ExecutionContext();
|
|
32186
|
+
context.seed(options.initialVariables);
|
|
31753
32187
|
if (data.accountRef && options.accounts?.[data.accountRef] !== void 0) {
|
|
31754
32188
|
context.seedAccount(options.accounts[data.accountRef]);
|
|
31755
32189
|
}
|
|
31756
32190
|
const steps = [];
|
|
32191
|
+
const warnings = [];
|
|
31757
32192
|
const evidenceAdapters = /* @__PURE__ */ new Map();
|
|
31758
32193
|
let caseError;
|
|
31759
|
-
|
|
31760
|
-
|
|
31761
|
-
|
|
32194
|
+
let caseErrorCode;
|
|
32195
|
+
const executionId = safeArtifactName(`${data.id}-${rowId}`);
|
|
32196
|
+
await bus.emit({
|
|
32197
|
+
type: "case-started",
|
|
32198
|
+
runId,
|
|
32199
|
+
caseId: data.id,
|
|
32200
|
+
rowId,
|
|
32201
|
+
file: file2,
|
|
32202
|
+
ts: startedAt.toISOString()
|
|
32203
|
+
});
|
|
32204
|
+
await artifacts.logger.info(`case ${data.id}#${rowId} started (${file2})`);
|
|
32205
|
+
if (options.missingDependencies.length > 0) {
|
|
32206
|
+
const finishedAt2 = /* @__PURE__ */ new Date();
|
|
32207
|
+
const skipped = {
|
|
32208
|
+
caseId: data.id,
|
|
32209
|
+
caseName: data.name,
|
|
32210
|
+
file: file2,
|
|
32211
|
+
rowId,
|
|
32212
|
+
rowIndex,
|
|
32213
|
+
status: "skipped",
|
|
32214
|
+
skipReason: "dependency-not-ready",
|
|
32215
|
+
missingDependencies: options.missingDependencies,
|
|
32216
|
+
steps,
|
|
32217
|
+
startedAt: startedAt.toISOString(),
|
|
32218
|
+
finishedAt: finishedAt2.toISOString(),
|
|
32219
|
+
durationMs: finishedAt2.getTime() - startedAt.getTime()
|
|
32220
|
+
};
|
|
31762
32221
|
await bus.emit({
|
|
31763
|
-
type: "
|
|
32222
|
+
type: "case-finished",
|
|
31764
32223
|
runId,
|
|
31765
32224
|
caseId: data.id,
|
|
31766
|
-
|
|
31767
|
-
|
|
31768
|
-
|
|
31769
|
-
ts:
|
|
32225
|
+
rowId,
|
|
32226
|
+
status: "skipped",
|
|
32227
|
+
durationMs: skipped.durationMs,
|
|
32228
|
+
ts: finishedAt2.toISOString()
|
|
31770
32229
|
});
|
|
31771
|
-
|
|
31772
|
-
|
|
31773
|
-
|
|
31774
|
-
|
|
31775
|
-
|
|
31776
|
-
|
|
31777
|
-
|
|
31778
|
-
|
|
31779
|
-
|
|
31780
|
-
|
|
31781
|
-
|
|
32230
|
+
await artifacts.logger.info(
|
|
32231
|
+
`case ${data.id}#${rowId} skipped: missing ${options.missingDependencies.join(", ")}`
|
|
32232
|
+
);
|
|
32233
|
+
return skipped;
|
|
32234
|
+
}
|
|
32235
|
+
const runPhase = async (phase, phaseSteps) => {
|
|
32236
|
+
let passed = true;
|
|
32237
|
+
for (const [index, step] of phaseSteps.entries()) {
|
|
32238
|
+
await bus.emit({
|
|
32239
|
+
type: "step-started",
|
|
32240
|
+
runId,
|
|
32241
|
+
caseId: data.id,
|
|
32242
|
+
rowId,
|
|
32243
|
+
phase,
|
|
32244
|
+
index,
|
|
32245
|
+
target: step.target,
|
|
32246
|
+
action: step.action,
|
|
32247
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
32248
|
+
});
|
|
32249
|
+
let result;
|
|
32250
|
+
try {
|
|
32251
|
+
const adapter = await (await resolver.session(step.target)).get();
|
|
32252
|
+
if (adapter.startEvidence && !evidenceAdapters.has(step.target)) {
|
|
32253
|
+
try {
|
|
32254
|
+
await adapter.startEvidence(executionId);
|
|
32255
|
+
evidenceAdapters.set(step.target, adapter);
|
|
32256
|
+
} catch (err) {
|
|
32257
|
+
await artifacts.logger.info(
|
|
32258
|
+
`evidence start failed for ${step.target}: ${err instanceof Error ? err.message : String(err)}`
|
|
32259
|
+
);
|
|
32260
|
+
}
|
|
31782
32261
|
}
|
|
32262
|
+
result = await executeStep(step, index, adapter, context, artifacts, {
|
|
32263
|
+
screenshotName: `${executionId}-${phase}-${String(index).padStart(2, "0")}`
|
|
32264
|
+
});
|
|
32265
|
+
} catch (err) {
|
|
32266
|
+
result = {
|
|
32267
|
+
index,
|
|
32268
|
+
target: step.target,
|
|
32269
|
+
action: step.action,
|
|
32270
|
+
status: "failed",
|
|
32271
|
+
durationMs: 0,
|
|
32272
|
+
error: err instanceof Error ? err.message : String(err),
|
|
32273
|
+
errorCode: err instanceof TestPilotError ? err.code : void 0
|
|
32274
|
+
};
|
|
31783
32275
|
}
|
|
31784
|
-
result =
|
|
31785
|
-
|
|
31786
|
-
|
|
31787
|
-
|
|
31788
|
-
|
|
32276
|
+
result.phase = phase;
|
|
32277
|
+
if (phase === "teardown" && result.status === "failed") {
|
|
32278
|
+
result.status = "warning";
|
|
32279
|
+
warnings.push(result.error ?? `teardown step ${index} failed`);
|
|
32280
|
+
}
|
|
32281
|
+
steps.push(result);
|
|
32282
|
+
await artifacts.logger.info(
|
|
32283
|
+
`${phase} step ${index} ${step.action} -> ${result.status}${result.error ? ` - ${result.error}` : ""}`
|
|
32284
|
+
);
|
|
32285
|
+
await bus.emit({
|
|
32286
|
+
type: "step-finished",
|
|
32287
|
+
runId,
|
|
32288
|
+
caseId: data.id,
|
|
32289
|
+
rowId,
|
|
32290
|
+
phase,
|
|
31789
32291
|
index,
|
|
31790
32292
|
target: step.target,
|
|
31791
32293
|
action: step.action,
|
|
32294
|
+
status: result.status,
|
|
32295
|
+
durationMs: result.durationMs,
|
|
32296
|
+
error: result.error,
|
|
32297
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
32298
|
+
});
|
|
32299
|
+
if (result.status === "failed") {
|
|
32300
|
+
passed = false;
|
|
32301
|
+
caseError = result.error;
|
|
32302
|
+
caseErrorCode = result.errorCode;
|
|
32303
|
+
for (const [skipIndex, skippedStep] of phaseSteps.entries()) {
|
|
32304
|
+
if (skipIndex <= index) continue;
|
|
32305
|
+
steps.push({
|
|
32306
|
+
index: skipIndex,
|
|
32307
|
+
phase,
|
|
32308
|
+
target: skippedStep.target,
|
|
32309
|
+
action: skippedStep.action,
|
|
32310
|
+
status: "skipped",
|
|
32311
|
+
durationMs: 0
|
|
32312
|
+
});
|
|
32313
|
+
}
|
|
32314
|
+
break;
|
|
32315
|
+
}
|
|
32316
|
+
}
|
|
32317
|
+
return passed;
|
|
32318
|
+
};
|
|
32319
|
+
if (options.blockedError) {
|
|
32320
|
+
const first = data.setup?.[0] ?? data.steps[0];
|
|
32321
|
+
if (first) {
|
|
32322
|
+
steps.push({
|
|
32323
|
+
index: 0,
|
|
32324
|
+
phase: "setup",
|
|
32325
|
+
target: first.target,
|
|
32326
|
+
action: first.action,
|
|
31792
32327
|
status: "failed",
|
|
31793
32328
|
durationMs: 0,
|
|
31794
|
-
error:
|
|
31795
|
-
|
|
32329
|
+
error: options.blockedError.message,
|
|
32330
|
+
errorCode: options.blockedError.code
|
|
32331
|
+
});
|
|
31796
32332
|
}
|
|
31797
|
-
|
|
31798
|
-
|
|
31799
|
-
|
|
31800
|
-
|
|
31801
|
-
|
|
31802
|
-
|
|
31803
|
-
|
|
31804
|
-
|
|
31805
|
-
action: step.action,
|
|
31806
|
-
status: result.status,
|
|
31807
|
-
durationMs: result.durationMs,
|
|
31808
|
-
error: result.error,
|
|
31809
|
-
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
31810
|
-
});
|
|
31811
|
-
if (result.status === "failed") {
|
|
31812
|
-
caseError = result.error;
|
|
31813
|
-
for (const [skipIndex, skippedStep] of data.steps.entries()) {
|
|
31814
|
-
if (skipIndex <= index) continue;
|
|
32333
|
+
caseError = options.blockedError.message;
|
|
32334
|
+
caseErrorCode = options.blockedError.code;
|
|
32335
|
+
} else {
|
|
32336
|
+
const setupPassed = await runPhase("setup", data.setup ?? []);
|
|
32337
|
+
if (setupPassed) {
|
|
32338
|
+
await runPhase("steps", data.steps);
|
|
32339
|
+
} else {
|
|
32340
|
+
for (const [index, step] of data.steps.entries()) {
|
|
31815
32341
|
steps.push({
|
|
31816
|
-
index
|
|
31817
|
-
|
|
31818
|
-
|
|
32342
|
+
index,
|
|
32343
|
+
phase: "steps",
|
|
32344
|
+
target: step.target,
|
|
32345
|
+
action: step.action,
|
|
31819
32346
|
status: "skipped",
|
|
31820
32347
|
durationMs: 0
|
|
31821
32348
|
});
|
|
31822
32349
|
}
|
|
31823
|
-
break;
|
|
31824
32350
|
}
|
|
32351
|
+
await runPhase("teardown", data.teardown ?? []);
|
|
31825
32352
|
}
|
|
31826
32353
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
31827
|
-
const status = caseError ?
|
|
32354
|
+
const status = caseError ? caseStatusFromErrorCode(caseErrorCode) : "passed";
|
|
31828
32355
|
const caseResult = {
|
|
31829
32356
|
caseId: data.id,
|
|
31830
32357
|
caseName: data.name,
|
|
31831
32358
|
file: file2,
|
|
32359
|
+
rowId,
|
|
32360
|
+
rowIndex,
|
|
31832
32361
|
status,
|
|
31833
32362
|
steps,
|
|
31834
32363
|
startedAt: startedAt.toISOString(),
|
|
@@ -31836,15 +32365,17 @@ var ExecutionEngine = class {
|
|
|
31836
32365
|
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
31837
32366
|
};
|
|
31838
32367
|
if (caseError) caseResult.error = caseError;
|
|
32368
|
+
if (caseErrorCode) caseResult.errorCode = caseErrorCode;
|
|
32369
|
+
if (warnings.length > 0) caseResult.warnings = warnings;
|
|
31839
32370
|
for (const [target, adapter] of evidenceAdapters) {
|
|
31840
32371
|
if (!adapter.stopEvidence) continue;
|
|
31841
32372
|
try {
|
|
31842
|
-
const evidence = await adapter.stopEvidence(
|
|
32373
|
+
const evidence = await adapter.stopEvidence(executionId);
|
|
31843
32374
|
if (evidence.video) {
|
|
31844
|
-
caseResult.video = await artifacts.saveVideo(`${
|
|
32375
|
+
caseResult.video = await artifacts.saveVideo(`${executionId}-${target}.webm`, evidence.video);
|
|
31845
32376
|
}
|
|
31846
32377
|
if (evidence.trace) {
|
|
31847
|
-
caseResult.trace = await artifacts.saveTrace(`${
|
|
32378
|
+
caseResult.trace = await artifacts.saveTrace(`${executionId}-${target}.zip`, evidence.trace);
|
|
31848
32379
|
}
|
|
31849
32380
|
} catch (err) {
|
|
31850
32381
|
await artifacts.logger.info(
|
|
@@ -31856,33 +32387,47 @@ var ExecutionEngine = class {
|
|
|
31856
32387
|
type: "case-finished",
|
|
31857
32388
|
runId,
|
|
31858
32389
|
caseId: data.id,
|
|
32390
|
+
rowId,
|
|
31859
32391
|
status,
|
|
31860
32392
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
31861
32393
|
ts: finishedAt.toISOString()
|
|
31862
32394
|
});
|
|
31863
|
-
await artifacts.logger.info(`case ${data.id} ${status}`);
|
|
32395
|
+
await artifacts.logger.info(`case ${data.id}#${rowId} ${status}`);
|
|
31864
32396
|
return caseResult;
|
|
31865
32397
|
}
|
|
31866
32398
|
};
|
|
32399
|
+
function safeArtifactName(value) {
|
|
32400
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "case";
|
|
32401
|
+
}
|
|
31867
32402
|
|
|
31868
32403
|
// ../../packages/execution-engine/src/engine/result-collector.ts
|
|
31869
32404
|
function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
31870
32405
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
31871
32406
|
const totals = {
|
|
32407
|
+
templates: options.templates ?? new Set(cases.map((item) => item.caseId)).size,
|
|
31872
32408
|
cases: cases.length,
|
|
31873
32409
|
passed: cases.filter((item) => item.status === "passed").length,
|
|
31874
32410
|
failed: cases.filter((item) => item.status === "failed").length,
|
|
32411
|
+
skipped: cases.filter((item) => item.status === "skipped").length,
|
|
32412
|
+
...cases.some((item) => item.status === "blocked") ? { blocked: cases.filter((item) => item.status === "blocked").length } : {},
|
|
32413
|
+
...cases.some((item) => item.status === "inconclusive") ? { inconclusive: cases.filter((item) => item.status === "inconclusive").length } : {},
|
|
32414
|
+
warnings: 0,
|
|
31875
32415
|
steps: 0,
|
|
31876
32416
|
stepsPassed: 0,
|
|
31877
32417
|
stepsFailed: 0,
|
|
31878
|
-
stepsSkipped: 0
|
|
32418
|
+
stepsSkipped: 0,
|
|
32419
|
+
stepsWarning: 0
|
|
31879
32420
|
};
|
|
31880
32421
|
for (const item of cases) {
|
|
31881
32422
|
for (const step of item.steps) {
|
|
31882
32423
|
totals.steps++;
|
|
31883
32424
|
if (step.status === "passed") totals.stepsPassed++;
|
|
31884
32425
|
else if (step.status === "failed") totals.stepsFailed++;
|
|
31885
|
-
else totals.stepsSkipped++;
|
|
32426
|
+
else if (step.status === "skipped") totals.stepsSkipped++;
|
|
32427
|
+
else {
|
|
32428
|
+
totals.stepsWarning++;
|
|
32429
|
+
totals.warnings++;
|
|
32430
|
+
}
|
|
31886
32431
|
}
|
|
31887
32432
|
}
|
|
31888
32433
|
return {
|
|
@@ -31890,7 +32435,8 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
31890
32435
|
startedAt: startedAt.toISOString(),
|
|
31891
32436
|
finishedAt: finishedAt.toISOString(),
|
|
31892
32437
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
31893
|
-
|
|
32438
|
+
// run 状态按三桶优先级推导:产品失败 > 证据不足 > 环境阻塞 > 通过 > 跳过
|
|
32439
|
+
status: totals.failed > 0 ? "failed" : (totals.inconclusive ?? 0) > 0 ? "inconclusive" : (totals.blocked ?? 0) > 0 ? "blocked" : totals.passed > 0 || totals.cases === 0 ? "passed" : "skipped",
|
|
31894
32440
|
...options.cancelled ? { cancelled: true } : {},
|
|
31895
32441
|
cases,
|
|
31896
32442
|
totals
|
|
@@ -31899,7 +32445,7 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
31899
32445
|
|
|
31900
32446
|
// ../../packages/execution-engine/src/engine/test-runner.ts
|
|
31901
32447
|
import { writeFile as writeFile9 } from "node:fs/promises";
|
|
31902
|
-
import { join as join15, resolve as
|
|
32448
|
+
import { join as join15, resolve as resolve6 } from "node:path";
|
|
31903
32449
|
var TestRunner = class {
|
|
31904
32450
|
resolver;
|
|
31905
32451
|
runsRoot;
|
|
@@ -31909,7 +32455,7 @@ var TestRunner = class {
|
|
|
31909
32455
|
accounts;
|
|
31910
32456
|
constructor(options) {
|
|
31911
32457
|
this.resolver = options.resolver;
|
|
31912
|
-
this.runsRoot =
|
|
32458
|
+
this.runsRoot = resolve6(options.runsRoot ?? join15(process.cwd(), ".testpilot", "artifacts", "runs"));
|
|
31913
32459
|
this.customRunId = options.runId;
|
|
31914
32460
|
this.extraSubscriber = options.onEvent;
|
|
31915
32461
|
this.signal = options.signal;
|
|
@@ -31937,6 +32483,12 @@ var TestRunner = class {
|
|
|
31937
32483
|
await engine.runCase(item.data, {
|
|
31938
32484
|
runId,
|
|
31939
32485
|
file: item.file,
|
|
32486
|
+
templateId: item.templateId ?? item.data.id,
|
|
32487
|
+
rowId: item.rowId ?? "default",
|
|
32488
|
+
rowIndex: item.rowIndex ?? 0,
|
|
32489
|
+
initialVariables: item.initialVariables ?? {},
|
|
32490
|
+
missingDependencies: item.missingDependencies ?? [],
|
|
32491
|
+
blockedError: item.blockedError,
|
|
31940
32492
|
artifacts,
|
|
31941
32493
|
bus,
|
|
31942
32494
|
resolver: this.resolver,
|
|
@@ -31946,7 +32498,8 @@ var TestRunner = class {
|
|
|
31946
32498
|
}
|
|
31947
32499
|
const cancelled = this.signal?.aborted === true;
|
|
31948
32500
|
await this.resolver.closeAll();
|
|
31949
|
-
const
|
|
32501
|
+
const templates = new Set(cases.map((item) => item.templateId ?? item.data.id)).size;
|
|
32502
|
+
const summary = buildRunSummary(runId, startedAt, results, { cancelled, templates });
|
|
31950
32503
|
await bus.emit({
|
|
31951
32504
|
type: "run-finished",
|
|
31952
32505
|
runId,
|
|
@@ -31988,10 +32541,11 @@ async function openCaseContext(bundle, options, evidenceTmpDir) {
|
|
|
31988
32541
|
}
|
|
31989
32542
|
|
|
31990
32543
|
// ../../packages/adapter-playwright/src/page.ts
|
|
31991
|
-
import { existsSync as
|
|
31992
|
-
import { mkdtemp, readFile as
|
|
32544
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
32545
|
+
import { mkdtemp, readFile as readFile6, rm } from "node:fs/promises";
|
|
31993
32546
|
import { tmpdir } from "node:os";
|
|
31994
32547
|
import { join as join16 } from "node:path";
|
|
32548
|
+
import { errors } from "playwright";
|
|
31995
32549
|
var PlaywrightAdapter = class {
|
|
31996
32550
|
constructor(bundle, options = {}) {
|
|
31997
32551
|
this.bundle = bundle;
|
|
@@ -31999,6 +32553,7 @@ var PlaywrightAdapter = class {
|
|
|
31999
32553
|
}
|
|
32000
32554
|
target = "web";
|
|
32001
32555
|
current;
|
|
32556
|
+
requestCapture;
|
|
32002
32557
|
/** 浏览器在 create() 时已启动;context/page 按用例惰性创建,launch 保持幂等 */
|
|
32003
32558
|
async launch() {
|
|
32004
32559
|
}
|
|
@@ -32013,6 +32568,7 @@ var PlaywrightAdapter = class {
|
|
|
32013
32568
|
}
|
|
32014
32569
|
/** 结束取证:trace 落 zip、录屏在 context 关闭后完成,返回二者字节 */
|
|
32015
32570
|
async stopEvidence(_caseId) {
|
|
32571
|
+
await this.stopRequestCapture().catch(() => []);
|
|
32016
32572
|
if (!this.current) return {};
|
|
32017
32573
|
const { context, page, evidenceTmpDir } = this.current;
|
|
32018
32574
|
this.current = void 0;
|
|
@@ -32020,45 +32576,76 @@ var PlaywrightAdapter = class {
|
|
|
32020
32576
|
await context.tracing.stop({ path: traceZipPath }).catch(() => void 0);
|
|
32021
32577
|
const recordedVideoPath = await page.video()?.path().catch(() => void 0);
|
|
32022
32578
|
await context.close().catch(() => void 0);
|
|
32023
|
-
const video = recordedVideoPath &&
|
|
32024
|
-
const trace = evidenceTmpDir &&
|
|
32579
|
+
const video = recordedVideoPath && existsSync3(recordedVideoPath) ? await readFile6(recordedVideoPath) : void 0;
|
|
32580
|
+
const trace = evidenceTmpDir && existsSync3(traceZipPath) ? await readFile6(traceZipPath) : void 0;
|
|
32025
32581
|
if (evidenceTmpDir) await rm(evidenceTmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
32026
32582
|
return { video, trace };
|
|
32027
32583
|
}
|
|
32584
|
+
async startRequestCapture() {
|
|
32585
|
+
if (this.requestCapture) await this.stopRequestCapture();
|
|
32586
|
+
const page = await this.ensurePage();
|
|
32587
|
+
const requests = [];
|
|
32588
|
+
const listener = (request) => {
|
|
32589
|
+
requests.push({ method: request.method(), url: request.url() });
|
|
32590
|
+
};
|
|
32591
|
+
page.on("request", listener);
|
|
32592
|
+
this.requestCapture = { page, listener, requests };
|
|
32593
|
+
}
|
|
32594
|
+
async stopRequestCapture() {
|
|
32595
|
+
if (!this.requestCapture) return [];
|
|
32596
|
+
const capture = this.requestCapture;
|
|
32597
|
+
this.requestCapture = void 0;
|
|
32598
|
+
capture.page.off("request", capture.listener);
|
|
32599
|
+
return capture.requests;
|
|
32600
|
+
}
|
|
32028
32601
|
async navigate(url2) {
|
|
32029
32602
|
const options = this.options;
|
|
32030
32603
|
const page = await this.ensurePage();
|
|
32031
32604
|
const target = options.baseUrl && !/^https?:\/\//i.test(url2) ? new URL(url2, options.baseUrl).toString() : url2;
|
|
32032
|
-
|
|
32605
|
+
try {
|
|
32606
|
+
await page.goto(target);
|
|
32607
|
+
} catch (err) {
|
|
32608
|
+
if (err instanceof errors.TimeoutError) {
|
|
32609
|
+
throw new TestPilotError("STEP_TIMEOUT", err.message, { cause: err });
|
|
32610
|
+
}
|
|
32611
|
+
if (err instanceof Error && err.message.includes("net::ERR")) {
|
|
32612
|
+
throw new TestPilotError("ENVIRONMENT_UNAVAILABLE", err.message, { cause: err });
|
|
32613
|
+
}
|
|
32614
|
+
throw err;
|
|
32615
|
+
}
|
|
32033
32616
|
}
|
|
32034
32617
|
async click(locator) {
|
|
32035
|
-
|
|
32618
|
+
const target = await this.toLocator(locator);
|
|
32619
|
+
await this.coded(() => target.click());
|
|
32036
32620
|
}
|
|
32037
32621
|
async input(locator, value) {
|
|
32038
|
-
|
|
32622
|
+
const target = await this.toLocator(locator);
|
|
32623
|
+
await this.coded(() => target.fill(value));
|
|
32039
32624
|
}
|
|
32040
32625
|
async select(locator, value) {
|
|
32041
|
-
|
|
32626
|
+
const target = await this.toLocator(locator);
|
|
32627
|
+
await this.coded(() => target.selectOption(value));
|
|
32042
32628
|
}
|
|
32043
32629
|
async wait(locator, timeoutMs) {
|
|
32044
32630
|
const page = await this.ensurePage();
|
|
32045
32631
|
if (locator) {
|
|
32046
|
-
await this.toLocatorOn(page, locator).waitFor({ state: "visible", timeout: timeoutMs });
|
|
32632
|
+
await this.coded(() => this.toLocatorOn(page, locator).waitFor({ state: "visible", timeout: timeoutMs }));
|
|
32047
32633
|
return;
|
|
32048
32634
|
}
|
|
32049
32635
|
await page.waitForTimeout(timeoutMs ?? 1e3);
|
|
32050
32636
|
}
|
|
32051
32637
|
async assert(locator, expected) {
|
|
32052
32638
|
const target = await this.toLocator(locator);
|
|
32053
|
-
await target.waitFor({ state: "visible" });
|
|
32054
|
-
const actual = (await target.innerText()).trim();
|
|
32639
|
+
await this.coded(() => target.waitFor({ state: "visible" }));
|
|
32640
|
+
const actual = (await this.coded(() => target.innerText())).trim();
|
|
32055
32641
|
const wanted = expected.trim();
|
|
32056
32642
|
if (!actual.includes(wanted)) {
|
|
32057
|
-
throw new
|
|
32643
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u65AD\u8A00\u5931\u8D25:\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`);
|
|
32058
32644
|
}
|
|
32059
32645
|
}
|
|
32060
32646
|
async extract(locator) {
|
|
32061
|
-
|
|
32647
|
+
const target = await this.toLocator(locator);
|
|
32648
|
+
return this.coded(() => target.innerText()).then((text) => text.trim());
|
|
32062
32649
|
}
|
|
32063
32650
|
async screenshot() {
|
|
32064
32651
|
return (await this.ensurePage()).screenshot();
|
|
@@ -32076,6 +32663,17 @@ var PlaywrightAdapter = class {
|
|
|
32076
32663
|
async toLocator(locator) {
|
|
32077
32664
|
return this.toLocatorOn(await this.ensurePage(), locator);
|
|
32078
32665
|
}
|
|
32666
|
+
/** 定位类操作的统一兜底:等待超时按「元素没等到」归类(可能是回归也可能是 selector 腐烂,不允许静默) */
|
|
32667
|
+
async coded(fn) {
|
|
32668
|
+
try {
|
|
32669
|
+
return await fn();
|
|
32670
|
+
} catch (err) {
|
|
32671
|
+
if (err instanceof errors.TimeoutError) {
|
|
32672
|
+
throw new TestPilotError("ELEMENT_NOT_FOUND", err.message, { cause: err });
|
|
32673
|
+
}
|
|
32674
|
+
throw err;
|
|
32675
|
+
}
|
|
32676
|
+
}
|
|
32079
32677
|
toLocatorOn(page, locator) {
|
|
32080
32678
|
if (locator.text !== void 0) {
|
|
32081
32679
|
return page.getByText(locator.text, { exact: true });
|
|
@@ -32086,6 +32684,7 @@ var PlaywrightAdapter = class {
|
|
|
32086
32684
|
throw new Error(`\u65E0\u6548\u7684 locator:${JSON.stringify(locator)}`);
|
|
32087
32685
|
}
|
|
32088
32686
|
async closeContextQuietly() {
|
|
32687
|
+
await this.stopRequestCapture().catch(() => []);
|
|
32089
32688
|
if (!this.current) return;
|
|
32090
32689
|
const { context } = this.current;
|
|
32091
32690
|
this.current = void 0;
|
|
@@ -32124,8 +32723,8 @@ var automatorLauncher = {
|
|
|
32124
32723
|
}
|
|
32125
32724
|
}
|
|
32126
32725
|
};
|
|
32127
|
-
function
|
|
32128
|
-
return new Promise((
|
|
32726
|
+
function delay2(ms) {
|
|
32727
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
32129
32728
|
}
|
|
32130
32729
|
async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
32131
32730
|
const startedAt = Date.now();
|
|
@@ -32135,7 +32734,7 @@ async function connectWithRetry(wsEndpoint, timeoutMs) {
|
|
|
32135
32734
|
return await automator.connect({ wsEndpoint });
|
|
32136
32735
|
} catch (err) {
|
|
32137
32736
|
lastError = err;
|
|
32138
|
-
await
|
|
32737
|
+
await delay2(1e3);
|
|
32139
32738
|
}
|
|
32140
32739
|
}
|
|
32141
32740
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -32152,12 +32751,12 @@ function startCliAuto(cliPath, projectPath, port) {
|
|
|
32152
32751
|
child.stderr?.on("data", (chunk) => {
|
|
32153
32752
|
output2 += chunk.toString("utf8");
|
|
32154
32753
|
});
|
|
32155
|
-
child.once("error", (err) => rejectPromise(new
|
|
32754
|
+
child.once("error", (err) => rejectPromise(new TestPilotError("ENVIRONMENT_UNAVAILABLE", `\u62C9\u8D77\u5F00\u53D1\u8005\u5DE5\u5177 cli \u5931\u8D25:${err.message}`)));
|
|
32156
32755
|
child.once("exit", (code) => {
|
|
32157
32756
|
if (code === 0) {
|
|
32158
32757
|
resolvePromise();
|
|
32159
32758
|
} else {
|
|
32160
|
-
rejectPromise(new
|
|
32759
|
+
rejectPromise(new TestPilotError("ENVIRONMENT_UNAVAILABLE", `cli auto \u9000\u51FA\u7801 ${code}:${output2.slice(-400)}`));
|
|
32161
32760
|
}
|
|
32162
32761
|
});
|
|
32163
32762
|
const startedAt = Date.now();
|
|
@@ -32170,7 +32769,7 @@ function startCliAuto(cliPath, projectPath, port) {
|
|
|
32170
32769
|
socket.once("error", () => {
|
|
32171
32770
|
socket.destroy();
|
|
32172
32771
|
if (Date.now() - startedAt > AUTO_PORT_FALLBACK_WAIT_MS) {
|
|
32173
|
-
rejectPromise(new
|
|
32772
|
+
rejectPromise(new TestPilotError("ENVIRONMENT_UNAVAILABLE", `\u7B49\u5F85\u5F00\u53D1\u8005\u5DE5\u5177\u81EA\u52A8\u5316\u7AEF\u53E3 ${port} \u8D85\u65F6;\u8BF7\u786E\u8BA4:1) \u5DE5\u5177\u5DF2\u767B\u5F55 2) \u8BBE\u7F6E->\u5B89\u5168\u8BBE\u7F6E->\u670D\u52A1\u7AEF\u53E3\u5DF2\u5F00\u542F`));
|
|
32174
32773
|
} else {
|
|
32175
32774
|
setTimeout(poll, 500);
|
|
32176
32775
|
}
|
|
@@ -32186,6 +32785,7 @@ var MiniAppAdapter = class {
|
|
|
32186
32785
|
}
|
|
32187
32786
|
target = "miniapp";
|
|
32188
32787
|
program;
|
|
32788
|
+
requestCapture;
|
|
32189
32789
|
/** 拉起开发者工具并建立自动化连接;需幂等 */
|
|
32190
32790
|
async launch() {
|
|
32191
32791
|
if (this.program) return;
|
|
@@ -32206,7 +32806,7 @@ var MiniAppAdapter = class {
|
|
|
32206
32806
|
return;
|
|
32207
32807
|
} catch (err) {
|
|
32208
32808
|
lastError = err;
|
|
32209
|
-
await
|
|
32809
|
+
await delay2(3e3);
|
|
32210
32810
|
}
|
|
32211
32811
|
}
|
|
32212
32812
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -32218,7 +32818,7 @@ var MiniAppAdapter = class {
|
|
|
32218
32818
|
await this.runWithReconnect(async () => (await this.element(locator)).input(value));
|
|
32219
32819
|
}
|
|
32220
32820
|
async select() {
|
|
32221
|
-
throw new
|
|
32821
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", "\u5C0F\u7A0B\u5E8F\u7AEF\u6682\u4E0D\u652F\u6301 select(picker \u4EA4\u4E92),\u8BF7\u6539\u7528 click \u5B8C\u6210\u9009\u62E9");
|
|
32222
32822
|
}
|
|
32223
32823
|
async wait(locator, timeoutMs) {
|
|
32224
32824
|
await this.runWithReconnect(async () => {
|
|
@@ -32236,7 +32836,7 @@ var MiniAppAdapter = class {
|
|
|
32236
32836
|
const actual = (await element.text()).trim();
|
|
32237
32837
|
const wanted = expected.trim();
|
|
32238
32838
|
if (!actual.includes(wanted)) {
|
|
32239
|
-
throw new
|
|
32839
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u65AD\u8A00\u5931\u8D25:\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`);
|
|
32240
32840
|
}
|
|
32241
32841
|
});
|
|
32242
32842
|
}
|
|
@@ -32252,7 +32852,43 @@ var MiniAppAdapter = class {
|
|
|
32252
32852
|
return Buffer.from(base643, "base64");
|
|
32253
32853
|
});
|
|
32254
32854
|
}
|
|
32855
|
+
async startRequestCapture() {
|
|
32856
|
+
if (this.requestCapture) await this.stopRequestCapture();
|
|
32857
|
+
const program2 = await this.getProgram();
|
|
32858
|
+
const requests = [];
|
|
32859
|
+
const exposedName = `__testpilotRequest_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
32860
|
+
await program2.exposeFunction(exposedName, (value) => {
|
|
32861
|
+
if (value === null || typeof value !== "object") return;
|
|
32862
|
+
const request = value;
|
|
32863
|
+
if (typeof request.url !== "string") return;
|
|
32864
|
+
requests.push({
|
|
32865
|
+
method: typeof request.method === "string" ? request.method : "GET",
|
|
32866
|
+
url: request.url
|
|
32867
|
+
});
|
|
32868
|
+
});
|
|
32869
|
+
await program2.mockWxMethod(
|
|
32870
|
+
"request",
|
|
32871
|
+
function(options, callbackName) {
|
|
32872
|
+
const callbacks = globalThis;
|
|
32873
|
+
callbacks[callbackName]?.({
|
|
32874
|
+
method: typeof options.method === "string" ? options.method : "GET",
|
|
32875
|
+
url: typeof options.url === "string" ? options.url : ""
|
|
32876
|
+
});
|
|
32877
|
+
return this.origin(options);
|
|
32878
|
+
},
|
|
32879
|
+
exposedName
|
|
32880
|
+
);
|
|
32881
|
+
this.requestCapture = { program: program2, exposedName, requests };
|
|
32882
|
+
}
|
|
32883
|
+
async stopRequestCapture() {
|
|
32884
|
+
if (!this.requestCapture) return [];
|
|
32885
|
+
const capture = this.requestCapture;
|
|
32886
|
+
this.requestCapture = void 0;
|
|
32887
|
+
await capture.program.restoreWxMethod("request");
|
|
32888
|
+
return capture.requests;
|
|
32889
|
+
}
|
|
32255
32890
|
async close() {
|
|
32891
|
+
await this.stopRequestCapture().catch(() => []);
|
|
32256
32892
|
if (!this.program) return;
|
|
32257
32893
|
const program2 = this.program;
|
|
32258
32894
|
this.program = void 0;
|
|
@@ -32298,19 +32934,19 @@ var MiniAppAdapter = class {
|
|
|
32298
32934
|
const element = await page.$(selector);
|
|
32299
32935
|
if (element) return element;
|
|
32300
32936
|
if (Date.now() - startedAt > ELEMENT_WAIT_MS) {
|
|
32301
|
-
throw new
|
|
32937
|
+
throw new TestPilotError("ELEMENT_NOT_FOUND", `\u7B49\u5F85\u5143\u7D20\u8D85\u65F6(${ELEMENT_WAIT_MS}ms):${selector}`);
|
|
32302
32938
|
}
|
|
32303
|
-
await
|
|
32939
|
+
await delay2(500);
|
|
32304
32940
|
}
|
|
32305
32941
|
}
|
|
32306
32942
|
};
|
|
32307
32943
|
|
|
32308
32944
|
// ../../packages/adapter-wechatide/src/project.ts
|
|
32309
|
-
import { existsSync as
|
|
32945
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
32310
32946
|
async function resolveMiniappConfig(input2) {
|
|
32311
32947
|
const projectPath = input2.projectPath ?? process.env.TESTPILOT_MINIPROGRAM_PROJECT_PATH;
|
|
32312
32948
|
if (!projectPath) return void 0;
|
|
32313
|
-
if (!
|
|
32949
|
+
if (!existsSync4(projectPath)) {
|
|
32314
32950
|
throw new Error(`\u5C0F\u7A0B\u5E8F\u9879\u76EE\u8DEF\u5F84\u4E0D\u5B58\u5728:${projectPath}`);
|
|
32315
32951
|
}
|
|
32316
32952
|
const cliPath = input2.cliPath ?? process.env.WECHAT_DEVTOOLS_CLI ?? await findWechatDevToolsCli();
|
|
@@ -32376,7 +33012,7 @@ var ApiAdapter = class {
|
|
|
32376
33012
|
signal: AbortSignal.timeout(this.options.timeoutMs ?? 15e3)
|
|
32377
33013
|
});
|
|
32378
33014
|
} catch (err) {
|
|
32379
|
-
throw new
|
|
33015
|
+
throw new TestPilotError("ENVIRONMENT_UNAVAILABLE", `API \u8BF7\u6C42\u5931\u8D25 ${request.method.toUpperCase()} ${url2}:${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
32380
33016
|
}
|
|
32381
33017
|
const bodyText = await res.text();
|
|
32382
33018
|
let json2;
|
|
@@ -32398,7 +33034,7 @@ var ApiAdapter = class {
|
|
|
32398
33034
|
if (!this.last) throw new Error("\u5C1A\u672A\u53D1\u8D77\u4EFB\u4F55 API \u8BF7\u6C42,\u65E0\u6CD5\u65AD\u8A00\u54CD\u5E94");
|
|
32399
33035
|
if (!this.last.bodyText.includes(expected)) {
|
|
32400
33036
|
const snippet2 = this.last.bodyText.slice(0, 200);
|
|
32401
|
-
throw new
|
|
33037
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u54CD\u5E94\u4F53\u4E0D\u5305\u542B "${expected}":HTTP ${this.last.status} ${snippet2}`);
|
|
32402
33038
|
}
|
|
32403
33039
|
}
|
|
32404
33040
|
async extractResponse(path) {
|
|
@@ -32485,7 +33121,7 @@ function createDefaultAdapters(config2) {
|
|
|
32485
33121
|
async function runCases(options = {}) {
|
|
32486
33122
|
const root = resolveRoot(options.root);
|
|
32487
33123
|
const config2 = await loadProjectConfig(root);
|
|
32488
|
-
const inputs = options.paths && options.paths.length > 0 ? options.paths.map((input2) =>
|
|
33124
|
+
const inputs = options.paths && options.paths.length > 0 ? options.paths.map((input2) => isAbsolute3(input2) ? input2 : join17(root, input2)) : [join17(root, config2.casesDir)];
|
|
32489
33125
|
const cases = await collectCases(inputs, { root });
|
|
32490
33126
|
if (cases.length === 0) {
|
|
32491
33127
|
throw new RunError("ENO_CASES", `\u672A\u627E\u5230\u7528\u4F8B\u6587\u4EF6(\u67E5\u627E:${inputs.join(", ")})`);
|
|
@@ -32517,8 +33153,39 @@ async function runCases(options = {}) {
|
|
|
32517
33153
|
signal: options.signal,
|
|
32518
33154
|
accounts: options.accounts
|
|
32519
33155
|
});
|
|
33156
|
+
const prepared = (await Promise.all(
|
|
33157
|
+
runnable.map(
|
|
33158
|
+
(item) => prepareCaseExecutions(
|
|
33159
|
+
{ data: item.case, file: item.file },
|
|
33160
|
+
{
|
|
33161
|
+
root,
|
|
33162
|
+
dataDir: join17(root, "tests", "e2e", "data"),
|
|
33163
|
+
variables: config2.variables,
|
|
33164
|
+
accounts: options.accounts
|
|
33165
|
+
}
|
|
33166
|
+
).catch((err) => {
|
|
33167
|
+
if (err instanceof TestPilotError && err.code === "FIXTURE_MISSING") {
|
|
33168
|
+
return [
|
|
33169
|
+
{
|
|
33170
|
+
data: item.case,
|
|
33171
|
+
file: item.file,
|
|
33172
|
+
templateId: item.case.id,
|
|
33173
|
+
rowId: "default",
|
|
33174
|
+
rowIndex: 0,
|
|
33175
|
+
initialVariables: {},
|
|
33176
|
+
missingDependencies: [],
|
|
33177
|
+
blockedError: { code: err.code, message: err.message }
|
|
33178
|
+
}
|
|
33179
|
+
];
|
|
33180
|
+
}
|
|
33181
|
+
throw err;
|
|
33182
|
+
})
|
|
33183
|
+
)
|
|
33184
|
+
)).flat();
|
|
32520
33185
|
const summary = await runner.run(
|
|
32521
|
-
|
|
33186
|
+
prepared.map(
|
|
33187
|
+
(item) => item
|
|
33188
|
+
)
|
|
32522
33189
|
);
|
|
32523
33190
|
return { summary, invalid, tagFiltered, missingAccounts };
|
|
32524
33191
|
}
|
|
@@ -32542,20 +33209,20 @@ async function getRun(runId, options = {}) {
|
|
|
32542
33209
|
const base = runsRoot(options.root);
|
|
32543
33210
|
const file2 = join17(base, runId, RESULT_FILE);
|
|
32544
33211
|
try {
|
|
32545
|
-
return JSON.parse(await
|
|
33212
|
+
return JSON.parse(await readFile7(file2, "utf8"));
|
|
32546
33213
|
} catch {
|
|
32547
33214
|
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
33215
|
}
|
|
32549
33216
|
}
|
|
32550
33217
|
async function getRunEvents(runId, options = {}) {
|
|
32551
33218
|
const file2 = join17(runsRoot(options.root), runId, "events.ndjson");
|
|
32552
|
-
const source = await
|
|
33219
|
+
const source = await readFile7(file2, "utf8").catch(() => "");
|
|
32553
33220
|
return source.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
32554
33221
|
}
|
|
32555
33222
|
async function readRunMeta(base, runId) {
|
|
32556
33223
|
try {
|
|
32557
33224
|
const summary = JSON.parse(
|
|
32558
|
-
await
|
|
33225
|
+
await readFile7(join17(base, runId, RESULT_FILE), "utf8")
|
|
32559
33226
|
);
|
|
32560
33227
|
return {
|
|
32561
33228
|
status: summary.status,
|
|
@@ -32579,7 +33246,7 @@ async function generateReport(runId, options = {}) {
|
|
|
32579
33246
|
const runDir = join18(resolveRoot(options.root), RUNS_DIR, id);
|
|
32580
33247
|
let summary;
|
|
32581
33248
|
try {
|
|
32582
|
-
summary = JSON.parse(await
|
|
33249
|
+
summary = JSON.parse(await readFile8(join18(runDir, "result.json"), "utf8"));
|
|
32583
33250
|
} catch {
|
|
32584
33251
|
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
33252
|
}
|
|
@@ -32610,13 +33277,13 @@ async function generateSummaryReport(options = {}) {
|
|
|
32610
33277
|
}
|
|
32611
33278
|
async function readReport(runId, options = {}) {
|
|
32612
33279
|
const file2 = join18(resolveRoot(options.root), RUNS_DIR, runId, "report.json");
|
|
32613
|
-
const source = await
|
|
33280
|
+
const source = await readFile8(file2, "utf8").catch(() => void 0);
|
|
32614
33281
|
return source ? JSON.parse(source) : void 0;
|
|
32615
33282
|
}
|
|
32616
33283
|
|
|
32617
33284
|
// ../../packages/sdk/src/client.ts
|
|
32618
33285
|
function resolveInput(root, input2) {
|
|
32619
|
-
return
|
|
33286
|
+
return isAbsolute4(input2) ? input2 : join19(root, input2);
|
|
32620
33287
|
}
|
|
32621
33288
|
var TestPilotClient = class {
|
|
32622
33289
|
/** 业务项目根目录(绝对路径) */
|
|
@@ -32692,13 +33359,13 @@ var TestPilotClient = class {
|
|
|
32692
33359
|
};
|
|
32693
33360
|
|
|
32694
33361
|
// src/commands/init/adapter-detector.ts
|
|
32695
|
-
import { existsSync as
|
|
33362
|
+
import { existsSync as existsSync5, readFileSync } from "node:fs";
|
|
32696
33363
|
import { join as join20 } from "node:path";
|
|
32697
33364
|
async function detectAdapters(root, projectType) {
|
|
32698
33365
|
const required2 = projectType === "hybrid" ? ["playwright", "wechatide"] : projectType === "wechat-miniapp" ? ["wechatide"] : projectType === "web" ? ["playwright"] : [];
|
|
32699
33366
|
const pkg = readPackageJsonDeps(root);
|
|
32700
33367
|
const hasPlaywrightDep = pkg !== void 0 && ("playwright" in pkg || "@playwright/test" in pkg);
|
|
32701
|
-
const playwrightInstalled = hasPlaywrightDep ||
|
|
33368
|
+
const playwrightInstalled = hasPlaywrightDep || existsSync5(join20(root, "node_modules", "playwright"));
|
|
32702
33369
|
const devtoolsCli = await findWechatDevToolsCli().catch(() => void 0);
|
|
32703
33370
|
return [
|
|
32704
33371
|
{
|
|
@@ -32717,7 +33384,7 @@ async function detectAdapters(root, projectType) {
|
|
|
32717
33384
|
}
|
|
32718
33385
|
function readPackageJsonDeps(root) {
|
|
32719
33386
|
const file2 = join20(root, "package.json");
|
|
32720
|
-
if (!
|
|
33387
|
+
if (!existsSync5(file2)) return void 0;
|
|
32721
33388
|
try {
|
|
32722
33389
|
const pkg = JSON.parse(readFileSync(file2, "utf8"));
|
|
32723
33390
|
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
@@ -32727,13 +33394,13 @@ function readPackageJsonDeps(root) {
|
|
|
32727
33394
|
}
|
|
32728
33395
|
|
|
32729
33396
|
// src/commands/init/environment-initializer.ts
|
|
32730
|
-
import { readFile as
|
|
33397
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
32731
33398
|
import { join as join21 } from "node:path";
|
|
32732
33399
|
var ENV_REF_PATTERN = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
32733
33400
|
async function collectEnvVarRefs(root) {
|
|
32734
33401
|
let text;
|
|
32735
33402
|
try {
|
|
32736
|
-
text = await
|
|
33403
|
+
text = await readFile9(join21(root, "testpilot.yaml"), "utf8");
|
|
32737
33404
|
} catch {
|
|
32738
33405
|
return [];
|
|
32739
33406
|
}
|
|
@@ -32749,7 +33416,7 @@ async function collectEnvVarRefs(root) {
|
|
|
32749
33416
|
}
|
|
32750
33417
|
|
|
32751
33418
|
// src/commands/init/project-detector.ts
|
|
32752
|
-
import { existsSync as
|
|
33419
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
32753
33420
|
import { basename, join as join22 } from "node:path";
|
|
32754
33421
|
|
|
32755
33422
|
// src/lib/git.ts
|
|
@@ -32788,7 +33455,7 @@ var WEB_DEP_MARKERS = [
|
|
|
32788
33455
|
var E2E_DIR_CANDIDATES = ["tests/e2e", "test/e2e", "e2e", "cypress"];
|
|
32789
33456
|
function readPackageJson(root) {
|
|
32790
33457
|
const file2 = join22(root, "package.json");
|
|
32791
|
-
if (!
|
|
33458
|
+
if (!existsSync6(file2)) return void 0;
|
|
32792
33459
|
try {
|
|
32793
33460
|
return JSON.parse(readFileSync2(file2, "utf8"));
|
|
32794
33461
|
} catch {
|
|
@@ -32798,8 +33465,8 @@ function readPackageJson(root) {
|
|
|
32798
33465
|
function detectProjectType(root) {
|
|
32799
33466
|
const pkg = readPackageJson(root);
|
|
32800
33467
|
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
32801
|
-
const isWechat =
|
|
32802
|
-
const isWeb =
|
|
33468
|
+
const isWechat = existsSync6(join22(root, "project.config.json")) || existsSync6(join22(root, "app.json")) || existsSync6(join22(root, "miniprogram", "app.json"));
|
|
33469
|
+
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
33470
|
if (isWechat && isWeb) return "hybrid";
|
|
32804
33471
|
if (isWechat) return "wechat-miniapp";
|
|
32805
33472
|
if (isWeb) return "web";
|
|
@@ -32808,9 +33475,9 @@ function detectProjectType(root) {
|
|
|
32808
33475
|
async function detectProject(root) {
|
|
32809
33476
|
const pkg = readPackageJson(root);
|
|
32810
33477
|
const type = detectProjectType(root);
|
|
32811
|
-
const language =
|
|
32812
|
-
const packageManager =
|
|
32813
|
-
const existingE2eDirs = E2E_DIR_CANDIDATES.filter((dir) =>
|
|
33478
|
+
const language = existsSync6(join22(root, "tsconfig.json")) ? "TypeScript" : pkg || existsSync6(join22(root, "src")) ? "JavaScript" : "Unknown";
|
|
33479
|
+
const packageManager = existsSync6(join22(root, "pnpm-lock.yaml")) ? "pnpm" : existsSync6(join22(root, "yarn.lock")) ? "yarn" : existsSync6(join22(root, "package-lock.json")) ? "npm" : "npx";
|
|
33480
|
+
const existingE2eDirs = E2E_DIR_CANDIDATES.filter((dir) => existsSync6(join22(root, dir)));
|
|
32814
33481
|
return {
|
|
32815
33482
|
root,
|
|
32816
33483
|
name: pkg?.name ?? basename(root),
|
|
@@ -32820,8 +33487,8 @@ async function detectProject(root) {
|
|
|
32820
33487
|
git: await readGitInfo(root),
|
|
32821
33488
|
hasTestsE2e: existingE2eDirs.includes("tests/e2e"),
|
|
32822
33489
|
existingE2eDirs,
|
|
32823
|
-
hasTestpilotConfig:
|
|
32824
|
-
hasProjectLink:
|
|
33490
|
+
hasTestpilotConfig: existsSync6(join22(root, "testpilot.yaml")),
|
|
33491
|
+
hasProjectLink: existsSync6(join22(root, ".testpilot", "project.json"))
|
|
32825
33492
|
};
|
|
32826
33493
|
}
|
|
32827
33494
|
function defaultAdapterFor(type) {
|
|
@@ -32836,9 +33503,9 @@ function adaptersFor(type) {
|
|
|
32836
33503
|
}
|
|
32837
33504
|
|
|
32838
33505
|
// src/commands/init/skill-installer.ts
|
|
32839
|
-
import { existsSync as
|
|
33506
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
|
|
32840
33507
|
import { access as access2, cp, mkdir as mkdir12, writeFile as writeFile10 } from "node:fs/promises";
|
|
32841
|
-
import { dirname as
|
|
33508
|
+
import { dirname as dirname9, join as join23 } from "node:path";
|
|
32842
33509
|
import { fileURLToPath } from "node:url";
|
|
32843
33510
|
var CORE_FILES = ["SKILL.md", "manifest.yaml"];
|
|
32844
33511
|
var CORE_DIRS = ["rules", "workflows"];
|
|
@@ -32852,23 +33519,23 @@ async function exists3(path) {
|
|
|
32852
33519
|
}
|
|
32853
33520
|
function readManifestVersion(dir) {
|
|
32854
33521
|
const manifestPath = join23(dir, "manifest.yaml");
|
|
32855
|
-
if (!
|
|
33522
|
+
if (!existsSync7(manifestPath)) return void 0;
|
|
32856
33523
|
return /^version:\s*([^\s#]+)/m.exec(readFileSync3(manifestPath, "utf8"))?.[1];
|
|
32857
33524
|
}
|
|
32858
33525
|
function resolveSkillSourceDir() {
|
|
32859
33526
|
const fromEnv = process.env.TESTPILOT_SKILL_DIR;
|
|
32860
33527
|
if (fromEnv) return fromEnv;
|
|
32861
33528
|
const bundled = fileURLToPath(new URL("./skills/testpilot", import.meta.url));
|
|
32862
|
-
if (
|
|
33529
|
+
if (existsSync7(bundled)) return bundled;
|
|
32863
33530
|
const inRepo = fileURLToPath(new URL("../../../../../skills/testpilot", import.meta.url));
|
|
32864
|
-
return
|
|
33531
|
+
return existsSync7(inRepo) ? inRepo : void 0;
|
|
32865
33532
|
}
|
|
32866
33533
|
async function copyCoreFiles(src, dest) {
|
|
32867
33534
|
for (const name of CORE_FILES) {
|
|
32868
33535
|
await cp(join23(src, name), join23(dest, name), { recursive: true, force: true });
|
|
32869
33536
|
}
|
|
32870
33537
|
for (const dir of CORE_DIRS) {
|
|
32871
|
-
if (
|
|
33538
|
+
if (existsSync7(join23(src, dir))) {
|
|
32872
33539
|
await cp(join23(src, dir), join23(dest, dir), { recursive: true, force: true });
|
|
32873
33540
|
}
|
|
32874
33541
|
}
|
|
@@ -32883,7 +33550,7 @@ async function syncSkillFiles(root, options = {}) {
|
|
|
32883
33550
|
"\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
33551
|
);
|
|
32885
33552
|
}
|
|
32886
|
-
await mkdir12(
|
|
33553
|
+
await mkdir12(dirname9(dest), { recursive: true });
|
|
32887
33554
|
await cp(src, dest, { recursive: true });
|
|
32888
33555
|
return { core: "created", bundledVersion, dest };
|
|
32889
33556
|
}
|
|
@@ -32957,6 +33624,12 @@ function renderTestConventionsMd() {
|
|
|
32957
33624
|
6. \u4F18\u5148\u590D\u7528\u5DF2\u6709 Case
|
|
32958
33625
|
7. \u65B0\u589E Case \u5FC5\u987B\u901A\u8FC7 csspilot validate
|
|
32959
33626
|
8. Case ID \u5FC5\u987B\u552F\u4E00
|
|
33627
|
+
9. fixture/dataset \u4F7F\u7528 YAML \u6216 JSON,\u7EDF\u4E00\u4F4D\u4E8E tests/e2e/data
|
|
33628
|
+
10. \u9700\u6C42\u89C4\u5219\u77E9\u9635\u4E0E QA \u8986\u76D6\u6620\u5C04\u5199\u5165 tests/e2e/reviews/<\u9700\u6C42>-coverage.md
|
|
33629
|
+
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
|
|
33630
|
+
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
|
|
33631
|
+
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.*}
|
|
33632
|
+
14. setup/teardown \u4F18\u5148 API;teardown \u5931\u8D25\u4F5C\u4E3A warning,\u4E0D\u6539\u53D8\u4E3B\u6D41\u7A0B\u7ED3\u679C
|
|
32960
33633
|
`;
|
|
32961
33634
|
}
|
|
32962
33635
|
function renderAdaptersMd(adapters) {
|
|
@@ -32997,8 +33670,8 @@ async function runDoctor(root) {
|
|
|
32997
33670
|
const projectItems = [
|
|
32998
33671
|
ok(`Project ${info.name}(${info.type})`),
|
|
32999
33672
|
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
|
-
|
|
33673
|
+
existsSync8(join24(root, ".testpilot", "project.json")) ? ok(".testpilot/project.json") : error62(".testpilot/project.json", "\u8FD0\u884C npx csspilot init \u751F\u6210"),
|
|
33674
|
+
existsSync8(join24(root, "testpilot.yaml")) ? ok("testpilot.yaml") : error62("testpilot.yaml", "\u8FD0\u884C npx csspilot init \u751F\u6210")
|
|
33002
33675
|
];
|
|
33003
33676
|
push("Project", projectItems);
|
|
33004
33677
|
const config2 = await loadTestpilotConfig(root).catch(() => void 0);
|
|
@@ -33007,8 +33680,8 @@ async function runDoctor(root) {
|
|
|
33007
33680
|
const invalidCount = cases.filter((item) => !item.valid).length;
|
|
33008
33681
|
const testDir = join24(root, "tests", "e2e");
|
|
33009
33682
|
push("Test", [
|
|
33010
|
-
|
|
33011
|
-
|
|
33683
|
+
existsSync8(testDir) ? ok("tests/e2e") : error62("tests/e2e", "\u8FD0\u884C npx csspilot init \u521B\u5EFA"),
|
|
33684
|
+
existsSync8(join24(root, casesDir)) ? ok(`cases directory(${casesDir})`) : error62(`cases directory(${casesDir})`, "\u8FD0\u884C npx csspilot init \u521B\u5EFA"),
|
|
33012
33685
|
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
33686
|
]);
|
|
33014
33687
|
const skillDir = join24(root, ".agents", "skills", "testpilot");
|
|
@@ -33017,7 +33690,7 @@ async function runDoctor(root) {
|
|
|
33017
33690
|
const bundledSkillVersion = skillSource ? readManifestVersion(skillSource) : void 0;
|
|
33018
33691
|
const skillItems = [
|
|
33019
33692
|
installedSkillVersion ? ok(`TestPilot Skill(.agents/skills/testpilot,v${installedSkillVersion})`) : error62("TestPilot Skill", "\u8FD0\u884C npx csspilot init \u5B89\u88C5"),
|
|
33020
|
-
|
|
33693
|
+
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
33694
|
];
|
|
33022
33695
|
if (installedSkillVersion && bundledSkillVersion && installedSkillVersion !== bundledSkillVersion) {
|
|
33023
33696
|
skillItems.push(
|
|
@@ -33112,7 +33785,7 @@ function makeDoctorCommand() {
|
|
|
33112
33785
|
import { join as join27 } from "node:path";
|
|
33113
33786
|
|
|
33114
33787
|
// src/commands/init/config-initializer.ts
|
|
33115
|
-
import { access as access3, mkdir as mkdir13, readFile as
|
|
33788
|
+
import { access as access3, mkdir as mkdir13, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
|
|
33116
33789
|
import { basename as basename2, join as join25 } from "node:path";
|
|
33117
33790
|
async function exists4(path) {
|
|
33118
33791
|
try {
|
|
@@ -33144,6 +33817,10 @@ environment:
|
|
|
33144
33817
|
staging:
|
|
33145
33818
|
baseUrl: \${STAGING_BASE_URL}
|
|
33146
33819
|
|
|
33820
|
+
# Case \u53EF\u89C1\u7684\u9879\u76EE\u53D8\u91CF\u767D\u540D\u5355(\u503C\u6765\u81EA .env / CI Secret;Case \u7528 \${variable.<name>} \u5F15\u7528)
|
|
33821
|
+
# variables:
|
|
33822
|
+
# ppmProjectId: \${PPM_PROJECT_ID}
|
|
33823
|
+
|
|
33147
33824
|
# \u9879\u76EE\u58F0\u660E\u7684\u6267\u884C\u7AEF(\u5B9E\u9645\u53EF\u7528\u6027\u4EE5 csspilot doctor \u68C0\u6D4B\u4E3A\u51C6)
|
|
33148
33825
|
adapters:
|
|
33149
33826
|
${adapters}
|
|
@@ -33194,7 +33871,7 @@ ${entry}
|
|
|
33194
33871
|
`, "utf8");
|
|
33195
33872
|
return "created";
|
|
33196
33873
|
}
|
|
33197
|
-
const content = await
|
|
33874
|
+
const content = await readFile10(gitignorePath, "utf8");
|
|
33198
33875
|
if (content.split(/\r?\n/).some((item) => item.trim() === entry)) {
|
|
33199
33876
|
return "exists";
|
|
33200
33877
|
}
|
|
@@ -33278,19 +33955,21 @@ function randomId() {
|
|
|
33278
33955
|
}
|
|
33279
33956
|
|
|
33280
33957
|
// src/commands/init/test-structure.ts
|
|
33281
|
-
import { existsSync as
|
|
33958
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2 } from "node:fs";
|
|
33282
33959
|
import { mkdir as mkdir14, writeFile as writeFile12 } from "node:fs/promises";
|
|
33283
33960
|
import { join as join26 } from "node:path";
|
|
33284
33961
|
async function initTestStructure(root, _info) {
|
|
33285
33962
|
const dirs = {
|
|
33286
33963
|
cases: join26(root, "tests", "e2e", "cases"),
|
|
33287
33964
|
fixtures: join26(root, "tests", "e2e", "fixtures"),
|
|
33288
|
-
data: join26(root, "tests", "e2e", "data")
|
|
33965
|
+
data: join26(root, "tests", "e2e", "data"),
|
|
33966
|
+
reviews: join26(root, "tests", "e2e", "reviews")
|
|
33289
33967
|
};
|
|
33290
33968
|
const existed = {
|
|
33291
|
-
cases:
|
|
33292
|
-
fixtures:
|
|
33293
|
-
data:
|
|
33969
|
+
cases: existsSync9(dirs.cases),
|
|
33970
|
+
fixtures: existsSync9(dirs.fixtures),
|
|
33971
|
+
data: existsSync9(dirs.data),
|
|
33972
|
+
reviews: existsSync9(dirs.reviews)
|
|
33294
33973
|
};
|
|
33295
33974
|
for (const dir of Object.values(dirs)) {
|
|
33296
33975
|
await mkdir14(dir, { recursive: true });
|
|
@@ -33303,7 +33982,8 @@ async function initTestStructure(root, _info) {
|
|
|
33303
33982
|
return {
|
|
33304
33983
|
cases: existed.cases ? "reused" : "created",
|
|
33305
33984
|
fixtures: existed.fixtures ? "reused" : "created",
|
|
33306
|
-
data: existed.data ? "reused" : "created"
|
|
33985
|
+
data: existed.data ? "reused" : "created",
|
|
33986
|
+
reviews: existed.reviews ? "reused" : "created"
|
|
33307
33987
|
};
|
|
33308
33988
|
}
|
|
33309
33989
|
|
|
@@ -33343,7 +34023,7 @@ async function runInit(root, options = {}) {
|
|
|
33343
34023
|
const testStructure = await initTestStructure(root, project);
|
|
33344
34024
|
const reuseNote = project.existingE2eDirs.filter((dir) => dir !== "tests/e2e");
|
|
33345
34025
|
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}"
|
|
34026
|
+
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
34027
|
);
|
|
33348
34028
|
if (reuseNote.length > 0) {
|
|
33349
34029
|
console.log(` \u26A0 \u68C0\u6D4B\u5230\u5176\u4ED6 E2E \u76EE\u5F55:${reuseNote.join(", ")}(\u4FDD\u7559\u4E0D\u52A8,\u672A\u5F3A\u5236\u8FC1\u79FB)`);
|
|
@@ -33465,6 +34145,11 @@ function openInBrowser(file2) {
|
|
|
33465
34145
|
|
|
33466
34146
|
// src/commands/run.ts
|
|
33467
34147
|
import { join as join28 } from "node:path";
|
|
34148
|
+
function exitCodeForSummary(status) {
|
|
34149
|
+
if (status === "failed" || status === "inconclusive") return 1;
|
|
34150
|
+
if (status === "blocked") return 2;
|
|
34151
|
+
return 0;
|
|
34152
|
+
}
|
|
33468
34153
|
function makeRunCommand() {
|
|
33469
34154
|
return new Command("run").description("\u6267\u884C\u6D4B\u8BD5\u7528\u4F8B(\u9ED8\u8BA4\u8FD0\u884C testpilot.yaml \u914D\u7F6E\u7684 Case)").argument("[paths...]", "\u7528\u4F8B\u6587\u4EF6\u6216\u76EE\u5F55,\u9ED8\u8BA4\u6309\u9879\u76EE\u914D\u7F6E").option("--tag <tag>", "\u6309\u6807\u7B7E\u8FC7\u6EE4\u7528\u4F8B").action(async (paths, options) => {
|
|
33470
34155
|
const client = new TestPilotClient();
|
|
@@ -33486,11 +34171,11 @@ function makeRunCommand() {
|
|
|
33486
34171
|
].filter(Boolean).join(",");
|
|
33487
34172
|
console.log("");
|
|
33488
34173
|
console.log(
|
|
33489
|
-
`Run ${summary.runId}${summary.cancelled ? "(\u5DF2\u53D6\u6D88)" : ""}: ${summary.status.toUpperCase()} \u2014 \
|
|
34174
|
+
`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} \u2298${summary.totals.blocked ?? 0} ?${summary.totals.inconclusive ?? 0} \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
34175
|
);
|
|
33491
34176
|
console.log(`\u4EA7\u7269\u76EE\u5F55:${join28(".testpilot", "artifacts", "runs", summary.runId)}`);
|
|
33492
34177
|
console.log("\u67E5\u770B\u62A5\u544A:npx csspilot report");
|
|
33493
|
-
|
|
34178
|
+
process.exitCode = exitCodeForSummary(summary.status);
|
|
33494
34179
|
} catch (err) {
|
|
33495
34180
|
console.error(err instanceof Error ? err.message : err);
|
|
33496
34181
|
process.exitCode = 1;
|
|
@@ -33513,12 +34198,12 @@ function consoleEventHandler(event) {
|
|
|
33513
34198
|
`);
|
|
33514
34199
|
break;
|
|
33515
34200
|
case "case-started":
|
|
33516
|
-
console.log(`\u25B6 ${event.caseId} (${event.file})`);
|
|
34201
|
+
console.log(`\u25B6 ${event.caseId}#${event.rowId ?? "default"} (${event.file})`);
|
|
33517
34202
|
break;
|
|
33518
34203
|
case "step-finished": {
|
|
33519
|
-
const mark = event.status === "passed" ? " \u2713" : event.status === "failed" ? " \u2717" : " \u21B7";
|
|
34204
|
+
const mark = event.status === "passed" ? " \u2713" : event.status === "failed" ? " \u2717" : event.status === "warning" ? " \u26A0" : " \u21B7";
|
|
33520
34205
|
console.log(
|
|
33521
|
-
`${mark} ${event.target}/${event.action} ${event.durationMs}ms${event.error ? ` \u2014 ${event.error}` : ""}`
|
|
34206
|
+
`${mark} ${event.phase ?? "steps"} ${event.target}/${event.action} ${event.durationMs}ms${event.error ? ` \u2014 ${event.error}` : ""}`
|
|
33522
34207
|
);
|
|
33523
34208
|
break;
|
|
33524
34209
|
}
|
|
@@ -33528,7 +34213,7 @@ function consoleEventHandler(event) {
|
|
|
33528
34213
|
}
|
|
33529
34214
|
|
|
33530
34215
|
// src/commands/sync-metadata.ts
|
|
33531
|
-
import { relative } from "node:path";
|
|
34216
|
+
import { relative as relative2 } from "node:path";
|
|
33532
34217
|
function makeSyncMetadataCommand() {
|
|
33533
34218
|
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
34219
|
const client = new TestPilotClient();
|
|
@@ -33555,7 +34240,7 @@ function makeSyncMetadataCommand() {
|
|
|
33555
34240
|
const cases = infos.filter((item) => item.valid && item.case).map((item) => ({
|
|
33556
34241
|
id: item.case.id,
|
|
33557
34242
|
title: item.case.name,
|
|
33558
|
-
file:
|
|
34243
|
+
file: relative2(process.cwd(), item.file).split("\\").join("/"),
|
|
33559
34244
|
tags: item.case.tags ?? []
|
|
33560
34245
|
}));
|
|
33561
34246
|
const skipped = infos.length - cases.length;
|
|
@@ -33665,7 +34350,7 @@ function makeValidateCommand() {
|
|
|
33665
34350
|
}
|
|
33666
34351
|
|
|
33667
34352
|
// src/cli.ts
|
|
33668
|
-
var CLI_VERSION = "0.
|
|
34353
|
+
var CLI_VERSION = "0.7.0";
|
|
33669
34354
|
async function createCli() {
|
|
33670
34355
|
const program2 = new Command();
|
|
33671
34356
|
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", () => {
|