csspilot 0.6.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/dist/bin.js +235 -74
- package/dist/index.js +235 -74
- package/dist/skills/testpilot/SKILL.md +84 -82
- package/dist/skills/testpilot/rules/case-design.md +47 -41
- package/dist/skills/testpilot/workflows/create-case.md +33 -20
- package/dist/skills/testpilot/workflows/test-requirements-review.md +143 -103
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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":
|
|
@@ -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();
|
|
@@ -10327,9 +10327,9 @@ var require_dist = __commonJS({
|
|
|
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;
|
|
@@ -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";
|
|
@@ -30716,6 +30738,9 @@ async function loadCaseDataFile(reference, options) {
|
|
|
30716
30738
|
const [realDataDir, realTarget] = await Promise.all([
|
|
30717
30739
|
realpath(dataDir).catch(() => dataDir),
|
|
30718
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
|
+
}
|
|
30719
30744
|
throw new Error(
|
|
30720
30745
|
`\u8BFB\u53D6\u6570\u636E\u6587\u4EF6\u5931\u8D25:${reference}:${error62 instanceof Error ? error62.message : String(error62)}`
|
|
30721
30746
|
);
|
|
@@ -30943,6 +30968,9 @@ async function validateCaseDataSource(source, file2, root) {
|
|
|
30943
30968
|
);
|
|
30944
30969
|
return validation;
|
|
30945
30970
|
} catch (error62) {
|
|
30971
|
+
if (error62 instanceof TestPilotError && error62.code === "FIXTURE_MISSING") {
|
|
30972
|
+
return validation;
|
|
30973
|
+
}
|
|
30946
30974
|
return {
|
|
30947
30975
|
ok: false,
|
|
30948
30976
|
data: validation.data,
|
|
@@ -30991,6 +31019,24 @@ function classifyFailure(message) {
|
|
|
30991
31019
|
}
|
|
30992
31020
|
return "other";
|
|
30993
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
|
+
}
|
|
30994
31040
|
|
|
30995
31041
|
// ../../packages/reporter/src/history.ts
|
|
30996
31042
|
import { readFile as readFile5, readdir as readdir2 } from "node:fs/promises";
|
|
@@ -31048,7 +31094,7 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
31048
31094
|
const cases = summary.cases.map((item) => {
|
|
31049
31095
|
const categories = /* @__PURE__ */ new Set();
|
|
31050
31096
|
const steps = item.steps.map((step) => {
|
|
31051
|
-
const category = step.status === "failed" ? classifyFailure(step.error) : void 0;
|
|
31097
|
+
const category = classifyFromCode(step.errorCode) ?? (step.status === "failed" ? classifyFailure(step.error) : void 0);
|
|
31052
31098
|
if (category) {
|
|
31053
31099
|
categories.add(category);
|
|
31054
31100
|
failures.push({
|
|
@@ -31056,7 +31102,8 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
31056
31102
|
label: categoryLabel(category),
|
|
31057
31103
|
caseId: item.caseId,
|
|
31058
31104
|
index: step.index,
|
|
31059
|
-
message: step.error ?? ""
|
|
31105
|
+
message: step.error ?? "",
|
|
31106
|
+
verdict: verdictFromErrorCode(step.errorCode ?? item.errorCode)
|
|
31060
31107
|
});
|
|
31061
31108
|
}
|
|
31062
31109
|
return { ...step, category };
|
|
@@ -31079,6 +31126,20 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
31079
31126
|
await writeFile4(htmlPath, renderHtml(data), "utf8");
|
|
31080
31127
|
return { json: jsonPath, html: htmlPath };
|
|
31081
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
|
+
}
|
|
31082
31143
|
function categoryChips(data) {
|
|
31083
31144
|
const counts = /* @__PURE__ */ new Map();
|
|
31084
31145
|
for (const failure2 of data.failures) {
|
|
@@ -31104,19 +31165,20 @@ function renderHtml(data) {
|
|
|
31104
31165
|
const trendSvg = renderTrend(data);
|
|
31105
31166
|
const casesHtml = groupedCasesHtml(summary.cases);
|
|
31106
31167
|
const chips = categoryChips(data);
|
|
31168
|
+
const verdictHtml = verdictGroupsHtml(data);
|
|
31107
31169
|
return `<!doctype html>
|
|
31108
31170
|
<html lang="zh-CN">
|
|
31109
31171
|
<head>
|
|
31110
31172
|
<meta charset="utf-8" />
|
|
31111
31173
|
<title>TestPilot Report ${escapeHtml(summary.runId)}</title>
|
|
31112
31174
|
<style>
|
|
31113
|
-
:root { --ok:#1a7f37; --fail:#cf222e; --warn:#9a6700; --skip:#6e7781; --line:#d0d7de; --muted:#59636e; }
|
|
31175
|
+
:root { --ok:#1a7f37; --fail:#cf222e; --warn:#9a6700; --skip:#6e7781; --blocked:#8250df; --inconclusive:#9a6700; --line:#d0d7de; --muted:#59636e; }
|
|
31114
31176
|
* { box-sizing: border-box; }
|
|
31115
31177
|
body { font-family: system-ui, "Segoe UI", sans-serif; margin: 0; color: #1f2328; background: #f6f8fa; }
|
|
31116
31178
|
.wrap { max-width: 1080px; margin: 0 auto; padding: 20px 16px 48px; }
|
|
31117
31179
|
h1 { font-size: 20px; margin: 8px 0; } h2 { font-size: 15px; margin: 24px 0 8px; }
|
|
31118
31180
|
.badge { display: inline-block; padding: 2px 10px; border-radius: 10px; color: #fff; font-size: 12px; }
|
|
31119
|
-
.passed { background: var(--ok); } .failed { background: var(--fail); } .warning { background: var(--warn); } .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); }
|
|
31120
31182
|
code { background: #eef1f4; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
|
|
31121
31183
|
.cards { display: flex; gap: 12px; flex-wrap: wrap; margin: 14px 0; }
|
|
31122
31184
|
.card { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 10px 16px; min-width: 130px; }
|
|
@@ -31143,6 +31205,11 @@ function renderHtml(data) {
|
|
|
31143
31205
|
.muted { color: var(--muted); }
|
|
31144
31206
|
.hide { display: none !important; }
|
|
31145
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; }
|
|
31146
31213
|
</style>
|
|
31147
31214
|
</head>
|
|
31148
31215
|
<body>
|
|
@@ -31163,6 +31230,9 @@ function renderHtml(data) {
|
|
|
31163
31230
|
<h2>\u5386\u53F2\u8D8B\u52BF(\u6700\u8FD1 ${data.history.length + 1} \u6B21\u8FD0\u884C)</h2>
|
|
31164
31231
|
<div class="trend">${trendSvg}</div>
|
|
31165
31232
|
|
|
31233
|
+
<h2>\u5931\u8D25\u4E09\u6876</h2>
|
|
31234
|
+
<div class="verdicts">${verdictHtml}</div>
|
|
31235
|
+
|
|
31166
31236
|
<h2>\u5931\u8D25\u5206\u7C7B</h2>
|
|
31167
31237
|
<div class="toolbar" id="cat-chips">${chips}</div>
|
|
31168
31238
|
|
|
@@ -31371,7 +31441,7 @@ function buildSummaryData(runs) {
|
|
|
31371
31441
|
for (const item of summary.cases) {
|
|
31372
31442
|
const platforms = [...new Set(item.steps.map((step) => step.target))];
|
|
31373
31443
|
const failedStep = item.steps.find((step) => step.status === "failed");
|
|
31374
|
-
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);
|
|
31375
31445
|
executions.push({
|
|
31376
31446
|
runId,
|
|
31377
31447
|
caseId: item.caseId,
|
|
@@ -31434,6 +31504,10 @@ function buildSummaryData(runs) {
|
|
|
31434
31504
|
})).filter((item) => item.count > 0);
|
|
31435
31505
|
const durations = runs.map((item) => item.summary.durationMs).filter((value) => typeof value === "number");
|
|
31436
31506
|
return {
|
|
31507
|
+
buckets: {
|
|
31508
|
+
blocked: executions.filter((exec2) => exec2.status === "blocked").length,
|
|
31509
|
+
inconclusive: executions.filter((exec2) => exec2.status === "inconclusive").length
|
|
31510
|
+
},
|
|
31437
31511
|
window: {
|
|
31438
31512
|
runs: runs.length,
|
|
31439
31513
|
from: runPoints[0]?.startedAt,
|
|
@@ -31518,7 +31592,7 @@ function renderSummaryHtml(data) {
|
|
|
31518
31592
|
th.num, td.num { text-align: right; }
|
|
31519
31593
|
code { background: #eef1f4; padding: 1px 5px; border-radius: 4px; font-size: 12px; cursor: pointer; }
|
|
31520
31594
|
.badge { display: inline-block; padding: 1px 9px; border-radius: 10px; color: #fff; font-size: 11px; }
|
|
31521
|
-
.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; }
|
|
31522
31596
|
.toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 10px 0; }
|
|
31523
31597
|
.chip { border: 1px solid var(--line); background: #fff; border-radius: 14px; padding: 3px 12px; font-size: 12px; cursor: pointer; }
|
|
31524
31598
|
.chip.active { background: #0969da; color: #fff; border-color: #0969da; }
|
|
@@ -31544,6 +31618,8 @@ function renderSummaryHtml(data) {
|
|
|
31544
31618
|
<div class="card"><b>${rateText}</b><span>\u6574\u4F53\u901A\u8FC7\u7387</span></div>
|
|
31545
31619
|
<div class="card"><b>${(kpis.avgDurationMs / 1e3).toFixed(1)}s</b><span>\u5E73\u5747\u8FD0\u884C\u8017\u65F6</span></div>
|
|
31546
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>` : ""}
|
|
31547
31623
|
</div>
|
|
31548
31624
|
|
|
31549
31625
|
<h2>\u8D8B\u52BF(\u6BCF\u6B21\u8FD0\u884C\u7ED3\u679C\u4E0E\u901A\u8FC7\u7387,\u60AC\u6D6E\u770B\u8BE6\u60C5)</h2>
|
|
@@ -31752,14 +31828,23 @@ var ExecutionContext = class {
|
|
|
31752
31828
|
}
|
|
31753
31829
|
}
|
|
31754
31830
|
/**
|
|
31755
|
-
* 将字符串中的 ${name}
|
|
31756
|
-
*
|
|
31831
|
+
* 将字符串中的 ${name} 替换为已提取的变量值;未定义的引用抛 TestPilotError——
|
|
31832
|
+
* 校验阶段已静态拦截"从未声明"的引用,运行时未命中即数据未兑现
|
|
31833
|
+
* (前置 extract 未执行或 fixture/dataset/凭据缺失),按三态归 unverified/account 桶。
|
|
31834
|
+
* 凭据桶仅限 `account` 整值或 `account.*` 字段;用户变量撞 account 前缀(如 accountId)
|
|
31835
|
+
* 不误归凭据桶。名字允许点号,用于引擎注入的 account.username 一类凭据变量
|
|
31836
|
+
* (用户 extract 变量名不含点号)。
|
|
31757
31837
|
*/
|
|
31758
31838
|
resolve(template) {
|
|
31759
|
-
return template.replace(
|
|
31760
|
-
|
|
31761
|
-
(
|
|
31762
|
-
|
|
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
|
+
});
|
|
31763
31848
|
}
|
|
31764
31849
|
/**
|
|
31765
31850
|
* 注入 accountRef 凭据:值为 JSON 对象时按 `${account.<字段>}` 展开为字符串,
|
|
@@ -31918,7 +32003,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31918
32003
|
} : void 0;
|
|
31919
32004
|
if (step.expectRequests) {
|
|
31920
32005
|
if (!adapter.startRequestCapture || !adapter.stopRequestCapture) {
|
|
31921
|
-
throw new
|
|
32006
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301\u8BF7\u6C42\u6B21\u6570\u65AD\u8A00`);
|
|
31922
32007
|
}
|
|
31923
32008
|
await adapter.startRequestCapture();
|
|
31924
32009
|
captureStarted = true;
|
|
@@ -31944,7 +32029,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31944
32029
|
break;
|
|
31945
32030
|
case "assert":
|
|
31946
32031
|
if (step.target === "api") {
|
|
31947
|
-
if (!adapter.assertResponse) throw new
|
|
32032
|
+
if (!adapter.assertResponse) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api \u65AD\u8A00");
|
|
31948
32033
|
await adapter.assertResponse(context.resolve(step.expected ?? ""));
|
|
31949
32034
|
} else {
|
|
31950
32035
|
await adapter.assert(locator, context.resolve(step.expected ?? ""));
|
|
@@ -31964,7 +32049,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31964
32049
|
break;
|
|
31965
32050
|
}
|
|
31966
32051
|
case "request": {
|
|
31967
|
-
if (!adapter.request) throw new
|
|
32052
|
+
if (!adapter.request) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api request");
|
|
31968
32053
|
const method = step.method ?? "GET";
|
|
31969
32054
|
const url2 = context.resolve(step.url ?? "");
|
|
31970
32055
|
const headers = resolveHeaders(step.headers, context);
|
|
@@ -31981,7 +32066,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31981
32066
|
if (step.expected !== void 0) {
|
|
31982
32067
|
const expectedStatus = context.resolve(step.expected);
|
|
31983
32068
|
if (String(response.status) !== expectedStatus) {
|
|
31984
|
-
throw new
|
|
32069
|
+
throw new TestPilotError(
|
|
32070
|
+
"STATUS_MISMATCH",
|
|
31985
32071
|
`HTTP \u72B6\u6001\u7801 ${response.status} ${response.statusText},\u671F\u671B ${expectedStatus}:${snippet(response.bodyText, 200)}`
|
|
31986
32072
|
);
|
|
31987
32073
|
}
|
|
@@ -31990,7 +32076,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31990
32076
|
}
|
|
31991
32077
|
default: {
|
|
31992
32078
|
const exhaustive = step.action;
|
|
31993
|
-
throw new
|
|
32079
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u4E0D\u652F\u6301\u7684 action:${String(exhaustive)}`);
|
|
31994
32080
|
}
|
|
31995
32081
|
}
|
|
31996
32082
|
if (step.expectRequests) {
|
|
@@ -32001,7 +32087,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32001
32087
|
result.requestAssertions = buildRequestAssertions(step, requests, context);
|
|
32002
32088
|
const mismatches = result.requestAssertions.filter((item) => item.actualCount !== item.count);
|
|
32003
32089
|
if (mismatches.length > 0) {
|
|
32004
|
-
throw new
|
|
32090
|
+
throw new TestPilotError(
|
|
32091
|
+
"REQUEST_COUNT_MISMATCH",
|
|
32005
32092
|
mismatches.map(
|
|
32006
32093
|
(item) => `\u8BF7\u6C42\u6B21\u6570\u4E0D\u5339\u914D:${item.method ?? "*"} ${item.urlContains},\u5B9E\u9645 ${item.actualCount},\u671F\u671B ${item.count}`
|
|
32007
32094
|
).join("; ")
|
|
@@ -32018,6 +32105,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32018
32105
|
}
|
|
32019
32106
|
result.status = "failed";
|
|
32020
32107
|
result.error = err instanceof Error ? err.message : String(err);
|
|
32108
|
+
result.errorCode = err instanceof TestPilotError ? err.code : void 0;
|
|
32021
32109
|
try {
|
|
32022
32110
|
const data = await adapter.screenshot();
|
|
32023
32111
|
result.screenshot = await artifacts.saveStepScreenshot(`${options.screenshotName}-failed.png`, data);
|
|
@@ -32028,8 +32116,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32028
32116
|
return result;
|
|
32029
32117
|
}
|
|
32030
32118
|
async function extractApiResponse(adapter, path) {
|
|
32031
|
-
if (!adapter.extractResponse) throw new
|
|
32032
|
-
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)");
|
|
32033
32121
|
return adapter.extractResponse(path);
|
|
32034
32122
|
}
|
|
32035
32123
|
function resolveHeaders(headers, context) {
|
|
@@ -32103,6 +32191,7 @@ var ExecutionEngine = class {
|
|
|
32103
32191
|
const warnings = [];
|
|
32104
32192
|
const evidenceAdapters = /* @__PURE__ */ new Map();
|
|
32105
32193
|
let caseError;
|
|
32194
|
+
let caseErrorCode;
|
|
32106
32195
|
const executionId = safeArtifactName(`${data.id}-${rowId}`);
|
|
32107
32196
|
await bus.emit({
|
|
32108
32197
|
type: "case-started",
|
|
@@ -32180,7 +32269,8 @@ var ExecutionEngine = class {
|
|
|
32180
32269
|
action: step.action,
|
|
32181
32270
|
status: "failed",
|
|
32182
32271
|
durationMs: 0,
|
|
32183
|
-
error: err instanceof Error ? err.message : String(err)
|
|
32272
|
+
error: err instanceof Error ? err.message : String(err),
|
|
32273
|
+
errorCode: err instanceof TestPilotError ? err.code : void 0
|
|
32184
32274
|
};
|
|
32185
32275
|
}
|
|
32186
32276
|
result.phase = phase;
|
|
@@ -32209,6 +32299,7 @@ var ExecutionEngine = class {
|
|
|
32209
32299
|
if (result.status === "failed") {
|
|
32210
32300
|
passed = false;
|
|
32211
32301
|
caseError = result.error;
|
|
32302
|
+
caseErrorCode = result.errorCode;
|
|
32212
32303
|
for (const [skipIndex, skippedStep] of phaseSteps.entries()) {
|
|
32213
32304
|
if (skipIndex <= index) continue;
|
|
32214
32305
|
steps.push({
|
|
@@ -32225,24 +32316,42 @@ var ExecutionEngine = class {
|
|
|
32225
32316
|
}
|
|
32226
32317
|
return passed;
|
|
32227
32318
|
};
|
|
32228
|
-
|
|
32229
|
-
|
|
32230
|
-
|
|
32231
|
-
} else {
|
|
32232
|
-
for (const [index, step] of data.steps.entries()) {
|
|
32319
|
+
if (options.blockedError) {
|
|
32320
|
+
const first = data.setup?.[0] ?? data.steps[0];
|
|
32321
|
+
if (first) {
|
|
32233
32322
|
steps.push({
|
|
32234
|
-
index,
|
|
32235
|
-
phase: "
|
|
32236
|
-
target:
|
|
32237
|
-
action:
|
|
32238
|
-
status: "
|
|
32239
|
-
durationMs: 0
|
|
32323
|
+
index: 0,
|
|
32324
|
+
phase: "setup",
|
|
32325
|
+
target: first.target,
|
|
32326
|
+
action: first.action,
|
|
32327
|
+
status: "failed",
|
|
32328
|
+
durationMs: 0,
|
|
32329
|
+
error: options.blockedError.message,
|
|
32330
|
+
errorCode: options.blockedError.code
|
|
32240
32331
|
});
|
|
32241
32332
|
}
|
|
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()) {
|
|
32341
|
+
steps.push({
|
|
32342
|
+
index,
|
|
32343
|
+
phase: "steps",
|
|
32344
|
+
target: step.target,
|
|
32345
|
+
action: step.action,
|
|
32346
|
+
status: "skipped",
|
|
32347
|
+
durationMs: 0
|
|
32348
|
+
});
|
|
32349
|
+
}
|
|
32350
|
+
}
|
|
32351
|
+
await runPhase("teardown", data.teardown ?? []);
|
|
32242
32352
|
}
|
|
32243
|
-
await runPhase("teardown", data.teardown ?? []);
|
|
32244
32353
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
32245
|
-
const status = caseError ?
|
|
32354
|
+
const status = caseError ? caseStatusFromErrorCode(caseErrorCode) : "passed";
|
|
32246
32355
|
const caseResult = {
|
|
32247
32356
|
caseId: data.id,
|
|
32248
32357
|
caseName: data.name,
|
|
@@ -32256,6 +32365,7 @@ var ExecutionEngine = class {
|
|
|
32256
32365
|
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
32257
32366
|
};
|
|
32258
32367
|
if (caseError) caseResult.error = caseError;
|
|
32368
|
+
if (caseErrorCode) caseResult.errorCode = caseErrorCode;
|
|
32259
32369
|
if (warnings.length > 0) caseResult.warnings = warnings;
|
|
32260
32370
|
for (const [target, adapter] of evidenceAdapters) {
|
|
32261
32371
|
if (!adapter.stopEvidence) continue;
|
|
@@ -32299,6 +32409,8 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
32299
32409
|
passed: cases.filter((item) => item.status === "passed").length,
|
|
32300
32410
|
failed: cases.filter((item) => item.status === "failed").length,
|
|
32301
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 } : {},
|
|
32302
32414
|
warnings: 0,
|
|
32303
32415
|
steps: 0,
|
|
32304
32416
|
stepsPassed: 0,
|
|
@@ -32323,7 +32435,8 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
32323
32435
|
startedAt: startedAt.toISOString(),
|
|
32324
32436
|
finishedAt: finishedAt.toISOString(),
|
|
32325
32437
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
32326
|
-
|
|
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",
|
|
32327
32440
|
...options.cancelled ? { cancelled: true } : {},
|
|
32328
32441
|
cases,
|
|
32329
32442
|
totals
|
|
@@ -32375,6 +32488,7 @@ var TestRunner = class {
|
|
|
32375
32488
|
rowIndex: item.rowIndex ?? 0,
|
|
32376
32489
|
initialVariables: item.initialVariables ?? {},
|
|
32377
32490
|
missingDependencies: item.missingDependencies ?? [],
|
|
32491
|
+
blockedError: item.blockedError,
|
|
32378
32492
|
artifacts,
|
|
32379
32493
|
bus,
|
|
32380
32494
|
resolver: this.resolver,
|
|
@@ -32431,6 +32545,7 @@ import { existsSync as existsSync3 } from "node:fs";
|
|
|
32431
32545
|
import { mkdtemp, readFile as readFile6, rm } from "node:fs/promises";
|
|
32432
32546
|
import { tmpdir } from "node:os";
|
|
32433
32547
|
import { join as join16 } from "node:path";
|
|
32548
|
+
import { errors } from "playwright";
|
|
32434
32549
|
var PlaywrightAdapter = class {
|
|
32435
32550
|
constructor(bundle, options = {}) {
|
|
32436
32551
|
this.bundle = bundle;
|
|
@@ -32487,36 +32602,50 @@ var PlaywrightAdapter = class {
|
|
|
32487
32602
|
const options = this.options;
|
|
32488
32603
|
const page = await this.ensurePage();
|
|
32489
32604
|
const target = options.baseUrl && !/^https?:\/\//i.test(url2) ? new URL(url2, options.baseUrl).toString() : url2;
|
|
32490
|
-
|
|
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
|
+
}
|
|
32491
32616
|
}
|
|
32492
32617
|
async click(locator) {
|
|
32493
|
-
|
|
32618
|
+
const target = await this.toLocator(locator);
|
|
32619
|
+
await this.coded(() => target.click());
|
|
32494
32620
|
}
|
|
32495
32621
|
async input(locator, value) {
|
|
32496
|
-
|
|
32622
|
+
const target = await this.toLocator(locator);
|
|
32623
|
+
await this.coded(() => target.fill(value));
|
|
32497
32624
|
}
|
|
32498
32625
|
async select(locator, value) {
|
|
32499
|
-
|
|
32626
|
+
const target = await this.toLocator(locator);
|
|
32627
|
+
await this.coded(() => target.selectOption(value));
|
|
32500
32628
|
}
|
|
32501
32629
|
async wait(locator, timeoutMs) {
|
|
32502
32630
|
const page = await this.ensurePage();
|
|
32503
32631
|
if (locator) {
|
|
32504
|
-
await this.toLocatorOn(page, locator).waitFor({ state: "visible", timeout: timeoutMs });
|
|
32632
|
+
await this.coded(() => this.toLocatorOn(page, locator).waitFor({ state: "visible", timeout: timeoutMs }));
|
|
32505
32633
|
return;
|
|
32506
32634
|
}
|
|
32507
32635
|
await page.waitForTimeout(timeoutMs ?? 1e3);
|
|
32508
32636
|
}
|
|
32509
32637
|
async assert(locator, expected) {
|
|
32510
32638
|
const target = await this.toLocator(locator);
|
|
32511
|
-
await target.waitFor({ state: "visible" });
|
|
32512
|
-
const actual = (await target.innerText()).trim();
|
|
32639
|
+
await this.coded(() => target.waitFor({ state: "visible" }));
|
|
32640
|
+
const actual = (await this.coded(() => target.innerText())).trim();
|
|
32513
32641
|
const wanted = expected.trim();
|
|
32514
32642
|
if (!actual.includes(wanted)) {
|
|
32515
|
-
throw new
|
|
32643
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u65AD\u8A00\u5931\u8D25:\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`);
|
|
32516
32644
|
}
|
|
32517
32645
|
}
|
|
32518
32646
|
async extract(locator) {
|
|
32519
|
-
|
|
32647
|
+
const target = await this.toLocator(locator);
|
|
32648
|
+
return this.coded(() => target.innerText()).then((text) => text.trim());
|
|
32520
32649
|
}
|
|
32521
32650
|
async screenshot() {
|
|
32522
32651
|
return (await this.ensurePage()).screenshot();
|
|
@@ -32534,6 +32663,17 @@ var PlaywrightAdapter = class {
|
|
|
32534
32663
|
async toLocator(locator) {
|
|
32535
32664
|
return this.toLocatorOn(await this.ensurePage(), locator);
|
|
32536
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
|
+
}
|
|
32537
32677
|
toLocatorOn(page, locator) {
|
|
32538
32678
|
if (locator.text !== void 0) {
|
|
32539
32679
|
return page.getByText(locator.text, { exact: true });
|
|
@@ -32611,12 +32751,12 @@ function startCliAuto(cliPath, projectPath, port) {
|
|
|
32611
32751
|
child.stderr?.on("data", (chunk) => {
|
|
32612
32752
|
output2 += chunk.toString("utf8");
|
|
32613
32753
|
});
|
|
32614
|
-
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}`)));
|
|
32615
32755
|
child.once("exit", (code) => {
|
|
32616
32756
|
if (code === 0) {
|
|
32617
32757
|
resolvePromise();
|
|
32618
32758
|
} else {
|
|
32619
|
-
rejectPromise(new
|
|
32759
|
+
rejectPromise(new TestPilotError("ENVIRONMENT_UNAVAILABLE", `cli auto \u9000\u51FA\u7801 ${code}:${output2.slice(-400)}`));
|
|
32620
32760
|
}
|
|
32621
32761
|
});
|
|
32622
32762
|
const startedAt = Date.now();
|
|
@@ -32629,7 +32769,7 @@ function startCliAuto(cliPath, projectPath, port) {
|
|
|
32629
32769
|
socket.once("error", () => {
|
|
32630
32770
|
socket.destroy();
|
|
32631
32771
|
if (Date.now() - startedAt > AUTO_PORT_FALLBACK_WAIT_MS) {
|
|
32632
|
-
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`));
|
|
32633
32773
|
} else {
|
|
32634
32774
|
setTimeout(poll, 500);
|
|
32635
32775
|
}
|
|
@@ -32678,7 +32818,7 @@ var MiniAppAdapter = class {
|
|
|
32678
32818
|
await this.runWithReconnect(async () => (await this.element(locator)).input(value));
|
|
32679
32819
|
}
|
|
32680
32820
|
async select() {
|
|
32681
|
-
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");
|
|
32682
32822
|
}
|
|
32683
32823
|
async wait(locator, timeoutMs) {
|
|
32684
32824
|
await this.runWithReconnect(async () => {
|
|
@@ -32696,7 +32836,7 @@ var MiniAppAdapter = class {
|
|
|
32696
32836
|
const actual = (await element.text()).trim();
|
|
32697
32837
|
const wanted = expected.trim();
|
|
32698
32838
|
if (!actual.includes(wanted)) {
|
|
32699
|
-
throw new
|
|
32839
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u65AD\u8A00\u5931\u8D25:\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`);
|
|
32700
32840
|
}
|
|
32701
32841
|
});
|
|
32702
32842
|
}
|
|
@@ -32794,7 +32934,7 @@ var MiniAppAdapter = class {
|
|
|
32794
32934
|
const element = await page.$(selector);
|
|
32795
32935
|
if (element) return element;
|
|
32796
32936
|
if (Date.now() - startedAt > ELEMENT_WAIT_MS) {
|
|
32797
|
-
throw new
|
|
32937
|
+
throw new TestPilotError("ELEMENT_NOT_FOUND", `\u7B49\u5F85\u5143\u7D20\u8D85\u65F6(${ELEMENT_WAIT_MS}ms):${selector}`);
|
|
32798
32938
|
}
|
|
32799
32939
|
await delay2(500);
|
|
32800
32940
|
}
|
|
@@ -32872,7 +33012,7 @@ var ApiAdapter = class {
|
|
|
32872
33012
|
signal: AbortSignal.timeout(this.options.timeoutMs ?? 15e3)
|
|
32873
33013
|
});
|
|
32874
33014
|
} catch (err) {
|
|
32875
|
-
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 });
|
|
32876
33016
|
}
|
|
32877
33017
|
const bodyText = await res.text();
|
|
32878
33018
|
let json2;
|
|
@@ -32894,7 +33034,7 @@ var ApiAdapter = class {
|
|
|
32894
33034
|
if (!this.last) throw new Error("\u5C1A\u672A\u53D1\u8D77\u4EFB\u4F55 API \u8BF7\u6C42,\u65E0\u6CD5\u65AD\u8A00\u54CD\u5E94");
|
|
32895
33035
|
if (!this.last.bodyText.includes(expected)) {
|
|
32896
33036
|
const snippet2 = this.last.bodyText.slice(0, 200);
|
|
32897
|
-
throw new
|
|
33037
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u54CD\u5E94\u4F53\u4E0D\u5305\u542B "${expected}":HTTP ${this.last.status} ${snippet2}`);
|
|
32898
33038
|
}
|
|
32899
33039
|
}
|
|
32900
33040
|
async extractResponse(path) {
|
|
@@ -33023,7 +33163,23 @@ async function runCases(options = {}) {
|
|
|
33023
33163
|
variables: config2.variables,
|
|
33024
33164
|
accounts: options.accounts
|
|
33025
33165
|
}
|
|
33026
|
-
)
|
|
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
|
+
})
|
|
33027
33183
|
)
|
|
33028
33184
|
)).flat();
|
|
33029
33185
|
const summary = await runner.run(
|
|
@@ -33989,6 +34145,11 @@ function openInBrowser(file2) {
|
|
|
33989
34145
|
|
|
33990
34146
|
// src/commands/run.ts
|
|
33991
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
|
+
}
|
|
33992
34153
|
function makeRunCommand() {
|
|
33993
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) => {
|
|
33994
34155
|
const client = new TestPilotClient();
|
|
@@ -34010,11 +34171,11 @@ function makeRunCommand() {
|
|
|
34010
34171
|
].filter(Boolean).join(",");
|
|
34011
34172
|
console.log("");
|
|
34012
34173
|
console.log(
|
|
34013
|
-
`Run ${summary.runId}${summary.cancelled ? "(\u5DF2\u53D6\u6D88)" : ""}: ${summary.status.toUpperCase()} \u2014 \u6A21\u677F ${summary.totals.templates ?? summary.totals.cases},\u6570\u636E\u884C \u2713${summary.totals.passed} \u2717${summary.totals.failed} \u21B7${summary.totals.skipped ?? 0} \u26A0${summary.totals.warnings ?? 0},\u6B65\u9AA4 \u2713${summary.totals.stepsPassed} \u2717${summary.totals.stepsFailed} \u21B7${summary.totals.stepsSkipped} \u26A0${summary.totals.stepsWarning ?? 0}${notes ? `(${notes})` : ""}`
|
|
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})` : ""}`
|
|
34014
34175
|
);
|
|
34015
34176
|
console.log(`\u4EA7\u7269\u76EE\u5F55:${join28(".testpilot", "artifacts", "runs", summary.runId)}`);
|
|
34016
34177
|
console.log("\u67E5\u770B\u62A5\u544A:npx csspilot report");
|
|
34017
|
-
|
|
34178
|
+
process.exitCode = exitCodeForSummary(summary.status);
|
|
34018
34179
|
} catch (err) {
|
|
34019
34180
|
console.error(err instanceof Error ? err.message : err);
|
|
34020
34181
|
process.exitCode = 1;
|
|
@@ -34189,7 +34350,7 @@ function makeValidateCommand() {
|
|
|
34189
34350
|
}
|
|
34190
34351
|
|
|
34191
34352
|
// src/cli.ts
|
|
34192
|
-
var CLI_VERSION = "0.
|
|
34353
|
+
var CLI_VERSION = "0.7.0";
|
|
34193
34354
|
async function createCli() {
|
|
34194
34355
|
const program2 = new Command();
|
|
34195
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", () => {
|