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/dist/index.cjs
CHANGED
|
@@ -1466,6 +1466,7 @@ function aggregateResults(results) {
|
|
|
1466
1466
|
pending: 0
|
|
1467
1467
|
};
|
|
1468
1468
|
const byType = {};
|
|
1469
|
+
let coverage;
|
|
1469
1470
|
for (const result of results) {
|
|
1470
1471
|
const typeKey = result.type;
|
|
1471
1472
|
if (!byType[typeKey]) {
|
|
@@ -1489,6 +1490,16 @@ function aggregateResults(results) {
|
|
|
1489
1490
|
}
|
|
1490
1491
|
}
|
|
1491
1492
|
}
|
|
1493
|
+
if (result.coverage) {
|
|
1494
|
+
if (!coverage) {
|
|
1495
|
+
coverage = { ...result.coverage };
|
|
1496
|
+
} else {
|
|
1497
|
+
coverage.lines = Math.max(coverage.lines, result.coverage.lines);
|
|
1498
|
+
coverage.statements = Math.max(coverage.statements, result.coverage.statements);
|
|
1499
|
+
coverage.functions = Math.max(coverage.functions, result.coverage.functions);
|
|
1500
|
+
coverage.branches = Math.max(coverage.branches, result.coverage.branches);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1492
1503
|
}
|
|
1493
1504
|
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
|
|
1494
1505
|
return {
|
|
@@ -1496,7 +1507,8 @@ function aggregateResults(results) {
|
|
|
1496
1507
|
duration: totalDuration,
|
|
1497
1508
|
results,
|
|
1498
1509
|
summary,
|
|
1499
|
-
byType
|
|
1510
|
+
byType,
|
|
1511
|
+
coverage: coverage?.lines ? coverage : void 0
|
|
1500
1512
|
};
|
|
1501
1513
|
}
|
|
1502
1514
|
function formatDuration(ms) {
|
|
@@ -1516,56 +1528,149 @@ function formatTimestamp(ts) {
|
|
|
1516
1528
|
second: "2-digit"
|
|
1517
1529
|
});
|
|
1518
1530
|
}
|
|
1519
|
-
function
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1531
|
+
function pct(value) {
|
|
1532
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
1533
|
+
}
|
|
1534
|
+
function renderCoverageMD(coverage) {
|
|
1535
|
+
return `
|
|
1536
|
+
### \u8986\u76D6\u7387
|
|
1525
1537
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1538
|
+
| \u6307\u6807 | \u8986\u76D6\u7387 | \u8FDB\u5EA6 |
|
|
1539
|
+
|------|--------|------|
|
|
1540
|
+
| \u8BED\u53E5 (Statements) | ${pct(coverage.statements)} | ${renderProgressBar(coverage.statements)} |
|
|
1541
|
+
| \u5206\u652F (Branches) | ${pct(coverage.branches)} | ${renderProgressBar(coverage.branches)} |
|
|
1542
|
+
| \u51FD\u6570 (Functions) | ${pct(coverage.functions)} | ${renderProgressBar(coverage.functions)} |
|
|
1543
|
+
| \u884C (Lines) | ${pct(coverage.lines)} | ${renderProgressBar(coverage.lines)} |
|
|
1544
|
+
`;
|
|
1545
|
+
}
|
|
1546
|
+
function renderProgressBar(value) {
|
|
1547
|
+
const filled = Math.round(value * 10);
|
|
1548
|
+
const empty = 10 - filled;
|
|
1549
|
+
return `${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}`;
|
|
1550
|
+
}
|
|
1551
|
+
var TYPE_LABELS = {
|
|
1552
|
+
unit: "\u5355\u5143\u6D4B\u8BD5",
|
|
1553
|
+
component: "\u7EC4\u4EF6\u6D4B\u8BD5",
|
|
1554
|
+
e2e: "E2E \u6D4B\u8BD5",
|
|
1555
|
+
api: "API \u6D4B\u8BD5",
|
|
1556
|
+
visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5",
|
|
1557
|
+
performance: "\u6027\u80FD\u6D4B\u8BD5"
|
|
1558
|
+
};
|
|
1559
|
+
function generateMDReport(data) {
|
|
1560
|
+
const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
|
|
1561
|
+
const rateIcon = parseFloat(passRate) >= 80 ? "\u2705" : parseFloat(passRate) >= 50 ? "\u26A0\uFE0F" : "\u274C";
|
|
1562
|
+
const lines = [];
|
|
1563
|
+
lines.push(`# QAT \u6D4B\u8BD5\u62A5\u544A`);
|
|
1564
|
+
lines.push("");
|
|
1565
|
+
lines.push(`> \u751F\u6210\u65F6\u95F4: ${formatTimestamp(data.timestamp)} | \u603B\u8017\u65F6: ${formatDuration(data.duration)}`);
|
|
1566
|
+
lines.push("");
|
|
1567
|
+
lines.push(`## \u603B\u89C8`);
|
|
1568
|
+
lines.push("");
|
|
1569
|
+
lines.push(`| \u6307\u6807 | \u6570\u503C |`);
|
|
1570
|
+
lines.push(`|------|------|`);
|
|
1571
|
+
lines.push(`| \u901A\u8FC7\u7387 | ${rateIcon} **${passRate}%** |`);
|
|
1572
|
+
lines.push(`| \u603B\u7528\u4F8B | ${data.summary.total} |`);
|
|
1573
|
+
lines.push(`| \u2705 \u901A\u8FC7 | ${data.summary.passed} |`);
|
|
1574
|
+
if (data.summary.failed > 0) lines.push(`| \u274C \u5931\u8D25 | ${data.summary.failed} |`);
|
|
1575
|
+
if (data.summary.skipped > 0) lines.push(`| \u23ED\uFE0F \u8DF3\u8FC7 | ${data.summary.skipped} |`);
|
|
1576
|
+
if (data.summary.pending > 0) lines.push(`| \u23F3 \u5F85\u5B9A | ${data.summary.pending} |`);
|
|
1577
|
+
lines.push(`| \u23F1\uFE0F \u8017\u65F6 | ${formatDuration(data.duration)} |`);
|
|
1578
|
+
lines.push("");
|
|
1579
|
+
if (Object.keys(data.byType).length > 0) {
|
|
1580
|
+
lines.push(`## \u6309\u7C7B\u578B\u7EDF\u8BA1`);
|
|
1581
|
+
lines.push("");
|
|
1582
|
+
lines.push(`| \u7C7B\u578B | \u901A\u8FC7 | \u5931\u8D25 | \u8DF3\u8FC7 | \u603B\u8BA1 | \u901A\u8FC7\u7387 |`);
|
|
1583
|
+
lines.push(`|------|------|------|------|------|--------|`);
|
|
1584
|
+
for (const [type, stats] of Object.entries(data.byType)) {
|
|
1585
|
+
const label = TYPE_LABELS[type] || type;
|
|
1586
|
+
const rate = stats.total > 0 ? (stats.passed / stats.total * 100).toFixed(0) + "%" : "-";
|
|
1587
|
+
lines.push(`| ${label} | ${stats.passed} | ${stats.failed} | ${stats.skipped} | ${stats.total} | ${rate} |`);
|
|
1588
|
+
}
|
|
1589
|
+
lines.push("");
|
|
1590
|
+
}
|
|
1591
|
+
if (data.coverage) {
|
|
1592
|
+
lines.push(renderCoverageMD(data.coverage));
|
|
1593
|
+
lines.push("");
|
|
1594
|
+
}
|
|
1595
|
+
lines.push(`## \u6D4B\u8BD5\u8BE6\u60C5`);
|
|
1596
|
+
lines.push("");
|
|
1597
|
+
for (const result of data.results) {
|
|
1598
|
+
const typeLabel = TYPE_LABELS[result.type] || result.type;
|
|
1599
|
+
const statusIcon = result.status === "passed" ? "\u2705" : result.status === "failed" ? "\u274C" : "\u26A0\uFE0F";
|
|
1600
|
+
lines.push(`### ${statusIcon} ${typeLabel}`);
|
|
1601
|
+
lines.push("");
|
|
1602
|
+
if (result.suites.length === 0) {
|
|
1603
|
+
lines.push(`*\u65E0\u6D4B\u8BD5\u7ED3\u679C*`);
|
|
1604
|
+
lines.push("");
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
for (const suite of result.suites) {
|
|
1608
|
+
const suiteIcon = suite.status === "passed" ? "\u2705" : suite.status === "failed" ? "\u274C" : "\u26A0\uFE0F";
|
|
1609
|
+
lines.push(`#### ${suiteIcon} ${suite.name}`);
|
|
1610
|
+
lines.push("");
|
|
1611
|
+
lines.push(`- \u6587\u4EF6: \`${suite.file}\``);
|
|
1612
|
+
lines.push(`- \u8017\u65F6: ${formatDuration(suite.duration)}`);
|
|
1613
|
+
lines.push("");
|
|
1614
|
+
if (suite.tests.length > 0) {
|
|
1615
|
+
lines.push(`| \u72B6\u6001 | \u6D4B\u8BD5\u540D\u79F0 | \u8017\u65F6 |`);
|
|
1616
|
+
lines.push(`|------|----------|------|`);
|
|
1617
|
+
for (const test of suite.tests) {
|
|
1618
|
+
const testIcon = test.status === "passed" ? "\u2705" : test.status === "failed" ? "\u274C" : test.status === "skipped" ? "\u23ED\uFE0F" : "\u23F3";
|
|
1619
|
+
const name = test.error ? `**${test.name}**` : test.name;
|
|
1620
|
+
lines.push(`| ${testIcon} | ${name} | ${formatDuration(test.duration)} |`);
|
|
1621
|
+
}
|
|
1622
|
+
lines.push("");
|
|
1623
|
+
}
|
|
1624
|
+
const failedTests = suite.tests.filter((t) => t.status === "failed" && t.error);
|
|
1625
|
+
if (failedTests.length > 0) {
|
|
1626
|
+
lines.push(`<details>`);
|
|
1627
|
+
lines.push(`<summary>\u274C \u5931\u8D25\u8BE6\u60C5 (${failedTests.length})</summary>`);
|
|
1628
|
+
lines.push("");
|
|
1629
|
+
for (const test of failedTests) {
|
|
1630
|
+
lines.push(`**${test.name}**`);
|
|
1631
|
+
lines.push("```");
|
|
1632
|
+
lines.push(test.error.message);
|
|
1633
|
+
if (test.error.stack) {
|
|
1634
|
+
lines.push(test.error.stack);
|
|
1635
|
+
}
|
|
1636
|
+
if (test.error.expected && test.error.actual) {
|
|
1637
|
+
lines.push(`Expected: ${test.error.expected}`);
|
|
1638
|
+
lines.push(`Actual: ${test.error.actual}`);
|
|
1639
|
+
}
|
|
1640
|
+
lines.push("```");
|
|
1641
|
+
lines.push("");
|
|
1642
|
+
}
|
|
1643
|
+
lines.push(`</details>`);
|
|
1644
|
+
lines.push("");
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
lines.push("---");
|
|
1649
|
+
lines.push("");
|
|
1650
|
+
lines.push(`*\u7531 QAT \u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177\u751F\u6210 | ${formatTimestamp(data.timestamp)}*`);
|
|
1651
|
+
return lines.join("\n");
|
|
1652
|
+
}
|
|
1653
|
+
function writeReportToDisk(data, outputDir) {
|
|
1654
|
+
const md = generateMDReport(data);
|
|
1655
|
+
const dir = import_node_path5.default.resolve(outputDir);
|
|
1656
|
+
if (!import_node_fs5.default.existsSync(dir)) {
|
|
1657
|
+
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
1658
|
+
}
|
|
1659
|
+
const mdPath = import_node_path5.default.join(dir, "report.md");
|
|
1660
|
+
import_node_fs5.default.writeFileSync(mdPath, md, "utf-8");
|
|
1661
|
+
const jsonPath = import_node_path5.default.join(dir, "report.json");
|
|
1662
|
+
import_node_fs5.default.writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf-8");
|
|
1663
|
+
return mdPath;
|
|
1553
1664
|
}
|
|
1554
1665
|
function generateHTMLReport(data) {
|
|
1555
1666
|
const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
|
|
1556
|
-
const
|
|
1557
|
-
const
|
|
1558
|
-
const
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
<span class="failed">${stats.failed} \u5931\u8D25</span>
|
|
1564
|
-
<span class="skipped">${stats.skipped} \u8DF3\u8FC7</span>
|
|
1565
|
-
</div>
|
|
1566
|
-
<div class="type-rate">${rate}%</div>
|
|
1567
|
-
</div>`;
|
|
1568
|
-
}).join("\n");
|
|
1667
|
+
const md = generateMDReport(data);
|
|
1668
|
+
const html = md.replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>").replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/^---$/gm, "<hr>").replace(/\|(.+)\|/g, (match) => {
|
|
1669
|
+
const cells = match.split("|").filter(Boolean).map((c) => c.trim());
|
|
1670
|
+
if (cells.every((c) => c.startsWith("-") || c === "")) return "";
|
|
1671
|
+
const tds = cells.map((c) => `<td>${c}</td>`).join("");
|
|
1672
|
+
return `<tr>${tds}</tr>`;
|
|
1673
|
+
}).replace(/<tr>/g, "<table><tr>").replace(/<\/tr>(?!\s*<table>)/g, "</tr></table>").replace(/<\/table>\s*<table>/g, "").replace(/^✅/gm, '<span style="color:#22c55e">\u2705</span>').replace(/^❌/gm, '<span style="color:#ef4444">\u274C</span>').replace(/^⚠️/gm, '<span style="color:#f59e0b">\u26A0\uFE0F</span>');
|
|
1569
1674
|
return `<!DOCTYPE html>
|
|
1570
1675
|
<html lang="zh-CN">
|
|
1571
1676
|
<head>
|
|
@@ -1573,152 +1678,39 @@ function generateHTMLReport(data) {
|
|
|
1573
1678
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1574
1679
|
<title>QAT \u6D4B\u8BD5\u62A5\u544A - ${formatTimestamp(data.timestamp)}</title>
|
|
1575
1680
|
<style>
|
|
1576
|
-
:root {
|
|
1577
|
-
--color-passed: #22c55e;
|
|
1578
|
-
--color-failed: #ef4444;
|
|
1579
|
-
--color-skipped: #f59e0b;
|
|
1580
|
-
--color-info: #3b82f6;
|
|
1581
|
-
--bg-primary: #ffffff;
|
|
1582
|
-
--bg-secondary: #f8fafc;
|
|
1583
|
-
--text-primary: #1e293b;
|
|
1584
|
-
--text-secondary: #64748b;
|
|
1585
|
-
--border-color: #e2e8f0;
|
|
1586
|
-
}
|
|
1587
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1588
1681
|
body {
|
|
1589
1682
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1590
|
-
|
|
1591
|
-
color:
|
|
1592
|
-
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
.
|
|
1612
|
-
.card.total { background: var(--color-info); }
|
|
1613
|
-
.card .card-value { font-size: 28px; }
|
|
1614
|
-
.card .card-label { font-size: 13px; opacity: 0.9; }
|
|
1615
|
-
.pass-rate {
|
|
1616
|
-
font-size: 48px; font-weight: 700; text-align: center; margin: 20px 0;
|
|
1617
|
-
color: ${parseFloat(passRate) >= 80 ? "var(--color-passed)" : parseFloat(passRate) >= 50 ? "var(--color-skipped)" : "var(--color-failed)"};
|
|
1618
|
-
}
|
|
1619
|
-
.pass-rate-label { text-align: center; color: var(--text-secondary); margin-bottom: 20px; }
|
|
1620
|
-
.by-type { display: flex; gap: 12px; flex-wrap: wrap; margin: 20px 0; }
|
|
1621
|
-
.type-card {
|
|
1622
|
-
background: white; border: 1px solid var(--border-color); border-radius: 8px;
|
|
1623
|
-
padding: 12px 16px; min-width: 180px;
|
|
1624
|
-
}
|
|
1625
|
-
.type-name { font-weight: 600; text-transform: capitalize; margin-bottom: 4px; }
|
|
1626
|
-
.type-stats { font-size: 13px; }
|
|
1627
|
-
.type-stats span { margin-right: 8px; }
|
|
1628
|
-
.type-stats .passed { color: var(--color-passed); }
|
|
1629
|
-
.type-stats .failed { color: var(--color-failed); }
|
|
1630
|
-
.type-stats .skipped { color: var(--color-skipped); }
|
|
1631
|
-
.type-rate { font-size: 20px; font-weight: 700; margin-top: 4px; }
|
|
1632
|
-
.suite {
|
|
1633
|
-
background: white; border: 1px solid var(--border-color);
|
|
1634
|
-
border-radius: 8px; margin-bottom: 12px; overflow: hidden;
|
|
1635
|
-
}
|
|
1636
|
-
.suite-header {
|
|
1637
|
-
padding: 12px 16px; display: flex; justify-content: space-between;
|
|
1638
|
-
align-items: center; cursor: pointer; user-select: none;
|
|
1639
|
-
}
|
|
1640
|
-
.suite-header:hover { background: var(--bg-secondary); }
|
|
1641
|
-
.suite-header div { display: flex; align-items: center; gap: 8px; }
|
|
1642
|
-
.suite.collapsed .suite-body { display: none; }
|
|
1643
|
-
.suite-file { color: var(--text-secondary); font-size: 12px; }
|
|
1644
|
-
.toggle-icon { font-size: 12px; color: var(--text-secondary); transition: transform 0.2s; }
|
|
1645
|
-
.suite.collapsed .toggle-icon { transform: rotate(-90deg); }
|
|
1646
|
-
.suite-body { border-top: 1px solid var(--border-color); padding: 8px 16px; }
|
|
1647
|
-
.test-item {
|
|
1648
|
-
padding: 8px 0; display: flex; align-items: center; gap: 8px;
|
|
1649
|
-
}
|
|
1650
|
-
.test-item + .test-item { border-top: 1px solid var(--border-color); }
|
|
1651
|
-
.status-dot {
|
|
1652
|
-
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
|
|
1653
|
-
}
|
|
1654
|
-
.status-dot.passed { background: var(--color-passed); }
|
|
1655
|
-
.status-dot.failed { background: var(--color-failed); }
|
|
1656
|
-
.status-dot.skipped { background: var(--color-skipped); }
|
|
1657
|
-
.status-dot.pending { background: var(--text-secondary); }
|
|
1658
|
-
.test-name { flex: 1; }
|
|
1659
|
-
.duration { color: var(--text-secondary); font-size: 13px; }
|
|
1660
|
-
.retries { color: var(--color-skipped); font-size: 12px; }
|
|
1661
|
-
.error-message {
|
|
1662
|
-
background: #fef2f2; color: #991b1b; padding: 12px;
|
|
1663
|
-
border-radius: 4px; font-family: 'Fira Code', monospace;
|
|
1664
|
-
font-size: 13px; margin: 8px 0 8px 16px; white-space: pre-wrap;
|
|
1665
|
-
word-break: break-all;
|
|
1666
|
-
}
|
|
1667
|
-
footer {
|
|
1668
|
-
text-align: center; padding: 20px; color: var(--text-secondary);
|
|
1669
|
-
font-size: 13px; margin-top: 40px;
|
|
1670
|
-
}
|
|
1683
|
+
max-width: 960px; margin: 0 auto; padding: 20px;
|
|
1684
|
+
background: #f8fafc; color: #1e293b; line-height: 1.6;
|
|
1685
|
+
}
|
|
1686
|
+
h1 { border-bottom: 2px solid #3b82f6; padding-bottom: 8px; }
|
|
1687
|
+
h2 { margin-top: 24px; color: #1e40af; }
|
|
1688
|
+
h3 { margin-top: 16px; color: #334155; }
|
|
1689
|
+
h4 { margin-top: 12px; color: #475569; }
|
|
1690
|
+
blockquote { color: #64748b; border-left: 3px solid #cbd5e1; padding-left: 12px; margin: 8px 0; }
|
|
1691
|
+
table { border-collapse: collapse; width: 100%; margin: 12px 0; }
|
|
1692
|
+
td, th { border: 1px solid #e2e8f0; padding: 8px 12px; text-align: left; }
|
|
1693
|
+
tr:nth-child(even) { background: #f1f5f9; }
|
|
1694
|
+
code { background: #e2e8f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }
|
|
1695
|
+
pre { background: #1e293b; color: #e2e8f0; padding: 16px; border-radius: 8px; overflow-x: auto; }
|
|
1696
|
+
pre code { background: none; padding: 0; }
|
|
1697
|
+
hr { border: none; border-top: 1px solid #e2e8f0; margin: 24px 0; }
|
|
1698
|
+
details { margin: 8px 0; }
|
|
1699
|
+
summary { cursor: pointer; color: #ef4444; font-weight: 600; }
|
|
1700
|
+
strong { color: #1e293b; }
|
|
1701
|
+
.pass-rate { font-size: 48px; font-weight: 700; text-align: center; margin: 20px 0;
|
|
1702
|
+
color: ${parseFloat(passRate) >= 80 ? "#22c55e" : parseFloat(passRate) >= 50 ? "#f59e0b" : "#ef4444"};
|
|
1703
|
+
}
|
|
1704
|
+
.pass-rate-label { text-align: center; color: #64748b; margin-bottom: 20px; }
|
|
1671
1705
|
</style>
|
|
1672
1706
|
</head>
|
|
1673
1707
|
<body>
|
|
1674
|
-
<div class="
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
<div class="meta">\u751F\u6210\u65F6\u95F4: ${formatTimestamp(data.timestamp)} | \u603B\u8017\u65F6: ${formatDuration(data.duration)}</div>
|
|
1678
|
-
</header>
|
|
1679
|
-
|
|
1680
|
-
<div class="pass-rate">${passRate}%</div>
|
|
1681
|
-
<div class="pass-rate-label">\u6D4B\u8BD5\u901A\u8FC7\u7387</div>
|
|
1682
|
-
|
|
1683
|
-
<div class="summary">
|
|
1684
|
-
<div class="card total">
|
|
1685
|
-
<div class="card-value">${data.summary.total}</div>
|
|
1686
|
-
<div class="card-label">\u603B\u8BA1</div>
|
|
1687
|
-
</div>
|
|
1688
|
-
<div class="card passed">
|
|
1689
|
-
<div class="card-value">${data.summary.passed}</div>
|
|
1690
|
-
<div class="card-label">\u901A\u8FC7</div>
|
|
1691
|
-
</div>
|
|
1692
|
-
<div class="card failed">
|
|
1693
|
-
<div class="card-value">${data.summary.failed}</div>
|
|
1694
|
-
<div class="card-label">\u5931\u8D25</div>
|
|
1695
|
-
</div>
|
|
1696
|
-
<div class="card skipped">
|
|
1697
|
-
<div class="card-value">${data.summary.skipped}</div>
|
|
1698
|
-
<div class="card-label">\u8DF3\u8FC7</div>
|
|
1699
|
-
</div>
|
|
1700
|
-
</div>
|
|
1701
|
-
|
|
1702
|
-
${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>` : ""}
|
|
1703
|
-
|
|
1704
|
-
<h2 style="margin-top:30px;margin-bottom:10px;">\u6D4B\u8BD5\u8BE6\u60C5</h2>
|
|
1705
|
-
${suitesHTML || '<p style="color:var(--text-secondary)">\u6682\u65E0\u6D4B\u8BD5\u7ED3\u679C</p>'}
|
|
1706
|
-
|
|
1707
|
-
<footer>\u7531 QAT \u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177\u751F\u6210</footer>
|
|
1708
|
-
</div>
|
|
1708
|
+
<div class="pass-rate">${passRate}%</div>
|
|
1709
|
+
<div class="pass-rate-label">\u6D4B\u8BD5\u901A\u8FC7\u7387</div>
|
|
1710
|
+
${html}
|
|
1709
1711
|
</body>
|
|
1710
1712
|
</html>`;
|
|
1711
1713
|
}
|
|
1712
|
-
function writeReportToDisk(data, outputDir) {
|
|
1713
|
-
const html = generateHTMLReport(data);
|
|
1714
|
-
const dir = import_node_path5.default.resolve(outputDir);
|
|
1715
|
-
if (!import_node_fs5.default.existsSync(dir)) {
|
|
1716
|
-
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
1717
|
-
}
|
|
1718
|
-
const indexPath = import_node_path5.default.join(dir, "index.html");
|
|
1719
|
-
import_node_fs5.default.writeFileSync(indexPath, html, "utf-8");
|
|
1720
|
-
return indexPath;
|
|
1721
|
-
}
|
|
1722
1714
|
|
|
1723
1715
|
// src/runners/vitest-runner.ts
|
|
1724
1716
|
var import_node_child_process = require("child_process");
|
|
@@ -1793,34 +1785,109 @@ function buildVitestArgs(options) {
|
|
|
1793
1785
|
return args;
|
|
1794
1786
|
}
|
|
1795
1787
|
async function execVitest(args) {
|
|
1788
|
+
const os = await import("os");
|
|
1789
|
+
const fs9 = await import("fs");
|
|
1790
|
+
const tmpFile = import_node_path6.default.join(os.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
|
|
1791
|
+
const argsWithOutput = [...args, "--outputFile", tmpFile];
|
|
1796
1792
|
return new Promise((resolve, reject) => {
|
|
1797
1793
|
const npx = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
1798
|
-
const child = (0, import_node_child_process.execFile)(npx,
|
|
1794
|
+
const child = (0, import_node_child_process.execFile)(npx, argsWithOutput, {
|
|
1799
1795
|
cwd: process.cwd(),
|
|
1800
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1796
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
1801
1797
|
maxBuffer: 50 * 1024 * 1024,
|
|
1802
|
-
// 50MB
|
|
1803
1798
|
shell: true
|
|
1804
1799
|
}, (error, stdout, stderr) => {
|
|
1805
|
-
const
|
|
1800
|
+
const rawOutput = stdout || stderr || "";
|
|
1801
|
+
let jsonResult = null;
|
|
1806
1802
|
try {
|
|
1807
|
-
|
|
1808
|
-
|
|
1803
|
+
if (fs9.existsSync(tmpFile)) {
|
|
1804
|
+
jsonResult = fs9.readFileSync(tmpFile, "utf-8");
|
|
1805
|
+
fs9.unlinkSync(tmpFile);
|
|
1806
|
+
}
|
|
1809
1807
|
} catch {
|
|
1810
|
-
|
|
1811
|
-
|
|
1808
|
+
}
|
|
1809
|
+
if (jsonResult) {
|
|
1810
|
+
try {
|
|
1811
|
+
const parsed = parseVitestJSONResult(jsonResult);
|
|
1812
|
+
resolve({ ...parsed, rawOutput });
|
|
1813
|
+
return;
|
|
1814
|
+
} catch {
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
try {
|
|
1818
|
+
const parsed = parseVitestJSONOutput(rawOutput);
|
|
1819
|
+
resolve({ ...parsed, rawOutput });
|
|
1820
|
+
} catch {
|
|
1821
|
+
if (rawOutput) {
|
|
1822
|
+
resolve({ ...parseVitestTextOutput(rawOutput, !!error), rawOutput });
|
|
1812
1823
|
} else if (error && error.message.includes("ENOENT")) {
|
|
1813
1824
|
reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
|
|
1814
1825
|
} else {
|
|
1815
|
-
resolve({ success: !error, suites: [] });
|
|
1826
|
+
resolve({ success: !error, suites: [], rawOutput });
|
|
1816
1827
|
}
|
|
1817
1828
|
}
|
|
1818
1829
|
});
|
|
1819
1830
|
child.on("error", (err) => {
|
|
1831
|
+
try {
|
|
1832
|
+
fs9.unlinkSync(tmpFile);
|
|
1833
|
+
} catch {
|
|
1834
|
+
}
|
|
1820
1835
|
reject(new Error(`Vitest \u6267\u884C\u5931\u8D25: ${err.message}`));
|
|
1821
1836
|
});
|
|
1822
1837
|
});
|
|
1823
1838
|
}
|
|
1839
|
+
function parseVitestJSONResult(jsonStr) {
|
|
1840
|
+
const data = JSON.parse(jsonStr);
|
|
1841
|
+
const suites = [];
|
|
1842
|
+
if (data.testResults && Array.isArray(data.testResults)) {
|
|
1843
|
+
for (const fileResult of data.testResults) {
|
|
1844
|
+
const suiteTests = [];
|
|
1845
|
+
const assertions = fileResult.assertionResults || fileResult.tests || [];
|
|
1846
|
+
for (const assertion of assertions) {
|
|
1847
|
+
suiteTests.push({
|
|
1848
|
+
name: assertion.title || assertion.fullName || assertion.name || "unknown",
|
|
1849
|
+
file: fileResult.name || "unknown",
|
|
1850
|
+
status: mapVitestStatus(assertion.status),
|
|
1851
|
+
duration: assertion.duration || 0,
|
|
1852
|
+
error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : assertion.failureMessage ? { message: assertion.failureMessage } : void 0,
|
|
1853
|
+
retries: 0
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
if (suiteTests.length === 0 && fileResult.numPassingTests !== void 0) {
|
|
1857
|
+
const counts = [
|
|
1858
|
+
{ n: fileResult.numPassingTests || 0, s: "passed" },
|
|
1859
|
+
{ n: fileResult.numFailingTests || 0, s: "failed" },
|
|
1860
|
+
{ n: fileResult.numPendingTests || 0, s: "skipped" }
|
|
1861
|
+
];
|
|
1862
|
+
for (const { n, s } of counts) {
|
|
1863
|
+
for (let i = 0; i < n; i++) {
|
|
1864
|
+
suiteTests.push({
|
|
1865
|
+
name: `${s} test ${i + 1}`,
|
|
1866
|
+
file: fileResult.name || "unknown",
|
|
1867
|
+
status: s,
|
|
1868
|
+
duration: 0,
|
|
1869
|
+
retries: 0
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
suites.push({
|
|
1875
|
+
name: import_node_path6.default.basename(fileResult.name || "unknown"),
|
|
1876
|
+
file: fileResult.name || "unknown",
|
|
1877
|
+
type: "unit",
|
|
1878
|
+
status: mapVitestStatus(fileResult.status),
|
|
1879
|
+
duration: fileResult.duration || 0,
|
|
1880
|
+
tests: suiteTests
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
let coverage;
|
|
1885
|
+
if (data.coverageMap) {
|
|
1886
|
+
coverage = extractCoverage(data.coverageMap);
|
|
1887
|
+
}
|
|
1888
|
+
const success = data.success !== false && data.numFailedTests === void 0 ? suites.every((s) => s.status !== "failed") : (data.numFailedTests || 0) === 0;
|
|
1889
|
+
return { success, suites, coverage };
|
|
1890
|
+
}
|
|
1824
1891
|
function parseVitestJSONOutput(output) {
|
|
1825
1892
|
const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
|
|
1826
1893
|
if (!jsonMatch) {
|