qat-cli 0.2.6 → 0.2.8
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 +209 -80
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +6 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -3
- 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
|
@@ -467,17 +467,20 @@ async function loadConfig(configPath, forceReload = false) {
|
|
|
467
467
|
}
|
|
468
468
|
async function importConfig(filePath) {
|
|
469
469
|
const { resolve } = await import("path");
|
|
470
|
+
const { pathToFileURL } = await import("url");
|
|
470
471
|
const absolutePath = resolve(process.cwd(), filePath);
|
|
471
472
|
if (!fs2.existsSync(absolutePath)) {
|
|
472
473
|
throw new Error(`Cannot find module '${absolutePath}'`);
|
|
473
474
|
}
|
|
475
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
474
476
|
try {
|
|
475
|
-
const module = await import(
|
|
477
|
+
const module = await import(fileUrl);
|
|
476
478
|
return module.default || module;
|
|
477
479
|
} catch {
|
|
478
480
|
const jsPath = absolutePath.replace(/\.ts$/, ".js");
|
|
479
|
-
if (fs2.existsSync(jsPath)) {
|
|
480
|
-
const
|
|
481
|
+
if (jsPath !== absolutePath && fs2.existsSync(jsPath)) {
|
|
482
|
+
const jsUrl = pathToFileURL(jsPath).href;
|
|
483
|
+
const module = await import(jsUrl);
|
|
481
484
|
return module.default || module;
|
|
482
485
|
}
|
|
483
486
|
throw new Error(`\u65E0\u6CD5\u52A0\u8F7D\u914D\u7F6E\u6587\u4EF6: ${absolutePath}`);
|
|
@@ -3166,6 +3169,8 @@ function displayCreateResult(createdFiles, skippedCount, usedAI) {
|
|
|
3166
3169
|
import chalk5 from "chalk";
|
|
3167
3170
|
import inquirer3 from "inquirer";
|
|
3168
3171
|
import ora4 from "ora";
|
|
3172
|
+
import fs9 from "fs";
|
|
3173
|
+
import path11 from "path";
|
|
3169
3174
|
|
|
3170
3175
|
// src/runners/vitest-runner.ts
|
|
3171
3176
|
import { execFile } from "child_process";
|
|
@@ -3831,6 +3836,8 @@ function calculateAverageMetrics(results) {
|
|
|
3831
3836
|
}
|
|
3832
3837
|
|
|
3833
3838
|
// src/commands/run.ts
|
|
3839
|
+
var RESULTS_DIR = ".qat-results";
|
|
3840
|
+
var SERVER_REQUIRED_TYPES = ["e2e", "visual", "performance"];
|
|
3834
3841
|
var TYPE_RUNNERS = {
|
|
3835
3842
|
unit: "Vitest",
|
|
3836
3843
|
component: "Vitest",
|
|
@@ -3875,6 +3882,62 @@ async function executeRun(options) {
|
|
|
3875
3882
|
console.log(chalk5.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\uFF08\u8BF7\u5728 qat.config.ts \u4E2D\u542F\u7528\uFF09\n"));
|
|
3876
3883
|
return;
|
|
3877
3884
|
}
|
|
3885
|
+
const serverNeededTypes = typesToRun.filter((t) => SERVER_REQUIRED_TYPES.includes(t));
|
|
3886
|
+
if (serverNeededTypes.length > 0) {
|
|
3887
|
+
const serverOk = await checkDevServer(config);
|
|
3888
|
+
if (!serverOk) {
|
|
3889
|
+
const { action } = await inquirer3.prompt([
|
|
3890
|
+
{
|
|
3891
|
+
type: "list",
|
|
3892
|
+
name: "action",
|
|
3893
|
+
message: `\u8FD0\u884C ${serverNeededTypes.map((t) => TYPE_LABELS[t]).join("\u3001")} \u9700\u8981\u9879\u76EE\u670D\u52A1\u8FD0\u884C\uFF0C\u5982\u4F55\u5904\u7406\uFF1F`,
|
|
3894
|
+
choices: [
|
|
3895
|
+
{ name: "\u81EA\u52A8\u542F\u52A8 dev server \u5E76\u8FD0\u884C", value: "start" },
|
|
3896
|
+
{ name: "\u8DF3\u8FC7\u8FD9\u4E9B\u6D4B\u8BD5\u7C7B\u578B\uFF0C\u4EC5\u8FD0\u884C\u5176\u4ED6\u6D4B\u8BD5", value: "skip" },
|
|
3897
|
+
{ name: "\u53D6\u6D88\u8FD0\u884C", value: "cancel" }
|
|
3898
|
+
],
|
|
3899
|
+
default: "start"
|
|
3900
|
+
}
|
|
3901
|
+
]);
|
|
3902
|
+
if (action === "cancel") {
|
|
3903
|
+
console.log(chalk5.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
if (action === "skip") {
|
|
3907
|
+
typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));
|
|
3908
|
+
if (typesToRun.length === 0) {
|
|
3909
|
+
console.log(chalk5.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
if (action === "start") {
|
|
3914
|
+
const started = await startDevServer(config);
|
|
3915
|
+
if (!started) {
|
|
3916
|
+
const { fallback } = await inquirer3.prompt([
|
|
3917
|
+
{
|
|
3918
|
+
type: "list",
|
|
3919
|
+
name: "fallback",
|
|
3920
|
+
message: "dev server \u542F\u52A8\u5931\u8D25\uFF0C\u5982\u4F55\u5904\u7406\uFF1F",
|
|
3921
|
+
choices: [
|
|
3922
|
+
{ name: "\u8DF3\u8FC7\u9700\u8981\u670D\u52A1\u7684\u6D4B\u8BD5\uFF0C\u4EC5\u8FD0\u884C\u5176\u4ED6\u6D4B\u8BD5", value: "skip" },
|
|
3923
|
+
{ name: "\u53D6\u6D88\u8FD0\u884C", value: "cancel" }
|
|
3924
|
+
],
|
|
3925
|
+
default: "skip"
|
|
3926
|
+
}
|
|
3927
|
+
]);
|
|
3928
|
+
if (fallback === "cancel") {
|
|
3929
|
+
console.log(chalk5.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));
|
|
3933
|
+
if (typesToRun.length === 0) {
|
|
3934
|
+
console.log(chalk5.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3878
3941
|
if (!type && !file) {
|
|
3879
3942
|
const { runMode } = await inquirer3.prompt([
|
|
3880
3943
|
{
|
|
@@ -3921,6 +3984,7 @@ async function executeRun(options) {
|
|
|
3921
3984
|
}
|
|
3922
3985
|
}
|
|
3923
3986
|
displaySummary(results, config);
|
|
3987
|
+
saveRunResults(results);
|
|
3924
3988
|
}
|
|
3925
3989
|
function printDryRunCommands(types, options, config) {
|
|
3926
3990
|
console.log();
|
|
@@ -4248,6 +4312,71 @@ function scoreColor(score) {
|
|
|
4248
4312
|
if (score >= 50) return chalk5.yellow("\u25CF");
|
|
4249
4313
|
return chalk5.red("\u25CF");
|
|
4250
4314
|
}
|
|
4315
|
+
async function checkDevServer(config) {
|
|
4316
|
+
const baseUrl = config.playwright.baseURL || "http://localhost:5173";
|
|
4317
|
+
try {
|
|
4318
|
+
const controller = new AbortController();
|
|
4319
|
+
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
4320
|
+
const response = await fetch(baseUrl, { signal: controller.signal, method: "HEAD" });
|
|
4321
|
+
clearTimeout(timer);
|
|
4322
|
+
return response.status > 0;
|
|
4323
|
+
} catch {
|
|
4324
|
+
return false;
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
async function startDevServer(config) {
|
|
4328
|
+
const { spawn } = await import("child_process");
|
|
4329
|
+
const hasPnpm = fs9.existsSync(path11.join(process.cwd(), "pnpm-lock.yaml"));
|
|
4330
|
+
const hasYarn = fs9.existsSync(path11.join(process.cwd(), "yarn.lock"));
|
|
4331
|
+
const pkgCmd = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
|
|
4332
|
+
console.log(chalk5.cyan(` \u6B63\u5728\u542F\u52A8 dev server (${pkgCmd} run dev) ...`));
|
|
4333
|
+
const child = spawn(pkgCmd, ["run", "dev"], {
|
|
4334
|
+
cwd: process.cwd(),
|
|
4335
|
+
stdio: "pipe",
|
|
4336
|
+
shell: true,
|
|
4337
|
+
detached: true
|
|
4338
|
+
});
|
|
4339
|
+
const baseUrl = config.playwright.baseURL || "http://localhost:5173";
|
|
4340
|
+
const maxWait = 3e4;
|
|
4341
|
+
const interval = 1e3;
|
|
4342
|
+
let waited = 0;
|
|
4343
|
+
while (waited < maxWait) {
|
|
4344
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
4345
|
+
waited += interval;
|
|
4346
|
+
const isUp = await checkDevServer(config);
|
|
4347
|
+
if (isUp) {
|
|
4348
|
+
console.log(chalk5.green(` \u2713 dev server \u5DF2\u542F\u52A8 (${baseUrl})`));
|
|
4349
|
+
child.unref();
|
|
4350
|
+
return true;
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
child.kill();
|
|
4354
|
+
console.log(chalk5.red(" \u2717 dev server \u542F\u52A8\u8D85\u65F6"));
|
|
4355
|
+
return false;
|
|
4356
|
+
}
|
|
4357
|
+
function saveRunResults(results) {
|
|
4358
|
+
if (results.length === 0) return;
|
|
4359
|
+
const resultsPath = path11.join(process.cwd(), RESULTS_DIR);
|
|
4360
|
+
if (!fs9.existsSync(resultsPath)) {
|
|
4361
|
+
fs9.mkdirSync(resultsPath, { recursive: true });
|
|
4362
|
+
}
|
|
4363
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4364
|
+
const fileName = `result-${timestamp}.json`;
|
|
4365
|
+
const filePath = path11.join(resultsPath, fileName);
|
|
4366
|
+
const data = {
|
|
4367
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4368
|
+
results
|
|
4369
|
+
};
|
|
4370
|
+
fs9.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
4371
|
+
try {
|
|
4372
|
+
const files = fs9.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
|
|
4373
|
+
while (files.length > 20) {
|
|
4374
|
+
const oldest = files.shift();
|
|
4375
|
+
fs9.unlinkSync(path11.join(resultsPath, oldest));
|
|
4376
|
+
}
|
|
4377
|
+
} catch {
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4251
4380
|
|
|
4252
4381
|
// src/commands/mock.ts
|
|
4253
4382
|
import chalk6 from "chalk";
|
|
@@ -4371,12 +4500,12 @@ function showStatus() {
|
|
|
4371
4500
|
// src/commands/report.ts
|
|
4372
4501
|
import chalk7 from "chalk";
|
|
4373
4502
|
import ora6 from "ora";
|
|
4374
|
-
import
|
|
4375
|
-
import
|
|
4503
|
+
import fs11 from "fs";
|
|
4504
|
+
import path13 from "path";
|
|
4376
4505
|
|
|
4377
4506
|
// src/services/reporter.ts
|
|
4378
|
-
import
|
|
4379
|
-
import
|
|
4507
|
+
import fs10 from "fs";
|
|
4508
|
+
import path12 from "path";
|
|
4380
4509
|
function aggregateResults(results) {
|
|
4381
4510
|
const summary = {
|
|
4382
4511
|
total: 0,
|
|
@@ -4631,17 +4760,17 @@ function generateHTMLReport(data) {
|
|
|
4631
4760
|
}
|
|
4632
4761
|
function writeReportToDisk(data, outputDir) {
|
|
4633
4762
|
const html = generateHTMLReport(data);
|
|
4634
|
-
const dir =
|
|
4635
|
-
if (!
|
|
4636
|
-
|
|
4763
|
+
const dir = path12.resolve(outputDir);
|
|
4764
|
+
if (!fs10.existsSync(dir)) {
|
|
4765
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
4637
4766
|
}
|
|
4638
|
-
const indexPath =
|
|
4639
|
-
|
|
4767
|
+
const indexPath = path12.join(dir, "index.html");
|
|
4768
|
+
fs10.writeFileSync(indexPath, html, "utf-8");
|
|
4640
4769
|
return indexPath;
|
|
4641
4770
|
}
|
|
4642
4771
|
|
|
4643
4772
|
// src/commands/report.ts
|
|
4644
|
-
var
|
|
4773
|
+
var RESULTS_DIR2 = ".qat-results";
|
|
4645
4774
|
function registerReportCommand(program2) {
|
|
4646
4775
|
program2.command("report").description("\u751F\u6210\u6D4B\u8BD5\u62A5\u544A - \u805A\u5408\u6240\u6709\u6D4B\u8BD5\u7ED3\u679C\u5E76\u8F93\u51FAHTML").option("-o, --output <dir>", "\u62A5\u544A\u8F93\u51FA\u76EE\u5F55").option("--open", "\u751F\u6210\u540E\u81EA\u52A8\u6253\u5F00\u62A5\u544A", false).action(async (options) => {
|
|
4647
4776
|
try {
|
|
@@ -4676,15 +4805,15 @@ async function executeReport(options) {
|
|
|
4676
4805
|
}
|
|
4677
4806
|
function collectResults() {
|
|
4678
4807
|
const results = [];
|
|
4679
|
-
const resultsPath =
|
|
4680
|
-
if (!
|
|
4808
|
+
const resultsPath = path13.join(process.cwd(), RESULTS_DIR2);
|
|
4809
|
+
if (!fs11.existsSync(resultsPath)) {
|
|
4681
4810
|
return results;
|
|
4682
4811
|
}
|
|
4683
|
-
const files =
|
|
4812
|
+
const files = fs11.readdirSync(resultsPath).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
4684
4813
|
if (files.length > 0) {
|
|
4685
|
-
const latestFile =
|
|
4814
|
+
const latestFile = path13.join(resultsPath, files[0]);
|
|
4686
4815
|
try {
|
|
4687
|
-
const data = JSON.parse(
|
|
4816
|
+
const data = JSON.parse(fs11.readFileSync(latestFile, "utf-8"));
|
|
4688
4817
|
if (Array.isArray(data)) {
|
|
4689
4818
|
results.push(...data);
|
|
4690
4819
|
} else if (data.results) {
|
|
@@ -4696,22 +4825,22 @@ function collectResults() {
|
|
|
4696
4825
|
return results;
|
|
4697
4826
|
}
|
|
4698
4827
|
function saveResultToHistory(reportData) {
|
|
4699
|
-
const resultsPath =
|
|
4700
|
-
if (!
|
|
4701
|
-
|
|
4828
|
+
const resultsPath = path13.join(process.cwd(), RESULTS_DIR2);
|
|
4829
|
+
if (!fs11.existsSync(resultsPath)) {
|
|
4830
|
+
fs11.mkdirSync(resultsPath, { recursive: true });
|
|
4702
4831
|
}
|
|
4703
4832
|
const timestamp = new Date(reportData.timestamp).toISOString().replace(/[:.]/g, "-");
|
|
4704
4833
|
const fileName = `result-${timestamp}.json`;
|
|
4705
|
-
const filePath =
|
|
4706
|
-
|
|
4707
|
-
const files =
|
|
4834
|
+
const filePath = path13.join(resultsPath, fileName);
|
|
4835
|
+
fs11.writeFileSync(filePath, JSON.stringify(reportData, null, 2), "utf-8");
|
|
4836
|
+
const files = fs11.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
|
|
4708
4837
|
while (files.length > 20) {
|
|
4709
4838
|
const oldest = files.shift();
|
|
4710
|
-
|
|
4839
|
+
fs11.unlinkSync(path13.join(resultsPath, oldest));
|
|
4711
4840
|
}
|
|
4712
4841
|
}
|
|
4713
4842
|
function displayReportResult(reportPath, data) {
|
|
4714
|
-
const relativePath =
|
|
4843
|
+
const relativePath = path13.relative(process.cwd(), reportPath);
|
|
4715
4844
|
console.log();
|
|
4716
4845
|
console.log(chalk7.green(" \u2713 \u6D4B\u8BD5\u62A5\u544A\u5DF2\u751F\u6210"));
|
|
4717
4846
|
console.log();
|
|
@@ -4758,19 +4887,19 @@ import chalk8 from "chalk";
|
|
|
4758
4887
|
import ora7 from "ora";
|
|
4759
4888
|
|
|
4760
4889
|
// src/services/visual.ts
|
|
4761
|
-
import
|
|
4762
|
-
import
|
|
4890
|
+
import fs12 from "fs";
|
|
4891
|
+
import path14 from "path";
|
|
4763
4892
|
import pixelmatch from "pixelmatch";
|
|
4764
4893
|
import { PNG } from "pngjs";
|
|
4765
4894
|
function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
|
|
4766
|
-
if (!
|
|
4895
|
+
if (!fs12.existsSync(baselinePath)) {
|
|
4767
4896
|
throw new Error(`\u57FA\u7EBF\u56FE\u7247\u4E0D\u5B58\u5728: ${baselinePath}`);
|
|
4768
4897
|
}
|
|
4769
|
-
if (!
|
|
4898
|
+
if (!fs12.existsSync(currentPath)) {
|
|
4770
4899
|
throw new Error(`\u5F53\u524D\u56FE\u7247\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
4771
4900
|
}
|
|
4772
|
-
const baseline = PNG.sync.read(
|
|
4773
|
-
const current = PNG.sync.read(
|
|
4901
|
+
const baseline = PNG.sync.read(fs12.readFileSync(baselinePath));
|
|
4902
|
+
const current = PNG.sync.read(fs12.readFileSync(currentPath));
|
|
4774
4903
|
if (baseline.width !== current.width || baseline.height !== current.height) {
|
|
4775
4904
|
return {
|
|
4776
4905
|
passed: false,
|
|
@@ -4798,11 +4927,11 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
4798
4927
|
const passed = diffRatio <= threshold;
|
|
4799
4928
|
let diffPath;
|
|
4800
4929
|
if (diffPixels > 0) {
|
|
4801
|
-
const diffDir =
|
|
4802
|
-
if (!
|
|
4803
|
-
|
|
4930
|
+
const diffDir = path14.dirname(diffOutputPath);
|
|
4931
|
+
if (!fs12.existsSync(diffDir)) {
|
|
4932
|
+
fs12.mkdirSync(diffDir, { recursive: true });
|
|
4804
4933
|
}
|
|
4805
|
-
|
|
4934
|
+
fs12.writeFileSync(diffOutputPath, PNG.sync.write(diff));
|
|
4806
4935
|
diffPath = diffOutputPath;
|
|
4807
4936
|
}
|
|
4808
4937
|
return {
|
|
@@ -4816,69 +4945,69 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
4816
4945
|
};
|
|
4817
4946
|
}
|
|
4818
4947
|
function createBaseline(currentPath, baselinePath) {
|
|
4819
|
-
if (!
|
|
4948
|
+
if (!fs12.existsSync(currentPath)) {
|
|
4820
4949
|
throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
4821
4950
|
}
|
|
4822
|
-
const baselineDir =
|
|
4823
|
-
if (!
|
|
4824
|
-
|
|
4951
|
+
const baselineDir = path14.dirname(baselinePath);
|
|
4952
|
+
if (!fs12.existsSync(baselineDir)) {
|
|
4953
|
+
fs12.mkdirSync(baselineDir, { recursive: true });
|
|
4825
4954
|
}
|
|
4826
|
-
|
|
4955
|
+
fs12.copyFileSync(currentPath, baselinePath);
|
|
4827
4956
|
return baselinePath;
|
|
4828
4957
|
}
|
|
4829
4958
|
function updateAllBaselines(currentDir, baselineDir) {
|
|
4830
4959
|
const updated = [];
|
|
4831
|
-
if (!
|
|
4960
|
+
if (!fs12.existsSync(currentDir)) {
|
|
4832
4961
|
return updated;
|
|
4833
4962
|
}
|
|
4834
|
-
const files =
|
|
4963
|
+
const files = fs12.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
4835
4964
|
for (const file of files) {
|
|
4836
|
-
const currentPath =
|
|
4837
|
-
const baselinePath =
|
|
4838
|
-
const baselineDirAbs =
|
|
4839
|
-
if (!
|
|
4840
|
-
|
|
4965
|
+
const currentPath = path14.join(currentDir, file);
|
|
4966
|
+
const baselinePath = path14.join(baselineDir, file);
|
|
4967
|
+
const baselineDirAbs = path14.dirname(baselinePath);
|
|
4968
|
+
if (!fs12.existsSync(baselineDirAbs)) {
|
|
4969
|
+
fs12.mkdirSync(baselineDirAbs, { recursive: true });
|
|
4841
4970
|
}
|
|
4842
|
-
|
|
4971
|
+
fs12.copyFileSync(currentPath, baselinePath);
|
|
4843
4972
|
updated.push(file);
|
|
4844
4973
|
}
|
|
4845
4974
|
return updated;
|
|
4846
4975
|
}
|
|
4847
4976
|
function cleanBaselines(baselineDir) {
|
|
4848
|
-
if (!
|
|
4977
|
+
if (!fs12.existsSync(baselineDir)) {
|
|
4849
4978
|
return 0;
|
|
4850
4979
|
}
|
|
4851
|
-
const files =
|
|
4980
|
+
const files = fs12.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
|
|
4852
4981
|
let count = 0;
|
|
4853
4982
|
for (const file of files) {
|
|
4854
|
-
|
|
4983
|
+
fs12.unlinkSync(path14.join(baselineDir, file));
|
|
4855
4984
|
count++;
|
|
4856
4985
|
}
|
|
4857
4986
|
return count;
|
|
4858
4987
|
}
|
|
4859
4988
|
function cleanDiffs(diffDir) {
|
|
4860
|
-
if (!
|
|
4989
|
+
if (!fs12.existsSync(diffDir)) {
|
|
4861
4990
|
return 0;
|
|
4862
4991
|
}
|
|
4863
|
-
const files =
|
|
4992
|
+
const files = fs12.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
|
|
4864
4993
|
let count = 0;
|
|
4865
4994
|
for (const file of files) {
|
|
4866
|
-
|
|
4995
|
+
fs12.unlinkSync(path14.join(diffDir, file));
|
|
4867
4996
|
count++;
|
|
4868
4997
|
}
|
|
4869
4998
|
return count;
|
|
4870
4999
|
}
|
|
4871
5000
|
function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
4872
5001
|
const results = [];
|
|
4873
|
-
if (!
|
|
5002
|
+
if (!fs12.existsSync(currentDir)) {
|
|
4874
5003
|
return results;
|
|
4875
5004
|
}
|
|
4876
|
-
const currentFiles =
|
|
5005
|
+
const currentFiles = fs12.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
4877
5006
|
for (const file of currentFiles) {
|
|
4878
|
-
const currentPath =
|
|
4879
|
-
const baselinePath =
|
|
4880
|
-
const diffPath =
|
|
4881
|
-
if (!
|
|
5007
|
+
const currentPath = path14.join(currentDir, file);
|
|
5008
|
+
const baselinePath = path14.join(baselineDir, file);
|
|
5009
|
+
const diffPath = path14.join(diffDir, file);
|
|
5010
|
+
if (!fs12.existsSync(baselinePath)) {
|
|
4882
5011
|
createBaseline(currentPath, baselinePath);
|
|
4883
5012
|
results.push({
|
|
4884
5013
|
passed: true,
|
|
@@ -4910,8 +5039,8 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
|
4910
5039
|
}
|
|
4911
5040
|
|
|
4912
5041
|
// src/commands/visual.ts
|
|
4913
|
-
import
|
|
4914
|
-
import
|
|
5042
|
+
import fs13 from "fs";
|
|
5043
|
+
import path15 from "path";
|
|
4915
5044
|
function registerVisualCommand(program2) {
|
|
4916
5045
|
program2.command("visual").description("\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5 - \u622A\u56FE\u6BD4\u5BF9\u4E0E\u57FA\u7EBF\u7BA1\u7406").argument("<action>", "\u64CD\u4F5C\u7C7B\u578B (test|approve|clean)").option("--threshold <number>", "\u50CF\u7D20\u5DEE\u5F02\u9608\u503C (0-1)", "0.1").action(async (action, options) => {
|
|
4917
5046
|
try {
|
|
@@ -4986,12 +5115,12 @@ async function runVisualTest(threshold, baselineDir, diffDir, config) {
|
|
|
4986
5115
|
}
|
|
4987
5116
|
function findCurrentScreenshotsDir(baselineDir) {
|
|
4988
5117
|
const possibleDirs = [
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
5118
|
+
path15.join(process.cwd(), "test-results"),
|
|
5119
|
+
path15.join(process.cwd(), "tests", "visual", "current"),
|
|
5120
|
+
path15.join(process.cwd(), baselineDir, "..", "current")
|
|
4992
5121
|
];
|
|
4993
5122
|
for (const dir of possibleDirs) {
|
|
4994
|
-
if (
|
|
5123
|
+
if (fs13.existsSync(dir)) {
|
|
4995
5124
|
const pngs = findPngFiles(dir);
|
|
4996
5125
|
if (pngs.length > 0) return dir;
|
|
4997
5126
|
}
|
|
@@ -5001,10 +5130,10 @@ function findCurrentScreenshotsDir(baselineDir) {
|
|
|
5001
5130
|
function findPngFiles(dir) {
|
|
5002
5131
|
const files = [];
|
|
5003
5132
|
function walk(d) {
|
|
5004
|
-
if (!
|
|
5005
|
-
const entries =
|
|
5133
|
+
if (!fs13.existsSync(d)) return;
|
|
5134
|
+
const entries = fs13.readdirSync(d, { withFileTypes: true });
|
|
5006
5135
|
for (const entry of entries) {
|
|
5007
|
-
const fullPath =
|
|
5136
|
+
const fullPath = path15.join(d, entry.name);
|
|
5008
5137
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
5009
5138
|
walk(fullPath);
|
|
5010
5139
|
} else if (entry.name.endsWith(".png")) {
|
|
@@ -5033,7 +5162,7 @@ function displayVisualResults(results, threshold) {
|
|
|
5033
5162
|
console.log();
|
|
5034
5163
|
console.log(chalk8.green(" \u901A\u8FC7\u7684\u622A\u56FE:"));
|
|
5035
5164
|
for (const result of passed) {
|
|
5036
|
-
const name =
|
|
5165
|
+
const name = path15.basename(result.baselinePath);
|
|
5037
5166
|
if (result.totalPixels > 0) {
|
|
5038
5167
|
const diffPct = (result.diffRatio * 100).toFixed(2);
|
|
5039
5168
|
console.log(chalk8.green(` \u2713 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
@@ -5046,7 +5175,7 @@ function displayVisualResults(results, threshold) {
|
|
|
5046
5175
|
console.log();
|
|
5047
5176
|
console.log(chalk8.red(" \u5931\u8D25\u7684\u622A\u56FE:"));
|
|
5048
5177
|
for (const result of failed) {
|
|
5049
|
-
const name =
|
|
5178
|
+
const name = path15.basename(result.baselinePath);
|
|
5050
5179
|
if (result.diffPixels === -1) {
|
|
5051
5180
|
console.log(chalk8.red(` \u2717 ${name} (\u5C3A\u5BF8\u4E0D\u5339\u914D)`));
|
|
5052
5181
|
} else {
|
|
@@ -5054,7 +5183,7 @@ function displayVisualResults(results, threshold) {
|
|
|
5054
5183
|
console.log(chalk8.red(` \u2717 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
5055
5184
|
}
|
|
5056
5185
|
if (result.diffPath) {
|
|
5057
|
-
console.log(chalk8.gray(` \u5DEE\u5F02\u56FE: ${
|
|
5186
|
+
console.log(chalk8.gray(` \u5DEE\u5F02\u56FE: ${path15.relative(process.cwd(), result.diffPath)}`));
|
|
5058
5187
|
}
|
|
5059
5188
|
}
|
|
5060
5189
|
console.log();
|
|
@@ -5100,8 +5229,8 @@ import chalk9 from "chalk";
|
|
|
5100
5229
|
import inquirer4 from "inquirer";
|
|
5101
5230
|
import ora8 from "ora";
|
|
5102
5231
|
import { execFile as execFile4 } from "child_process";
|
|
5103
|
-
import
|
|
5104
|
-
import
|
|
5232
|
+
import fs14 from "fs";
|
|
5233
|
+
import path16 from "path";
|
|
5105
5234
|
var DEPENDENCY_GROUPS = [
|
|
5106
5235
|
{
|
|
5107
5236
|
name: "Vitest (\u5355\u5143/\u7EC4\u4EF6/API\u6D4B\u8BD5)",
|
|
@@ -5135,7 +5264,7 @@ function registerSetupCommand(program2) {
|
|
|
5135
5264
|
async function executeSetup(options) {
|
|
5136
5265
|
console.log(chalk9.cyan("\n QAT \u4F9D\u8D56\u5B89\u88C5\u5668\n"));
|
|
5137
5266
|
const projectInfo = detectProject();
|
|
5138
|
-
if (!
|
|
5267
|
+
if (!fs14.existsSync(path16.join(process.cwd(), "package.json"))) {
|
|
5139
5268
|
throw new Error("\u672A\u627E\u5230 package.json\uFF0C\u8BF7\u5728\u9879\u76EE\u6839\u76EE\u5F55\u6267\u884C\u6B64\u547D\u4EE4");
|
|
5140
5269
|
}
|
|
5141
5270
|
if (projectInfo.frameworkConfidence > 0) {
|
|
@@ -5169,8 +5298,8 @@ async function executeSetup(options) {
|
|
|
5169
5298
|
}
|
|
5170
5299
|
]);
|
|
5171
5300
|
if (chooseDir !== "root") {
|
|
5172
|
-
installDir =
|
|
5173
|
-
if (!
|
|
5301
|
+
installDir = path16.join(process.cwd(), chooseDir);
|
|
5302
|
+
if (!fs14.existsSync(path16.join(installDir, "package.json"))) {
|
|
5174
5303
|
throw new Error(`${chooseDir} \u4E0B\u6CA1\u6709 package.json`);
|
|
5175
5304
|
}
|
|
5176
5305
|
}
|
|
@@ -5209,7 +5338,7 @@ ${allPackages.map((p) => ` - ${p}`).join("\n")}`,
|
|
|
5209
5338
|
const pm = getPackageManager(projectInfo.packageManager);
|
|
5210
5339
|
const installCmd = pm === "npm" ? "npm install -D" : pm === "yarn" ? "yarn add -D" : pm === "pnpm" ? "pnpm add -D" : "bun add -D";
|
|
5211
5340
|
for (const group of selectedGroups) {
|
|
5212
|
-
const dirHint = installDir !== process.cwd() ? ` (\u5728 ${
|
|
5341
|
+
const dirHint = installDir !== process.cwd() ? ` (\u5728 ${path16.relative(process.cwd(), installDir) || installDir})` : "";
|
|
5213
5342
|
console.log(chalk9.white(` ${installCmd} ${group.packages.join(" ")}${dirHint}`));
|
|
5214
5343
|
if (group.postInstall) {
|
|
5215
5344
|
for (const cmd of group.postInstall) {
|
|
@@ -5490,7 +5619,7 @@ async function executeChange(_options) {
|
|
|
5490
5619
|
}
|
|
5491
5620
|
|
|
5492
5621
|
// src/cli.ts
|
|
5493
|
-
var VERSION = "0.2.
|
|
5622
|
+
var VERSION = "0.2.8";
|
|
5494
5623
|
function printLogo() {
|
|
5495
5624
|
const logo = `
|
|
5496
5625
|
${chalk12.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}
|