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/bin.js
CHANGED
|
@@ -5110,7 +5110,7 @@ var require_composer = __commonJS({
|
|
|
5110
5110
|
var node_process = __require("process");
|
|
5111
5111
|
var directives = require_directives();
|
|
5112
5112
|
var Document = require_Document();
|
|
5113
|
-
var
|
|
5113
|
+
var errors2 = require_errors();
|
|
5114
5114
|
var identity = require_identity();
|
|
5115
5115
|
var composeDoc = require_compose_doc();
|
|
5116
5116
|
var resolveEnd = require_resolve_end();
|
|
@@ -5157,9 +5157,9 @@ var require_composer = __commonJS({
|
|
|
5157
5157
|
this.onError = (source, code, message, warning) => {
|
|
5158
5158
|
const pos = getErrorPos(source);
|
|
5159
5159
|
if (warning)
|
|
5160
|
-
this.warnings.push(new
|
|
5160
|
+
this.warnings.push(new errors2.YAMLWarning(pos, code, message));
|
|
5161
5161
|
else
|
|
5162
|
-
this.errors.push(new
|
|
5162
|
+
this.errors.push(new errors2.YAMLParseError(pos, code, message));
|
|
5163
5163
|
};
|
|
5164
5164
|
this.directives = new directives.Directives({ version: options.version || "1.2" });
|
|
5165
5165
|
this.options = options;
|
|
@@ -5257,7 +5257,7 @@ ${cb}` : comment;
|
|
|
5257
5257
|
break;
|
|
5258
5258
|
case "error": {
|
|
5259
5259
|
const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
|
|
5260
|
-
const error62 = new
|
|
5260
|
+
const error62 = new errors2.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
|
|
5261
5261
|
if (this.atDirectives || !this.doc)
|
|
5262
5262
|
this.errors.push(error62);
|
|
5263
5263
|
else
|
|
@@ -5267,7 +5267,7 @@ ${cb}` : comment;
|
|
|
5267
5267
|
case "doc-end": {
|
|
5268
5268
|
if (!this.doc) {
|
|
5269
5269
|
const msg = "Unexpected doc-end without preceding document";
|
|
5270
|
-
this.errors.push(new
|
|
5270
|
+
this.errors.push(new errors2.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
|
|
5271
5271
|
break;
|
|
5272
5272
|
}
|
|
5273
5273
|
this.doc.directives.docEnd = true;
|
|
@@ -5282,7 +5282,7 @@ ${end.comment}` : end.comment;
|
|
|
5282
5282
|
break;
|
|
5283
5283
|
}
|
|
5284
5284
|
default:
|
|
5285
|
-
this.errors.push(new
|
|
5285
|
+
this.errors.push(new errors2.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
|
|
5286
5286
|
}
|
|
5287
5287
|
}
|
|
5288
5288
|
/**
|
|
@@ -5317,7 +5317,7 @@ var require_cst_scalar = __commonJS({
|
|
|
5317
5317
|
"use strict";
|
|
5318
5318
|
var resolveBlockScalar = require_resolve_block_scalar();
|
|
5319
5319
|
var resolveFlowScalar = require_resolve_flow_scalar();
|
|
5320
|
-
var
|
|
5320
|
+
var errors2 = require_errors();
|
|
5321
5321
|
var stringifyString = require_stringifyString();
|
|
5322
5322
|
function resolveAsScalar(token, strict = true, onError) {
|
|
5323
5323
|
if (token) {
|
|
@@ -5326,7 +5326,7 @@ var require_cst_scalar = __commonJS({
|
|
|
5326
5326
|
if (onError)
|
|
5327
5327
|
onError(offset, code, message);
|
|
5328
5328
|
else
|
|
5329
|
-
throw new
|
|
5329
|
+
throw new errors2.YAMLParseError([offset, offset + 1], code, message);
|
|
5330
5330
|
};
|
|
5331
5331
|
switch (token.type) {
|
|
5332
5332
|
case "scalar":
|
|
@@ -7221,7 +7221,7 @@ var require_public_api = __commonJS({
|
|
|
7221
7221
|
"use strict";
|
|
7222
7222
|
var composer = require_composer();
|
|
7223
7223
|
var Document = require_Document();
|
|
7224
|
-
var
|
|
7224
|
+
var errors2 = require_errors();
|
|
7225
7225
|
var log = require_log();
|
|
7226
7226
|
var identity = require_identity();
|
|
7227
7227
|
var lineCounter = require_line_counter();
|
|
@@ -7238,8 +7238,8 @@ var require_public_api = __commonJS({
|
|
|
7238
7238
|
const docs = Array.from(composer$1.compose(parser$1.parse(source)));
|
|
7239
7239
|
if (prettyErrors && lineCounter2)
|
|
7240
7240
|
for (const doc of docs) {
|
|
7241
|
-
doc.errors.forEach(
|
|
7242
|
-
doc.warnings.forEach(
|
|
7241
|
+
doc.errors.forEach(errors2.prettifyError(source, lineCounter2));
|
|
7242
|
+
doc.warnings.forEach(errors2.prettifyError(source, lineCounter2));
|
|
7243
7243
|
}
|
|
7244
7244
|
if (docs.length > 0)
|
|
7245
7245
|
return docs;
|
|
@@ -7254,13 +7254,13 @@ var require_public_api = __commonJS({
|
|
|
7254
7254
|
if (!doc)
|
|
7255
7255
|
doc = _doc;
|
|
7256
7256
|
else if (doc.options.logLevel !== "silent") {
|
|
7257
|
-
doc.errors.push(new
|
|
7257
|
+
doc.errors.push(new errors2.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
|
|
7258
7258
|
break;
|
|
7259
7259
|
}
|
|
7260
7260
|
}
|
|
7261
7261
|
if (prettyErrors && lineCounter2) {
|
|
7262
|
-
doc.errors.forEach(
|
|
7263
|
-
doc.warnings.forEach(
|
|
7262
|
+
doc.errors.forEach(errors2.prettifyError(source, lineCounter2));
|
|
7263
|
+
doc.warnings.forEach(errors2.prettifyError(source, lineCounter2));
|
|
7264
7264
|
}
|
|
7265
7265
|
return doc;
|
|
7266
7266
|
}
|
|
@@ -7319,7 +7319,7 @@ var require_dist = __commonJS({
|
|
|
7319
7319
|
var composer = require_composer();
|
|
7320
7320
|
var Document = require_Document();
|
|
7321
7321
|
var Schema = require_Schema();
|
|
7322
|
-
var
|
|
7322
|
+
var errors2 = require_errors();
|
|
7323
7323
|
var Alias = require_Alias();
|
|
7324
7324
|
var identity = require_identity();
|
|
7325
7325
|
var Pair = require_Pair();
|
|
@@ -7335,9 +7335,9 @@ var require_dist = __commonJS({
|
|
|
7335
7335
|
exports.Composer = composer.Composer;
|
|
7336
7336
|
exports.Document = Document.Document;
|
|
7337
7337
|
exports.Schema = Schema.Schema;
|
|
7338
|
-
exports.YAMLError =
|
|
7339
|
-
exports.YAMLParseError =
|
|
7340
|
-
exports.YAMLWarning =
|
|
7338
|
+
exports.YAMLError = errors2.YAMLError;
|
|
7339
|
+
exports.YAMLParseError = errors2.YAMLParseError;
|
|
7340
|
+
exports.YAMLWarning = errors2.YAMLWarning;
|
|
7341
7341
|
exports.Alias = Alias.Alias;
|
|
7342
7342
|
exports.isAlias = identity.isAlias;
|
|
7343
7343
|
exports.isCollection = identity.isCollection;
|
|
@@ -10367,6 +10367,28 @@ var TestPilotError = class extends Error {
|
|
|
10367
10367
|
}
|
|
10368
10368
|
};
|
|
10369
10369
|
|
|
10370
|
+
// ../../packages/core/src/verdict.ts
|
|
10371
|
+
var INFRA_CODES = /* @__PURE__ */ new Set([
|
|
10372
|
+
"ENVIRONMENT_UNAVAILABLE",
|
|
10373
|
+
"ADAPTER_NOT_FOUND",
|
|
10374
|
+
"UNSUPPORTED_ACTION",
|
|
10375
|
+
"FIXTURE_MISSING",
|
|
10376
|
+
"ACCOUNT_UNAVAILABLE",
|
|
10377
|
+
"CONFIG_MISSING"
|
|
10378
|
+
]);
|
|
10379
|
+
var UNVERIFIED_CODES = /* @__PURE__ */ new Set(["PREREQ_VARIABLE_MISSING"]);
|
|
10380
|
+
function verdictFromErrorCode(code) {
|
|
10381
|
+
if (code !== void 0 && INFRA_CODES.has(code)) return "infra";
|
|
10382
|
+
if (code !== void 0 && UNVERIFIED_CODES.has(code)) return "unverified";
|
|
10383
|
+
return "product";
|
|
10384
|
+
}
|
|
10385
|
+
function caseStatusFromErrorCode(code) {
|
|
10386
|
+
const verdict = verdictFromErrorCode(code);
|
|
10387
|
+
if (verdict === "infra") return "blocked";
|
|
10388
|
+
if (verdict === "unverified") return "inconclusive";
|
|
10389
|
+
return "failed";
|
|
10390
|
+
}
|
|
10391
|
+
|
|
10370
10392
|
// ../../packages/core/src/constants/index.ts
|
|
10371
10393
|
import { join } from "node:path";
|
|
10372
10394
|
var TESTPILOT_DIR = ".testpilot";
|
|
@@ -30717,6 +30739,9 @@ async function loadCaseDataFile(reference, options) {
|
|
|
30717
30739
|
const [realDataDir, realTarget] = await Promise.all([
|
|
30718
30740
|
realpath(dataDir).catch(() => dataDir),
|
|
30719
30741
|
realpath(target).catch((error62) => {
|
|
30742
|
+
if (error62 instanceof Error && error62.code === "ENOENT") {
|
|
30743
|
+
throw new TestPilotError("FIXTURE_MISSING", `\u6570\u636E\u6587\u4EF6\u4E0D\u5B58\u5728:${reference}`);
|
|
30744
|
+
}
|
|
30720
30745
|
throw new Error(
|
|
30721
30746
|
`\u8BFB\u53D6\u6570\u636E\u6587\u4EF6\u5931\u8D25:${reference}:${error62 instanceof Error ? error62.message : String(error62)}`
|
|
30722
30747
|
);
|
|
@@ -30944,6 +30969,9 @@ async function validateCaseDataSource(source, file2, root) {
|
|
|
30944
30969
|
);
|
|
30945
30970
|
return validation;
|
|
30946
30971
|
} catch (error62) {
|
|
30972
|
+
if (error62 instanceof TestPilotError && error62.code === "FIXTURE_MISSING") {
|
|
30973
|
+
return validation;
|
|
30974
|
+
}
|
|
30947
30975
|
return {
|
|
30948
30976
|
ok: false,
|
|
30949
30977
|
data: validation.data,
|
|
@@ -30992,6 +31020,24 @@ function classifyFailure(message) {
|
|
|
30992
31020
|
}
|
|
30993
31021
|
return "other";
|
|
30994
31022
|
}
|
|
31023
|
+
var CODE_CATEGORIES = {
|
|
31024
|
+
ASSERTION_FAILED: "assert",
|
|
31025
|
+
REQUEST_COUNT_MISMATCH: "assert",
|
|
31026
|
+
STATUS_MISMATCH: "assert",
|
|
31027
|
+
STEP_TIMEOUT: "assert",
|
|
31028
|
+
ELEMENT_NOT_FOUND: "element",
|
|
31029
|
+
ADAPTER_NOT_FOUND: "adapter",
|
|
31030
|
+
UNSUPPORTED_ACTION: "adapter",
|
|
31031
|
+
CONFIG_MISSING: "adapter",
|
|
31032
|
+
ENVIRONMENT_UNAVAILABLE: "env",
|
|
31033
|
+
FIXTURE_MISSING: "env",
|
|
31034
|
+
ACCOUNT_UNAVAILABLE: "env",
|
|
31035
|
+
PREREQ_VARIABLE_MISSING: "other"
|
|
31036
|
+
};
|
|
31037
|
+
function classifyFromCode(code) {
|
|
31038
|
+
if (code === void 0) return void 0;
|
|
31039
|
+
return CODE_CATEGORIES[code];
|
|
31040
|
+
}
|
|
30995
31041
|
|
|
30996
31042
|
// ../../packages/reporter/src/history.ts
|
|
30997
31043
|
import { readFile as readFile5, readdir as readdir2 } from "node:fs/promises";
|
|
@@ -31049,7 +31095,7 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
31049
31095
|
const cases = summary.cases.map((item) => {
|
|
31050
31096
|
const categories = /* @__PURE__ */ new Set();
|
|
31051
31097
|
const steps = item.steps.map((step) => {
|
|
31052
|
-
const category = step.status === "failed" ? classifyFailure(step.error) : void 0;
|
|
31098
|
+
const category = classifyFromCode(step.errorCode) ?? (step.status === "failed" ? classifyFailure(step.error) : void 0);
|
|
31053
31099
|
if (category) {
|
|
31054
31100
|
categories.add(category);
|
|
31055
31101
|
failures.push({
|
|
@@ -31057,7 +31103,8 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
31057
31103
|
label: categoryLabel(category),
|
|
31058
31104
|
caseId: item.caseId,
|
|
31059
31105
|
index: step.index,
|
|
31060
|
-
message: step.error ?? ""
|
|
31106
|
+
message: step.error ?? "",
|
|
31107
|
+
verdict: verdictFromErrorCode(step.errorCode ?? item.errorCode)
|
|
31061
31108
|
});
|
|
31062
31109
|
}
|
|
31063
31110
|
return { ...step, category };
|
|
@@ -31080,6 +31127,20 @@ async function writeReports(runDir, summary, options = {}) {
|
|
|
31080
31127
|
await writeFile4(htmlPath, renderHtml(data), "utf8");
|
|
31081
31128
|
return { json: jsonPath, html: htmlPath };
|
|
31082
31129
|
}
|
|
31130
|
+
var VERDICT_GROUPS = [
|
|
31131
|
+
{ verdict: "product", title: "\u4EA7\u54C1\u5931\u8D25" },
|
|
31132
|
+
{ verdict: "infra", title: "\u73AF\u5883\u4E0E\u524D\u7F6E(\u975E\u4EA7\u54C1\u56DE\u5F52)" },
|
|
31133
|
+
{ verdict: "unverified", title: "\u672A\u9A8C\u8BC1" }
|
|
31134
|
+
];
|
|
31135
|
+
function verdictGroupsHtml(data) {
|
|
31136
|
+
return VERDICT_GROUPS.map((group) => {
|
|
31137
|
+
const items = data.failures.filter((failure2) => failure2.verdict === group.verdict);
|
|
31138
|
+
const list = items.length > 0 ? items.map(
|
|
31139
|
+
(failure2) => `<li><code>${escapeHtml(failure2.caseId)}</code> #${failure2.index} ${escapeHtml(failure2.message.slice(0, 160))}</li>`
|
|
31140
|
+
).join("\n") : '<li class="muted">\u65E0</li>';
|
|
31141
|
+
return `<div class="verdict-group"><h3>${escapeHtml(group.title)}(${items.length})</h3><ul>${list}</ul></div>`;
|
|
31142
|
+
}).join("\n");
|
|
31143
|
+
}
|
|
31083
31144
|
function categoryChips(data) {
|
|
31084
31145
|
const counts = /* @__PURE__ */ new Map();
|
|
31085
31146
|
for (const failure2 of data.failures) {
|
|
@@ -31105,19 +31166,20 @@ function renderHtml(data) {
|
|
|
31105
31166
|
const trendSvg = renderTrend(data);
|
|
31106
31167
|
const casesHtml = groupedCasesHtml(summary.cases);
|
|
31107
31168
|
const chips = categoryChips(data);
|
|
31169
|
+
const verdictHtml = verdictGroupsHtml(data);
|
|
31108
31170
|
return `<!doctype html>
|
|
31109
31171
|
<html lang="zh-CN">
|
|
31110
31172
|
<head>
|
|
31111
31173
|
<meta charset="utf-8" />
|
|
31112
31174
|
<title>TestPilot Report ${escapeHtml(summary.runId)}</title>
|
|
31113
31175
|
<style>
|
|
31114
|
-
:root { --ok:#1a7f37; --fail:#cf222e; --warn:#9a6700; --skip:#6e7781; --line:#d0d7de; --muted:#59636e; }
|
|
31176
|
+
:root { --ok:#1a7f37; --fail:#cf222e; --warn:#9a6700; --skip:#6e7781; --blocked:#8250df; --inconclusive:#9a6700; --line:#d0d7de; --muted:#59636e; }
|
|
31115
31177
|
* { box-sizing: border-box; }
|
|
31116
31178
|
body { font-family: system-ui, "Segoe UI", sans-serif; margin: 0; color: #1f2328; background: #f6f8fa; }
|
|
31117
31179
|
.wrap { max-width: 1080px; margin: 0 auto; padding: 20px 16px 48px; }
|
|
31118
31180
|
h1 { font-size: 20px; margin: 8px 0; } h2 { font-size: 15px; margin: 24px 0 8px; }
|
|
31119
31181
|
.badge { display: inline-block; padding: 2px 10px; border-radius: 10px; color: #fff; font-size: 12px; }
|
|
31120
|
-
.passed { background: var(--ok); } .failed { background: var(--fail); } .warning { background: var(--warn); } .skipped { background: var(--skip); }
|
|
31182
|
+
.passed { background: var(--ok); } .failed { background: var(--fail); } .warning { background: var(--warn); } .skipped { background: var(--skip); } .blocked { background: var(--blocked); } .inconclusive { background: var(--inconclusive); }
|
|
31121
31183
|
code { background: #eef1f4; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
|
|
31122
31184
|
.cards { display: flex; gap: 12px; flex-wrap: wrap; margin: 14px 0; }
|
|
31123
31185
|
.card { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 10px 16px; min-width: 130px; }
|
|
@@ -31144,6 +31206,11 @@ function renderHtml(data) {
|
|
|
31144
31206
|
.muted { color: var(--muted); }
|
|
31145
31207
|
.hide { display: none !important; }
|
|
31146
31208
|
.meta { color: var(--muted); font-size: 12px; margin: 6px 0; }
|
|
31209
|
+
.verdicts { display: flex; gap: 14px; flex-wrap: wrap; }
|
|
31210
|
+
.verdict-group { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 10px 14px; flex: 1; min-width: 220px; }
|
|
31211
|
+
.verdict-group h3 { font-size: 13px; margin: 0 0 6px; }
|
|
31212
|
+
.verdict-group ul { margin: 0; padding-left: 18px; font-size: 12px; }
|
|
31213
|
+
.verdict-group li { margin: 3px 0; }
|
|
31147
31214
|
</style>
|
|
31148
31215
|
</head>
|
|
31149
31216
|
<body>
|
|
@@ -31164,6 +31231,9 @@ function renderHtml(data) {
|
|
|
31164
31231
|
<h2>\u5386\u53F2\u8D8B\u52BF(\u6700\u8FD1 ${data.history.length + 1} \u6B21\u8FD0\u884C)</h2>
|
|
31165
31232
|
<div class="trend">${trendSvg}</div>
|
|
31166
31233
|
|
|
31234
|
+
<h2>\u5931\u8D25\u4E09\u6876</h2>
|
|
31235
|
+
<div class="verdicts">${verdictHtml}</div>
|
|
31236
|
+
|
|
31167
31237
|
<h2>\u5931\u8D25\u5206\u7C7B</h2>
|
|
31168
31238
|
<div class="toolbar" id="cat-chips">${chips}</div>
|
|
31169
31239
|
|
|
@@ -31372,7 +31442,7 @@ function buildSummaryData(runs) {
|
|
|
31372
31442
|
for (const item of summary.cases) {
|
|
31373
31443
|
const platforms = [...new Set(item.steps.map((step) => step.target))];
|
|
31374
31444
|
const failedStep = item.steps.find((step) => step.status === "failed");
|
|
31375
|
-
const category = item.status === "failed" ? classifyFailure(failedStep?.error) : void 0;
|
|
31445
|
+
const category = classifyFromCode(item.errorCode ?? failedStep?.errorCode) ?? (item.status === "failed" ? classifyFailure(failedStep?.error) : void 0);
|
|
31376
31446
|
executions.push({
|
|
31377
31447
|
runId,
|
|
31378
31448
|
caseId: item.caseId,
|
|
@@ -31435,6 +31505,10 @@ function buildSummaryData(runs) {
|
|
|
31435
31505
|
})).filter((item) => item.count > 0);
|
|
31436
31506
|
const durations = runs.map((item) => item.summary.durationMs).filter((value) => typeof value === "number");
|
|
31437
31507
|
return {
|
|
31508
|
+
buckets: {
|
|
31509
|
+
blocked: executions.filter((exec2) => exec2.status === "blocked").length,
|
|
31510
|
+
inconclusive: executions.filter((exec2) => exec2.status === "inconclusive").length
|
|
31511
|
+
},
|
|
31438
31512
|
window: {
|
|
31439
31513
|
runs: runs.length,
|
|
31440
31514
|
from: runPoints[0]?.startedAt,
|
|
@@ -31519,7 +31593,7 @@ function renderSummaryHtml(data) {
|
|
|
31519
31593
|
th.num, td.num { text-align: right; }
|
|
31520
31594
|
code { background: #eef1f4; padding: 1px 5px; border-radius: 4px; font-size: 12px; cursor: pointer; }
|
|
31521
31595
|
.badge { display: inline-block; padding: 1px 9px; border-radius: 10px; color: #fff; font-size: 11px; }
|
|
31522
|
-
.passed { background: #1a7f37; } .failed { background: #cf222e; } .unknown { background: #6e7781; }
|
|
31596
|
+
.passed { background: #1a7f37; } .failed { background: #cf222e; } .unknown { background: #6e7781; } .blocked { background: #8250df; } .inconclusive { background: #9a6700; }
|
|
31523
31597
|
.toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 10px 0; }
|
|
31524
31598
|
.chip { border: 1px solid var(--line); background: #fff; border-radius: 14px; padding: 3px 12px; font-size: 12px; cursor: pointer; }
|
|
31525
31599
|
.chip.active { background: #0969da; color: #fff; border-color: #0969da; }
|
|
@@ -31545,6 +31619,8 @@ function renderSummaryHtml(data) {
|
|
|
31545
31619
|
<div class="card"><b>${rateText}</b><span>\u6574\u4F53\u901A\u8FC7\u7387</span></div>
|
|
31546
31620
|
<div class="card"><b>${(kpis.avgDurationMs / 1e3).toFixed(1)}s</b><span>\u5E73\u5747\u8FD0\u884C\u8017\u65F6</span></div>
|
|
31547
31621
|
<div class="card"><b>${kpis.flakyCount}</b><span>Flaky \u7528\u4F8B</span></div>
|
|
31622
|
+
${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>` : ""}
|
|
31623
|
+
${data.buckets.inconclusive > 0 ? `<div class="card"><b>${data.buckets.inconclusive} ?</b><span>\u672A\u9A8C\u8BC1(\u8BC1\u636E\u4E0D\u8DB3)</span></div>` : ""}
|
|
31548
31624
|
</div>
|
|
31549
31625
|
|
|
31550
31626
|
<h2>\u8D8B\u52BF(\u6BCF\u6B21\u8FD0\u884C\u7ED3\u679C\u4E0E\u901A\u8FC7\u7387,\u60AC\u6D6E\u770B\u8BE6\u60C5)</h2>
|
|
@@ -31753,14 +31829,23 @@ var ExecutionContext = class {
|
|
|
31753
31829
|
}
|
|
31754
31830
|
}
|
|
31755
31831
|
/**
|
|
31756
|
-
* 将字符串中的 ${name}
|
|
31757
|
-
*
|
|
31832
|
+
* 将字符串中的 ${name} 替换为已提取的变量值;未定义的引用抛 TestPilotError——
|
|
31833
|
+
* 校验阶段已静态拦截"从未声明"的引用,运行时未命中即数据未兑现
|
|
31834
|
+
* (前置 extract 未执行或 fixture/dataset/凭据缺失),按三态归 unverified/account 桶。
|
|
31835
|
+
* 凭据桶仅限 `account` 整值或 `account.*` 字段;用户变量撞 account 前缀(如 accountId)
|
|
31836
|
+
* 不误归凭据桶。名字允许点号,用于引擎注入的 account.username 一类凭据变量
|
|
31837
|
+
* (用户 extract 变量名不含点号)。
|
|
31758
31838
|
*/
|
|
31759
31839
|
resolve(template) {
|
|
31760
|
-
return template.replace(
|
|
31761
|
-
|
|
31762
|
-
(
|
|
31763
|
-
|
|
31840
|
+
return template.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (_match, name) => {
|
|
31841
|
+
const value = this.variables.get(name);
|
|
31842
|
+
if (value !== void 0) return value;
|
|
31843
|
+
const isAccount = name === "account" || name.startsWith("account.");
|
|
31844
|
+
throw new TestPilotError(
|
|
31845
|
+
isAccount ? "ACCOUNT_UNAVAILABLE" : "PREREQ_VARIABLE_MISSING",
|
|
31846
|
+
`\u53D8\u91CF "${name}" \u672A\u5B9A\u4E49(\u524D\u7F6E extract \u672A\u6267\u884C\u6216 fixture/dataset/\u51ED\u636E\u7F3A\u5931)`
|
|
31847
|
+
);
|
|
31848
|
+
});
|
|
31764
31849
|
}
|
|
31765
31850
|
/**
|
|
31766
31851
|
* 注入 accountRef 凭据:值为 JSON 对象时按 `${account.<字段>}` 展开为字符串,
|
|
@@ -31919,7 +32004,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31919
32004
|
} : void 0;
|
|
31920
32005
|
if (step.expectRequests) {
|
|
31921
32006
|
if (!adapter.startRequestCapture || !adapter.stopRequestCapture) {
|
|
31922
|
-
throw new
|
|
32007
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u5F53\u524D ${step.target} adapter \u4E0D\u652F\u6301\u8BF7\u6C42\u6B21\u6570\u65AD\u8A00`);
|
|
31923
32008
|
}
|
|
31924
32009
|
await adapter.startRequestCapture();
|
|
31925
32010
|
captureStarted = true;
|
|
@@ -31945,7 +32030,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31945
32030
|
break;
|
|
31946
32031
|
case "assert":
|
|
31947
32032
|
if (step.target === "api") {
|
|
31948
|
-
if (!adapter.assertResponse) throw new
|
|
32033
|
+
if (!adapter.assertResponse) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api \u65AD\u8A00");
|
|
31949
32034
|
await adapter.assertResponse(context.resolve(step.expected ?? ""));
|
|
31950
32035
|
} else {
|
|
31951
32036
|
await adapter.assert(locator, context.resolve(step.expected ?? ""));
|
|
@@ -31965,7 +32050,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31965
32050
|
break;
|
|
31966
32051
|
}
|
|
31967
32052
|
case "request": {
|
|
31968
|
-
if (!adapter.request) throw new
|
|
32053
|
+
if (!adapter.request) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api request");
|
|
31969
32054
|
const method = step.method ?? "GET";
|
|
31970
32055
|
const url2 = context.resolve(step.url ?? "");
|
|
31971
32056
|
const headers = resolveHeaders(step.headers, context);
|
|
@@ -31982,7 +32067,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31982
32067
|
if (step.expected !== void 0) {
|
|
31983
32068
|
const expectedStatus = context.resolve(step.expected);
|
|
31984
32069
|
if (String(response.status) !== expectedStatus) {
|
|
31985
|
-
throw new
|
|
32070
|
+
throw new TestPilotError(
|
|
32071
|
+
"STATUS_MISMATCH",
|
|
31986
32072
|
`HTTP \u72B6\u6001\u7801 ${response.status} ${response.statusText},\u671F\u671B ${expectedStatus}:${snippet(response.bodyText, 200)}`
|
|
31987
32073
|
);
|
|
31988
32074
|
}
|
|
@@ -31991,7 +32077,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
31991
32077
|
}
|
|
31992
32078
|
default: {
|
|
31993
32079
|
const exhaustive = step.action;
|
|
31994
|
-
throw new
|
|
32080
|
+
throw new TestPilotError("UNSUPPORTED_ACTION", `\u4E0D\u652F\u6301\u7684 action:${String(exhaustive)}`);
|
|
31995
32081
|
}
|
|
31996
32082
|
}
|
|
31997
32083
|
if (step.expectRequests) {
|
|
@@ -32002,7 +32088,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32002
32088
|
result.requestAssertions = buildRequestAssertions(step, requests, context);
|
|
32003
32089
|
const mismatches = result.requestAssertions.filter((item) => item.actualCount !== item.count);
|
|
32004
32090
|
if (mismatches.length > 0) {
|
|
32005
|
-
throw new
|
|
32091
|
+
throw new TestPilotError(
|
|
32092
|
+
"REQUEST_COUNT_MISMATCH",
|
|
32006
32093
|
mismatches.map(
|
|
32007
32094
|
(item) => `\u8BF7\u6C42\u6B21\u6570\u4E0D\u5339\u914D:${item.method ?? "*"} ${item.urlContains},\u5B9E\u9645 ${item.actualCount},\u671F\u671B ${item.count}`
|
|
32008
32095
|
).join("; ")
|
|
@@ -32019,6 +32106,7 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32019
32106
|
}
|
|
32020
32107
|
result.status = "failed";
|
|
32021
32108
|
result.error = err instanceof Error ? err.message : String(err);
|
|
32109
|
+
result.errorCode = err instanceof TestPilotError ? err.code : void 0;
|
|
32022
32110
|
try {
|
|
32023
32111
|
const data = await adapter.screenshot();
|
|
32024
32112
|
result.screenshot = await artifacts.saveStepScreenshot(`${options.screenshotName}-failed.png`, data);
|
|
@@ -32029,8 +32117,8 @@ async function executeStep(step, index, adapter, context, artifacts, options) {
|
|
|
32029
32117
|
return result;
|
|
32030
32118
|
}
|
|
32031
32119
|
async function extractApiResponse(adapter, path) {
|
|
32032
|
-
if (!adapter.extractResponse) throw new
|
|
32033
|
-
if (!path) throw new
|
|
32120
|
+
if (!adapter.extractResponse) throw new TestPilotError("UNSUPPORTED_ACTION", "\u5F53\u524D adapter \u4E0D\u652F\u6301 api \u63D0\u53D6");
|
|
32121
|
+
if (!path) throw new TestPilotError("UNSUPPORTED_ACTION", "api \u7AEF extract \u9700\u8981\u63D0\u4F9B value(\u70B9\u53F7 JSON path)");
|
|
32034
32122
|
return adapter.extractResponse(path);
|
|
32035
32123
|
}
|
|
32036
32124
|
function resolveHeaders(headers, context) {
|
|
@@ -32104,6 +32192,7 @@ var ExecutionEngine = class {
|
|
|
32104
32192
|
const warnings = [];
|
|
32105
32193
|
const evidenceAdapters = /* @__PURE__ */ new Map();
|
|
32106
32194
|
let caseError;
|
|
32195
|
+
let caseErrorCode;
|
|
32107
32196
|
const executionId = safeArtifactName(`${data.id}-${rowId}`);
|
|
32108
32197
|
await bus.emit({
|
|
32109
32198
|
type: "case-started",
|
|
@@ -32181,7 +32270,8 @@ var ExecutionEngine = class {
|
|
|
32181
32270
|
action: step.action,
|
|
32182
32271
|
status: "failed",
|
|
32183
32272
|
durationMs: 0,
|
|
32184
|
-
error: err instanceof Error ? err.message : String(err)
|
|
32273
|
+
error: err instanceof Error ? err.message : String(err),
|
|
32274
|
+
errorCode: err instanceof TestPilotError ? err.code : void 0
|
|
32185
32275
|
};
|
|
32186
32276
|
}
|
|
32187
32277
|
result.phase = phase;
|
|
@@ -32210,6 +32300,7 @@ var ExecutionEngine = class {
|
|
|
32210
32300
|
if (result.status === "failed") {
|
|
32211
32301
|
passed = false;
|
|
32212
32302
|
caseError = result.error;
|
|
32303
|
+
caseErrorCode = result.errorCode;
|
|
32213
32304
|
for (const [skipIndex, skippedStep] of phaseSteps.entries()) {
|
|
32214
32305
|
if (skipIndex <= index) continue;
|
|
32215
32306
|
steps.push({
|
|
@@ -32226,24 +32317,42 @@ var ExecutionEngine = class {
|
|
|
32226
32317
|
}
|
|
32227
32318
|
return passed;
|
|
32228
32319
|
};
|
|
32229
|
-
|
|
32230
|
-
|
|
32231
|
-
|
|
32232
|
-
} else {
|
|
32233
|
-
for (const [index, step] of data.steps.entries()) {
|
|
32320
|
+
if (options.blockedError) {
|
|
32321
|
+
const first = data.setup?.[0] ?? data.steps[0];
|
|
32322
|
+
if (first) {
|
|
32234
32323
|
steps.push({
|
|
32235
|
-
index,
|
|
32236
|
-
phase: "
|
|
32237
|
-
target:
|
|
32238
|
-
action:
|
|
32239
|
-
status: "
|
|
32240
|
-
durationMs: 0
|
|
32324
|
+
index: 0,
|
|
32325
|
+
phase: "setup",
|
|
32326
|
+
target: first.target,
|
|
32327
|
+
action: first.action,
|
|
32328
|
+
status: "failed",
|
|
32329
|
+
durationMs: 0,
|
|
32330
|
+
error: options.blockedError.message,
|
|
32331
|
+
errorCode: options.blockedError.code
|
|
32241
32332
|
});
|
|
32242
32333
|
}
|
|
32334
|
+
caseError = options.blockedError.message;
|
|
32335
|
+
caseErrorCode = options.blockedError.code;
|
|
32336
|
+
} else {
|
|
32337
|
+
const setupPassed = await runPhase("setup", data.setup ?? []);
|
|
32338
|
+
if (setupPassed) {
|
|
32339
|
+
await runPhase("steps", data.steps);
|
|
32340
|
+
} else {
|
|
32341
|
+
for (const [index, step] of data.steps.entries()) {
|
|
32342
|
+
steps.push({
|
|
32343
|
+
index,
|
|
32344
|
+
phase: "steps",
|
|
32345
|
+
target: step.target,
|
|
32346
|
+
action: step.action,
|
|
32347
|
+
status: "skipped",
|
|
32348
|
+
durationMs: 0
|
|
32349
|
+
});
|
|
32350
|
+
}
|
|
32351
|
+
}
|
|
32352
|
+
await runPhase("teardown", data.teardown ?? []);
|
|
32243
32353
|
}
|
|
32244
|
-
await runPhase("teardown", data.teardown ?? []);
|
|
32245
32354
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
32246
|
-
const status = caseError ?
|
|
32355
|
+
const status = caseError ? caseStatusFromErrorCode(caseErrorCode) : "passed";
|
|
32247
32356
|
const caseResult = {
|
|
32248
32357
|
caseId: data.id,
|
|
32249
32358
|
caseName: data.name,
|
|
@@ -32257,6 +32366,7 @@ var ExecutionEngine = class {
|
|
|
32257
32366
|
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
32258
32367
|
};
|
|
32259
32368
|
if (caseError) caseResult.error = caseError;
|
|
32369
|
+
if (caseErrorCode) caseResult.errorCode = caseErrorCode;
|
|
32260
32370
|
if (warnings.length > 0) caseResult.warnings = warnings;
|
|
32261
32371
|
for (const [target, adapter] of evidenceAdapters) {
|
|
32262
32372
|
if (!adapter.stopEvidence) continue;
|
|
@@ -32300,6 +32410,8 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
32300
32410
|
passed: cases.filter((item) => item.status === "passed").length,
|
|
32301
32411
|
failed: cases.filter((item) => item.status === "failed").length,
|
|
32302
32412
|
skipped: cases.filter((item) => item.status === "skipped").length,
|
|
32413
|
+
...cases.some((item) => item.status === "blocked") ? { blocked: cases.filter((item) => item.status === "blocked").length } : {},
|
|
32414
|
+
...cases.some((item) => item.status === "inconclusive") ? { inconclusive: cases.filter((item) => item.status === "inconclusive").length } : {},
|
|
32303
32415
|
warnings: 0,
|
|
32304
32416
|
steps: 0,
|
|
32305
32417
|
stepsPassed: 0,
|
|
@@ -32324,7 +32436,8 @@ function buildRunSummary(runId, startedAt, cases, options = {}) {
|
|
|
32324
32436
|
startedAt: startedAt.toISOString(),
|
|
32325
32437
|
finishedAt: finishedAt.toISOString(),
|
|
32326
32438
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
32327
|
-
|
|
32439
|
+
// run 状态按三桶优先级推导:产品失败 > 证据不足 > 环境阻塞 > 通过 > 跳过
|
|
32440
|
+
status: totals.failed > 0 ? "failed" : (totals.inconclusive ?? 0) > 0 ? "inconclusive" : (totals.blocked ?? 0) > 0 ? "blocked" : totals.passed > 0 || totals.cases === 0 ? "passed" : "skipped",
|
|
32328
32441
|
...options.cancelled ? { cancelled: true } : {},
|
|
32329
32442
|
cases,
|
|
32330
32443
|
totals
|
|
@@ -32376,6 +32489,7 @@ var TestRunner = class {
|
|
|
32376
32489
|
rowIndex: item.rowIndex ?? 0,
|
|
32377
32490
|
initialVariables: item.initialVariables ?? {},
|
|
32378
32491
|
missingDependencies: item.missingDependencies ?? [],
|
|
32492
|
+
blockedError: item.blockedError,
|
|
32379
32493
|
artifacts,
|
|
32380
32494
|
bus,
|
|
32381
32495
|
resolver: this.resolver,
|
|
@@ -32432,6 +32546,7 @@ import { existsSync as existsSync3 } from "node:fs";
|
|
|
32432
32546
|
import { mkdtemp, readFile as readFile6, rm } from "node:fs/promises";
|
|
32433
32547
|
import { tmpdir } from "node:os";
|
|
32434
32548
|
import { join as join16 } from "node:path";
|
|
32549
|
+
import { errors } from "playwright";
|
|
32435
32550
|
var PlaywrightAdapter = class {
|
|
32436
32551
|
constructor(bundle, options = {}) {
|
|
32437
32552
|
this.bundle = bundle;
|
|
@@ -32488,36 +32603,50 @@ var PlaywrightAdapter = class {
|
|
|
32488
32603
|
const options = this.options;
|
|
32489
32604
|
const page = await this.ensurePage();
|
|
32490
32605
|
const target = options.baseUrl && !/^https?:\/\//i.test(url2) ? new URL(url2, options.baseUrl).toString() : url2;
|
|
32491
|
-
|
|
32606
|
+
try {
|
|
32607
|
+
await page.goto(target);
|
|
32608
|
+
} catch (err) {
|
|
32609
|
+
if (err instanceof errors.TimeoutError) {
|
|
32610
|
+
throw new TestPilotError("STEP_TIMEOUT", err.message, { cause: err });
|
|
32611
|
+
}
|
|
32612
|
+
if (err instanceof Error && err.message.includes("net::ERR")) {
|
|
32613
|
+
throw new TestPilotError("ENVIRONMENT_UNAVAILABLE", err.message, { cause: err });
|
|
32614
|
+
}
|
|
32615
|
+
throw err;
|
|
32616
|
+
}
|
|
32492
32617
|
}
|
|
32493
32618
|
async click(locator) {
|
|
32494
|
-
|
|
32619
|
+
const target = await this.toLocator(locator);
|
|
32620
|
+
await this.coded(() => target.click());
|
|
32495
32621
|
}
|
|
32496
32622
|
async input(locator, value) {
|
|
32497
|
-
|
|
32623
|
+
const target = await this.toLocator(locator);
|
|
32624
|
+
await this.coded(() => target.fill(value));
|
|
32498
32625
|
}
|
|
32499
32626
|
async select(locator, value) {
|
|
32500
|
-
|
|
32627
|
+
const target = await this.toLocator(locator);
|
|
32628
|
+
await this.coded(() => target.selectOption(value));
|
|
32501
32629
|
}
|
|
32502
32630
|
async wait(locator, timeoutMs) {
|
|
32503
32631
|
const page = await this.ensurePage();
|
|
32504
32632
|
if (locator) {
|
|
32505
|
-
await this.toLocatorOn(page, locator).waitFor({ state: "visible", timeout: timeoutMs });
|
|
32633
|
+
await this.coded(() => this.toLocatorOn(page, locator).waitFor({ state: "visible", timeout: timeoutMs }));
|
|
32506
32634
|
return;
|
|
32507
32635
|
}
|
|
32508
32636
|
await page.waitForTimeout(timeoutMs ?? 1e3);
|
|
32509
32637
|
}
|
|
32510
32638
|
async assert(locator, expected) {
|
|
32511
32639
|
const target = await this.toLocator(locator);
|
|
32512
|
-
await target.waitFor({ state: "visible" });
|
|
32513
|
-
const actual = (await target.innerText()).trim();
|
|
32640
|
+
await this.coded(() => target.waitFor({ state: "visible" }));
|
|
32641
|
+
const actual = (await this.coded(() => target.innerText())).trim();
|
|
32514
32642
|
const wanted = expected.trim();
|
|
32515
32643
|
if (!actual.includes(wanted)) {
|
|
32516
|
-
throw new
|
|
32644
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u65AD\u8A00\u5931\u8D25:\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`);
|
|
32517
32645
|
}
|
|
32518
32646
|
}
|
|
32519
32647
|
async extract(locator) {
|
|
32520
|
-
|
|
32648
|
+
const target = await this.toLocator(locator);
|
|
32649
|
+
return this.coded(() => target.innerText()).then((text) => text.trim());
|
|
32521
32650
|
}
|
|
32522
32651
|
async screenshot() {
|
|
32523
32652
|
return (await this.ensurePage()).screenshot();
|
|
@@ -32535,6 +32664,17 @@ var PlaywrightAdapter = class {
|
|
|
32535
32664
|
async toLocator(locator) {
|
|
32536
32665
|
return this.toLocatorOn(await this.ensurePage(), locator);
|
|
32537
32666
|
}
|
|
32667
|
+
/** 定位类操作的统一兜底:等待超时按「元素没等到」归类(可能是回归也可能是 selector 腐烂,不允许静默) */
|
|
32668
|
+
async coded(fn) {
|
|
32669
|
+
try {
|
|
32670
|
+
return await fn();
|
|
32671
|
+
} catch (err) {
|
|
32672
|
+
if (err instanceof errors.TimeoutError) {
|
|
32673
|
+
throw new TestPilotError("ELEMENT_NOT_FOUND", err.message, { cause: err });
|
|
32674
|
+
}
|
|
32675
|
+
throw err;
|
|
32676
|
+
}
|
|
32677
|
+
}
|
|
32538
32678
|
toLocatorOn(page, locator) {
|
|
32539
32679
|
if (locator.text !== void 0) {
|
|
32540
32680
|
return page.getByText(locator.text, { exact: true });
|
|
@@ -32612,12 +32752,12 @@ function startCliAuto(cliPath, projectPath, port) {
|
|
|
32612
32752
|
child.stderr?.on("data", (chunk) => {
|
|
32613
32753
|
output2 += chunk.toString("utf8");
|
|
32614
32754
|
});
|
|
32615
|
-
child.once("error", (err) => rejectPromise(new
|
|
32755
|
+
child.once("error", (err) => rejectPromise(new TestPilotError("ENVIRONMENT_UNAVAILABLE", `\u62C9\u8D77\u5F00\u53D1\u8005\u5DE5\u5177 cli \u5931\u8D25:${err.message}`)));
|
|
32616
32756
|
child.once("exit", (code) => {
|
|
32617
32757
|
if (code === 0) {
|
|
32618
32758
|
resolvePromise();
|
|
32619
32759
|
} else {
|
|
32620
|
-
rejectPromise(new
|
|
32760
|
+
rejectPromise(new TestPilotError("ENVIRONMENT_UNAVAILABLE", `cli auto \u9000\u51FA\u7801 ${code}:${output2.slice(-400)}`));
|
|
32621
32761
|
}
|
|
32622
32762
|
});
|
|
32623
32763
|
const startedAt = Date.now();
|
|
@@ -32630,7 +32770,7 @@ function startCliAuto(cliPath, projectPath, port) {
|
|
|
32630
32770
|
socket.once("error", () => {
|
|
32631
32771
|
socket.destroy();
|
|
32632
32772
|
if (Date.now() - startedAt > AUTO_PORT_FALLBACK_WAIT_MS) {
|
|
32633
|
-
rejectPromise(new
|
|
32773
|
+
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`));
|
|
32634
32774
|
} else {
|
|
32635
32775
|
setTimeout(poll, 500);
|
|
32636
32776
|
}
|
|
@@ -32679,7 +32819,7 @@ var MiniAppAdapter = class {
|
|
|
32679
32819
|
await this.runWithReconnect(async () => (await this.element(locator)).input(value));
|
|
32680
32820
|
}
|
|
32681
32821
|
async select() {
|
|
32682
|
-
throw new
|
|
32822
|
+
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");
|
|
32683
32823
|
}
|
|
32684
32824
|
async wait(locator, timeoutMs) {
|
|
32685
32825
|
await this.runWithReconnect(async () => {
|
|
@@ -32697,7 +32837,7 @@ var MiniAppAdapter = class {
|
|
|
32697
32837
|
const actual = (await element.text()).trim();
|
|
32698
32838
|
const wanted = expected.trim();
|
|
32699
32839
|
if (!actual.includes(wanted)) {
|
|
32700
|
-
throw new
|
|
32840
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u65AD\u8A00\u5931\u8D25:\u671F\u671B\u5305\u542B "${wanted}",\u5B9E\u9645\u4E3A "${actual.slice(0, 200)}"`);
|
|
32701
32841
|
}
|
|
32702
32842
|
});
|
|
32703
32843
|
}
|
|
@@ -32795,7 +32935,7 @@ var MiniAppAdapter = class {
|
|
|
32795
32935
|
const element = await page.$(selector);
|
|
32796
32936
|
if (element) return element;
|
|
32797
32937
|
if (Date.now() - startedAt > ELEMENT_WAIT_MS) {
|
|
32798
|
-
throw new
|
|
32938
|
+
throw new TestPilotError("ELEMENT_NOT_FOUND", `\u7B49\u5F85\u5143\u7D20\u8D85\u65F6(${ELEMENT_WAIT_MS}ms):${selector}`);
|
|
32799
32939
|
}
|
|
32800
32940
|
await delay2(500);
|
|
32801
32941
|
}
|
|
@@ -32873,7 +33013,7 @@ var ApiAdapter = class {
|
|
|
32873
33013
|
signal: AbortSignal.timeout(this.options.timeoutMs ?? 15e3)
|
|
32874
33014
|
});
|
|
32875
33015
|
} catch (err) {
|
|
32876
|
-
throw new
|
|
33016
|
+
throw new TestPilotError("ENVIRONMENT_UNAVAILABLE", `API \u8BF7\u6C42\u5931\u8D25 ${request.method.toUpperCase()} ${url2}:${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
32877
33017
|
}
|
|
32878
33018
|
const bodyText = await res.text();
|
|
32879
33019
|
let json2;
|
|
@@ -32895,7 +33035,7 @@ var ApiAdapter = class {
|
|
|
32895
33035
|
if (!this.last) throw new Error("\u5C1A\u672A\u53D1\u8D77\u4EFB\u4F55 API \u8BF7\u6C42,\u65E0\u6CD5\u65AD\u8A00\u54CD\u5E94");
|
|
32896
33036
|
if (!this.last.bodyText.includes(expected)) {
|
|
32897
33037
|
const snippet2 = this.last.bodyText.slice(0, 200);
|
|
32898
|
-
throw new
|
|
33038
|
+
throw new TestPilotError("ASSERTION_FAILED", `\u54CD\u5E94\u4F53\u4E0D\u5305\u542B "${expected}":HTTP ${this.last.status} ${snippet2}`);
|
|
32899
33039
|
}
|
|
32900
33040
|
}
|
|
32901
33041
|
async extractResponse(path) {
|
|
@@ -33024,7 +33164,23 @@ async function runCases(options = {}) {
|
|
|
33024
33164
|
variables: config2.variables,
|
|
33025
33165
|
accounts: options.accounts
|
|
33026
33166
|
}
|
|
33027
|
-
)
|
|
33167
|
+
).catch((err) => {
|
|
33168
|
+
if (err instanceof TestPilotError && err.code === "FIXTURE_MISSING") {
|
|
33169
|
+
return [
|
|
33170
|
+
{
|
|
33171
|
+
data: item.case,
|
|
33172
|
+
file: item.file,
|
|
33173
|
+
templateId: item.case.id,
|
|
33174
|
+
rowId: "default",
|
|
33175
|
+
rowIndex: 0,
|
|
33176
|
+
initialVariables: {},
|
|
33177
|
+
missingDependencies: [],
|
|
33178
|
+
blockedError: { code: err.code, message: err.message }
|
|
33179
|
+
}
|
|
33180
|
+
];
|
|
33181
|
+
}
|
|
33182
|
+
throw err;
|
|
33183
|
+
})
|
|
33028
33184
|
)
|
|
33029
33185
|
)).flat();
|
|
33030
33186
|
const summary = await runner.run(
|
|
@@ -33990,6 +34146,11 @@ function openInBrowser(file2) {
|
|
|
33990
34146
|
|
|
33991
34147
|
// src/commands/run.ts
|
|
33992
34148
|
import { join as join28 } from "node:path";
|
|
34149
|
+
function exitCodeForSummary(status) {
|
|
34150
|
+
if (status === "failed" || status === "inconclusive") return 1;
|
|
34151
|
+
if (status === "blocked") return 2;
|
|
34152
|
+
return 0;
|
|
34153
|
+
}
|
|
33993
34154
|
function makeRunCommand() {
|
|
33994
34155
|
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) => {
|
|
33995
34156
|
const client = new TestPilotClient();
|
|
@@ -34011,11 +34172,11 @@ function makeRunCommand() {
|
|
|
34011
34172
|
].filter(Boolean).join(",");
|
|
34012
34173
|
console.log("");
|
|
34013
34174
|
console.log(
|
|
34014
|
-
`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})` : ""}`
|
|
34175
|
+
`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})` : ""}`
|
|
34015
34176
|
);
|
|
34016
34177
|
console.log(`\u4EA7\u7269\u76EE\u5F55:${join28(".testpilot", "artifacts", "runs", summary.runId)}`);
|
|
34017
34178
|
console.log("\u67E5\u770B\u62A5\u544A:npx csspilot report");
|
|
34018
|
-
|
|
34179
|
+
process.exitCode = exitCodeForSummary(summary.status);
|
|
34019
34180
|
} catch (err) {
|
|
34020
34181
|
console.error(err instanceof Error ? err.message : err);
|
|
34021
34182
|
process.exitCode = 1;
|
|
@@ -34190,7 +34351,7 @@ function makeValidateCommand() {
|
|
|
34190
34351
|
}
|
|
34191
34352
|
|
|
34192
34353
|
// src/cli.ts
|
|
34193
|
-
var CLI_VERSION = "0.
|
|
34354
|
+
var CLI_VERSION = "0.7.0";
|
|
34194
34355
|
async function createCli() {
|
|
34195
34356
|
const program2 = new Command();
|
|
34196
34357
|
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", () => {
|