qat-cli 0.2.98 → 0.3.2
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 +1455 -1414
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +440 -267
- 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 +440 -267
- 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,162 +1678,60 @@ 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");
|
|
1725
1717
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
1718
|
+
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
1719
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
1720
|
+
var isVerbose = () => process.env.QAT_VERBOSE === "true";
|
|
1721
|
+
function debug(label, ...args) {
|
|
1722
|
+
if (isVerbose()) {
|
|
1723
|
+
console.log(`\x1B[90m [debug:${label}]\x1B[0m`, ...args);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
1726
|
async function runVitest(options) {
|
|
1727
1727
|
const startTime = Date.now();
|
|
1728
1728
|
const args = buildVitestArgs(options);
|
|
1729
|
+
debug("vitest", "\u547D\u4EE4\u53C2\u6570:", args.join(" "));
|
|
1729
1730
|
try {
|
|
1730
1731
|
const result = await execVitest(args);
|
|
1731
1732
|
const endTime = Date.now();
|
|
1733
|
+
debug("vitest", `\u89E3\u6790\u7ED3\u679C: ${result.suites.length} \u4E2A\u5957\u4EF6, ${result.suites.reduce((s, su) => s + su.tests.length, 0)} \u4E2A\u7528\u4F8B`);
|
|
1734
|
+
debug("vitest", "\u89E3\u6790\u65B9\u5F0F:", result.parseMethod);
|
|
1732
1735
|
return {
|
|
1733
1736
|
type: options.type,
|
|
1734
1737
|
status: result.success ? "passed" : "failed",
|
|
@@ -1793,83 +1796,228 @@ function buildVitestArgs(options) {
|
|
|
1793
1796
|
return args;
|
|
1794
1797
|
}
|
|
1795
1798
|
async function execVitest(args) {
|
|
1799
|
+
const tmpFile = import_node_path6.default.join(import_node_os.default.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
|
|
1800
|
+
const argsWithOutput = [...args, "--outputFile", tmpFile];
|
|
1796
1801
|
return new Promise((resolve, reject) => {
|
|
1797
1802
|
const npx = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
1798
|
-
|
|
1803
|
+
debug("vitest", "\u6267\u884C\u547D\u4EE4:", npx, argsWithOutput.join(" "));
|
|
1804
|
+
const child = (0, import_node_child_process.execFile)(npx, argsWithOutput, {
|
|
1799
1805
|
cwd: process.cwd(),
|
|
1800
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1806
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
1801
1807
|
maxBuffer: 50 * 1024 * 1024,
|
|
1802
|
-
// 50MB
|
|
1803
1808
|
shell: true
|
|
1804
1809
|
}, (error, stdout, stderr) => {
|
|
1805
|
-
const
|
|
1810
|
+
const rawOutput = stdout || stderr || "";
|
|
1811
|
+
const exitCode = error && "code" in error ? error.code : 0;
|
|
1812
|
+
debug("vitest", `\u9000\u51FA\u7801: ${exitCode}`);
|
|
1813
|
+
debug("vitest", `stdout \u957F\u5EA6: ${stdout?.length || 0}, stderr \u957F\u5EA6: ${stderr?.length || 0}`);
|
|
1814
|
+
let jsonResult = null;
|
|
1806
1815
|
try {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
} else if (error && error.message.includes("ENOENT")) {
|
|
1813
|
-
reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
|
|
1816
|
+
if (import_node_fs6.default.existsSync(tmpFile)) {
|
|
1817
|
+
jsonResult = import_node_fs6.default.readFileSync(tmpFile, "utf-8");
|
|
1818
|
+
debug("vitest", `\u4ECE\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5230 JSON (${jsonResult.length} \u5B57\u7B26)`);
|
|
1819
|
+
debug("vitest", "JSON \u524D 500 \u5B57\u7B26:", jsonResult.substring(0, 500));
|
|
1820
|
+
import_node_fs6.default.unlinkSync(tmpFile);
|
|
1814
1821
|
} else {
|
|
1815
|
-
|
|
1822
|
+
debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u4E0D\u5B58\u5728:", tmpFile);
|
|
1816
1823
|
}
|
|
1824
|
+
} catch (e) {
|
|
1825
|
+
debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25:", e instanceof Error ? e.message : String(e));
|
|
1817
1826
|
}
|
|
1827
|
+
if (jsonResult) {
|
|
1828
|
+
try {
|
|
1829
|
+
const parsed = parseVitestJSON(jsonResult);
|
|
1830
|
+
debug("vitest", "\u4ECE\u4E34\u65F6\u6587\u4EF6\u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
|
|
1831
|
+
resolve({ ...parsed, rawOutput, parseMethod: "outputFile-JSON" });
|
|
1832
|
+
return;
|
|
1833
|
+
} catch (e) {
|
|
1834
|
+
debug("vitest", "\u4E34\u65F6\u6587\u4EF6 JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
debug("vitest", "\u5C1D\u8BD5\u4ECE stdout \u63D0\u53D6 JSON...");
|
|
1838
|
+
try {
|
|
1839
|
+
const parsed = parseFromStdout(rawOutput);
|
|
1840
|
+
debug("vitest", "\u4ECE stdout \u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
|
|
1841
|
+
resolve({ ...parsed, rawOutput, parseMethod: "stdout-JSON" });
|
|
1842
|
+
return;
|
|
1843
|
+
} catch (e) {
|
|
1844
|
+
debug("vitest", "stdout JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
|
|
1845
|
+
}
|
|
1846
|
+
debug("vitest", "\u5C1D\u8BD5\u4ECE\u6587\u672C\u8F93\u51FA\u89E3\u6790...");
|
|
1847
|
+
if (rawOutput) {
|
|
1848
|
+
const parsed = parseVitestTextOutput(rawOutput, !!error);
|
|
1849
|
+
debug("vitest", "\u6587\u672C\u89E3\u6790\u7ED3\u679C:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
|
|
1850
|
+
resolve({ ...parsed, rawOutput, parseMethod: "text-fallback" });
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (error && error.message.includes("ENOENT")) {
|
|
1854
|
+
reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
debug("vitest", "\u6240\u6709\u89E3\u6790\u65B9\u5F0F\u5747\u5931\u8D25\uFF0C\u8FD4\u56DE\u7A7A\u7ED3\u679C");
|
|
1858
|
+
resolve({ success: !error, suites: [], rawOutput, parseMethod: "none" });
|
|
1818
1859
|
});
|
|
1819
1860
|
child.on("error", (err) => {
|
|
1861
|
+
try {
|
|
1862
|
+
import_node_fs6.default.unlinkSync(tmpFile);
|
|
1863
|
+
} catch {
|
|
1864
|
+
}
|
|
1820
1865
|
reject(new Error(`Vitest \u6267\u884C\u5931\u8D25: ${err.message}`));
|
|
1821
1866
|
});
|
|
1822
1867
|
});
|
|
1823
1868
|
}
|
|
1824
|
-
function
|
|
1825
|
-
const
|
|
1826
|
-
|
|
1827
|
-
|
|
1869
|
+
function parseVitestJSON(jsonStr) {
|
|
1870
|
+
const data = JSON.parse(jsonStr);
|
|
1871
|
+
const suites = [];
|
|
1872
|
+
debug("vitest-json", "JSON \u9876\u5C42\u5B57\u6BB5:", Object.keys(data).join(", "));
|
|
1873
|
+
if (data.testResults && Array.isArray(data.testResults)) {
|
|
1874
|
+
debug("vitest-json", `testResults \u6570\u91CF: ${data.testResults.length}`);
|
|
1875
|
+
for (const fileResult of data.testResults) {
|
|
1876
|
+
const suiteTests = parseTestResults(fileResult);
|
|
1877
|
+
suites.push({
|
|
1878
|
+
name: import_node_path6.default.basename(fileResult.name || "unknown"),
|
|
1879
|
+
file: fileResult.name || "unknown",
|
|
1880
|
+
type: "unit",
|
|
1881
|
+
status: mapVitestStatus(fileResult.status),
|
|
1882
|
+
duration: fileResult.duration || 0,
|
|
1883
|
+
tests: suiteTests
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1828
1886
|
}
|
|
1829
|
-
|
|
1887
|
+
if (suites.length === 0 && data.numTotalTests !== void 0) {
|
|
1888
|
+
debug("vitest-json", `\u4F7F\u7528\u6C47\u603B\u683C\u5F0F: total=${data.numTotalTests}, passed=${data.numPassedTests}`);
|
|
1889
|
+
suites.push({
|
|
1890
|
+
name: "Vitest Results",
|
|
1891
|
+
file: "unknown",
|
|
1892
|
+
type: "unit",
|
|
1893
|
+
status: data.numFailedTests > 0 ? "failed" : "passed",
|
|
1894
|
+
duration: 0,
|
|
1895
|
+
tests: buildTestsFromSummary(data)
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
if (suites.length === 0 && data.suites && Array.isArray(data.suites)) {
|
|
1899
|
+
debug("vitest-json", `suites \u6570\u91CF: ${data.suites.length}`);
|
|
1900
|
+
for (const suiteData of data.suites) {
|
|
1901
|
+
const suiteTests = [];
|
|
1902
|
+
for (const test of suiteData.tests || []) {
|
|
1903
|
+
suiteTests.push({
|
|
1904
|
+
name: test.name || test.title || "unknown",
|
|
1905
|
+
file: test.file || suiteData.file || "unknown",
|
|
1906
|
+
status: mapVitestStatus(test.status || test.result?.status),
|
|
1907
|
+
duration: test.duration || test.result?.duration || 0,
|
|
1908
|
+
error: test.result?.errors?.[0] ? { message: test.result.errors[0].message || String(test.result.errors[0]) } : void 0,
|
|
1909
|
+
retries: 0
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
suites.push({
|
|
1913
|
+
name: suiteData.name || "unknown",
|
|
1914
|
+
file: suiteData.file || "unknown",
|
|
1915
|
+
type: "unit",
|
|
1916
|
+
status: suiteTests.some((t) => t.status === "failed") ? "failed" : "passed",
|
|
1917
|
+
duration: 0,
|
|
1918
|
+
tests: suiteTests
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
let coverage;
|
|
1923
|
+
if (data.coverageMap) {
|
|
1924
|
+
coverage = extractCoverage(data.coverageMap);
|
|
1925
|
+
}
|
|
1926
|
+
if (!coverage && data.coverage && typeof data.coverage === "object") {
|
|
1927
|
+
const cov = data.coverage;
|
|
1928
|
+
const totals = cov.totals || cov;
|
|
1929
|
+
const getVal = (key) => {
|
|
1930
|
+
const v = totals[key];
|
|
1931
|
+
return typeof v === "number" ? v : typeof v === "object" && v !== null && "pct" in v ? v.pct / 100 : 0;
|
|
1932
|
+
};
|
|
1933
|
+
coverage = {
|
|
1934
|
+
lines: getVal("lines"),
|
|
1935
|
+
statements: getVal("statements"),
|
|
1936
|
+
functions: getVal("functions"),
|
|
1937
|
+
branches: getVal("branches")
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
const success = data.success !== false ? data.numFailedTests !== void 0 ? data.numFailedTests === 0 : suites.every((s) => s.status !== "failed") : false;
|
|
1941
|
+
return { success, suites, coverage };
|
|
1942
|
+
}
|
|
1943
|
+
function parseTestResults(fileResult) {
|
|
1944
|
+
const tests = [];
|
|
1945
|
+
const assertions = fileResult.assertionResults || fileResult.tests || [];
|
|
1946
|
+
for (const assertion of assertions) {
|
|
1947
|
+
tests.push({
|
|
1948
|
+
name: assertion.title || assertion.fullName || assertion.name || "unknown",
|
|
1949
|
+
file: fileResult.name || "unknown",
|
|
1950
|
+
status: mapVitestStatus(assertion.status),
|
|
1951
|
+
duration: assertion.duration || 0,
|
|
1952
|
+
error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : assertion.failureMessage ? { message: assertion.failureMessage } : void 0,
|
|
1953
|
+
retries: 0
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
if (tests.length === 0 && fileResult.numPassingTests !== void 0) {
|
|
1957
|
+
tests.push(...buildTestsFromSummary(fileResult));
|
|
1958
|
+
}
|
|
1959
|
+
return tests;
|
|
1960
|
+
}
|
|
1961
|
+
function buildTestsFromSummary(data) {
|
|
1962
|
+
const tests = [];
|
|
1963
|
+
const counts = [
|
|
1964
|
+
[data.numPassedTests || 0, "passed"],
|
|
1965
|
+
[data.numFailedTests || 0, "failed"],
|
|
1966
|
+
[data.numPendingTests || 0, "skipped"]
|
|
1967
|
+
];
|
|
1968
|
+
for (const [n, s] of counts) {
|
|
1969
|
+
for (let i = 0; i < n; i++) {
|
|
1970
|
+
tests.push({
|
|
1971
|
+
name: `${s} test ${i + 1}`,
|
|
1972
|
+
file: data.name || "unknown",
|
|
1973
|
+
status: s,
|
|
1974
|
+
duration: 0,
|
|
1975
|
+
retries: 0
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return tests;
|
|
1980
|
+
}
|
|
1981
|
+
function parseFromStdout(output) {
|
|
1982
|
+
const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
|
|
1983
|
+
if (jsonMatch) {
|
|
1830
1984
|
const data = JSON.parse(jsonMatch[0]);
|
|
1831
1985
|
const suites = [];
|
|
1832
1986
|
if (data.testResults && Array.isArray(data.testResults)) {
|
|
1833
1987
|
for (const fileResult of data.testResults) {
|
|
1834
|
-
|
|
1835
|
-
name: import_node_path6.default.basename(fileResult.name ||
|
|
1988
|
+
suites.push({
|
|
1989
|
+
name: import_node_path6.default.basename(fileResult.name || "unknown"),
|
|
1836
1990
|
file: fileResult.name || "unknown",
|
|
1837
1991
|
type: "unit",
|
|
1838
1992
|
status: mapVitestStatus(fileResult.status),
|
|
1839
1993
|
duration: fileResult.duration || 0,
|
|
1840
|
-
tests: (fileResult
|
|
1841
|
-
|
|
1842
|
-
file: fileResult.name || "unknown",
|
|
1843
|
-
status: mapVitestStatus(assertion.status),
|
|
1844
|
-
duration: assertion.duration || 0,
|
|
1845
|
-
error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : void 0,
|
|
1846
|
-
retries: 0
|
|
1847
|
-
}))
|
|
1848
|
-
};
|
|
1849
|
-
suites.push(suite);
|
|
1994
|
+
tests: parseTestResults(fileResult)
|
|
1995
|
+
});
|
|
1850
1996
|
}
|
|
1851
1997
|
}
|
|
1852
|
-
let coverage;
|
|
1853
|
-
if (data.coverageMap) {
|
|
1854
|
-
coverage = extractCoverage(data.coverageMap);
|
|
1855
|
-
}
|
|
1856
1998
|
const success = data.success !== false && suites.every((s) => s.status !== "failed");
|
|
1999
|
+
let coverage;
|
|
2000
|
+
if (data.coverageMap) coverage = extractCoverage(data.coverageMap);
|
|
1857
2001
|
return { success, suites, coverage };
|
|
1858
|
-
} catch {
|
|
1859
|
-
return parseVitestTextOutput(output, false);
|
|
1860
2002
|
}
|
|
2003
|
+
const anyJsonMatch = output.match(/\{[\s\S]*"numTotalTests"[\s\S]*\}/);
|
|
2004
|
+
if (anyJsonMatch) {
|
|
2005
|
+
return parseVitestJSON(anyJsonMatch[0]);
|
|
2006
|
+
}
|
|
2007
|
+
throw new Error("stdout \u4E2D\u672A\u627E\u5230\u6709\u6548 JSON");
|
|
1861
2008
|
}
|
|
1862
2009
|
function parseVitestTextOutput(output, hasError) {
|
|
1863
2010
|
const suites = [];
|
|
2011
|
+
debug("vitest-text", "\u6587\u672C\u8F93\u51FA\u524D 1000 \u5B57\u7B26:", output.substring(0, 1e3));
|
|
2012
|
+
const suiteRegex = /[✓✗×✕]\s+(.+\.test\.(ts|js)|.+\.spec\.(ts|js))\s*\((\d+)[^)]*\)/i;
|
|
2013
|
+
const lines = output.split("\n");
|
|
1864
2014
|
let totalPassed = 0;
|
|
1865
2015
|
let totalFailed = 0;
|
|
1866
|
-
const suiteRegex = /[✓✗×]\s+(.+\.test\.ts|.+\.spec\.ts)\s*\((\d+)\s+test/i;
|
|
1867
|
-
const lines = output.split("\n");
|
|
1868
2016
|
for (const line of lines) {
|
|
1869
2017
|
const match = line.match(suiteRegex);
|
|
1870
2018
|
if (match) {
|
|
1871
2019
|
const file = match[1];
|
|
1872
|
-
const testCount = parseInt(match[
|
|
2020
|
+
const testCount = parseInt(match[4], 10);
|
|
1873
2021
|
const isPassed = line.includes("\u2713");
|
|
1874
2022
|
if (isPassed) totalPassed += testCount;
|
|
1875
2023
|
else totalFailed += testCount;
|
|
@@ -1889,15 +2037,39 @@ function parseVitestTextOutput(output, hasError) {
|
|
|
1889
2037
|
});
|
|
1890
2038
|
}
|
|
1891
2039
|
}
|
|
2040
|
+
if (suites.length === 0) {
|
|
2041
|
+
const summaryMatch = output.match(/Tests\s+(\d+)\s+(passed|failed)/i);
|
|
2042
|
+
if (summaryMatch) {
|
|
2043
|
+
const count = parseInt(summaryMatch[1], 10);
|
|
2044
|
+
const status = summaryMatch[2].toLowerCase() === "passed" ? "passed" : "failed";
|
|
2045
|
+
suites.push({
|
|
2046
|
+
name: "Vitest Summary",
|
|
2047
|
+
file: "unknown",
|
|
2048
|
+
type: "unit",
|
|
2049
|
+
status,
|
|
2050
|
+
duration: 0,
|
|
2051
|
+
tests: Array.from({ length: count }, (_, i) => ({
|
|
2052
|
+
name: `test ${i + 1}`,
|
|
2053
|
+
file: "unknown",
|
|
2054
|
+
status,
|
|
2055
|
+
duration: 0,
|
|
2056
|
+
retries: 0
|
|
2057
|
+
}))
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
debug("vitest-text", `\u89E3\u6790\u5230 ${suites.length} \u4E2A\u5957\u4EF6, ${totalPassed} \u901A\u8FC7, ${totalFailed} \u5931\u8D25`);
|
|
1892
2062
|
return {
|
|
1893
2063
|
success: !hasError || totalFailed === 0,
|
|
1894
2064
|
suites
|
|
1895
2065
|
};
|
|
1896
2066
|
}
|
|
1897
2067
|
function mapVitestStatus(status) {
|
|
2068
|
+
if (!status) return "pending";
|
|
1898
2069
|
switch (status) {
|
|
1899
2070
|
case "passed":
|
|
1900
2071
|
case "pass":
|
|
2072
|
+
case "done":
|
|
1901
2073
|
return "passed";
|
|
1902
2074
|
case "failed":
|
|
1903
2075
|
case "fail":
|
|
@@ -1905,6 +2077,7 @@ function mapVitestStatus(status) {
|
|
|
1905
2077
|
case "skipped":
|
|
1906
2078
|
case "skip":
|
|
1907
2079
|
case "pending":
|
|
2080
|
+
case "todo":
|
|
1908
2081
|
return "skipped";
|
|
1909
2082
|
default:
|
|
1910
2083
|
return "pending";
|
|
@@ -2841,7 +3014,7 @@ async function testAIConnection(config) {
|
|
|
2841
3014
|
}
|
|
2842
3015
|
|
|
2843
3016
|
// src/services/mock-server.ts
|
|
2844
|
-
var
|
|
3017
|
+
var import_node_fs7 = __toESM(require("fs"), 1);
|
|
2845
3018
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
2846
3019
|
var serverState = {
|
|
2847
3020
|
running: false,
|
|
@@ -2854,18 +3027,18 @@ function getMockServerState() {
|
|
|
2854
3027
|
}
|
|
2855
3028
|
async function loadMockRoutes(routesDir) {
|
|
2856
3029
|
const absDir = import_node_path8.default.resolve(process.cwd(), routesDir);
|
|
2857
|
-
if (!
|
|
3030
|
+
if (!import_node_fs7.default.existsSync(absDir)) {
|
|
2858
3031
|
return [];
|
|
2859
3032
|
}
|
|
2860
3033
|
const routes = [];
|
|
2861
|
-
const entries =
|
|
3034
|
+
const entries = import_node_fs7.default.readdirSync(absDir, { withFileTypes: true });
|
|
2862
3035
|
for (const entry of entries) {
|
|
2863
3036
|
if (!entry.isFile()) continue;
|
|
2864
3037
|
const filePath = import_node_path8.default.join(absDir, entry.name);
|
|
2865
3038
|
const ext = import_node_path8.default.extname(entry.name);
|
|
2866
3039
|
try {
|
|
2867
3040
|
if (ext === ".json") {
|
|
2868
|
-
const content =
|
|
3041
|
+
const content = import_node_fs7.default.readFileSync(filePath, "utf-8");
|
|
2869
3042
|
const parsed = JSON.parse(content);
|
|
2870
3043
|
if (Array.isArray(parsed)) {
|
|
2871
3044
|
routes.push(...parsed);
|
|
@@ -3057,11 +3230,11 @@ function generateMockRouteTemplate(name) {
|
|
|
3057
3230
|
}
|
|
3058
3231
|
function initMockRoutesDir(routesDir) {
|
|
3059
3232
|
const absDir = import_node_path8.default.resolve(process.cwd(), routesDir);
|
|
3060
|
-
if (!
|
|
3061
|
-
|
|
3233
|
+
if (!import_node_fs7.default.existsSync(absDir)) {
|
|
3234
|
+
import_node_fs7.default.mkdirSync(absDir, { recursive: true });
|
|
3062
3235
|
}
|
|
3063
3236
|
const examplePath = import_node_path8.default.join(absDir, "example.json");
|
|
3064
|
-
if (!
|
|
3237
|
+
if (!import_node_fs7.default.existsSync(examplePath)) {
|
|
3065
3238
|
const exampleRoutes = [
|
|
3066
3239
|
{
|
|
3067
3240
|
method: "GET",
|
|
@@ -3085,24 +3258,24 @@ function initMockRoutesDir(routesDir) {
|
|
|
3085
3258
|
}
|
|
3086
3259
|
}
|
|
3087
3260
|
];
|
|
3088
|
-
|
|
3261
|
+
import_node_fs7.default.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), "utf-8");
|
|
3089
3262
|
}
|
|
3090
3263
|
}
|
|
3091
3264
|
|
|
3092
3265
|
// src/services/visual.ts
|
|
3093
|
-
var
|
|
3266
|
+
var import_node_fs8 = __toESM(require("fs"), 1);
|
|
3094
3267
|
var import_node_path9 = __toESM(require("path"), 1);
|
|
3095
3268
|
var import_pixelmatch = __toESM(require("pixelmatch"), 1);
|
|
3096
3269
|
var import_pngjs = require("pngjs");
|
|
3097
3270
|
function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
|
|
3098
|
-
if (!
|
|
3271
|
+
if (!import_node_fs8.default.existsSync(baselinePath)) {
|
|
3099
3272
|
throw new Error(`\u57FA\u7EBF\u56FE\u7247\u4E0D\u5B58\u5728: ${baselinePath}`);
|
|
3100
3273
|
}
|
|
3101
|
-
if (!
|
|
3274
|
+
if (!import_node_fs8.default.existsSync(currentPath)) {
|
|
3102
3275
|
throw new Error(`\u5F53\u524D\u56FE\u7247\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
3103
3276
|
}
|
|
3104
|
-
const baseline = import_pngjs.PNG.sync.read(
|
|
3105
|
-
const current = import_pngjs.PNG.sync.read(
|
|
3277
|
+
const baseline = import_pngjs.PNG.sync.read(import_node_fs8.default.readFileSync(baselinePath));
|
|
3278
|
+
const current = import_pngjs.PNG.sync.read(import_node_fs8.default.readFileSync(currentPath));
|
|
3106
3279
|
if (baseline.width !== current.width || baseline.height !== current.height) {
|
|
3107
3280
|
return {
|
|
3108
3281
|
passed: false,
|
|
@@ -3131,10 +3304,10 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
3131
3304
|
let diffPath;
|
|
3132
3305
|
if (diffPixels > 0) {
|
|
3133
3306
|
const diffDir = import_node_path9.default.dirname(diffOutputPath);
|
|
3134
|
-
if (!
|
|
3135
|
-
|
|
3307
|
+
if (!import_node_fs8.default.existsSync(diffDir)) {
|
|
3308
|
+
import_node_fs8.default.mkdirSync(diffDir, { recursive: true });
|
|
3136
3309
|
}
|
|
3137
|
-
|
|
3310
|
+
import_node_fs8.default.writeFileSync(diffOutputPath, import_pngjs.PNG.sync.write(diff));
|
|
3138
3311
|
diffPath = diffOutputPath;
|
|
3139
3312
|
}
|
|
3140
3313
|
return {
|
|
@@ -3148,14 +3321,14 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
3148
3321
|
};
|
|
3149
3322
|
}
|
|
3150
3323
|
function createBaseline(currentPath, baselinePath) {
|
|
3151
|
-
if (!
|
|
3324
|
+
if (!import_node_fs8.default.existsSync(currentPath)) {
|
|
3152
3325
|
throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
3153
3326
|
}
|
|
3154
3327
|
const baselineDir = import_node_path9.default.dirname(baselinePath);
|
|
3155
|
-
if (!
|
|
3156
|
-
|
|
3328
|
+
if (!import_node_fs8.default.existsSync(baselineDir)) {
|
|
3329
|
+
import_node_fs8.default.mkdirSync(baselineDir, { recursive: true });
|
|
3157
3330
|
}
|
|
3158
|
-
|
|
3331
|
+
import_node_fs8.default.copyFileSync(currentPath, baselinePath);
|
|
3159
3332
|
return baselinePath;
|
|
3160
3333
|
}
|
|
3161
3334
|
function updateBaseline(currentPath, baselinePath) {
|
|
@@ -3163,69 +3336,69 @@ function updateBaseline(currentPath, baselinePath) {
|
|
|
3163
3336
|
}
|
|
3164
3337
|
function updateAllBaselines(currentDir, baselineDir) {
|
|
3165
3338
|
const updated = [];
|
|
3166
|
-
if (!
|
|
3339
|
+
if (!import_node_fs8.default.existsSync(currentDir)) {
|
|
3167
3340
|
return updated;
|
|
3168
3341
|
}
|
|
3169
|
-
const files =
|
|
3342
|
+
const files = import_node_fs8.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
3170
3343
|
for (const file of files) {
|
|
3171
3344
|
const currentPath = import_node_path9.default.join(currentDir, file);
|
|
3172
3345
|
const baselinePath = import_node_path9.default.join(baselineDir, file);
|
|
3173
3346
|
const baselineDirAbs = import_node_path9.default.dirname(baselinePath);
|
|
3174
|
-
if (!
|
|
3175
|
-
|
|
3347
|
+
if (!import_node_fs8.default.existsSync(baselineDirAbs)) {
|
|
3348
|
+
import_node_fs8.default.mkdirSync(baselineDirAbs, { recursive: true });
|
|
3176
3349
|
}
|
|
3177
|
-
|
|
3350
|
+
import_node_fs8.default.copyFileSync(currentPath, baselinePath);
|
|
3178
3351
|
updated.push(file);
|
|
3179
3352
|
}
|
|
3180
3353
|
return updated;
|
|
3181
3354
|
}
|
|
3182
3355
|
function cleanBaselines(baselineDir) {
|
|
3183
|
-
if (!
|
|
3356
|
+
if (!import_node_fs8.default.existsSync(baselineDir)) {
|
|
3184
3357
|
return 0;
|
|
3185
3358
|
}
|
|
3186
|
-
const files =
|
|
3359
|
+
const files = import_node_fs8.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
|
|
3187
3360
|
let count = 0;
|
|
3188
3361
|
for (const file of files) {
|
|
3189
|
-
|
|
3362
|
+
import_node_fs8.default.unlinkSync(import_node_path9.default.join(baselineDir, file));
|
|
3190
3363
|
count++;
|
|
3191
3364
|
}
|
|
3192
3365
|
return count;
|
|
3193
3366
|
}
|
|
3194
3367
|
function cleanDiffs(diffDir) {
|
|
3195
|
-
if (!
|
|
3368
|
+
if (!import_node_fs8.default.existsSync(diffDir)) {
|
|
3196
3369
|
return 0;
|
|
3197
3370
|
}
|
|
3198
|
-
const files =
|
|
3371
|
+
const files = import_node_fs8.default.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
|
|
3199
3372
|
let count = 0;
|
|
3200
3373
|
for (const file of files) {
|
|
3201
|
-
|
|
3374
|
+
import_node_fs8.default.unlinkSync(import_node_path9.default.join(diffDir, file));
|
|
3202
3375
|
count++;
|
|
3203
3376
|
}
|
|
3204
3377
|
return count;
|
|
3205
3378
|
}
|
|
3206
3379
|
function listBaselines(baselineDir) {
|
|
3207
|
-
if (!
|
|
3380
|
+
if (!import_node_fs8.default.existsSync(baselineDir)) {
|
|
3208
3381
|
return [];
|
|
3209
3382
|
}
|
|
3210
|
-
return
|
|
3383
|
+
return import_node_fs8.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png")).sort();
|
|
3211
3384
|
}
|
|
3212
3385
|
function listDiffs(diffDir) {
|
|
3213
|
-
if (!
|
|
3386
|
+
if (!import_node_fs8.default.existsSync(diffDir)) {
|
|
3214
3387
|
return [];
|
|
3215
3388
|
}
|
|
3216
|
-
return
|
|
3389
|
+
return import_node_fs8.default.readdirSync(diffDir).filter((f) => f.endsWith(".png")).sort();
|
|
3217
3390
|
}
|
|
3218
3391
|
function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
3219
3392
|
const results = [];
|
|
3220
|
-
if (!
|
|
3393
|
+
if (!import_node_fs8.default.existsSync(currentDir)) {
|
|
3221
3394
|
return results;
|
|
3222
3395
|
}
|
|
3223
|
-
const currentFiles =
|
|
3396
|
+
const currentFiles = import_node_fs8.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
3224
3397
|
for (const file of currentFiles) {
|
|
3225
3398
|
const currentPath = import_node_path9.default.join(currentDir, file);
|
|
3226
3399
|
const baselinePath = import_node_path9.default.join(baselineDir, file);
|
|
3227
3400
|
const diffPath = import_node_path9.default.join(diffDir, file);
|
|
3228
|
-
if (!
|
|
3401
|
+
if (!import_node_fs8.default.existsSync(baselinePath)) {
|
|
3229
3402
|
createBaseline(currentPath, baselinePath);
|
|
3230
3403
|
results.push({
|
|
3231
3404
|
passed: true,
|
|
@@ -3257,14 +3430,14 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
|
3257
3430
|
}
|
|
3258
3431
|
|
|
3259
3432
|
// src/services/source-analyzer.ts
|
|
3260
|
-
var
|
|
3433
|
+
var import_node_fs9 = __toESM(require("fs"), 1);
|
|
3261
3434
|
var import_node_path10 = __toESM(require("path"), 1);
|
|
3262
3435
|
function analyzeFile(filePath) {
|
|
3263
3436
|
const absolutePath = import_node_path10.default.resolve(process.cwd(), filePath);
|
|
3264
|
-
if (!
|
|
3437
|
+
if (!import_node_fs9.default.existsSync(absolutePath)) {
|
|
3265
3438
|
return { filePath, exports: [], apiCalls: [] };
|
|
3266
3439
|
}
|
|
3267
|
-
const content =
|
|
3440
|
+
const content = import_node_fs9.default.readFileSync(absolutePath, "utf-8");
|
|
3268
3441
|
const ext = import_node_path10.default.extname(filePath);
|
|
3269
3442
|
const result = {
|
|
3270
3443
|
filePath,
|
|
@@ -3619,13 +3792,13 @@ function cleanTemplateUrl(url) {
|
|
|
3619
3792
|
}
|
|
3620
3793
|
function scanAPICalls(srcDir) {
|
|
3621
3794
|
const absDir = import_node_path10.default.resolve(process.cwd(), srcDir);
|
|
3622
|
-
if (!
|
|
3795
|
+
if (!import_node_fs9.default.existsSync(absDir)) {
|
|
3623
3796
|
return [];
|
|
3624
3797
|
}
|
|
3625
3798
|
const allCalls = [];
|
|
3626
3799
|
const seen = /* @__PURE__ */ new Set();
|
|
3627
3800
|
function walk(dir) {
|
|
3628
|
-
const entries =
|
|
3801
|
+
const entries = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
|
|
3629
3802
|
for (const entry of entries) {
|
|
3630
3803
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") continue;
|
|
3631
3804
|
const fullPath = import_node_path10.default.join(dir, entry.name);
|
|
@@ -3633,7 +3806,7 @@ function scanAPICalls(srcDir) {
|
|
|
3633
3806
|
walk(fullPath);
|
|
3634
3807
|
} else if (entry.isFile() && /\.(ts|js|vue|mjs)$/.test(entry.name)) {
|
|
3635
3808
|
try {
|
|
3636
|
-
const content =
|
|
3809
|
+
const content = import_node_fs9.default.readFileSync(fullPath, "utf-8");
|
|
3637
3810
|
const calls = extractAPICalls(content, import_node_path10.default.relative(process.cwd(), fullPath));
|
|
3638
3811
|
for (const call of calls) {
|
|
3639
3812
|
const key = `${call.method}:${call.url}`;
|