qat-cli 0.2.98 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +238 -210
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +261 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +261 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Quick Auto Testing — 面向 Vue 项目,集成 Vitest & Playwright,AI 驱动覆盖测试全流程**
|
|
6
6
|
|
|
7
|
-
[](https://www.npmjs.com/package/qat-cli)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
10
|
[](https://www.typescriptlang.org/)
|
package/dist/cli.js
CHANGED
|
@@ -3245,34 +3245,109 @@ function buildVitestArgs(options) {
|
|
|
3245
3245
|
return args;
|
|
3246
3246
|
}
|
|
3247
3247
|
async function execVitest(args) {
|
|
3248
|
+
const os2 = await import("os");
|
|
3249
|
+
const fs15 = await import("fs");
|
|
3250
|
+
const tmpFile = path9.join(os2.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
|
|
3251
|
+
const argsWithOutput = [...args, "--outputFile", tmpFile];
|
|
3248
3252
|
return new Promise((resolve, reject) => {
|
|
3249
3253
|
const npx = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
3250
|
-
const child = execFile(npx,
|
|
3254
|
+
const child = execFile(npx, argsWithOutput, {
|
|
3251
3255
|
cwd: process.cwd(),
|
|
3252
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
3256
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3253
3257
|
maxBuffer: 50 * 1024 * 1024,
|
|
3254
|
-
// 50MB
|
|
3255
3258
|
shell: true
|
|
3256
3259
|
}, (error, stdout, stderr) => {
|
|
3257
|
-
const
|
|
3260
|
+
const rawOutput = stdout || stderr || "";
|
|
3261
|
+
let jsonResult = null;
|
|
3258
3262
|
try {
|
|
3259
|
-
|
|
3260
|
-
|
|
3263
|
+
if (fs15.existsSync(tmpFile)) {
|
|
3264
|
+
jsonResult = fs15.readFileSync(tmpFile, "utf-8");
|
|
3265
|
+
fs15.unlinkSync(tmpFile);
|
|
3266
|
+
}
|
|
3261
3267
|
} catch {
|
|
3262
|
-
|
|
3263
|
-
|
|
3268
|
+
}
|
|
3269
|
+
if (jsonResult) {
|
|
3270
|
+
try {
|
|
3271
|
+
const parsed = parseVitestJSONResult(jsonResult);
|
|
3272
|
+
resolve({ ...parsed, rawOutput });
|
|
3273
|
+
return;
|
|
3274
|
+
} catch {
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
try {
|
|
3278
|
+
const parsed = parseVitestJSONOutput(rawOutput);
|
|
3279
|
+
resolve({ ...parsed, rawOutput });
|
|
3280
|
+
} catch {
|
|
3281
|
+
if (rawOutput) {
|
|
3282
|
+
resolve({ ...parseVitestTextOutput(rawOutput, !!error), rawOutput });
|
|
3264
3283
|
} else if (error && error.message.includes("ENOENT")) {
|
|
3265
3284
|
reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
|
|
3266
3285
|
} else {
|
|
3267
|
-
resolve({ success: !error, suites: [] });
|
|
3286
|
+
resolve({ success: !error, suites: [], rawOutput });
|
|
3268
3287
|
}
|
|
3269
3288
|
}
|
|
3270
3289
|
});
|
|
3271
3290
|
child.on("error", (err) => {
|
|
3291
|
+
try {
|
|
3292
|
+
fs15.unlinkSync(tmpFile);
|
|
3293
|
+
} catch {
|
|
3294
|
+
}
|
|
3272
3295
|
reject(new Error(`Vitest \u6267\u884C\u5931\u8D25: ${err.message}`));
|
|
3273
3296
|
});
|
|
3274
3297
|
});
|
|
3275
3298
|
}
|
|
3299
|
+
function parseVitestJSONResult(jsonStr) {
|
|
3300
|
+
const data = JSON.parse(jsonStr);
|
|
3301
|
+
const suites = [];
|
|
3302
|
+
if (data.testResults && Array.isArray(data.testResults)) {
|
|
3303
|
+
for (const fileResult of data.testResults) {
|
|
3304
|
+
const suiteTests = [];
|
|
3305
|
+
const assertions = fileResult.assertionResults || fileResult.tests || [];
|
|
3306
|
+
for (const assertion of assertions) {
|
|
3307
|
+
suiteTests.push({
|
|
3308
|
+
name: assertion.title || assertion.fullName || assertion.name || "unknown",
|
|
3309
|
+
file: fileResult.name || "unknown",
|
|
3310
|
+
status: mapVitestStatus(assertion.status),
|
|
3311
|
+
duration: assertion.duration || 0,
|
|
3312
|
+
error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : assertion.failureMessage ? { message: assertion.failureMessage } : void 0,
|
|
3313
|
+
retries: 0
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
3316
|
+
if (suiteTests.length === 0 && fileResult.numPassingTests !== void 0) {
|
|
3317
|
+
const counts = [
|
|
3318
|
+
{ n: fileResult.numPassingTests || 0, s: "passed" },
|
|
3319
|
+
{ n: fileResult.numFailingTests || 0, s: "failed" },
|
|
3320
|
+
{ n: fileResult.numPendingTests || 0, s: "skipped" }
|
|
3321
|
+
];
|
|
3322
|
+
for (const { n, s } of counts) {
|
|
3323
|
+
for (let i = 0; i < n; i++) {
|
|
3324
|
+
suiteTests.push({
|
|
3325
|
+
name: `${s} test ${i + 1}`,
|
|
3326
|
+
file: fileResult.name || "unknown",
|
|
3327
|
+
status: s,
|
|
3328
|
+
duration: 0,
|
|
3329
|
+
retries: 0
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
suites.push({
|
|
3335
|
+
name: path9.basename(fileResult.name || "unknown"),
|
|
3336
|
+
file: fileResult.name || "unknown",
|
|
3337
|
+
type: "unit",
|
|
3338
|
+
status: mapVitestStatus(fileResult.status),
|
|
3339
|
+
duration: fileResult.duration || 0,
|
|
3340
|
+
tests: suiteTests
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
let coverage;
|
|
3345
|
+
if (data.coverageMap) {
|
|
3346
|
+
coverage = extractCoverage(data.coverageMap);
|
|
3347
|
+
}
|
|
3348
|
+
const success = data.success !== false && data.numFailedTests === void 0 ? suites.every((s) => s.status !== "failed") : (data.numFailedTests || 0) === 0;
|
|
3349
|
+
return { success, suites, coverage };
|
|
3350
|
+
}
|
|
3276
3351
|
function parseVitestJSONOutput(output) {
|
|
3277
3352
|
const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
|
|
3278
3353
|
if (!jsonMatch) {
|
|
@@ -4515,6 +4590,7 @@ function aggregateResults(results) {
|
|
|
4515
4590
|
pending: 0
|
|
4516
4591
|
};
|
|
4517
4592
|
const byType = {};
|
|
4593
|
+
let coverage;
|
|
4518
4594
|
for (const result of results) {
|
|
4519
4595
|
const typeKey = result.type;
|
|
4520
4596
|
if (!byType[typeKey]) {
|
|
@@ -4538,6 +4614,16 @@ function aggregateResults(results) {
|
|
|
4538
4614
|
}
|
|
4539
4615
|
}
|
|
4540
4616
|
}
|
|
4617
|
+
if (result.coverage) {
|
|
4618
|
+
if (!coverage) {
|
|
4619
|
+
coverage = { ...result.coverage };
|
|
4620
|
+
} else {
|
|
4621
|
+
coverage.lines = Math.max(coverage.lines, result.coverage.lines);
|
|
4622
|
+
coverage.statements = Math.max(coverage.statements, result.coverage.statements);
|
|
4623
|
+
coverage.functions = Math.max(coverage.functions, result.coverage.functions);
|
|
4624
|
+
coverage.branches = Math.max(coverage.branches, result.coverage.branches);
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4541
4627
|
}
|
|
4542
4628
|
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
|
|
4543
4629
|
return {
|
|
@@ -4545,7 +4631,8 @@ function aggregateResults(results) {
|
|
|
4545
4631
|
duration: totalDuration,
|
|
4546
4632
|
results,
|
|
4547
4633
|
summary,
|
|
4548
|
-
byType
|
|
4634
|
+
byType,
|
|
4635
|
+
coverage: coverage?.lines ? coverage : void 0
|
|
4549
4636
|
};
|
|
4550
4637
|
}
|
|
4551
4638
|
function formatDuration2(ms) {
|
|
@@ -4565,208 +4652,139 @@ function formatTimestamp(ts) {
|
|
|
4565
4652
|
second: "2-digit"
|
|
4566
4653
|
});
|
|
4567
4654
|
}
|
|
4568
|
-
function
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4655
|
+
function pct(value) {
|
|
4656
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
4657
|
+
}
|
|
4658
|
+
function renderCoverageMD(coverage) {
|
|
4659
|
+
return `
|
|
4660
|
+
### \u8986\u76D6\u7387
|
|
4574
4661
|
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
<div class="suite-body">${testsHTML}</div>
|
|
4598
|
-
</div>`;
|
|
4599
|
-
}
|
|
4600
|
-
function escapeHTML(str) {
|
|
4601
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4602
|
-
}
|
|
4603
|
-
function generateHTMLReport(data) {
|
|
4662
|
+
| \u6307\u6807 | \u8986\u76D6\u7387 | \u8FDB\u5EA6 |
|
|
4663
|
+
|------|--------|------|
|
|
4664
|
+
| \u8BED\u53E5 (Statements) | ${pct(coverage.statements)} | ${renderProgressBar(coverage.statements)} |
|
|
4665
|
+
| \u5206\u652F (Branches) | ${pct(coverage.branches)} | ${renderProgressBar(coverage.branches)} |
|
|
4666
|
+
| \u51FD\u6570 (Functions) | ${pct(coverage.functions)} | ${renderProgressBar(coverage.functions)} |
|
|
4667
|
+
| \u884C (Lines) | ${pct(coverage.lines)} | ${renderProgressBar(coverage.lines)} |
|
|
4668
|
+
`;
|
|
4669
|
+
}
|
|
4670
|
+
function renderProgressBar(value) {
|
|
4671
|
+
const filled = Math.round(value * 10);
|
|
4672
|
+
const empty = 10 - filled;
|
|
4673
|
+
return `${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}`;
|
|
4674
|
+
}
|
|
4675
|
+
var TYPE_LABELS2 = {
|
|
4676
|
+
unit: "\u5355\u5143\u6D4B\u8BD5",
|
|
4677
|
+
component: "\u7EC4\u4EF6\u6D4B\u8BD5",
|
|
4678
|
+
e2e: "E2E \u6D4B\u8BD5",
|
|
4679
|
+
api: "API \u6D4B\u8BD5",
|
|
4680
|
+
visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5",
|
|
4681
|
+
performance: "\u6027\u80FD\u6D4B\u8BD5"
|
|
4682
|
+
};
|
|
4683
|
+
function generateMDReport(data) {
|
|
4604
4684
|
const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
|
|
4605
|
-
const
|
|
4606
|
-
const
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
.
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
}
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
.test-item {
|
|
4697
|
-
padding: 8px 0; display: flex; align-items: center; gap: 8px;
|
|
4698
|
-
}
|
|
4699
|
-
.test-item + .test-item { border-top: 1px solid var(--border-color); }
|
|
4700
|
-
.status-dot {
|
|
4701
|
-
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
|
|
4702
|
-
}
|
|
4703
|
-
.status-dot.passed { background: var(--color-passed); }
|
|
4704
|
-
.status-dot.failed { background: var(--color-failed); }
|
|
4705
|
-
.status-dot.skipped { background: var(--color-skipped); }
|
|
4706
|
-
.status-dot.pending { background: var(--text-secondary); }
|
|
4707
|
-
.test-name { flex: 1; }
|
|
4708
|
-
.duration { color: var(--text-secondary); font-size: 13px; }
|
|
4709
|
-
.retries { color: var(--color-skipped); font-size: 12px; }
|
|
4710
|
-
.error-message {
|
|
4711
|
-
background: #fef2f2; color: #991b1b; padding: 12px;
|
|
4712
|
-
border-radius: 4px; font-family: 'Fira Code', monospace;
|
|
4713
|
-
font-size: 13px; margin: 8px 0 8px 16px; white-space: pre-wrap;
|
|
4714
|
-
word-break: break-all;
|
|
4715
|
-
}
|
|
4716
|
-
footer {
|
|
4717
|
-
text-align: center; padding: 20px; color: var(--text-secondary);
|
|
4718
|
-
font-size: 13px; margin-top: 40px;
|
|
4719
|
-
}
|
|
4720
|
-
</style>
|
|
4721
|
-
</head>
|
|
4722
|
-
<body>
|
|
4723
|
-
<div class="container">
|
|
4724
|
-
<header>
|
|
4725
|
-
<h1>QAT \u6D4B\u8BD5\u62A5\u544A</h1>
|
|
4726
|
-
<div class="meta">\u751F\u6210\u65F6\u95F4: ${formatTimestamp(data.timestamp)} | \u603B\u8017\u65F6: ${formatDuration2(data.duration)}</div>
|
|
4727
|
-
</header>
|
|
4728
|
-
|
|
4729
|
-
<div class="pass-rate">${passRate}%</div>
|
|
4730
|
-
<div class="pass-rate-label">\u6D4B\u8BD5\u901A\u8FC7\u7387</div>
|
|
4731
|
-
|
|
4732
|
-
<div class="summary">
|
|
4733
|
-
<div class="card total">
|
|
4734
|
-
<div class="card-value">${data.summary.total}</div>
|
|
4735
|
-
<div class="card-label">\u603B\u8BA1</div>
|
|
4736
|
-
</div>
|
|
4737
|
-
<div class="card passed">
|
|
4738
|
-
<div class="card-value">${data.summary.passed}</div>
|
|
4739
|
-
<div class="card-label">\u901A\u8FC7</div>
|
|
4740
|
-
</div>
|
|
4741
|
-
<div class="card failed">
|
|
4742
|
-
<div class="card-value">${data.summary.failed}</div>
|
|
4743
|
-
<div class="card-label">\u5931\u8D25</div>
|
|
4744
|
-
</div>
|
|
4745
|
-
<div class="card skipped">
|
|
4746
|
-
<div class="card-value">${data.summary.skipped}</div>
|
|
4747
|
-
<div class="card-label">\u8DF3\u8FC7</div>
|
|
4748
|
-
</div>
|
|
4749
|
-
</div>
|
|
4750
|
-
|
|
4751
|
-
${Object.keys(data.byType).length > 0 ? `<h2 style="margin-top:30px;margin-bottom:10px;">\u6309\u7C7B\u578B\u7EDF\u8BA1</h2><div class="by-type">${byTypeHTML}</div>` : ""}
|
|
4752
|
-
|
|
4753
|
-
<h2 style="margin-top:30px;margin-bottom:10px;">\u6D4B\u8BD5\u8BE6\u60C5</h2>
|
|
4754
|
-
${suitesHTML || '<p style="color:var(--text-secondary)">\u6682\u65E0\u6D4B\u8BD5\u7ED3\u679C</p>'}
|
|
4755
|
-
|
|
4756
|
-
<footer>\u7531 QAT \u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177\u751F\u6210</footer>
|
|
4757
|
-
</div>
|
|
4758
|
-
</body>
|
|
4759
|
-
</html>`;
|
|
4685
|
+
const rateIcon = parseFloat(passRate) >= 80 ? "\u2705" : parseFloat(passRate) >= 50 ? "\u26A0\uFE0F" : "\u274C";
|
|
4686
|
+
const lines = [];
|
|
4687
|
+
lines.push(`# QAT \u6D4B\u8BD5\u62A5\u544A`);
|
|
4688
|
+
lines.push("");
|
|
4689
|
+
lines.push(`> \u751F\u6210\u65F6\u95F4: ${formatTimestamp(data.timestamp)} | \u603B\u8017\u65F6: ${formatDuration2(data.duration)}`);
|
|
4690
|
+
lines.push("");
|
|
4691
|
+
lines.push(`## \u603B\u89C8`);
|
|
4692
|
+
lines.push("");
|
|
4693
|
+
lines.push(`| \u6307\u6807 | \u6570\u503C |`);
|
|
4694
|
+
lines.push(`|------|------|`);
|
|
4695
|
+
lines.push(`| \u901A\u8FC7\u7387 | ${rateIcon} **${passRate}%** |`);
|
|
4696
|
+
lines.push(`| \u603B\u7528\u4F8B | ${data.summary.total} |`);
|
|
4697
|
+
lines.push(`| \u2705 \u901A\u8FC7 | ${data.summary.passed} |`);
|
|
4698
|
+
if (data.summary.failed > 0) lines.push(`| \u274C \u5931\u8D25 | ${data.summary.failed} |`);
|
|
4699
|
+
if (data.summary.skipped > 0) lines.push(`| \u23ED\uFE0F \u8DF3\u8FC7 | ${data.summary.skipped} |`);
|
|
4700
|
+
if (data.summary.pending > 0) lines.push(`| \u23F3 \u5F85\u5B9A | ${data.summary.pending} |`);
|
|
4701
|
+
lines.push(`| \u23F1\uFE0F \u8017\u65F6 | ${formatDuration2(data.duration)} |`);
|
|
4702
|
+
lines.push("");
|
|
4703
|
+
if (Object.keys(data.byType).length > 0) {
|
|
4704
|
+
lines.push(`## \u6309\u7C7B\u578B\u7EDF\u8BA1`);
|
|
4705
|
+
lines.push("");
|
|
4706
|
+
lines.push(`| \u7C7B\u578B | \u901A\u8FC7 | \u5931\u8D25 | \u8DF3\u8FC7 | \u603B\u8BA1 | \u901A\u8FC7\u7387 |`);
|
|
4707
|
+
lines.push(`|------|------|------|------|------|--------|`);
|
|
4708
|
+
for (const [type, stats] of Object.entries(data.byType)) {
|
|
4709
|
+
const label = TYPE_LABELS2[type] || type;
|
|
4710
|
+
const rate = stats.total > 0 ? (stats.passed / stats.total * 100).toFixed(0) + "%" : "-";
|
|
4711
|
+
lines.push(`| ${label} | ${stats.passed} | ${stats.failed} | ${stats.skipped} | ${stats.total} | ${rate} |`);
|
|
4712
|
+
}
|
|
4713
|
+
lines.push("");
|
|
4714
|
+
}
|
|
4715
|
+
if (data.coverage) {
|
|
4716
|
+
lines.push(renderCoverageMD(data.coverage));
|
|
4717
|
+
lines.push("");
|
|
4718
|
+
}
|
|
4719
|
+
lines.push(`## \u6D4B\u8BD5\u8BE6\u60C5`);
|
|
4720
|
+
lines.push("");
|
|
4721
|
+
for (const result of data.results) {
|
|
4722
|
+
const typeLabel = TYPE_LABELS2[result.type] || result.type;
|
|
4723
|
+
const statusIcon = result.status === "passed" ? "\u2705" : result.status === "failed" ? "\u274C" : "\u26A0\uFE0F";
|
|
4724
|
+
lines.push(`### ${statusIcon} ${typeLabel}`);
|
|
4725
|
+
lines.push("");
|
|
4726
|
+
if (result.suites.length === 0) {
|
|
4727
|
+
lines.push(`*\u65E0\u6D4B\u8BD5\u7ED3\u679C*`);
|
|
4728
|
+
lines.push("");
|
|
4729
|
+
continue;
|
|
4730
|
+
}
|
|
4731
|
+
for (const suite of result.suites) {
|
|
4732
|
+
const suiteIcon = suite.status === "passed" ? "\u2705" : suite.status === "failed" ? "\u274C" : "\u26A0\uFE0F";
|
|
4733
|
+
lines.push(`#### ${suiteIcon} ${suite.name}`);
|
|
4734
|
+
lines.push("");
|
|
4735
|
+
lines.push(`- \u6587\u4EF6: \`${suite.file}\``);
|
|
4736
|
+
lines.push(`- \u8017\u65F6: ${formatDuration2(suite.duration)}`);
|
|
4737
|
+
lines.push("");
|
|
4738
|
+
if (suite.tests.length > 0) {
|
|
4739
|
+
lines.push(`| \u72B6\u6001 | \u6D4B\u8BD5\u540D\u79F0 | \u8017\u65F6 |`);
|
|
4740
|
+
lines.push(`|------|----------|------|`);
|
|
4741
|
+
for (const test of suite.tests) {
|
|
4742
|
+
const testIcon = test.status === "passed" ? "\u2705" : test.status === "failed" ? "\u274C" : test.status === "skipped" ? "\u23ED\uFE0F" : "\u23F3";
|
|
4743
|
+
const name = test.error ? `**${test.name}**` : test.name;
|
|
4744
|
+
lines.push(`| ${testIcon} | ${name} | ${formatDuration2(test.duration)} |`);
|
|
4745
|
+
}
|
|
4746
|
+
lines.push("");
|
|
4747
|
+
}
|
|
4748
|
+
const failedTests = suite.tests.filter((t) => t.status === "failed" && t.error);
|
|
4749
|
+
if (failedTests.length > 0) {
|
|
4750
|
+
lines.push(`<details>`);
|
|
4751
|
+
lines.push(`<summary>\u274C \u5931\u8D25\u8BE6\u60C5 (${failedTests.length})</summary>`);
|
|
4752
|
+
lines.push("");
|
|
4753
|
+
for (const test of failedTests) {
|
|
4754
|
+
lines.push(`**${test.name}**`);
|
|
4755
|
+
lines.push("```");
|
|
4756
|
+
lines.push(test.error.message);
|
|
4757
|
+
if (test.error.stack) {
|
|
4758
|
+
lines.push(test.error.stack);
|
|
4759
|
+
}
|
|
4760
|
+
if (test.error.expected && test.error.actual) {
|
|
4761
|
+
lines.push(`Expected: ${test.error.expected}`);
|
|
4762
|
+
lines.push(`Actual: ${test.error.actual}`);
|
|
4763
|
+
}
|
|
4764
|
+
lines.push("```");
|
|
4765
|
+
lines.push("");
|
|
4766
|
+
}
|
|
4767
|
+
lines.push(`</details>`);
|
|
4768
|
+
lines.push("");
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
lines.push("---");
|
|
4773
|
+
lines.push("");
|
|
4774
|
+
lines.push(`*\u7531 QAT \u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177\u751F\u6210 | ${formatTimestamp(data.timestamp)}*`);
|
|
4775
|
+
return lines.join("\n");
|
|
4760
4776
|
}
|
|
4761
4777
|
function writeReportToDisk(data, outputDir) {
|
|
4762
|
-
const
|
|
4778
|
+
const md = generateMDReport(data);
|
|
4763
4779
|
const dir = path12.resolve(outputDir);
|
|
4764
4780
|
if (!fs10.existsSync(dir)) {
|
|
4765
4781
|
fs10.mkdirSync(dir, { recursive: true });
|
|
4766
4782
|
}
|
|
4767
|
-
const
|
|
4768
|
-
fs10.writeFileSync(
|
|
4769
|
-
|
|
4783
|
+
const mdPath = path12.join(dir, "report.md");
|
|
4784
|
+
fs10.writeFileSync(mdPath, md, "utf-8");
|
|
4785
|
+
const jsonPath = path12.join(dir, "report.json");
|
|
4786
|
+
fs10.writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf-8");
|
|
4787
|
+
return mdPath;
|
|
4770
4788
|
}
|
|
4771
4789
|
|
|
4772
4790
|
// src/commands/report.ts
|
|
@@ -4793,7 +4811,7 @@ async function executeReport(options) {
|
|
|
4793
4811
|
console.log(chalk7.gray("\n \u63D0\u793A: \u5148\u8FD0\u884C qat run \u751F\u6210\u6D4B\u8BD5\u7ED3\u679C\n"));
|
|
4794
4812
|
return;
|
|
4795
4813
|
}
|
|
4796
|
-
spinner.text = "\u6B63\u5728\u751F\
|
|
4814
|
+
spinner.text = "\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u62A5\u544A...";
|
|
4797
4815
|
const reportData = aggregateResults(results);
|
|
4798
4816
|
const reportPath = writeReportToDisk(reportData, outputDir);
|
|
4799
4817
|
saveResultToHistory(reportData);
|
|
@@ -4841,29 +4859,39 @@ function saveResultToHistory(reportData) {
|
|
|
4841
4859
|
}
|
|
4842
4860
|
function displayReportResult(reportPath, data) {
|
|
4843
4861
|
const relativePath = path13.relative(process.cwd(), reportPath);
|
|
4862
|
+
const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
|
|
4844
4863
|
console.log();
|
|
4845
4864
|
console.log(chalk7.green(" \u2713 \u6D4B\u8BD5\u62A5\u544A\u5DF2\u751F\u6210"));
|
|
4846
4865
|
console.log();
|
|
4847
4866
|
console.log(chalk7.white(" \u62A5\u544A\u8DEF\u5F84:"), chalk7.cyan(relativePath));
|
|
4867
|
+
console.log(chalk7.white(" \u901A\u8FC7\u7387: "), parseFloat(passRate) >= 80 ? chalk7.green(`${passRate}%`) : parseFloat(passRate) >= 50 ? chalk7.yellow(`${passRate}%`) : chalk7.red(`${passRate}%`));
|
|
4848
4868
|
console.log(chalk7.white(" \u6D4B\u8BD5\u7528\u4F8B:"), `${data.summary.total} total`);
|
|
4849
|
-
console.log(chalk7.white(" \u901A\u8FC7:"), chalk7.green(String(data.summary.passed)));
|
|
4869
|
+
console.log(chalk7.white(" \u2705 \u901A\u8FC7: "), chalk7.green(String(data.summary.passed)));
|
|
4850
4870
|
if (data.summary.failed > 0) {
|
|
4851
|
-
console.log(chalk7.white(" \u5931\u8D25:"), chalk7.red(String(data.summary.failed)));
|
|
4871
|
+
console.log(chalk7.white(" \u274C \u5931\u8D25: "), chalk7.red(String(data.summary.failed)));
|
|
4852
4872
|
}
|
|
4853
4873
|
if (data.summary.skipped > 0) {
|
|
4854
|
-
console.log(chalk7.white(" \u8DF3\u8FC7:"), chalk7.yellow(String(data.summary.skipped)));
|
|
4874
|
+
console.log(chalk7.white(" \u23ED\uFE0F \u8DF3\u8FC7: "), chalk7.yellow(String(data.summary.skipped)));
|
|
4855
4875
|
}
|
|
4856
4876
|
if (Object.keys(data.byType).length > 0) {
|
|
4857
4877
|
console.log();
|
|
4858
4878
|
console.log(chalk7.white(" \u6309\u7C7B\u578B:"));
|
|
4859
4879
|
for (const [type, stats] of Object.entries(data.byType)) {
|
|
4860
4880
|
const rate = stats.total > 0 ? (stats.passed / stats.total * 100).toFixed(0) : "0";
|
|
4861
|
-
const icon = stats.failed > 0 ? chalk7.red("\
|
|
4881
|
+
const icon = stats.failed > 0 ? chalk7.red("\u274C") : chalk7.green("\u2705");
|
|
4862
4882
|
console.log(` ${icon} ${type}: ${stats.passed}/${stats.total} (${rate}%)`);
|
|
4863
4883
|
}
|
|
4864
4884
|
}
|
|
4885
|
+
if (data.coverage) {
|
|
4886
|
+
console.log();
|
|
4887
|
+
console.log(chalk7.white(" \u8986\u76D6\u7387:"));
|
|
4888
|
+
console.log(` \u8BED\u53E5: ${chalk7.cyan(pct2(data.coverage.statements))} \u5206\u652F: ${chalk7.cyan(pct2(data.coverage.branches))} \u51FD\u6570: ${chalk7.cyan(pct2(data.coverage.functions))} \u884C: ${chalk7.cyan(pct2(data.coverage.lines))}`);
|
|
4889
|
+
}
|
|
4865
4890
|
console.log();
|
|
4866
4891
|
}
|
|
4892
|
+
function pct2(value) {
|
|
4893
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
4894
|
+
}
|
|
4867
4895
|
async function openReport(reportPath) {
|
|
4868
4896
|
const { exec } = await import("child_process");
|
|
4869
4897
|
const platform = process.platform;
|
|
@@ -5619,7 +5647,7 @@ async function executeChange(_options) {
|
|
|
5619
5647
|
}
|
|
5620
5648
|
|
|
5621
5649
|
// src/cli.ts
|
|
5622
|
-
var VERSION = "0.
|
|
5650
|
+
var VERSION = "0.3.01";
|
|
5623
5651
|
function printLogo() {
|
|
5624
5652
|
const logo = `
|
|
5625
5653
|
${chalk12.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}
|